blob: c89aa1645d1ad7ccf26318ed399d0b06810ceaf5 [file] [log] [blame]
/*
* 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<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 (Config.DEBUG) {
Log.v(LOGTAG, "Add an example for gesture: " + entryName);
}
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
*/
@SuppressWarnings("unchecked")
public ArrayList<Gesture> getGestures(String entryName) {
ArrayList<Gesture> gestures = mEntryName2gestures.get(entryName);
if (gestures != null) {
return (ArrayList<Gesture>)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<String, ArrayList<Gesture>> maps = mEntryName2gestures;
Iterator<String> it = maps.keySet().iterator();
while (it.hasNext()) {
String key = it.next();
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();
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<Gesture> 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<Gesture>();
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) {
}
}
}