/* * 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.Set; import static com.android.gesture.GestureConstants.LOG_TAG; /** * GestureLibrary maintains gesture examples and makes predictions on a new * gesture */ public class GestureLibrary { private static final String NAMESPACE = ""; 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; 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 mSequenceType = SEQUENCE_SENSITIVE; private int mOrientationStyle = ORIENTATION_SENSITIVE; private final String mGestureFileName; private final 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 (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 */ public ArrayList getGestures(String entryName) { ArrayList gestures = mEntryName2gestures.get(entryName); if (gestures != null) { return new ArrayList(gestures); } else { return null; } } /** * Save the gesture library */ public boolean save() { if (!mChanged) { return true; } boolean result= false; PrintWriter writer = null; try { File file = new File(mGestureFileName); if (!file.getParentFile().exists()) { if (!file.getParentFile().mkdirs()) { return false; } } writer = new PrintWriter(new BufferedOutputStream(new FileOutputStream( mGestureFileName), GestureConstants.IO_BUFFER_SIZE)); final XmlSerializer serializer = Xml.newSerializer(); serializer.setOutput(writer); serializer.startDocument(Encoding.ISO_8859_1.name(), null); serializer.startTag(NAMESPACE, GestureConstants.XML_TAG_LIBRARY); final HashMap> maps = mEntryName2gestures; for (String key : maps.keySet()) { 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(); mChanged = false; result = true; } catch (IOException ex) { Log.d(LOG_TAG, "Failed to save gestures:", ex); } finally { GestureUtilities.closeStream(writer); } return result; } /** * Load the gesture library */ public boolean load() { boolean result = false; final File file = new File(mGestureFileName); if (file.exists()) { BufferedInputStream in = null; try { if (Config.DEBUG) { Log.v(LOG_TAG, "Load from " + mGestureFileName); } in = new BufferedInputStream(new FileInputStream( mGestureFileName), GestureConstants.IO_BUFFER_SIZE); Xml.parse(in, Encoding.ISO_8859_1, new CompactInkHandler()); result = true; } catch (SAXException ex) { Log.d(LOG_TAG, "Failed to load gestures:", ex); } catch (IOException ex) { Log.d(LOG_TAG, "Failed to load gestures:", ex); } finally { GestureUtilities.closeStream(in); } } return result; } private class CompactInkHandler implements ContentHandler { final StringBuilder mBuffer = new StringBuilder(GestureConstants.STROKE_STRING_BUFFER_SIZE); String mEntryName; Gesture mCurrentGesture = null; ArrayList mGestures; CompactInkHandler() { } public void characters(char[] ch, int start, int length) { mBuffer.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(mEntryName, mGestures); mGestures = null; } else if (localName.equals(GestureConstants.XML_TAG_GESTURE)) { mGestures.add(mCurrentGesture); mClassifier.addInstance(Instance.createInstance(GestureLibrary.this, mCurrentGesture, mEntryName)); mCurrentGesture = null; } else if (localName.equals(GestureConstants.XML_TAG_STROKE)) { mCurrentGesture.addStroke(GestureStroke.createFromString(mBuffer.toString())); mBuffer.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)) { mGestures = new ArrayList(); mEntryName = attributes.getValue(NAMESPACE, GestureConstants.XML_TAG_NAME); } else if (localName.equals(GestureConstants.XML_TAG_GESTURE)) { mCurrentGesture = new Gesture(); mCurrentGesture.setID(Long.parseLong(attributes.getValue(NAMESPACE, GestureConstants.XML_TAG_ID))); } } public void startPrefixMapping(String prefix, String uri) { } } }