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 // at most 2 directions can be recognized 69 public static final int ORIENTATION_SENSITIVE = 2; 70 // at most 4 directions can be recognized 71 static final int ORIENTATION_SENSITIVE_4 = 4; 72 // at most 8 directions can be recognized 73 static final int ORIENTATION_SENSITIVE_8 = 8; 74 75 private static final short FILE_FORMAT_VERSION = 1; 76 77 private static final boolean PROFILE_LOADING_SAVING = false; 78 79 private int mSequenceType = SEQUENCE_SENSITIVE; 80 private int mOrientationStyle = ORIENTATION_SENSITIVE; 81 82 private final HashMap<String, ArrayList<Gesture>> mNamedGestures = 83 new HashMap<String, ArrayList<Gesture>>(); 84 85 private Learner mClassifier; 86 87 private boolean mChanged = false; 88 GestureStore()89 public GestureStore() { 90 mClassifier = new InstanceLearner(); 91 } 92 93 /** 94 * Specify how the gesture library will handle orientation. 95 * Use ORIENTATION_INVARIANT or ORIENTATION_SENSITIVE 96 * 97 * @param style 98 */ setOrientationStyle(int style)99 public void setOrientationStyle(int style) { 100 mOrientationStyle = style; 101 } 102 getOrientationStyle()103 public int getOrientationStyle() { 104 return mOrientationStyle; 105 } 106 107 /** 108 * @param type SEQUENCE_INVARIANT or SEQUENCE_SENSITIVE 109 */ setSequenceType(int type)110 public void setSequenceType(int type) { 111 mSequenceType = type; 112 } 113 114 /** 115 * @return SEQUENCE_INVARIANT or SEQUENCE_SENSITIVE 116 */ getSequenceType()117 public int getSequenceType() { 118 return mSequenceType; 119 } 120 121 /** 122 * Get all the gesture entry names in the library 123 * 124 * @return a set of strings 125 */ getGestureEntries()126 public Set<String> getGestureEntries() { 127 return mNamedGestures.keySet(); 128 } 129 130 /** 131 * Recognize a gesture 132 * 133 * @param gesture the query 134 * @return a list of predictions of possible entries for a given gesture 135 */ recognize(Gesture gesture)136 public ArrayList<Prediction> recognize(Gesture gesture) { 137 Instance instance = Instance.createInstance(mSequenceType, 138 mOrientationStyle, gesture, null); 139 return mClassifier.classify(mSequenceType, mOrientationStyle, instance.vector); 140 } 141 142 /** 143 * Add a gesture for the entry 144 * 145 * @param entryName entry name 146 * @param gesture 147 */ addGesture(String entryName, Gesture gesture)148 public void addGesture(String entryName, Gesture gesture) { 149 if (entryName == null || entryName.length() == 0) { 150 return; 151 } 152 ArrayList<Gesture> gestures = mNamedGestures.get(entryName); 153 if (gestures == null) { 154 gestures = new ArrayList<Gesture>(); 155 mNamedGestures.put(entryName, gestures); 156 } 157 gestures.add(gesture); 158 mClassifier.addInstance( 159 Instance.createInstance(mSequenceType, mOrientationStyle, gesture, entryName)); 160 mChanged = true; 161 } 162 163 /** 164 * Remove a gesture from the library. If there are no more gestures for the 165 * given entry, the gesture entry will be removed. 166 * 167 * @param entryName entry name 168 * @param gesture 169 */ removeGesture(String entryName, Gesture gesture)170 public void removeGesture(String entryName, Gesture gesture) { 171 ArrayList<Gesture> gestures = mNamedGestures.get(entryName); 172 if (gestures == null) { 173 return; 174 } 175 176 gestures.remove(gesture); 177 178 // if there are no more samples, remove the entry automatically 179 if (gestures.isEmpty()) { 180 mNamedGestures.remove(entryName); 181 } 182 183 mClassifier.removeInstance(gesture.getID()); 184 185 mChanged = true; 186 } 187 188 /** 189 * Remove a entry of gestures 190 * 191 * @param entryName the entry name 192 */ removeEntry(String entryName)193 public void removeEntry(String entryName) { 194 mNamedGestures.remove(entryName); 195 mClassifier.removeInstances(entryName); 196 mChanged = true; 197 } 198 199 /** 200 * Get all the gestures of an entry 201 * 202 * @param entryName 203 * @return the list of gestures that is under this name 204 */ getGestures(String entryName)205 public ArrayList<Gesture> getGestures(String entryName) { 206 ArrayList<Gesture> gestures = mNamedGestures.get(entryName); 207 if (gestures != null) { 208 return new ArrayList<Gesture>(gestures); 209 } else { 210 return null; 211 } 212 } 213 hasChanged()214 public boolean hasChanged() { 215 return mChanged; 216 } 217 218 /** 219 * Save the gesture library 220 */ save(OutputStream stream)221 public void save(OutputStream stream) throws IOException { 222 save(stream, false); 223 } 224 save(OutputStream stream, boolean closeStream)225 public void save(OutputStream stream, boolean closeStream) throws IOException { 226 DataOutputStream out = null; 227 228 try { 229 long start; 230 if (PROFILE_LOADING_SAVING) { 231 start = SystemClock.elapsedRealtime(); 232 } 233 234 final HashMap<String, ArrayList<Gesture>> maps = mNamedGestures; 235 236 out = new DataOutputStream((stream instanceof BufferedOutputStream) ? stream : 237 new BufferedOutputStream(stream, GestureConstants.IO_BUFFER_SIZE)); 238 // Write version number 239 out.writeShort(FILE_FORMAT_VERSION); 240 // Write number of entries 241 out.writeInt(maps.size()); 242 243 for (Map.Entry<String, ArrayList<Gesture>> entry : maps.entrySet()) { 244 final String key = entry.getKey(); 245 final ArrayList<Gesture> examples = entry.getValue(); 246 final int count = examples.size(); 247 248 // Write entry name 249 out.writeUTF(key); 250 // Write number of examples for this entry 251 out.writeInt(count); 252 253 for (int i = 0; i < count; i++) { 254 examples.get(i).serialize(out); 255 } 256 } 257 258 out.flush(); 259 260 if (PROFILE_LOADING_SAVING) { 261 long end = SystemClock.elapsedRealtime(); 262 Log.d(LOG_TAG, "Saving gestures library = " + (end - start) + " ms"); 263 } 264 265 mChanged = false; 266 } finally { 267 if (closeStream) GestureUtils.closeStream(out); 268 } 269 } 270 271 /** 272 * Load the gesture library 273 */ load(InputStream stream)274 public void load(InputStream stream) throws IOException { 275 load(stream, false); 276 } 277 load(InputStream stream, boolean closeStream)278 public void load(InputStream stream, boolean closeStream) throws IOException { 279 DataInputStream in = null; 280 try { 281 in = new DataInputStream((stream instanceof BufferedInputStream) ? stream : 282 new BufferedInputStream(stream, GestureConstants.IO_BUFFER_SIZE)); 283 284 long start; 285 if (PROFILE_LOADING_SAVING) { 286 start = SystemClock.elapsedRealtime(); 287 } 288 289 // Read file format version number 290 final short versionNumber = in.readShort(); 291 switch (versionNumber) { 292 case 1: 293 readFormatV1(in); 294 break; 295 } 296 297 if (PROFILE_LOADING_SAVING) { 298 long end = SystemClock.elapsedRealtime(); 299 Log.d(LOG_TAG, "Loading gestures library = " + (end - start) + " ms"); 300 } 301 } finally { 302 if (closeStream) GestureUtils.closeStream(in); 303 } 304 } 305 readFormatV1(DataInputStream in)306 private void readFormatV1(DataInputStream in) throws IOException { 307 final Learner classifier = mClassifier; 308 final HashMap<String, ArrayList<Gesture>> namedGestures = mNamedGestures; 309 namedGestures.clear(); 310 311 // Number of entries in the library 312 final int entriesCount = in.readInt(); 313 314 for (int i = 0; i < entriesCount; i++) { 315 // Entry name 316 final String name = in.readUTF(); 317 // Number of gestures 318 final int gestureCount = in.readInt(); 319 320 final ArrayList<Gesture> gestures = new ArrayList<Gesture>(gestureCount); 321 for (int j = 0; j < gestureCount; j++) { 322 final Gesture gesture = Gesture.deserialize(in); 323 gestures.add(gesture); 324 classifier.addInstance( 325 Instance.createInstance(mSequenceType, mOrientationStyle, gesture, name)); 326 } 327 328 namedGestures.put(name, gestures); 329 } 330 } 331 getLearner()332 Learner getLearner() { 333 return mClassifier; 334 } 335 } 336