blob: c89aa1645d1ad7ccf26318ed399d0b06810ceaf5 [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
19import android.util.Config;
20import android.util.Log;
21import android.util.Xml;
22import android.util.Xml.Encoding;
23
24import org.xml.sax.Attributes;
25import org.xml.sax.ContentHandler;
26import org.xml.sax.Locator;
27import org.xml.sax.SAXException;
28import org.xmlpull.v1.XmlSerializer;
29
30import java.io.BufferedInputStream;
31import java.io.BufferedOutputStream;
32import java.io.File;
33import java.io.FileInputStream;
34import java.io.FileOutputStream;
35import java.io.IOException;
36import java.io.PrintWriter;
37import java.util.ArrayList;
38import java.util.HashMap;
39import java.util.Iterator;
40import java.util.Set;
41
42/**
43 * GestureLibrary maintains gesture examples and makes predictions on a new
44 * gesture
45 */
46public class GestureLibrary {
47
48 public static final int SEQUENCE_INVARIANT = 1;
49
50 // when SEQUENCE_SENSITIVE is used, only single stroke gestures are allowed
51 public static final int SEQUENCE_SENSITIVE = 2;
52
53 private int mSequenceType = SEQUENCE_SENSITIVE;
54
55 public static final int ORIENTATION_INVARIANT = 1;
56
57 // ORIENTATION_SENSITIVE is only available for single stroke gestures
58 public static final int ORIENTATION_SENSITIVE = 2;
59
60 private int mOrientationStyle = ORIENTATION_SENSITIVE;
61
62 private static final String LOGTAG = "GestureLibrary";
63
64 private static final String NAMESPACE = "";
65
66 private final String mGestureFileName;
67
68 private HashMap<String, ArrayList<Gesture>> mEntryName2gestures = new HashMap<String, ArrayList<Gesture>>();
69
70 private Learner mClassifier;
71
72 private boolean mChanged = false;
73
74 /**
75 * @param path where gesture data is stored
76 */
77 public GestureLibrary(String path) {
78 mGestureFileName = path;
79 mClassifier = new InstanceLearner();
80 }
81
82 /**
83 * Specify whether the gesture library will handle orientation sensitive
84 * gestures. Use ORIENTATION_INVARIANT or ORIENTATION_SENSITIVE
85 *
86 * @param style
87 */
88 public void setOrientationStyle(int style) {
89 mOrientationStyle = style;
90 }
91
92 public int getOrientationStyle() {
93 return mOrientationStyle;
94 }
95
96 public void setGestureType(int type) {
97 mSequenceType = type;
98 }
99
100 public int getGestureType() {
101 return mSequenceType;
102 }
103
104 /**
105 * Get all the gesture entry names in the library
106 *
107 * @return a set of strings
108 */
109 public Set<String> getGestureEntries() {
110 return mEntryName2gestures.keySet();
111 }
112
113 /**
114 * Recognize a gesture
115 *
116 * @param gesture the query
117 * @return a list of predictions of possible entries for a given gesture
118 */
119 public ArrayList<Prediction> recognize(Gesture gesture) {
120 Instance instance = Instance.createInstance(this, gesture, null);
121 return mClassifier.classify(this, instance);
122 }
123
124 /**
125 * Add a gesture for the entry
126 *
127 * @param entryName entry name
128 * @param gesture
129 */
130 public void addGesture(String entryName, Gesture gesture) {
131 if (Config.DEBUG) {
132 Log.v(LOGTAG, "Add an example for gesture: " + entryName);
133 }
134 if (entryName == null || entryName.length() == 0) {
135 return;
136 }
137 ArrayList<Gesture> gestures = mEntryName2gestures.get(entryName);
138 if (gestures == null) {
139 gestures = new ArrayList<Gesture>();
140 mEntryName2gestures.put(entryName, gestures);
141 }
142 gestures.add(gesture);
143 mClassifier.addInstance(Instance.createInstance(this, gesture, entryName));
144 mChanged = true;
145 }
146
147 /**
148 * Remove a gesture from the library. If there are no more gestures for the
149 * given entry, the gesture entry will be removed.
150 *
151 * @param entryName entry name
152 * @param gesture
153 */
154 public void removeGesture(String entryName, Gesture gesture) {
155 ArrayList<Gesture> gestures = mEntryName2gestures.get(entryName);
156 if (gestures == null) {
157 return;
158 }
159
160 gestures.remove(gesture);
161
162 // if there are no more samples, remove the entry automatically
163 if (gestures.isEmpty()) {
164 mEntryName2gestures.remove(entryName);
165 }
166
167 mClassifier.removeInstance(gesture.getID());
168
169 mChanged = true;
170 }
171
172 /**
173 * Remove a entry of gestures
174 *
175 * @param entryName the entry name
176 */
177 public void removeEntireEntry(String entryName) {
178 mEntryName2gestures.remove(entryName);
179 mClassifier.removeInstances(entryName);
180 mChanged = true;
181 }
182
183 /**
184 * Get all the gestures of an entry
185 *
186 * @param entryName
187 * @return the list of gestures that is under this name
188 */
189 @SuppressWarnings("unchecked")
190 public ArrayList<Gesture> getGestures(String entryName) {
191 ArrayList<Gesture> gestures = mEntryName2gestures.get(entryName);
192 if (gestures != null) {
193 return (ArrayList<Gesture>)gestures.clone();
194 } else {
195 return null;
196 }
197 }
198
199 /**
200 * Save the gesture library
201 */
202 public void save() {
203 if (!mChanged)
204 return;
205
206 try {
207 File file = new File(mGestureFileName);
208 if (!file.getParentFile().exists()) {
209 file.getParentFile().mkdirs();
210 }
211 if (Config.DEBUG) {
212 Log.v(LOGTAG, "Save to " + mGestureFileName);
213 }
214 BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(
215 mGestureFileName), GestureConstants.IO_BUFFER_SIZE);
216
217 PrintWriter writer = new PrintWriter(outputStream);
218 XmlSerializer serializer = Xml.newSerializer();
219 serializer.setOutput(writer);
220 serializer.startDocument(Encoding.ISO_8859_1.name(), null);
221 serializer.startTag(NAMESPACE, GestureConstants.XML_TAG_LIBRARY);
222 HashMap<String, ArrayList<Gesture>> maps = mEntryName2gestures;
223 Iterator<String> it = maps.keySet().iterator();
224 while (it.hasNext()) {
225 String key = it.next();
226 ArrayList<Gesture> examples = maps.get(key);
227 // save an entry
228 serializer.startTag(NAMESPACE, GestureConstants.XML_TAG_ENTRY);
229 serializer.attribute(NAMESPACE, GestureConstants.XML_TAG_NAME, key);
230 int count = examples.size();
231 for (int i = 0; i < count; i++) {
232 Gesture gesture = examples.get(i);
233 // save each gesture in the entry
234 gesture.toXML(NAMESPACE, serializer);
235 }
236 serializer.endTag(NAMESPACE, GestureConstants.XML_TAG_ENTRY);
237 }
238 serializer.endTag(NAMESPACE, GestureConstants.XML_TAG_LIBRARY);
239 serializer.endDocument();
240 serializer.flush();
241 writer.close();
242 outputStream.close();
243 mChanged = false;
244 } catch (IOException ex) {
245 Log.d(LOGTAG, "Failed to save gestures:", ex);
246 }
247 }
248
249 /**
250 * Load the gesture library
251 */
252 public void load() {
253 File file = new File(mGestureFileName);
254 if (file.exists()) {
255 try {
256 if (Config.DEBUG) {
257 Log.v(LOGTAG, "Load from " + mGestureFileName);
258 }
259 BufferedInputStream in = new BufferedInputStream(new FileInputStream(
260 mGestureFileName), GestureConstants.IO_BUFFER_SIZE);
261 Xml.parse(in, Encoding.ISO_8859_1, new CompactInkHandler());
262 in.close();
263 } catch (SAXException ex) {
264 Log.d(LOGTAG, "Failed to load gestures:", ex);
265 } catch (IOException ex) {
266 Log.d(LOGTAG, "Failed to load gestures:", ex);
267 }
268 }
269 }
270
271 private class CompactInkHandler implements ContentHandler {
272 Gesture currentGesture = null;
273
274 StringBuilder buffer = new StringBuilder(GestureConstants.STROKE_STRING_BUFFER_SIZE);
275
276 String entryName;
277
278 ArrayList<Gesture> gestures;
279
280 CompactInkHandler() {
281 }
282
283 public void characters(char[] ch, int start, int length) {
284 buffer.append(ch, start, length);
285 }
286
287 public void endDocument() {
288 }
289
290 public void endElement(String uri, String localName, String qName) {
291 if (localName.equals(GestureConstants.XML_TAG_ENTRY)) {
292 mEntryName2gestures.put(entryName, gestures);
293 gestures = null;
294 } else if (localName.equals(GestureConstants.XML_TAG_GESTURE)) {
295 gestures.add(currentGesture);
296 mClassifier.addInstance(Instance.createInstance(GestureLibrary.this,
297 currentGesture, entryName));
298 currentGesture = null;
299 } else if (localName.equals(GestureConstants.XML_TAG_STROKE)) {
300 currentGesture.addStroke(GestureStroke.createFromString(buffer.toString()));
301 buffer.setLength(0);
302 }
303 }
304
305 public void endPrefixMapping(String prefix) {
306 }
307
308 public void ignorableWhitespace(char[] ch, int start, int length) {
309 }
310
311 public void processingInstruction(String target, String data) {
312 }
313
314 public void setDocumentLocator(Locator locator) {
315 }
316
317 public void skippedEntity(String name) {
318 }
319
320 public void startDocument() {
321 }
322
323 public void startElement(String uri, String localName, String qName, Attributes attributes) {
324 if (localName.equals(GestureConstants.XML_TAG_ENTRY)) {
325 gestures = new ArrayList<Gesture>();
326 entryName = attributes.getValue(NAMESPACE, GestureConstants.XML_TAG_NAME);
327 } else if (localName.equals(GestureConstants.XML_TAG_GESTURE)) {
328 currentGesture = new Gesture();
329 currentGesture.setID(Long.parseLong(attributes.getValue(NAMESPACE,
330 GestureConstants.XML_TAG_ID)));
331 }
332 }
333
334 public void startPrefixMapping(String prefix, String uri) {
335 }
336 }
337}