blob: ddf1c8330b171af419c3945282c19a19ea0562e1 [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;
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
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 */
94 public void setOrientationStyle(int style) {
95 mOrientationStyle = style;
96 }
97
98 public int getOrientationStyle() {
99 return mOrientationStyle;
100 }
101
102 /**
103 * @param type SEQUENCE_INVARIANT or SEQUENCE_SENSITIVE
104 */
105 public void setSequenceType(int type) {
106 mSequenceType = type;
107 }
108
109 /**
110 * @return SEQUENCE_INVARIANT or SEQUENCE_SENSITIVE
111 */
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 */
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 */
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 */
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 */
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 */
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 */
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
209 /**
210 * Save the gesture library
211 */
212 public void save(OutputStream stream) throws IOException {
213 save(stream, false);
214 }
215
216 public void save(OutputStream stream, boolean closeStream) throws IOException {
217 if (!mChanged) {
218 return;
219 }
220
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) ? out :
232 new BufferedOutputStream(out, 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 */
269 public void load(InputStream stream) throws IOException {
270 load(stream, false);
271 }
272
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
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
327 Learner getLearner() {
328 return mClassifier;
329 }
330}