GestureStore.java revision 7fe416e9436a7b2a00e27e73ceb725de4e763f30
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
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) ? stream :
232                    new BufferedOutputStream(stream, 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}
331