blob: 7b5480caf5191085ea1e14dcad91f7ed5c53b16e [file] [log] [blame]
Yang Li35aa84b2009-05-18 18:29:05 -07001/*
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
17package com.android.gesture;
18
Yang Li35aa84b2009-05-18 18:29:05 -070019import android.util.Log;
Romain Guyb6d99b72009-05-21 15:05:50 -070020import android.os.SystemClock;
Yang Li35aa84b2009-05-18 18:29:05 -070021
22import java.io.BufferedInputStream;
23import java.io.BufferedOutputStream;
24import java.io.File;
25import java.io.FileInputStream;
26import java.io.FileOutputStream;
27import java.io.IOException;
Romain Guyb6d99b72009-05-21 15:05:50 -070028import java.io.DataOutputStream;
29import java.io.DataInputStream;
Yang Li35aa84b2009-05-18 18:29:05 -070030import java.util.ArrayList;
31import java.util.HashMap;
Yang Li35aa84b2009-05-18 18:29:05 -070032import java.util.Set;
Romain Guyb6d99b72009-05-21 15:05:50 -070033import java.util.Map;
Yang Li35aa84b2009-05-18 18:29:05 -070034
Romain Guyc5347272009-05-20 10:37:13 -070035import static com.android.gesture.GestureConstants.LOG_TAG;
36
Yang Li35aa84b2009-05-18 18:29:05 -070037/**
38 * GestureLibrary maintains gesture examples and makes predictions on a new
39 * gesture
40 */
Romain Guyb6d99b72009-05-21 15:05:50 -070041//
42// File format for GestureLibrary:
43//
44// Nb. bytes Java type Description
45// -----------------------------------
46// Header
47// 2 bytes short File format version number
48// 4 bytes int Number of entries
49// Entry
50// X bytes UTF String Entry name
51// 4 bytes int Number of gestures
52// Gesture
53// 8 bytes long Gesture ID
54// 4 bytes int Number of strokes
55// Stroke
56// 4 bytes int Number of points
57// Point
58// 4 bytes float X coordinate of the point
59// 4 bytes float Y coordinate of the point
60// 8 bytes long Time stamp
61//
Yang Li35aa84b2009-05-18 18:29:05 -070062public class GestureLibrary {
Romain Guyc5347272009-05-20 10:37:13 -070063 public static final int SEQUENCE_INVARIANT = 1;
Yang Lie6ea0032009-05-21 14:47:59 -070064 // when SEQUENCE_SENSITIVE is used, only single stroke gestures are currently allowed
Yang Li35aa84b2009-05-18 18:29:05 -070065 public static final int SEQUENCE_SENSITIVE = 2;
66
Yang Lie6ea0032009-05-21 14:47:59 -070067 // ORIENTATION_SENSITIVE and ORIENTATION_INVARIANT are only for SEQUENCE_SENSITIVE gestures
Yang Li35aa84b2009-05-18 18:29:05 -070068 public static final int ORIENTATION_INVARIANT = 1;
Yang Li35aa84b2009-05-18 18:29:05 -070069 public static final int ORIENTATION_SENSITIVE = 2;
70
Romain Guyb6d99b72009-05-21 15:05:50 -070071 private static final short FILE_FORMAT_VERSION = 1;
72
73 private static final boolean PROFILE_LOADING_SAVING = false;
74
Romain Guyc5347272009-05-20 10:37:13 -070075 private int mSequenceType = SEQUENCE_SENSITIVE;
Yang Li35aa84b2009-05-18 18:29:05 -070076 private int mOrientationStyle = ORIENTATION_SENSITIVE;
77
Yang Li35aa84b2009-05-18 18:29:05 -070078 private final String mGestureFileName;
79
Romain Guyb6d99b72009-05-21 15:05:50 -070080 private final HashMap<String, ArrayList<Gesture>> mNamedGestures =
Romain Guyc5347272009-05-20 10:37:13 -070081 new HashMap<String, ArrayList<Gesture>>();
Yang Li35aa84b2009-05-18 18:29:05 -070082
83 private Learner mClassifier;
84
85 private boolean mChanged = false;
86
87 /**
88 * @param path where gesture data is stored
89 */
90 public GestureLibrary(String path) {
91 mGestureFileName = path;
92 mClassifier = new InstanceLearner();
93 }
94
95 /**
Yang Lie6ea0032009-05-21 14:47:59 -070096 * Specify how the gesture library will handle orientation.
97 * Use ORIENTATION_INVARIANT or ORIENTATION_SENSITIVE
Yang Li35aa84b2009-05-18 18:29:05 -070098 *
99 * @param style
100 */
101 public void setOrientationStyle(int style) {
102 mOrientationStyle = style;
103 }
104
105 public int getOrientationStyle() {
106 return mOrientationStyle;
107 }
108
Yang Liac6a4b82009-05-21 16:08:35 -0700109 /**
110 * @param type SEQUENCE_INVARIANT or SEQUENCE_SENSITIVE
111 */
112 public void setSequenceType(int type) {
Yang Li35aa84b2009-05-18 18:29:05 -0700113 mSequenceType = type;
114 }
115
Yang Liac6a4b82009-05-21 16:08:35 -0700116 /**
117 * @return SEQUENCE_INVARIANT or SEQUENCE_SENSITIVE
118 */
119 public int getSequenceType() {
Yang Li35aa84b2009-05-18 18:29:05 -0700120 return mSequenceType;
121 }
122
123 /**
124 * Get all the gesture entry names in the library
125 *
126 * @return a set of strings
127 */
128 public Set<String> getGestureEntries() {
Romain Guyb6d99b72009-05-21 15:05:50 -0700129 return mNamedGestures.keySet();
Yang Li35aa84b2009-05-18 18:29:05 -0700130 }
131
132 /**
133 * Recognize a gesture
134 *
135 * @param gesture the query
136 * @return a list of predictions of possible entries for a given gesture
137 */
138 public ArrayList<Prediction> recognize(Gesture gesture) {
Yang Lie6ea0032009-05-21 14:47:59 -0700139 Instance instance = Instance.createInstance(mSequenceType, gesture, null);
140 return mClassifier.classify(mSequenceType, instance.vector);
Yang Li35aa84b2009-05-18 18:29:05 -0700141 }
142
143 /**
144 * Add a gesture for the entry
145 *
146 * @param entryName entry name
147 * @param gesture
148 */
149 public void addGesture(String entryName, Gesture gesture) {
Yang Li35aa84b2009-05-18 18:29:05 -0700150 if (entryName == null || entryName.length() == 0) {
151 return;
152 }
Romain Guyb6d99b72009-05-21 15:05:50 -0700153 ArrayList<Gesture> gestures = mNamedGestures.get(entryName);
Yang Li35aa84b2009-05-18 18:29:05 -0700154 if (gestures == null) {
155 gestures = new ArrayList<Gesture>();
Romain Guyb6d99b72009-05-21 15:05:50 -0700156 mNamedGestures.put(entryName, gestures);
Yang Li35aa84b2009-05-18 18:29:05 -0700157 }
158 gestures.add(gesture);
Yang Lie6ea0032009-05-21 14:47:59 -0700159 mClassifier.addInstance(Instance.createInstance(mSequenceType, gesture, entryName));
Yang Li35aa84b2009-05-18 18:29:05 -0700160 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 */
170 public void removeGesture(String entryName, Gesture gesture) {
Romain Guyb6d99b72009-05-21 15:05:50 -0700171 ArrayList<Gesture> gestures = mNamedGestures.get(entryName);
Yang Li35aa84b2009-05-18 18:29:05 -0700172 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()) {
Romain Guyb6d99b72009-05-21 15:05:50 -0700180 mNamedGestures.remove(entryName);
Yang Li35aa84b2009-05-18 18:29:05 -0700181 }
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 */
193 public void removeEntireEntry(String entryName) {
Romain Guyb6d99b72009-05-21 15:05:50 -0700194 mNamedGestures.remove(entryName);
Yang Li35aa84b2009-05-18 18:29:05 -0700195 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 */
Yang Li35aa84b2009-05-18 18:29:05 -0700205 public ArrayList<Gesture> getGestures(String entryName) {
Romain Guyb6d99b72009-05-21 15:05:50 -0700206 ArrayList<Gesture> gestures = mNamedGestures.get(entryName);
Yang Li35aa84b2009-05-18 18:29:05 -0700207 if (gestures != null) {
Romain Guyc5347272009-05-20 10:37:13 -0700208 return new ArrayList<Gesture>(gestures);
Yang Li35aa84b2009-05-18 18:29:05 -0700209 } else {
210 return null;
211 }
212 }
213
214 /**
215 * Save the gesture library
216 */
Romain Guyc5347272009-05-20 10:37:13 -0700217 public boolean save() {
218 if (!mChanged) {
219 return true;
220 }
221
Romain Guyb6d99b72009-05-21 15:05:50 -0700222 boolean result = false;
223 DataOutputStream out = null;
Yang Li35aa84b2009-05-18 18:29:05 -0700224
225 try {
226 File file = new File(mGestureFileName);
227 if (!file.getParentFile().exists()) {
Romain Guyc5347272009-05-20 10:37:13 -0700228 if (!file.getParentFile().mkdirs()) {
229 return false;
230 }
Yang Li35aa84b2009-05-18 18:29:05 -0700231 }
Yang Li35aa84b2009-05-18 18:29:05 -0700232
Romain Guyb6d99b72009-05-21 15:05:50 -0700233 long start;
234 if (PROFILE_LOADING_SAVING) {
235 start = SystemClock.elapsedRealtime();
Yang Li35aa84b2009-05-18 18:29:05 -0700236 }
Romain Guyc5347272009-05-20 10:37:13 -0700237
Romain Guyb6d99b72009-05-21 15:05:50 -0700238 final HashMap<String, ArrayList<Gesture>> maps = mNamedGestures;
239
240 out = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(file),
241 GestureConstants.IO_BUFFER_SIZE));
242 // Write version number
243 out.writeShort(FILE_FORMAT_VERSION);
244 // Write number of entries
245 out.writeInt(maps.size());
246
247 for (Map.Entry<String, ArrayList<Gesture>> entry : maps.entrySet()) {
248 final String key = entry.getKey();
249 final ArrayList<Gesture> examples = entry.getValue();
250 final int count = examples.size();
251
252 // Write entry name
253 out.writeUTF(key);
254 // Write number of examples for this entry
255 out.writeInt(count);
256
257 for (int i = 0; i < count; i++) {
258 examples.get(i).serialize(out);
259 }
260 }
261
262 out.flush();
263
264 if (PROFILE_LOADING_SAVING) {
265 long end = SystemClock.elapsedRealtime();
266 Log.d(LOG_TAG, "Saving gestures library = " + (end - start) + " ms");
267 }
Romain Guyc5347272009-05-20 10:37:13 -0700268
Yang Li35aa84b2009-05-18 18:29:05 -0700269 mChanged = false;
Romain Guyc5347272009-05-20 10:37:13 -0700270 result = true;
Yang Li35aa84b2009-05-18 18:29:05 -0700271 } catch (IOException ex) {
Romain Guyc5347272009-05-20 10:37:13 -0700272 Log.d(LOG_TAG, "Failed to save gestures:", ex);
273 } finally {
Romain Guyb6d99b72009-05-21 15:05:50 -0700274 GestureUtilities.closeStream(out);
Yang Li35aa84b2009-05-18 18:29:05 -0700275 }
Romain Guyc5347272009-05-20 10:37:13 -0700276
277 return result;
Yang Li35aa84b2009-05-18 18:29:05 -0700278 }
279
280 /**
281 * Load the gesture library
282 */
Romain Guyc5347272009-05-20 10:37:13 -0700283 public boolean load() {
284 boolean result = false;
285
286 final File file = new File(mGestureFileName);
Yang Li35aa84b2009-05-18 18:29:05 -0700287 if (file.exists()) {
Romain Guyb6d99b72009-05-21 15:05:50 -0700288 DataInputStream in = null;
Yang Li35aa84b2009-05-18 18:29:05 -0700289 try {
Romain Guyb6d99b72009-05-21 15:05:50 -0700290 in = new DataInputStream(new BufferedInputStream(
291 new FileInputStream(mGestureFileName), GestureConstants.IO_BUFFER_SIZE));
292
293 long start;
294 if (PROFILE_LOADING_SAVING) {
295 start = SystemClock.elapsedRealtime();
Yang Li35aa84b2009-05-18 18:29:05 -0700296 }
Romain Guyb6d99b72009-05-21 15:05:50 -0700297
298 // Read file format version number
299 final short versionNumber = in.readShort();
300 switch (versionNumber) {
301 case 1:
302 readFormatV1(in);
303 break;
304 }
305
306 if (PROFILE_LOADING_SAVING) {
307 long end = SystemClock.elapsedRealtime();
308 Log.d(LOG_TAG, "Loading gestures library = " + (end - start) + " ms");
309 }
310
Romain Guyc5347272009-05-20 10:37:13 -0700311 result = true;
Yang Li35aa84b2009-05-18 18:29:05 -0700312 } catch (IOException ex) {
Romain Guyc5347272009-05-20 10:37:13 -0700313 Log.d(LOG_TAG, "Failed to load gestures:", ex);
314 } finally {
315 GestureUtilities.closeStream(in);
Yang Li35aa84b2009-05-18 18:29:05 -0700316 }
317 }
Romain Guyc5347272009-05-20 10:37:13 -0700318
319 return result;
Yang Li35aa84b2009-05-18 18:29:05 -0700320 }
321
Romain Guyb6d99b72009-05-21 15:05:50 -0700322 private void readFormatV1(DataInputStream in) throws IOException {
323 final Learner classifier = mClassifier;
324 final HashMap<String, ArrayList<Gesture>> namedGestures = mNamedGestures;
325 namedGestures.clear();
Yang Li35aa84b2009-05-18 18:29:05 -0700326
Romain Guyb6d99b72009-05-21 15:05:50 -0700327 // Number of entries in the library
328 final int entriesCount = in.readInt();
Yang Li35aa84b2009-05-18 18:29:05 -0700329
Romain Guyb6d99b72009-05-21 15:05:50 -0700330 for (int i = 0; i < entriesCount; i++) {
331 // Entry name
332 final String name = in.readUTF();
333 // Number of gestures
334 final int gestureCount = in.readInt();
Yang Li35aa84b2009-05-18 18:29:05 -0700335
Romain Guyb6d99b72009-05-21 15:05:50 -0700336 final ArrayList<Gesture> gestures = new ArrayList<Gesture>(gestureCount);
337 for (int j = 0; j < gestureCount; j++) {
338 final Gesture gesture = Gesture.deserialize(in);
339 gestures.add(gesture);
340 classifier.addInstance(Instance.createInstance(mSequenceType, gesture, name));
Yang Li35aa84b2009-05-18 18:29:05 -0700341 }
Yang Li35aa84b2009-05-18 18:29:05 -0700342
Romain Guyb6d99b72009-05-21 15:05:50 -0700343 namedGestures.put(name, gestures);
Yang Li35aa84b2009-05-18 18:29:05 -0700344 }
345 }
346}