/* * Copyright (C) 2008-2009 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.gesture; import android.util.Config; import android.util.Log; import android.util.Xml; import android.util.Xml.Encoding; import org.xml.sax.Attributes; import org.xml.sax.ContentHandler; import org.xml.sax.Locator; import org.xml.sax.SAXException; import org.xmlpull.v1.XmlSerializer; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.Set; /** * GestureLibrary maintains gesture examples and makes predictions on a new * gesture */ public class GestureLibrary { public static final int SEQUENCE_INVARIANT = 1; // when SEQUENCE_SENSITIVE is used, only single stroke gestures are allowed public static final int SEQUENCE_SENSITIVE = 2; private int mSequenceType = SEQUENCE_SENSITIVE; public static final int ORIENTATION_INVARIANT = 1; // ORIENTATION_SENSITIVE is only available for single stroke gestures public static final int ORIENTATION_SENSITIVE = 2; private int mOrientationStyle = ORIENTATION_SENSITIVE; private static final String LOGTAG = "GestureLibrary"; private static final String NAMESPACE = ""; private final String mGestureFileName; private HashMap> mEntryName2gestures = new HashMap>(); private Learner mClassifier; private boolean mChanged = false; /** * @param path where gesture data is stored */ public GestureLibrary(String path) { mGestureFileName = path; mClassifier = new InstanceLearner(); } /** * Specify whether the gesture library will handle orientation sensitive * gestures. Use ORIENTATION_INVARIANT or ORIENTATION_SENSITIVE * * @param style */ public void setOrientationStyle(int style) { mOrientationStyle = style; } public int getOrientationStyle() { return mOrientationStyle; } public void setGestureType(int type) { mSequenceType = type; } public int getGestureType() { return mSequenceType; } /** * Get all the gesture entry names in the library * * @return a set of strings */ public Set getGestureEntries() { return mEntryName2gestures.keySet(); } /** * Recognize a gesture * * @param gesture the query * @return a list of predictions of possible entries for a given gesture */ public ArrayList recognize(Gesture gesture) { Instance instance = Instance.createInstance(this, gesture, null); return mClassifier.classify(this, instance); } /** * Add a gesture for the entry * * @param entryName entry name * @param gesture */ public void addGesture(String entryName, Gesture gesture) { if (Config.DEBUG) { Log.v(LOGTAG, "Add an example for gesture: " + entryName); } if (entryName == null || entryName.length() == 0) { return; } ArrayList gestures = mEntryName2gestures.get(entryName); if (gestures == null) { gestures = new ArrayList(); mEntryName2gestures.put(entryName, gestures); } gestures.add(gesture); mClassifier.addInstance(Instance.createInstance(this, gesture, entryName)); mChanged = true; } /** * Remove a gesture from the library. If there are no more gestures for the * given entry, the gesture entry will be removed. * * @param entryName entry name * @param gesture */ public void removeGesture(String entryName, Gesture gesture) { ArrayList gestures = mEntryName2gestures.get(entryName); if (gestures == null) { return; } gestures.remove(gesture); // if there are no more samples, remove the entry automatically if (gestures.isEmpty()) { mEntryName2gestures.remove(entryName); } mClassifier.removeInstance(gesture.getID()); mChanged = true; } /** * Remove a entry of gestures * * @param entryName the entry name */ public void removeEntireEntry(String entryName) { mEntryName2gestures.remove(entryName); mClassifier.removeInstances(entryName); mChanged = true; } /** * Get all the gestures of an entry * * @param entryName * @return the list of gestures that is under this name */ @SuppressWarnings("unchecked") public ArrayList getGestures(String entryName) { ArrayList gestures = mEntryName2gestures.get(entryName); if (gestures != null) { return (ArrayList)gestures.clone(); } else { return null; } } /** * Save the gesture library */ public void save() { if (!mChanged) return; try { File file = new File(mGestureFileName); if (!file.getParentFile().exists()) { file.getParentFile().mkdirs(); } if (Config.DEBUG) { Log.v(LOGTAG, "Save to " + mGestureFileName); } BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream( mGestureFileName), GestureConstants.IO_BUFFER_SIZE); PrintWriter writer = new PrintWriter(outputStream); XmlSerializer serializer = Xml.newSerializer(); serializer.setOutput(writer); serializer.startDocument(Encoding.ISO_8859_1.name(), null); serializer.startTag(NAMESPACE, GestureConstants.XML_TAG_LIBRARY); HashMap> maps = mEntryName2gestures; Iterator it = maps.keySet().iterator(); while (it.hasNext()) { String key = it.next(); ArrayList examples = maps.get(key); // save an entry serializer.startTag(NAMESPACE, GestureConstants.XML_TAG_ENTRY); serializer.attribute(NAMESPACE, GestureConstants.XML_TAG_NAME, key); int count = examples.size(); for (int i = 0; i < count; i++) { Gesture gesture = examples.get(i); // save each gesture in the entry gesture.toXML(NAMESPACE, serializer); } serializer.endTag(NAMESPACE, GestureConstants.XML_TAG_ENTRY); } serializer.endTag(NAMESPACE, GestureConstants.XML_TAG_LIBRARY); serializer.endDocument(); serializer.flush(); writer.close(); outputStream.close(); mChanged = false; } catch (IOException ex) { Log.d(LOGTAG, "Failed to save gestures:", ex); } } /** * Load the gesture library */ public void load() { File file = new File(mGestureFileName); if (file.exists()) { try { if (Config.DEBUG) { Log.v(LOGTAG, "Load from " + mGestureFileName); } BufferedInputStream in = new BufferedInputStream(new FileInputStream( mGestureFileName), GestureConstants.IO_BUFFER_SIZE); Xml.parse(in, Encoding.ISO_8859_1, new CompactInkHandler()); in.close(); } catch (SAXException ex) { Log.d(LOGTAG, "Failed to load gestures:", ex); } catch (IOException ex) { Log.d(LOGTAG, "Failed to load gestures:", ex); } } } private class CompactInkHandler implements ContentHandler { Gesture currentGesture = null; StringBuilder buffer = new StringBuilder(GestureConstants.STROKE_STRING_BUFFER_SIZE); String entryName; ArrayList gestures; CompactInkHandler() { } public void characters(char[] ch, int start, int length) { buffer.append(ch, start, length); } public void endDocument() { } public void endElement(String uri, String localName, String qName) { if (localName.equals(GestureConstants.XML_TAG_ENTRY)) { mEntryName2gestures.put(entryName, gestures); gestures = null; } else if (localName.equals(GestureConstants.XML_TAG_GESTURE)) { gestures.add(currentGesture); mClassifier.addInstance(Instance.createInstance(GestureLibrary.this, currentGesture, entryName)); currentGesture = null; } else if (localName.equals(GestureConstants.XML_TAG_STROKE)) { currentGesture.addStroke(GestureStroke.createFromString(buffer.toString())); buffer.setLength(0); } } public void endPrefixMapping(String prefix) { } public void ignorableWhitespace(char[] ch, int start, int length) { } public void processingInstruction(String target, String data) { } public void setDocumentLocator(Locator locator) { } public void skippedEntity(String name) { } public void startDocument() { } public void startElement(String uri, String localName, String qName, Attributes attributes) { if (localName.equals(GestureConstants.XML_TAG_ENTRY)) { gestures = new ArrayList(); entryName = attributes.getValue(NAMESPACE, GestureConstants.XML_TAG_NAME); } else if (localName.equals(GestureConstants.XML_TAG_GESTURE)) { currentGesture = new Gesture(); currentGesture.setID(Long.parseLong(attributes.getValue(NAMESPACE, GestureConstants.XML_TAG_ID))); } } public void startPrefixMapping(String prefix, String uri) { } } }