blob: 11b504491c10f9d4480ed2682319cf473158cb41 [file] [log] [blame]
Romain Guy0a637162009-05-29 14:43:54 -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 android.gesture;
18
19import android.util.Log;
20import android.os.SystemClock;
21
22import java.io.BufferedInputStream;
23import java.io.BufferedOutputStream;
24import java.io.IOException;
25import java.io.DataOutputStream;
26import java.io.DataInputStream;
27import java.io.InputStream;
28import java.io.OutputStream;
29import java.util.ArrayList;
30import java.util.HashMap;
31import java.util.Set;
32import java.util.Map;
33
34import 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//
61public 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;
Yang Li4758f122009-12-14 15:41:07 -080068 // at most 2 directions can be recognized
Romain Guy0a637162009-05-29 14:43:54 -070069 public static final int ORIENTATION_SENSITIVE = 2;
Yang Li4758f122009-12-14 15:41:07 -080070 // 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;
Romain Guy0a637162009-05-29 14:43:54 -070074
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
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 */
99 public void setOrientationStyle(int style) {
100 mOrientationStyle = style;
101 }
102
103 public int getOrientationStyle() {
104 return mOrientationStyle;
105 }
106
107 /**
108 * @param type SEQUENCE_INVARIANT or SEQUENCE_SENSITIVE
109 */
110 public void setSequenceType(int type) {
111 mSequenceType = type;
112 }
113
114 /**
115 * @return SEQUENCE_INVARIANT or SEQUENCE_SENSITIVE
116 */
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 */
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 */
136 public ArrayList<Prediction> recognize(Gesture gesture) {
137 Instance instance = Instance.createInstance(mSequenceType,
138 mOrientationStyle, gesture, null);
Yang Li4758f122009-12-14 15:41:07 -0800139 return mClassifier.classify(mSequenceType, mOrientationStyle, instance.vector);
Romain Guy0a637162009-05-29 14:43:54 -0700140 }
141
142 /**
143 * Add a gesture for the entry
144 *
145 * @param entryName entry name
146 * @param gesture
147 */
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 */
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 */
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 */
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
Romain Guy03f0b212009-06-09 04:15:22 -0700214 public boolean hasChanged() {
215 return mChanged;
216 }
217
Romain Guy0a637162009-05-29 14:43:54 -0700218 /**
219 * Save the gesture library
220 */
221 public void save(OutputStream stream) throws IOException {
222 save(stream, false);
223 }
224
225 public void save(OutputStream stream, boolean closeStream) throws IOException {
Romain Guy0a637162009-05-29 14:43:54 -0700226 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
Romain Guy7fe416e2009-06-09 01:42:15 -0700236 out = new DataOutputStream((stream instanceof BufferedOutputStream) ? stream :
237 new BufferedOutputStream(stream, GestureConstants.IO_BUFFER_SIZE));
Romain Guy0a637162009-05-29 14:43:54 -0700238 // 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 {
Romain Guy46c53122010-02-04 14:19:50 -0800267 if (closeStream) GestureUtils.closeStream(out);
Romain Guy0a637162009-05-29 14:43:54 -0700268 }
269 }
270
271 /**
272 * Load the gesture library
273 */
274 public void load(InputStream stream) throws IOException {
275 load(stream, false);
276 }
277
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 {
Romain Guy46c53122010-02-04 14:19:50 -0800302 if (closeStream) GestureUtils.closeStream(in);
Romain Guy0a637162009-05-29 14:43:54 -0700303 }
304 }
305
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
332 Learner getLearner() {
333 return mClassifier;
334 }
335}