/* * Copyright (C) 2008-2009 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.gesture; import android.util.Log; import android.os.SystemClock; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.IOException; import java.io.DataOutputStream; import java.io.DataInputStream; import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.Set; import java.util.Map; import static android.gesture.GestureConstants.LOG_TAG; /** * GestureLibrary maintains gesture examples and makes predictions on a new * gesture */ // // File format for GestureStore: // // Nb. bytes Java type Description // ----------------------------------- // Header // 2 bytes short File format version number // 4 bytes int Number of entries // Entry // X bytes UTF String Entry name // 4 bytes int Number of gestures // Gesture // 8 bytes long Gesture ID // 4 bytes int Number of strokes // Stroke // 4 bytes int Number of points // Point // 4 bytes float X coordinate of the point // 4 bytes float Y coordinate of the point // 8 bytes long Time stamp // public class GestureStore { public static final int SEQUENCE_INVARIANT = 1; // when SEQUENCE_SENSITIVE is used, only single stroke gestures are currently allowed public static final int SEQUENCE_SENSITIVE = 2; // ORIENTATION_SENSITIVE and ORIENTATION_INVARIANT are only for SEQUENCE_SENSITIVE gestures public static final int ORIENTATION_INVARIANT = 1; // at most 2 directions can be recognized public static final int ORIENTATION_SENSITIVE = 2; // at most 4 directions can be recognized static final int ORIENTATION_SENSITIVE_4 = 4; // at most 8 directions can be recognized static final int ORIENTATION_SENSITIVE_8 = 8; private static final short FILE_FORMAT_VERSION = 1; private static final boolean PROFILE_LOADING_SAVING = false; private int mSequenceType = SEQUENCE_SENSITIVE; private int mOrientationStyle = ORIENTATION_SENSITIVE; private final HashMap> mNamedGestures = new HashMap>(); private Learner mClassifier; private boolean mChanged = false; public GestureStore() { mClassifier = new InstanceLearner(); } /** * Specify how the gesture library will handle orientation. * Use ORIENTATION_INVARIANT or ORIENTATION_SENSITIVE * * @param style */ public void setOrientationStyle(int style) { mOrientationStyle = style; } public int getOrientationStyle() { return mOrientationStyle; } /** * @param type SEQUENCE_INVARIANT or SEQUENCE_SENSITIVE */ public void setSequenceType(int type) { mSequenceType = type; } /** * @return SEQUENCE_INVARIANT or SEQUENCE_SENSITIVE */ public int getSequenceType() { return mSequenceType; } /** * Get all the gesture entry names in the library * * @return a set of strings */ public Set getGestureEntries() { return mNamedGestures.keySet(); } /** * Recognize a gesture * * @param gesture the query * @return a list of predictions of possible entries for a given gesture */ public ArrayList recognize(Gesture gesture) { Instance instance = Instance.createInstance(mSequenceType, mOrientationStyle, gesture, null); return mClassifier.classify(mSequenceType, mOrientationStyle, instance.vector); } /** * Add a gesture for the entry * * @param entryName entry name * @param gesture */ public void addGesture(String entryName, Gesture gesture) { if (entryName == null || entryName.length() == 0) { return; } ArrayList gestures = mNamedGestures.get(entryName); if (gestures == null) { gestures = new ArrayList(); mNamedGestures.put(entryName, gestures); } gestures.add(gesture); mClassifier.addInstance( Instance.createInstance(mSequenceType, mOrientationStyle, gesture, entryName)); mChanged = true; } /** * Remove a gesture from the library. If there are no more gestures for the * given entry, the gesture entry will be removed. * * @param entryName entry name * @param gesture */ public void removeGesture(String entryName, Gesture gesture) { ArrayList gestures = mNamedGestures.get(entryName); if (gestures == null) { return; } gestures.remove(gesture); // if there are no more samples, remove the entry automatically if (gestures.isEmpty()) { mNamedGestures.remove(entryName); } mClassifier.removeInstance(gesture.getID()); mChanged = true; } /** * Remove a entry of gestures * * @param entryName the entry name */ public void removeEntry(String entryName) { mNamedGestures.remove(entryName); mClassifier.removeInstances(entryName); mChanged = true; } /** * Get all the gestures of an entry * * @param entryName * @return the list of gestures that is under this name */ public ArrayList getGestures(String entryName) { ArrayList gestures = mNamedGestures.get(entryName); if (gestures != null) { return new ArrayList(gestures); } else { return null; } } public boolean hasChanged() { return mChanged; } /** * Save the gesture library */ public void save(OutputStream stream) throws IOException { save(stream, false); } public void save(OutputStream stream, boolean closeStream) throws IOException { DataOutputStream out = null; try { long start; if (PROFILE_LOADING_SAVING) { start = SystemClock.elapsedRealtime(); } final HashMap> maps = mNamedGestures; out = new DataOutputStream((stream instanceof BufferedOutputStream) ? stream : new BufferedOutputStream(stream, GestureConstants.IO_BUFFER_SIZE)); // Write version number out.writeShort(FILE_FORMAT_VERSION); // Write number of entries out.writeInt(maps.size()); for (Map.Entry> entry : maps.entrySet()) { final String key = entry.getKey(); final ArrayList examples = entry.getValue(); final int count = examples.size(); // Write entry name out.writeUTF(key); // Write number of examples for this entry out.writeInt(count); for (int i = 0; i < count; i++) { examples.get(i).serialize(out); } } out.flush(); if (PROFILE_LOADING_SAVING) { long end = SystemClock.elapsedRealtime(); Log.d(LOG_TAG, "Saving gestures library = " + (end - start) + " ms"); } mChanged = false; } finally { if (closeStream) GestureUtils.closeStream(out); } } /** * Load the gesture library */ public void load(InputStream stream) throws IOException { load(stream, false); } public void load(InputStream stream, boolean closeStream) throws IOException { DataInputStream in = null; try { in = new DataInputStream((stream instanceof BufferedInputStream) ? stream : new BufferedInputStream(stream, GestureConstants.IO_BUFFER_SIZE)); long start; if (PROFILE_LOADING_SAVING) { start = SystemClock.elapsedRealtime(); } // Read file format version number final short versionNumber = in.readShort(); switch (versionNumber) { case 1: readFormatV1(in); break; } if (PROFILE_LOADING_SAVING) { long end = SystemClock.elapsedRealtime(); Log.d(LOG_TAG, "Loading gestures library = " + (end - start) + " ms"); } } finally { if (closeStream) GestureUtils.closeStream(in); } } private void readFormatV1(DataInputStream in) throws IOException { final Learner classifier = mClassifier; final HashMap> namedGestures = mNamedGestures; namedGestures.clear(); // Number of entries in the library final int entriesCount = in.readInt(); for (int i = 0; i < entriesCount; i++) { // Entry name final String name = in.readUTF(); // Number of gestures final int gestureCount = in.readInt(); final ArrayList gestures = new ArrayList(gestureCount); for (int j = 0; j < gestureCount; j++) { final Gesture gesture = Gesture.deserialize(in); gestures.add(gesture); classifier.addInstance( Instance.createInstance(mSequenceType, mOrientationStyle, gesture, name)); } namedGestures.put(name, gestures); } } Learner getLearner() { return mClassifier; } }