Modify how GestureLibrary stores its data. The XML format is now replaced by a more efficient binary format which should speed up saving/loading. The format is very similar to the one used by the letters recognizer. The format is documented in GestureLibrary.java.

This commit is contained in:
Romain Guy
2009-05-21 15:05:50 -07:00
parent aeed1816b7
commit b6d99b7d17
10 changed files with 289 additions and 279 deletions

View File

@@ -14,25 +14,34 @@
limitations under the License. limitations under the License.
--> -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.gesture.example" package="com.android.gesture.example"
android:versionCode="1" android:versionCode="1"
android:versionName="1.0.0"> android:versionName="1.0.0">
<uses-permission android:name="android.permission.READ_CONTACTS" />
<application android:icon="@drawable/icon" android:label="@string/app_name"> <uses-permission android:name="android.permission.READ_CONTACTS" />
<activity android:name="com.android.gesture.example.GestureEntry" <uses-permission android:name="android.permission.WRITE_SDCARD" />
android:label="@string/app_name">
<application android:icon="@drawable/icon" android:label="@string/app_name">
<activity
android:name="com.android.gesture.example.GestureEntry"
android:label="@string/app_name">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </activity>
<activity android:name="com.android.gesture.example.GestureLibViewer"/> <activity android:name="com.android.gesture.example.GestureLibViewer"/>
<activity android:name="com.android.gesture.example.ContactListGestureOverlay"
android:label="@string/overlay_name"> <activity
android:name="com.android.gesture.example.ContactListGestureOverlay"
android:label="@string/overlay_name">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </activity>
</application> </application>
</manifest> </manifest>

View File

@@ -23,12 +23,17 @@ import android.graphics.Path;
import android.graphics.RectF; import android.graphics.RectF;
import android.os.Parcel; import android.os.Parcel;
import android.os.Parcelable; import android.os.Parcelable;
import android.util.Log;
import org.xmlpull.v1.XmlSerializer;
import java.io.IOException; import java.io.IOException;
import java.io.DataOutputStream;
import java.io.DataInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ByteArrayInputStream;
import java.util.ArrayList; import java.util.ArrayList;
import static com.android.gesture.GestureConstants.LOG_TAG;
/** /**
* A gesture can have a single or multiple strokes * A gesture can have a single or multiple strokes
*/ */
@@ -149,10 +154,12 @@ public class Gesture implements Parcelable {
* @return the bitmap * @return the bitmap
*/ */
public Bitmap toBitmap(int width, int height, int edge, int numSample, int color) { public Bitmap toBitmap(int width, int height, int edge, int numSample, int color) {
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap); final Canvas canvas = new Canvas(bitmap);
canvas.translate(edge, edge); canvas.translate(edge, edge);
Paint paint = new Paint();
final Paint paint = new Paint();
paint.setAntiAlias(BITMAP_RENDERING_ANTIALIAS); paint.setAntiAlias(BITMAP_RENDERING_ANTIALIAS);
paint.setDither(BITMAP_RENDERING_DITHER); paint.setDither(BITMAP_RENDERING_DITHER);
paint.setColor(color); paint.setColor(color);
@@ -182,10 +189,12 @@ public class Gesture implements Parcelable {
* @return the bitmap * @return the bitmap
*/ */
public Bitmap toBitmap(int width, int height, int edge, int color) { public Bitmap toBitmap(int width, int height, int edge, int color) {
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap); final Canvas canvas = new Canvas(bitmap);
canvas.translate(edge, edge); canvas.translate(edge, edge);
Paint paint = new Paint();
final Paint paint = new Paint();
paint.setAntiAlias(BITMAP_RENDERING_ANTIALIAS); paint.setAntiAlias(BITMAP_RENDERING_ANTIALIAS);
paint.setDither(BITMAP_RENDERING_DITHER); paint.setDither(BITMAP_RENDERING_DITHER);
paint.setColor(color); paint.setColor(color);
@@ -193,53 +202,44 @@ public class Gesture implements Parcelable {
paint.setStrokeJoin(Paint.Join.ROUND); paint.setStrokeJoin(Paint.Join.ROUND);
paint.setStrokeCap(Paint.Cap.ROUND); paint.setStrokeCap(Paint.Cap.ROUND);
paint.setStrokeWidth(BITMAP_RENDERING_WIDTH); paint.setStrokeWidth(BITMAP_RENDERING_WIDTH);
ArrayList<GestureStroke> strokes = mStrokes;
int count = strokes.size(); final ArrayList<GestureStroke> strokes = mStrokes;
final int count = strokes.size();
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
GestureStroke stroke = strokes.get(i); strokes.get(i).draw(canvas, paint);
stroke.draw(canvas, paint);
} }
return bitmap; return bitmap;
} }
/** void serialize(DataOutputStream out) throws IOException {
* Save the gesture as XML final ArrayList<GestureStroke> strokes = mStrokes;
* final int count = strokes.size();
* @param namespace
* @param serializer // Write gesture ID
* @throws IOException out.writeLong(mGestureID);
*/ // Write number of strokes
void toXML(String namespace, XmlSerializer serializer) throws IOException { out.writeInt(count);
serializer.startTag(namespace, GestureConstants.XML_TAG_GESTURE);
serializer.attribute(namespace, GestureConstants.XML_TAG_ID, Long.toString(mGestureID));
ArrayList<GestureStroke> strokes = mStrokes;
int count = strokes.size();
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
GestureStroke stroke = strokes.get(i); strokes.get(i).serialize(out);
stroke.toXML(namespace, serializer);
} }
serializer.endTag(namespace, GestureConstants.XML_TAG_GESTURE);
} }
/** static Gesture deserialize(DataInputStream in) throws IOException {
* Create the gesture from a string final Gesture gesture = new Gesture();
*
* @param str // Gesture ID
*/ gesture.mGestureID = in.readLong();
public void createFromString(String str) { // Number of strokes
int startIndex = 0; final int count = in.readInt();
int endIndex;
while ((endIndex = for (int i = 0; i < count; i++) {
str.indexOf(GestureConstants.STRING_GESTURE_DELIIMITER, startIndex + 1)) != -1) { gesture.addStroke(GestureStroke.deserialize(in));
String token = str.substring(startIndex, endIndex);
if (startIndex > 0) { // stroke tokens
addStroke(GestureStroke.createFromString(token));
} else { // id token
mGestureID = Long.parseLong(token);
}
startIndex = endIndex + 1;
} }
return gesture;
} }
/** /**
@@ -247,12 +247,14 @@ public class Gesture implements Parcelable {
*/ */
@Override @Override
public String toString() { public String toString() {
StringBuilder str = new StringBuilder(); final StringBuilder str = new StringBuilder();
str.append(mGestureID); str.append(mGestureID);
ArrayList<GestureStroke> strokes = mStrokes;
int count = strokes.size(); final ArrayList<GestureStroke> strokes = mStrokes;
final int count = strokes.size();
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
GestureStroke stroke = strokes.get(i); final GestureStroke stroke = strokes.get(i);
str.append(GestureConstants.STRING_GESTURE_DELIIMITER); str.append(GestureConstants.STRING_GESTURE_DELIIMITER);
str.append(stroke.toString()); str.append(stroke.toString());
} }
@@ -262,9 +264,24 @@ public class Gesture implements Parcelable {
public static final Parcelable.Creator<Gesture> CREATOR = new Parcelable.Creator<Gesture>() { public static final Parcelable.Creator<Gesture> CREATOR = new Parcelable.Creator<Gesture>() {
public Gesture createFromParcel(Parcel in) { public Gesture createFromParcel(Parcel in) {
String str = in.readString(); Gesture gesture = null;
Gesture gesture = new Gesture(); final long gestureID = in.readLong();
gesture.createFromString(str);
final DataInputStream inStream = new DataInputStream(
new ByteArrayInputStream(in.createByteArray()));
try {
gesture = deserialize(inStream);
} catch (IOException e) {
Log.e(LOG_TAG, "Error reading Gesture from parcel:", e);
} finally {
GestureUtilities.closeStream(inStream);
}
if (gesture != null) {
gesture.mGestureID = gestureID;
}
return gesture; return gesture;
} }
@@ -273,35 +290,31 @@ public class Gesture implements Parcelable {
} }
}; };
/**
* Build a gesture from a byte array
*
* @param bytes
* @return the gesture
*/
static Gesture buildFromArray(byte[] bytes) {
String str = new String(bytes);
Gesture gesture = new Gesture();
gesture.createFromString(str);
return gesture;
}
/**
* Save a gesture to a byte array
*
* @param stroke
* @return the byte array
*/
static byte[] saveToArray(Gesture stroke) {
String str = stroke.toString();
return str.getBytes();
}
public void writeToParcel(Parcel out, int flags) { public void writeToParcel(Parcel out, int flags) {
out.writeString(toString()); out.writeLong(mGestureID);
boolean result = false;
final ByteArrayOutputStream byteStream =
new ByteArrayOutputStream(GestureConstants.IO_BUFFER_SIZE);
final DataOutputStream outStream = new DataOutputStream(byteStream);
try {
serialize(outStream);
result = true;
} catch (IOException e) {
Log.e(LOG_TAG, "Error writing Gesture to parcel:", e);
} finally {
GestureUtilities.closeStream(outStream);
GestureUtilities.closeStream(byteStream);
}
if (result) {
out.writeByteArray(byteStream.toByteArray());
}
} }
public int describeContents() { public int describeContents() {
return CONTENTS_FILE_DESCRIPTOR; return 0;
} }
} }

View File

@@ -17,16 +17,13 @@
package com.android.gesture; package com.android.gesture;
interface GestureConstants { interface GestureConstants {
static final String XML_TAG_LIBRARY = "library";
static final String XML_TAG_ENTRY = "entry";
static final String XML_TAG_GESTURE = "gesture";
static final String XML_TAG_STROKE = "stroke";
static final String XML_TAG_ID = "id";
static final String XML_TAG_NAME = "name";
static final String STRING_GESTURE_DELIIMITER = "#"; static final String STRING_GESTURE_DELIIMITER = "#";
static final String STRING_STROKE_DELIIMITER = ","; static final String STRING_STROKE_DELIIMITER = ",";
static final int STROKE_STRING_BUFFER_SIZE = 1024; static final int STROKE_STRING_BUFFER_SIZE = 1024;
static final int STROKE_POINT_BUFFER_SIZE = 100; // number of points static final int STROKE_POINT_BUFFER_SIZE = 100; // number of points
static final int IO_BUFFER_SIZE = 8 * 1024; // 8K
String LOG_TAG = "GestureLibrary"; static final int IO_BUFFER_SIZE = 32 * 1024; // 32K
static final String LOG_TAG = "Gestures";
} }

View File

@@ -16,16 +16,8 @@
package com.android.gesture; package com.android.gesture;
import android.util.Config;
import android.util.Log; import android.util.Log;
import android.util.Xml; import android.os.SystemClock;
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.BufferedInputStream;
import java.io.BufferedOutputStream; import java.io.BufferedOutputStream;
@@ -33,10 +25,12 @@ import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.PrintWriter; import java.io.DataOutputStream;
import java.io.DataInputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.Set; import java.util.Set;
import java.util.Map;
import static com.android.gesture.GestureConstants.LOG_TAG; import static com.android.gesture.GestureConstants.LOG_TAG;
@@ -44,10 +38,28 @@ import static com.android.gesture.GestureConstants.LOG_TAG;
* GestureLibrary maintains gesture examples and makes predictions on a new * GestureLibrary maintains gesture examples and makes predictions on a new
* gesture * gesture
*/ */
//
// File format for GestureLibrary:
//
// Nb. bytes Java type Description
// -----------------------------------
// Header
// 2 bytes short File format version number
// 4 bytes int Number of entries
// Entry
// X bytes UTF String Entry name
// 4 bytes int Number of gestures
// Gesture
// 8 bytes long Gesture ID
// 4 bytes int Number of strokes
// Stroke
// 4 bytes int Number of points
// Point
// 4 bytes float X coordinate of the point
// 4 bytes float Y coordinate of the point
// 8 bytes long Time stamp
//
public class GestureLibrary { public class GestureLibrary {
private static final String NAMESPACE = "";
public static final int SEQUENCE_INVARIANT = 1; public static final int SEQUENCE_INVARIANT = 1;
// when SEQUENCE_SENSITIVE is used, only single stroke gestures are currently allowed // when SEQUENCE_SENSITIVE is used, only single stroke gestures are currently allowed
public static final int SEQUENCE_SENSITIVE = 2; public static final int SEQUENCE_SENSITIVE = 2;
@@ -56,12 +68,16 @@ public class GestureLibrary {
public static final int ORIENTATION_INVARIANT = 1; public static final int ORIENTATION_INVARIANT = 1;
public static final int ORIENTATION_SENSITIVE = 2; public static final int ORIENTATION_SENSITIVE = 2;
private static final short FILE_FORMAT_VERSION = 1;
private static final boolean PROFILE_LOADING_SAVING = false;
private int mSequenceType = SEQUENCE_SENSITIVE; private int mSequenceType = SEQUENCE_SENSITIVE;
private int mOrientationStyle = ORIENTATION_SENSITIVE; private int mOrientationStyle = ORIENTATION_SENSITIVE;
private final String mGestureFileName; private final String mGestureFileName;
private final HashMap<String, ArrayList<Gesture>> mEntryName2gestures = private final HashMap<String, ArrayList<Gesture>> mNamedGestures =
new HashMap<String, ArrayList<Gesture>>(); new HashMap<String, ArrayList<Gesture>>();
private Learner mClassifier; private Learner mClassifier;
@@ -104,7 +120,7 @@ public class GestureLibrary {
* @return a set of strings * @return a set of strings
*/ */
public Set<String> getGestureEntries() { public Set<String> getGestureEntries() {
return mEntryName2gestures.keySet(); return mNamedGestures.keySet();
} }
/** /**
@@ -128,10 +144,10 @@ public class GestureLibrary {
if (entryName == null || entryName.length() == 0) { if (entryName == null || entryName.length() == 0) {
return; return;
} }
ArrayList<Gesture> gestures = mEntryName2gestures.get(entryName); ArrayList<Gesture> gestures = mNamedGestures.get(entryName);
if (gestures == null) { if (gestures == null) {
gestures = new ArrayList<Gesture>(); gestures = new ArrayList<Gesture>();
mEntryName2gestures.put(entryName, gestures); mNamedGestures.put(entryName, gestures);
} }
gestures.add(gesture); gestures.add(gesture);
mClassifier.addInstance(Instance.createInstance(mSequenceType, gesture, entryName)); mClassifier.addInstance(Instance.createInstance(mSequenceType, gesture, entryName));
@@ -146,7 +162,7 @@ public class GestureLibrary {
* @param gesture * @param gesture
*/ */
public void removeGesture(String entryName, Gesture gesture) { public void removeGesture(String entryName, Gesture gesture) {
ArrayList<Gesture> gestures = mEntryName2gestures.get(entryName); ArrayList<Gesture> gestures = mNamedGestures.get(entryName);
if (gestures == null) { if (gestures == null) {
return; return;
} }
@@ -155,7 +171,7 @@ public class GestureLibrary {
// if there are no more samples, remove the entry automatically // if there are no more samples, remove the entry automatically
if (gestures.isEmpty()) { if (gestures.isEmpty()) {
mEntryName2gestures.remove(entryName); mNamedGestures.remove(entryName);
} }
mClassifier.removeInstance(gesture.getID()); mClassifier.removeInstance(gesture.getID());
@@ -169,7 +185,7 @@ public class GestureLibrary {
* @param entryName the entry name * @param entryName the entry name
*/ */
public void removeEntireEntry(String entryName) { public void removeEntireEntry(String entryName) {
mEntryName2gestures.remove(entryName); mNamedGestures.remove(entryName);
mClassifier.removeInstances(entryName); mClassifier.removeInstances(entryName);
mChanged = true; mChanged = true;
} }
@@ -181,7 +197,7 @@ public class GestureLibrary {
* @return the list of gestures that is under this name * @return the list of gestures that is under this name
*/ */
public ArrayList<Gesture> getGestures(String entryName) { public ArrayList<Gesture> getGestures(String entryName) {
ArrayList<Gesture> gestures = mEntryName2gestures.get(entryName); ArrayList<Gesture> gestures = mNamedGestures.get(entryName);
if (gestures != null) { if (gestures != null) {
return new ArrayList<Gesture>(gestures); return new ArrayList<Gesture>(gestures);
} else { } else {
@@ -197,8 +213,8 @@ public class GestureLibrary {
return true; return true;
} }
boolean result= false; boolean result = false;
PrintWriter writer = null; DataOutputStream out = null;
try { try {
File file = new File(mGestureFileName); File file = new File(mGestureFileName);
@@ -208,40 +224,48 @@ public class GestureLibrary {
} }
} }
writer = new PrintWriter(new BufferedOutputStream(new FileOutputStream( long start;
mGestureFileName), GestureConstants.IO_BUFFER_SIZE)); if (PROFILE_LOADING_SAVING) {
start = SystemClock.elapsedRealtime();
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); final HashMap<String, ArrayList<Gesture>> maps = mNamedGestures;
serializer.endDocument();
serializer.flush(); out = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(file),
GestureConstants.IO_BUFFER_SIZE));
// Write version number
out.writeShort(FILE_FORMAT_VERSION);
// Write number of entries
out.writeInt(maps.size());
for (Map.Entry<String, ArrayList<Gesture>> entry : maps.entrySet()) {
final String key = entry.getKey();
final ArrayList<Gesture> examples = entry.getValue();
final int count = examples.size();
// Write entry name
out.writeUTF(key);
// Write number of examples for this entry
out.writeInt(count);
for (int i = 0; i < count; i++) {
examples.get(i).serialize(out);
}
}
out.flush();
if (PROFILE_LOADING_SAVING) {
long end = SystemClock.elapsedRealtime();
Log.d(LOG_TAG, "Saving gestures library = " + (end - start) + " ms");
}
mChanged = false; mChanged = false;
result = true; result = true;
} catch (IOException ex) { } catch (IOException ex) {
Log.d(LOG_TAG, "Failed to save gestures:", ex); Log.d(LOG_TAG, "Failed to save gestures:", ex);
} finally { } finally {
GestureUtilities.closeStream(writer); GestureUtilities.closeStream(out);
} }
return result; return result;
@@ -255,17 +279,30 @@ public class GestureLibrary {
final File file = new File(mGestureFileName); final File file = new File(mGestureFileName);
if (file.exists()) { if (file.exists()) {
BufferedInputStream in = null; DataInputStream in = null;
try { try {
if (Config.DEBUG) { in = new DataInputStream(new BufferedInputStream(
Log.v(LOG_TAG, "Load from " + mGestureFileName); new FileInputStream(mGestureFileName), GestureConstants.IO_BUFFER_SIZE));
long start;
if (PROFILE_LOADING_SAVING) {
start = SystemClock.elapsedRealtime();
} }
in = new BufferedInputStream(new FileInputStream(
mGestureFileName), GestureConstants.IO_BUFFER_SIZE); // Read file format version number
Xml.parse(in, Encoding.ISO_8859_1, new CompactInkHandler()); final short versionNumber = in.readShort();
switch (versionNumber) {
case 1:
readFormatV1(in);
break;
}
if (PROFILE_LOADING_SAVING) {
long end = SystemClock.elapsedRealtime();
Log.d(LOG_TAG, "Loading gestures library = " + (end - start) + " ms");
}
result = true; result = true;
} catch (SAXException ex) {
Log.d(LOG_TAG, "Failed to load gestures:", ex);
} catch (IOException ex) { } catch (IOException ex) {
Log.d(LOG_TAG, "Failed to load gestures:", ex); Log.d(LOG_TAG, "Failed to load gestures:", ex);
} finally { } finally {
@@ -276,69 +313,28 @@ public class GestureLibrary {
return result; return result;
} }
private class CompactInkHandler implements ContentHandler { private void readFormatV1(DataInputStream in) throws IOException {
final StringBuilder mBuffer = new StringBuilder(GestureConstants.STROKE_STRING_BUFFER_SIZE); final Learner classifier = mClassifier;
final HashMap<String, ArrayList<Gesture>> namedGestures = mNamedGestures;
namedGestures.clear();
String mEntryName; // Number of entries in the library
final int entriesCount = in.readInt();
Gesture mCurrentGesture = null; for (int i = 0; i < entriesCount; i++) {
ArrayList<Gesture> mGestures; // Entry name
final String name = in.readUTF();
// Number of gestures
final int gestureCount = in.readInt();
CompactInkHandler() { final ArrayList<Gesture> gestures = new ArrayList<Gesture>(gestureCount);
} for (int j = 0; j < gestureCount; j++) {
final Gesture gesture = Gesture.deserialize(in);
public void characters(char[] ch, int start, int length) { gestures.add(gesture);
mBuffer.append(ch, start, length); classifier.addInstance(Instance.createInstance(mSequenceType, gesture, name));
}
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(mSequenceType,
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) { namedGestures.put(name, gestures);
}
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) {
} }
} }
} }

View File

@@ -16,6 +16,9 @@
package com.android.gesture; package com.android.gesture;
import java.io.DataInputStream;
import java.io.IOException;
/** /**
* A timed point of a gesture stroke * A timed point of a gesture stroke
*/ */
@@ -31,4 +34,13 @@ public class GesturePoint {
this.y = y; this.y = y;
timestamp = t; timestamp = t;
} }
static GesturePoint deserialize(DataInputStream in) throws IOException {
// Read X and Y
final float x = in.readFloat();
final float y = in.readFloat();
// Read timestamp
final long timeStamp = in.readLong();
return new GesturePoint(x, y, timeStamp);
}
} }

View File

@@ -22,9 +22,9 @@ import android.graphics.Paint;
import android.graphics.Path; import android.graphics.Path;
import android.graphics.RectF; import android.graphics.RectF;
import org.xmlpull.v1.XmlSerializer;
import java.io.IOException; import java.io.IOException;
import java.io.DataOutputStream;
import java.io.DataInputStream;
import java.util.ArrayList; import java.util.ArrayList;
/** /**
@@ -167,57 +167,35 @@ public class GestureStroke {
return path; return path;
} }
/** void serialize(DataOutputStream out) throws IOException {
* Save the gesture stroke as XML final float[] pts = points;
* final long[] times = timestamps;
* @param namespace final int count = points.length;
* @param serializer
* @throws IOException // Write number of points
*/ out.writeInt(count / 2);
void toXML(String namespace, XmlSerializer serializer) throws IOException {
serializer.startTag(namespace, GestureConstants.XML_TAG_STROKE); for (int i = 0; i < count; i += 2) {
serializer.text(toString()); // Write X
serializer.endTag(namespace, GestureConstants.XML_TAG_STROKE); out.writeFloat(pts[i]);
// Write Y
out.writeFloat(pts[i + 1]);
// Write timestamp
out.writeLong(times[i / 2]);
}
} }
/** static GestureStroke deserialize(DataInputStream in) throws IOException {
* Create a gesture stroke from a string // Number of points
* final int count = in.readInt();
* @param str
* @return the gesture stroke
*/
public static GestureStroke createFromString(String str) {
final ArrayList<GesturePoint> points = new ArrayList<GesturePoint>(
GestureConstants.STROKE_POINT_BUFFER_SIZE);
int endIndex; final ArrayList<GesturePoint> points = new ArrayList<GesturePoint>(count);
int startIndex = 0; for (int i = 0; i < count; i++) {
points.add(GesturePoint.deserialize(in));
while ((endIndex =
str.indexOf(GestureConstants.STRING_STROKE_DELIIMITER, startIndex + 1)) != -1) {
// parse x
String token = str.substring(startIndex, endIndex);
float x = Float.parseFloat(token);
startIndex = endIndex + 1;
// parse y
endIndex = str.indexOf(GestureConstants.STRING_STROKE_DELIIMITER, startIndex + 1);
token = str.substring(startIndex, endIndex);
float y = Float.parseFloat(token);
startIndex = endIndex + 1;
// parse t
endIndex = str.indexOf(GestureConstants.STRING_STROKE_DELIIMITER, startIndex + 1);
token = str.substring(startIndex, endIndex);
long time = Long.parseLong(token);
startIndex = endIndex + 1;
points.add(new GesturePoint(x, y, time));
} }
return new GestureStroke(points); return new GestureStroke(points);
} }
/** /**
* Convert the stroke to string * Convert the stroke to string

View File

@@ -18,6 +18,7 @@ package com.android.gesture;
import android.graphics.RectF; import android.graphics.RectF;
import android.graphics.Matrix; import android.graphics.Matrix;
import android.util.Log;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@@ -42,7 +43,7 @@ final class GestureUtilities {
try { try {
stream.close(); stream.close();
} catch (IOException e) { } catch (IOException e) {
android.util.Log.e(LOG_TAG, "Could not close stream", e); Log.e(LOG_TAG, "Could not close stream", e);
} }
} }
} }
@@ -56,24 +57,25 @@ final class GestureUtilities {
float sx = targetPatchSize / rect.width(); float sx = targetPatchSize / rect.width();
float sy = targetPatchSize / rect.height(); float sy = targetPatchSize / rect.height();
float scale = sx < sy ? sx : sy; float scale = sx < sy ? sx : sy;
android.graphics.Matrix trans = new android.graphics.Matrix();
trans.setScale(scale, scale);
android.graphics.Matrix translate1 = new android.graphics.Matrix();
translate1.setTranslate(-rect.centerX(), -rect.centerY());
trans.preConcat(translate1);
android.graphics.Matrix translate2 = new android.graphics.Matrix();
translate2.setTranslate(targetPatchSize / 2, targetPatchSize / 2);
trans.postConcat(translate2);
ArrayList<GestureStroke> strokes = gesture.getStrokes(); Matrix trans = new Matrix();
int count = strokes.size(); trans.setScale(scale, scale);
trans.preTranslate(-rect.centerX(), -rect.centerY());
trans.postTranslate(targetPatchSize / 2, targetPatchSize / 2);
final ArrayList<GestureStroke> strokes = gesture.getStrokes();
final int count = strokes.size();
int size; int size;
float xpos; float xpos;
float ypos; float ypos;
for (int index = 0; index < count; index++) { for (int index = 0; index < count; index++) {
GestureStroke stroke = strokes.get(index); final GestureStroke stroke = strokes.get(index);
size = stroke.points.length; size = stroke.points.length;
float[] pts = new float[size];
final float[] pts = new float[size];
trans.mapPoints(pts, 0, stroke.points, 0, size / 2); trans.mapPoints(pts, 0, stroke.points, 0, size / 2);
float segmentEndX = -1; float segmentEndX = -1;
float segmentEndY = -1; float segmentEndY = -1;

View File

@@ -16,6 +16,8 @@
package com.android.gesture; package com.android.gesture;
import android.graphics.Matrix;
/** /**
* An instance represents a sample if the label is available or a query if the * An instance represents a sample if the label is available or a query if the
* label is null. * label is null.
@@ -47,10 +49,12 @@ class Instance {
private void normalize() { private void normalize() {
float[] sample = vector; float[] sample = vector;
float sum = 0; float sum = 0;
int size = sample.length; int size = sample.length;
for (int i = 0; i < size; i++) { for (int i = 0; i < size; i++) {
sum += sample[i] * sample[i]; sum += sample[i] * sample[i];
} }
float magnitude = (float) Math.sqrt(sum); float magnitude = (float) Math.sqrt(sum);
for (int i = 0; i < size; i++) { for (int i = 0; i < size; i++) {
sample[i] /= magnitude; sample[i] /= magnitude;
@@ -100,12 +104,11 @@ class Instance {
} }
} }
android.graphics.Matrix m = new android.graphics.Matrix(); Matrix m = new Matrix();
m.setTranslate(-center[0], -center[1]); m.setTranslate(-center[0], -center[1]);
android.graphics.Matrix rotation = new android.graphics.Matrix(); m.postRotate(adjustment);
rotation.setRotate(adjustment);
m.postConcat(rotation);
m.mapPoints(pts); m.mapPoints(pts);
return pts; return pts;
} }

View File

@@ -21,7 +21,6 @@ import android.content.res.Resources;
import android.util.Log; import android.util.Log;
import java.io.BufferedInputStream; import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataInputStream; import java.io.DataInputStream;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
@@ -29,9 +28,9 @@ import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
public class LetterRecognizer { import static com.android.gesture.GestureConstants.LOG_TAG;
private static final String LOG_TAG = "LetterRecognizer";
public class LetterRecognizer {
public final static int LATIN_LOWERCASE = 0; public final static int LATIN_LOWERCASE = 0;
private SigmoidUnit[] mHiddenLayer; private SigmoidUnit[] mHiddenLayer;
@@ -41,7 +40,7 @@ public class LetterRecognizer {
private final int mPatchSize; private final int mPatchSize;
static final String GESTURE_FILE_NAME = "letters.xml"; static final String GESTURE_FILE_NAME = "letters.gestures";
private GestureLibrary mGestureLibrary; private GestureLibrary mGestureLibrary;
private final static int ADJUST_RANGE = 3; private final static int ADJUST_RANGE = 3;
@@ -151,7 +150,8 @@ public class LetterRecognizer {
LetterRecognizer classifier = null; LetterRecognizer classifier = null;
try { try {
in = new DataInputStream(new BufferedInputStream(resources.openRawResource(resourceID))); in = new DataInputStream(new BufferedInputStream(resources.openRawResource(resourceID),
GestureConstants.IO_BUFFER_SIZE));
final int iCount = in.readInt(); final int iCount = in.readInt();
final int hCount = in.readInt(); final int hCount = in.readInt();

View File

@@ -49,7 +49,7 @@ public class GestureEntry extends Activity {
private static final String PARCEL_KEY = "gesture"; private static final String PARCEL_KEY = "gesture";
static final String GESTURE_FILE_NAME = Environment.getExternalStorageDirectory().getAbsolutePath() static final String GESTURE_FILE_NAME = Environment.getExternalStorageDirectory().getAbsolutePath()
+ File.separator + "gestureEntry.xml"; + File.separator + "demo_library.gestures";
private static final int DIALOG_NEW_ENTRY = 1; private static final int DIALOG_NEW_ENTRY = 1;