blob: 3e753e7c6715ee57b113da320cfcf50f4052dd4f [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;
Yang Li35aa84b2009-05-18 18:29:05 -070039import java.util.Set;
40
Romain Guyc5347272009-05-20 10:37:13 -070041import static com.android.gesture.GestureConstants.LOG_TAG;
42
Yang Li35aa84b2009-05-18 18:29:05 -070043/**
44 * GestureLibrary maintains gesture examples and makes predictions on a new
45 * gesture
46 */
47public class GestureLibrary {
48
Romain Guyc5347272009-05-20 10:37:13 -070049 private static final String NAMESPACE = "";
Yang Li35aa84b2009-05-18 18:29:05 -070050
Romain Guyc5347272009-05-20 10:37:13 -070051 public static final int SEQUENCE_INVARIANT = 1;
Yang Li35aa84b2009-05-18 18:29:05 -070052 // when SEQUENCE_SENSITIVE is used, only single stroke gestures are allowed
53 public static final int SEQUENCE_SENSITIVE = 2;
54
Yang Li35aa84b2009-05-18 18:29:05 -070055 public static final int ORIENTATION_INVARIANT = 1;
Yang Li35aa84b2009-05-18 18:29:05 -070056 // ORIENTATION_SENSITIVE is only available for single stroke gestures
57 public static final int ORIENTATION_SENSITIVE = 2;
58
Romain Guyc5347272009-05-20 10:37:13 -070059 private int mSequenceType = SEQUENCE_SENSITIVE;
Yang Li35aa84b2009-05-18 18:29:05 -070060 private int mOrientationStyle = ORIENTATION_SENSITIVE;
61
Yang Li35aa84b2009-05-18 18:29:05 -070062 private final String mGestureFileName;
63
Romain Guyc5347272009-05-20 10:37:13 -070064 private final HashMap<String, ArrayList<Gesture>> mEntryName2gestures =
65 new HashMap<String, ArrayList<Gesture>>();
Yang Li35aa84b2009-05-18 18:29:05 -070066
67 private Learner mClassifier;
68
69 private boolean mChanged = false;
70
71 /**
72 * @param path where gesture data is stored
73 */
74 public GestureLibrary(String path) {
75 mGestureFileName = path;
76 mClassifier = new InstanceLearner();
77 }
78
79 /**
80 * Specify whether the gesture library will handle orientation sensitive
81 * gestures. Use ORIENTATION_INVARIANT or ORIENTATION_SENSITIVE
82 *
83 * @param style
84 */
85 public void setOrientationStyle(int style) {
86 mOrientationStyle = style;
87 }
88
89 public int getOrientationStyle() {
90 return mOrientationStyle;
91 }
92
93 public void setGestureType(int type) {
94 mSequenceType = type;
95 }
96
97 public int getGestureType() {
98 return mSequenceType;
99 }
100
101 /**
102 * Get all the gesture entry names in the library
103 *
104 * @return a set of strings
105 */
106 public Set<String> getGestureEntries() {
107 return mEntryName2gestures.keySet();
108 }
109
110 /**
111 * Recognize a gesture
112 *
113 * @param gesture the query
114 * @return a list of predictions of possible entries for a given gesture
115 */
116 public ArrayList<Prediction> recognize(Gesture gesture) {
117 Instance instance = Instance.createInstance(this, gesture, null);
118 return mClassifier.classify(this, instance);
119 }
120
121 /**
122 * Add a gesture for the entry
123 *
124 * @param entryName entry name
125 * @param gesture
126 */
127 public void addGesture(String entryName, Gesture gesture) {
Yang Li35aa84b2009-05-18 18:29:05 -0700128 if (entryName == null || entryName.length() == 0) {
129 return;
130 }
131 ArrayList<Gesture> gestures = mEntryName2gestures.get(entryName);
132 if (gestures == null) {
133 gestures = new ArrayList<Gesture>();
134 mEntryName2gestures.put(entryName, gestures);
135 }
136 gestures.add(gesture);
137 mClassifier.addInstance(Instance.createInstance(this, gesture, entryName));
138 mChanged = true;
139 }
140
141 /**
142 * Remove a gesture from the library. If there are no more gestures for the
143 * given entry, the gesture entry will be removed.
144 *
145 * @param entryName entry name
146 * @param gesture
147 */
148 public void removeGesture(String entryName, Gesture gesture) {
149 ArrayList<Gesture> gestures = mEntryName2gestures.get(entryName);
150 if (gestures == null) {
151 return;
152 }
153
154 gestures.remove(gesture);
155
156 // if there are no more samples, remove the entry automatically
157 if (gestures.isEmpty()) {
158 mEntryName2gestures.remove(entryName);
159 }
160
161 mClassifier.removeInstance(gesture.getID());
162
163 mChanged = true;
164 }
165
166 /**
167 * Remove a entry of gestures
168 *
169 * @param entryName the entry name
170 */
171 public void removeEntireEntry(String entryName) {
172 mEntryName2gestures.remove(entryName);
173 mClassifier.removeInstances(entryName);
174 mChanged = true;
175 }
176
177 /**
178 * Get all the gestures of an entry
179 *
180 * @param entryName
181 * @return the list of gestures that is under this name
182 */
Yang Li35aa84b2009-05-18 18:29:05 -0700183 public ArrayList<Gesture> getGestures(String entryName) {
184 ArrayList<Gesture> gestures = mEntryName2gestures.get(entryName);
185 if (gestures != null) {
Romain Guyc5347272009-05-20 10:37:13 -0700186 return new ArrayList<Gesture>(gestures);
Yang Li35aa84b2009-05-18 18:29:05 -0700187 } else {
188 return null;
189 }
190 }
191
192 /**
193 * Save the gesture library
194 */
Romain Guyc5347272009-05-20 10:37:13 -0700195 public boolean save() {
196 if (!mChanged) {
197 return true;
198 }
199
200 boolean result= false;
201 PrintWriter writer = null;
Yang Li35aa84b2009-05-18 18:29:05 -0700202
203 try {
204 File file = new File(mGestureFileName);
205 if (!file.getParentFile().exists()) {
Romain Guyc5347272009-05-20 10:37:13 -0700206 if (!file.getParentFile().mkdirs()) {
207 return false;
208 }
Yang Li35aa84b2009-05-18 18:29:05 -0700209 }
Yang Li35aa84b2009-05-18 18:29:05 -0700210
Romain Guyc5347272009-05-20 10:37:13 -0700211 writer = new PrintWriter(new BufferedOutputStream(new FileOutputStream(
212 mGestureFileName), GestureConstants.IO_BUFFER_SIZE));
213
214 final XmlSerializer serializer = Xml.newSerializer();
Yang Li35aa84b2009-05-18 18:29:05 -0700215 serializer.setOutput(writer);
216 serializer.startDocument(Encoding.ISO_8859_1.name(), null);
217 serializer.startTag(NAMESPACE, GestureConstants.XML_TAG_LIBRARY);
Romain Guyc5347272009-05-20 10:37:13 -0700218
219 final HashMap<String, ArrayList<Gesture>> maps = mEntryName2gestures;
220
221 for (String key : maps.keySet()) {
Yang Li35aa84b2009-05-18 18:29:05 -0700222 ArrayList<Gesture> examples = maps.get(key);
223 // save an entry
224 serializer.startTag(NAMESPACE, GestureConstants.XML_TAG_ENTRY);
225 serializer.attribute(NAMESPACE, GestureConstants.XML_TAG_NAME, key);
226 int count = examples.size();
227 for (int i = 0; i < count; i++) {
228 Gesture gesture = examples.get(i);
229 // save each gesture in the entry
230 gesture.toXML(NAMESPACE, serializer);
231 }
232 serializer.endTag(NAMESPACE, GestureConstants.XML_TAG_ENTRY);
233 }
Romain Guyc5347272009-05-20 10:37:13 -0700234
Yang Li35aa84b2009-05-18 18:29:05 -0700235 serializer.endTag(NAMESPACE, GestureConstants.XML_TAG_LIBRARY);
236 serializer.endDocument();
237 serializer.flush();
Romain Guyc5347272009-05-20 10:37:13 -0700238
Yang Li35aa84b2009-05-18 18:29:05 -0700239 mChanged = false;
Romain Guyc5347272009-05-20 10:37:13 -0700240 result = true;
Yang Li35aa84b2009-05-18 18:29:05 -0700241 } catch (IOException ex) {
Romain Guyc5347272009-05-20 10:37:13 -0700242 Log.d(LOG_TAG, "Failed to save gestures:", ex);
243 } finally {
244 GestureUtilities.closeStream(writer);
Yang Li35aa84b2009-05-18 18:29:05 -0700245 }
Romain Guyc5347272009-05-20 10:37:13 -0700246
247 return result;
Yang Li35aa84b2009-05-18 18:29:05 -0700248 }
249
250 /**
251 * Load the gesture library
252 */
Romain Guyc5347272009-05-20 10:37:13 -0700253 public boolean load() {
254 boolean result = false;
255
256 final File file = new File(mGestureFileName);
Yang Li35aa84b2009-05-18 18:29:05 -0700257 if (file.exists()) {
Romain Guyc5347272009-05-20 10:37:13 -0700258 BufferedInputStream in = null;
Yang Li35aa84b2009-05-18 18:29:05 -0700259 try {
260 if (Config.DEBUG) {
Romain Guyc5347272009-05-20 10:37:13 -0700261 Log.v(LOG_TAG, "Load from " + mGestureFileName);
Yang Li35aa84b2009-05-18 18:29:05 -0700262 }
Romain Guyc5347272009-05-20 10:37:13 -0700263 in = new BufferedInputStream(new FileInputStream(
Yang Li35aa84b2009-05-18 18:29:05 -0700264 mGestureFileName), GestureConstants.IO_BUFFER_SIZE);
265 Xml.parse(in, Encoding.ISO_8859_1, new CompactInkHandler());
Romain Guyc5347272009-05-20 10:37:13 -0700266 result = true;
Yang Li35aa84b2009-05-18 18:29:05 -0700267 } catch (SAXException ex) {
Romain Guyc5347272009-05-20 10:37:13 -0700268 Log.d(LOG_TAG, "Failed to load gestures:", ex);
Yang Li35aa84b2009-05-18 18:29:05 -0700269 } catch (IOException ex) {
Romain Guyc5347272009-05-20 10:37:13 -0700270 Log.d(LOG_TAG, "Failed to load gestures:", ex);
271 } finally {
272 GestureUtilities.closeStream(in);
Yang Li35aa84b2009-05-18 18:29:05 -0700273 }
274 }
Romain Guyc5347272009-05-20 10:37:13 -0700275
276 return result;
Yang Li35aa84b2009-05-18 18:29:05 -0700277 }
278
279 private class CompactInkHandler implements ContentHandler {
Romain Guyc5347272009-05-20 10:37:13 -0700280 final StringBuilder mBuffer = new StringBuilder(GestureConstants.STROKE_STRING_BUFFER_SIZE);
Yang Li35aa84b2009-05-18 18:29:05 -0700281
Romain Guyc5347272009-05-20 10:37:13 -0700282 String mEntryName;
Yang Li35aa84b2009-05-18 18:29:05 -0700283
Romain Guyc5347272009-05-20 10:37:13 -0700284 Gesture mCurrentGesture = null;
285 ArrayList<Gesture> mGestures;
Yang Li35aa84b2009-05-18 18:29:05 -0700286
287 CompactInkHandler() {
288 }
289
290 public void characters(char[] ch, int start, int length) {
Romain Guyc5347272009-05-20 10:37:13 -0700291 mBuffer.append(ch, start, length);
Yang Li35aa84b2009-05-18 18:29:05 -0700292 }
293
294 public void endDocument() {
295 }
296
297 public void endElement(String uri, String localName, String qName) {
298 if (localName.equals(GestureConstants.XML_TAG_ENTRY)) {
Romain Guyc5347272009-05-20 10:37:13 -0700299 mEntryName2gestures.put(mEntryName, mGestures);
300 mGestures = null;
Yang Li35aa84b2009-05-18 18:29:05 -0700301 } else if (localName.equals(GestureConstants.XML_TAG_GESTURE)) {
Romain Guyc5347272009-05-20 10:37:13 -0700302 mGestures.add(mCurrentGesture);
Yang Li35aa84b2009-05-18 18:29:05 -0700303 mClassifier.addInstance(Instance.createInstance(GestureLibrary.this,
Romain Guyc5347272009-05-20 10:37:13 -0700304 mCurrentGesture, mEntryName));
305 mCurrentGesture = null;
Yang Li35aa84b2009-05-18 18:29:05 -0700306 } else if (localName.equals(GestureConstants.XML_TAG_STROKE)) {
Romain Guyc5347272009-05-20 10:37:13 -0700307 mCurrentGesture.addStroke(GestureStroke.createFromString(mBuffer.toString()));
308 mBuffer.setLength(0);
Yang Li35aa84b2009-05-18 18:29:05 -0700309 }
310 }
311
312 public void endPrefixMapping(String prefix) {
313 }
314
315 public void ignorableWhitespace(char[] ch, int start, int length) {
316 }
317
318 public void processingInstruction(String target, String data) {
319 }
320
321 public void setDocumentLocator(Locator locator) {
322 }
323
324 public void skippedEntity(String name) {
325 }
326
327 public void startDocument() {
328 }
329
330 public void startElement(String uri, String localName, String qName, Attributes attributes) {
331 if (localName.equals(GestureConstants.XML_TAG_ENTRY)) {
Romain Guyc5347272009-05-20 10:37:13 -0700332 mGestures = new ArrayList<Gesture>();
333 mEntryName = attributes.getValue(NAMESPACE, GestureConstants.XML_TAG_NAME);
Yang Li35aa84b2009-05-18 18:29:05 -0700334 } else if (localName.equals(GestureConstants.XML_TAG_GESTURE)) {
Romain Guyc5347272009-05-20 10:37:13 -0700335 mCurrentGesture = new Gesture();
336 mCurrentGesture.setID(Long.parseLong(attributes.getValue(NAMESPACE,
Yang Li35aa84b2009-05-18 18:29:05 -0700337 GestureConstants.XML_TAG_ID)));
338 }
339 }
340
341 public void startPrefixMapping(String prefix, String uri) {
342 }
343 }
344}