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