| /* |
| * 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<String, ArrayList<Gesture>> mEntryName2gestures = |
| new HashMap<String, ArrayList<Gesture>>(); |
| |
| 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<String> 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<Prediction> 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<Gesture> gestures = mEntryName2gestures.get(entryName); |
| if (gestures == null) { |
| gestures = new ArrayList<Gesture>(); |
| 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<Gesture> 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<Gesture> getGestures(String entryName) { |
| ArrayList<Gesture> gestures = mEntryName2gestures.get(entryName); |
| if (gestures != null) { |
| return new ArrayList<Gesture>(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<String, ArrayList<Gesture>> maps = mEntryName2gestures; |
| |
| for (String key : maps.keySet()) { |
| ArrayList<Gesture> 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<Gesture> 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<Gesture>(); |
| 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) { |
| } |
| } |
| } |