1 /* 2 * Copyright (C) 2008-2009 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.gesture; 18 19 import android.util.Log; 20 import android.os.SystemClock; 21 22 import java.io.BufferedInputStream; 23 import java.io.BufferedOutputStream; 24 import java.io.IOException; 25 import java.io.DataOutputStream; 26 import java.io.DataInputStream; 27 import java.io.InputStream; 28 import java.io.OutputStream; 29 import java.util.ArrayList; 30 import java.util.HashMap; 31 import java.util.Set; 32 import java.util.Map; 33 34 import static android.gesture.GestureConstants.LOG_TAG; 35 36 /** 37 * GestureLibrary maintains gesture examples and makes predictions on a new 38 * gesture 39 */ 40 // 41 // File format for GestureStore: 42 // 43 // Nb. bytes Java type Description 44 // ----------------------------------- 45 // Header 46 // 2 bytes short File format version number 47 // 4 bytes int Number of entries 48 // Entry 49 // X bytes UTF String Entry name 50 // 4 bytes int Number of gestures 51 // Gesture 52 // 8 bytes long Gesture ID 53 // 4 bytes int Number of strokes 54 // Stroke 55 // 4 bytes int Number of points 56 // Point 57 // 4 bytes float X coordinate of the point 58 // 4 bytes float Y coordinate of the point 59 // 8 bytes long Time stamp 60 // 61 public class GestureStore { 62 public static final int SEQUENCE_INVARIANT = 1; 63 // when SEQUENCE_SENSITIVE is used, only single stroke gestures are currently allowed 64 public static final int SEQUENCE_SENSITIVE = 2; 65 66 // ORIENTATION_SENSITIVE and ORIENTATION_INVARIANT are only for SEQUENCE_SENSITIVE gestures 67 public static final int ORIENTATION_INVARIANT = 1; 68 public static final int ORIENTATION_SENSITIVE = 2; 69 70 private static final short FILE_FORMAT_VERSION = 1; 71 72 private static final boolean PROFILE_LOADING_SAVING = false; 73 74 private int mSequenceType = SEQUENCE_SENSITIVE; 75 private int mOrientationStyle = ORIENTATION_SENSITIVE; 76 77 private final HashMap<String, ArrayList<Gesture>> mNamedGestures = 78 new HashMap<String, ArrayList<Gesture>>(); 79 80 private Learner mClassifier; 81 82 private boolean mChanged = false; 83 GestureStore()84 public GestureStore() { 85 mClassifier = new InstanceLearner(); 86 } 87 88 /** 89 * Specify how the gesture library will handle orientation. 90 * Use ORIENTATION_INVARIANT or ORIENTATION_SENSITIVE 91 * 92 * @param style 93 */ setOrientationStyle(int style)94 public void setOrientationStyle(int style) { 95 mOrientationStyle = style; 96 } 97 getOrientationStyle()98 public int getOrientationStyle() { 99 return mOrientationStyle; 100 } 101 102 /** 103 * @param type SEQUENCE_INVARIANT or SEQUENCE_SENSITIVE 104 */ setSequenceType(int type)105 public void setSequenceType(int type) { 106 mSequenceType = type; 107 } 108 109 /** 110 * @return SEQUENCE_INVARIANT or SEQUENCE_SENSITIVE 111 */ getSequenceType()112 public int getSequenceType() { 113 return mSequenceType; 114 } 115 116 /** 117 * Get all the gesture entry names in the library 118 * 119 * @return a set of strings 120 */ getGestureEntries()121 public Set<String> getGestureEntries() { 122 return mNamedGestures.keySet(); 123 } 124 125 /** 126 * Recognize a gesture 127 * 128 * @param gesture the query 129 * @return a list of predictions of possible entries for a given gesture 130 */ recognize(Gesture gesture)131 public ArrayList<Prediction> recognize(Gesture gesture) { 132 Instance instance = Instance.createInstance(mSequenceType, 133 mOrientationStyle, gesture, null); 134 return mClassifier.classify(mSequenceType, instance.vector); 135 } 136 137 /** 138 * Add a gesture for the entry 139 * 140 * @param entryName entry name 141 * @param gesture 142 */ addGesture(String entryName, Gesture gesture)143 public void addGesture(String entryName, Gesture gesture) { 144 if (entryName == null || entryName.length() == 0) { 145 return; 146 } 147 ArrayList<Gesture> gestures = mNamedGestures.get(entryName); 148 if (gestures == null) { 149 gestures = new ArrayList<Gesture>(); 150 mNamedGestures.put(entryName, gestures); 151 } 152 gestures.add(gesture); 153 mClassifier.addInstance( 154 Instance.createInstance(mSequenceType, mOrientationStyle, gesture, entryName)); 155 mChanged = true; 156 } 157 158 /** 159 * Remove a gesture from the library. If there are no more gestures for the 160 * given entry, the gesture entry will be removed. 161 * 162 * @param entryName entry name 163 * @param gesture 164 */ removeGesture(String entryName, Gesture gesture)165 public void removeGesture(String entryName, Gesture gesture) { 166 ArrayList<Gesture> gestures = mNamedGestures.get(entryName); 167 if (gestures == null) { 168 return; 169 } 170 171 gestures.remove(gesture); 172 173 // if there are no more samples, remove the entry automatically 174 if (gestures.isEmpty()) { 175 mNamedGestures.remove(entryName); 176 } 177 178 mClassifier.removeInstance(gesture.getID()); 179 180 mChanged = true; 181 } 182 183 /** 184 * Remove a entry of gestures 185 * 186 * @param entryName the entry name 187 */ removeEntry(String entryName)188 public void removeEntry(String entryName) { 189 mNamedGestures.remove(entryName); 190 mClassifier.removeInstances(entryName); 191 mChanged = true; 192 } 193 194 /** 195 * Get all the gestures of an entry 196 * 197 * @param entryName 198 * @return the list of gestures that is under this name 199 */ getGestures(String entryName)200 public ArrayList<Gesture> getGestures(String entryName) { 201 ArrayList<Gesture> gestures = mNamedGestures.get(entryName); 202 if (gestures != null) { 203 return new ArrayList<Gesture>(gestures); 204 } else { 205 return null; 206 } 207 } 208 hasChanged()209 public boolean hasChanged() { 210 return mChanged; 211 } 212 213 /** 214 * Save the gesture library 215 */ save(OutputStream stream)216 public void save(OutputStream stream) throws IOException { 217 save(stream, false); 218 } 219 save(OutputStream stream, boolean closeStream)220 public void save(OutputStream stream, boolean closeStream) throws IOException { 221 DataOutputStream out = null; 222 223 try { 224 long start; 225 if (PROFILE_LOADING_SAVING) { 226 start = SystemClock.elapsedRealtime(); 227 } 228 229 final HashMap<String, ArrayList<Gesture>> maps = mNamedGestures; 230 231 out = new DataOutputStream((stream instanceof BufferedOutputStream) ? stream : 232 new BufferedOutputStream(stream, GestureConstants.IO_BUFFER_SIZE)); 233 // Write version number 234 out.writeShort(FILE_FORMAT_VERSION); 235 // Write number of entries 236 out.writeInt(maps.size()); 237 238 for (Map.Entry<String, ArrayList<Gesture>> entry : maps.entrySet()) { 239 final String key = entry.getKey(); 240 final ArrayList<Gesture> examples = entry.getValue(); 241 final int count = examples.size(); 242 243 // Write entry name 244 out.writeUTF(key); 245 // Write number of examples for this entry 246 out.writeInt(count); 247 248 for (int i = 0; i < count; i++) { 249 examples.get(i).serialize(out); 250 } 251 } 252 253 out.flush(); 254 255 if (PROFILE_LOADING_SAVING) { 256 long end = SystemClock.elapsedRealtime(); 257 Log.d(LOG_TAG, "Saving gestures library = " + (end - start) + " ms"); 258 } 259 260 mChanged = false; 261 } finally { 262 if (closeStream) GestureUtilities.closeStream(out); 263 } 264 } 265 266 /** 267 * Load the gesture library 268 */ load(InputStream stream)269 public void load(InputStream stream) throws IOException { 270 load(stream, false); 271 } 272 load(InputStream stream, boolean closeStream)273 public void load(InputStream stream, boolean closeStream) throws IOException { 274 DataInputStream in = null; 275 try { 276 in = new DataInputStream((stream instanceof BufferedInputStream) ? stream : 277 new BufferedInputStream(stream, GestureConstants.IO_BUFFER_SIZE)); 278 279 long start; 280 if (PROFILE_LOADING_SAVING) { 281 start = SystemClock.elapsedRealtime(); 282 } 283 284 // Read file format version number 285 final short versionNumber = in.readShort(); 286 switch (versionNumber) { 287 case 1: 288 readFormatV1(in); 289 break; 290 } 291 292 if (PROFILE_LOADING_SAVING) { 293 long end = SystemClock.elapsedRealtime(); 294 Log.d(LOG_TAG, "Loading gestures library = " + (end - start) + " ms"); 295 } 296 } finally { 297 if (closeStream) GestureUtilities.closeStream(in); 298 } 299 } 300 readFormatV1(DataInputStream in)301 private void readFormatV1(DataInputStream in) throws IOException { 302 final Learner classifier = mClassifier; 303 final HashMap<String, ArrayList<Gesture>> namedGestures = mNamedGestures; 304 namedGestures.clear(); 305 306 // Number of entries in the library 307 final int entriesCount = in.readInt(); 308 309 for (int i = 0; i < entriesCount; i++) { 310 // Entry name 311 final String name = in.readUTF(); 312 // Number of gestures 313 final int gestureCount = in.readInt(); 314 315 final ArrayList<Gesture> gestures = new ArrayList<Gesture>(gestureCount); 316 for (int j = 0; j < gestureCount; j++) { 317 final Gesture gesture = Gesture.deserialize(in); 318 gestures.add(gesture); 319 classifier.addInstance( 320 Instance.createInstance(mSequenceType, mOrientationStyle, gesture, name)); 321 } 322 323 namedGestures.put(name, gestures); 324 } 325 } 326 getLearner()327 Learner getLearner() { 328 return mClassifier; 329 } 330 } 331