Merge change 2285 into donut

* changes:
  Move the Gestures API to the framework in android.gesture.
This commit is contained in:
Android (Google) Code Review
2009-05-21 18:16:51 -07:00
23 changed files with 1369 additions and 303 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package com.android.gesture; package android.gesture;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.Canvas; import android.graphics.Canvas;
@@ -32,8 +32,6 @@ import java.io.ByteArrayOutputStream;
import java.io.ByteArrayInputStream; 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
*/ */
@@ -242,26 +240,6 @@ public class Gesture implements Parcelable {
return gesture; return gesture;
} }
/**
* Convert the gesture to string
*/
@Override
public String toString() {
final StringBuilder str = new StringBuilder();
str.append(mGestureID);
final ArrayList<GestureStroke> strokes = mStrokes;
final int count = strokes.size();
for (int i = 0; i < count; i++) {
final GestureStroke stroke = strokes.get(i);
str.append(GestureConstants.STRING_GESTURE_DELIIMITER);
str.append(stroke.toString());
}
return str.toString();
}
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) {
Gesture gesture = null; Gesture gesture = null;
@@ -273,7 +251,7 @@ public class Gesture implements Parcelable {
try { try {
gesture = deserialize(inStream); gesture = deserialize(inStream);
} catch (IOException e) { } catch (IOException e) {
Log.e(LOG_TAG, "Error reading Gesture from parcel:", e); Log.e(GestureConstants.LOG_TAG, "Error reading Gesture from parcel:", e);
} finally { } finally {
GestureUtilities.closeStream(inStream); GestureUtilities.closeStream(inStream);
} }
@@ -302,7 +280,7 @@ public class Gesture implements Parcelable {
serialize(outStream); serialize(outStream);
result = true; result = true;
} catch (IOException e) { } catch (IOException e) {
Log.e(LOG_TAG, "Error writing Gesture to parcel:", e); Log.e(GestureConstants.LOG_TAG, "Error writing Gesture to parcel:", e);
} finally { } finally {
GestureUtilities.closeStream(outStream); GestureUtilities.closeStream(outStream);
GestureUtilities.closeStream(byteStream); GestureUtilities.closeStream(byteStream);

View File

@@ -14,12 +14,9 @@
* limitations under the License. * limitations under the License.
*/ */
package com.android.gesture; package android.gesture;
interface GestureConstants { interface GestureConstants {
static final String STRING_GESTURE_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

View File

@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package com.android.gesture; package android.gesture;
import android.util.Log; import android.util.Log;
import android.os.SystemClock; import android.os.SystemClock;
@@ -32,7 +32,7 @@ import java.util.HashMap;
import java.util.Set; import java.util.Set;
import java.util.Map; import java.util.Map;
import static com.android.gesture.GestureConstants.LOG_TAG; import static 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
@@ -190,7 +190,7 @@ public class GestureLibrary {
* *
* @param entryName the entry name * @param entryName the entry name
*/ */
public void removeEntireEntry(String entryName) { public void removeEntry(String entryName) {
mNamedGestures.remove(entryName); mNamedGestures.remove(entryName);
mClassifier.removeInstances(entryName); mClassifier.removeInstances(entryName);
mChanged = true; mChanged = true;

View File

@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package com.android.gesture; package android.gesture;
import android.content.Context; import android.content.Context;
import android.graphics.Bitmap; import android.graphics.Bitmap;
@@ -23,6 +23,7 @@ import android.graphics.Canvas;
import android.graphics.Paint; import android.graphics.Paint;
import android.graphics.Path; import android.graphics.Path;
import android.graphics.Rect; import android.graphics.Rect;
import android.graphics.Color;
import android.os.Handler; import android.os.Handler;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.MotionEvent; import android.view.MotionEvent;
@@ -35,7 +36,7 @@ import java.util.ArrayList;
* widgets. The view can also be opaque. * widgets. The view can also be opaque.
*/ */
public class GestureOverlay extends View { public class GestureOverlayView extends View {
static final float TOUCH_TOLERANCE = 3; static final float TOUCH_TOLERANCE = 3;
// TODO: Move all these values into XML attributes // TODO: Move all these values into XML attributes
@@ -52,20 +53,22 @@ public class GestureOverlay extends View {
private static final boolean DITHER_FLAG = true; private static final boolean DITHER_FLAG = true;
public static final int DEFAULT_GESTURE_COLOR = 0xFFFFFF00; public static final int DEFAULT_GESTURE_COLOR = 0xFFFFFF00;
public static final int DEFAULT_UNCERTAIN_GESTURE_COLOR = Color.argb(60, 255, 255, 0);
private static final int REFRESH_RANGE = 10; private static final int REFRESH_RANGE = 10;
private static final BlurMaskFilter BLUR_MASK_FILTER = private static final BlurMaskFilter BLUR_MASK_FILTER =
new BlurMaskFilter(1, BlurMaskFilter.Blur.NORMAL); new BlurMaskFilter(1, BlurMaskFilter.Blur.NORMAL);
// double buffering
private Paint mGesturePaint; private Paint mGesturePaint;
private final Paint mBitmapPaint = new Paint(Paint.DITHER_FLAG); private final Paint mBitmapPaint = new Paint(Paint.DITHER_FLAG);
private Bitmap mBitmap; // with transparent background private Bitmap mBitmap; // with transparent background
private Canvas mBitmapCanvas; private Canvas mBitmapCanvas;
private int mCertainGestureColor = DEFAULT_GESTURE_COLOR;
private int mUncertainGestureColor = DEFAULT_UNCERTAIN_GESTURE_COLOR;
// for rendering immediate ink feedback // for rendering immediate ink feedback
private Rect mInvalidRect = new Rect(); private Rect mInvalidRect = new Rect();
@@ -81,7 +84,7 @@ public class GestureOverlay extends View {
private Gesture mCurrentGesture = null; private Gesture mCurrentGesture = null;
// TODO: Make this a list of WeakReferences // TODO: Make this a list of WeakReferences
private final ArrayList<GestureListener> mGestureListeners = new ArrayList<GestureListener>(); private final ArrayList<OnGestureListener> mOnGestureListeners = new ArrayList<OnGestureListener>();
private ArrayList<GesturePoint> mPointBuffer = null; private ArrayList<GesturePoint> mPointBuffer = null;
// fading out effect // fading out effect
@@ -107,12 +110,12 @@ public class GestureOverlay extends View {
} }
}; };
public GestureOverlay(Context context) { public GestureOverlayView(Context context) {
super(context); super(context);
init(); init();
} }
public GestureOverlay(Context context, AttributeSet attrs) { public GestureOverlayView(Context context, AttributeSet attrs) {
super(context, attrs); super(context, attrs);
init(); init();
} }
@@ -130,7 +133,7 @@ public class GestureOverlay extends View {
* *
* @param color * @param color
*/ */
public void setGestureColor(int color) { public void setGestureDrawingColor(int color) {
mGesturePaint.setColor(color); mGesturePaint.setColor(color);
if (mCurrentGesture != null) { if (mCurrentGesture != null) {
mBitmap.eraseColor(TRANSPARENT_BACKGROUND); mBitmap.eraseColor(TRANSPARENT_BACKGROUND);
@@ -138,6 +141,22 @@ public class GestureOverlay extends View {
} }
} }
public void setGestureColor(int color) {
mCertainGestureColor = color;
}
public void setUncertainGestureColor(int color) {
mUncertainGestureColor = color;
}
public int getUncertainGestureColor() {
return mUncertainGestureColor;
}
public int getGestureColor() {
return mCertainGestureColor;
}
/** /**
* Set the gesture to be shown in the view * Set the gesture to be shown in the view
* *
@@ -199,12 +218,12 @@ public class GestureOverlay extends View {
} }
} }
public void addGestureListener(GestureListener listener) { public void addOnGestureListener(OnGestureListener listener) {
mGestureListeners.add(listener); mOnGestureListeners.add(listener);
} }
public void removeGestureListener(GestureListener listener) { public void removeOnGestureListener(OnGestureListener listener) {
mGestureListeners.remove(listener); mOnGestureListeners.remove(listener);
} }
@Override @Override
@@ -281,11 +300,11 @@ public class GestureOverlay extends View {
private Rect touchStart(MotionEvent event) { private Rect touchStart(MotionEvent event) {
// pass the event to handlers // pass the event to handlers
final ArrayList<GestureListener> listeners = mGestureListeners; final ArrayList<OnGestureListener> listeners = mOnGestureListeners;
final int count = listeners.size(); final int count = listeners.size();
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
GestureListener listener = listeners.get(i); OnGestureListener listener = listeners.get(i);
listener.onStartGesture(this, event); listener.onGestureStarted(this, event);
} }
// if there is fading out going on, stop it. // if there is fading out going on, stop it.
@@ -358,7 +377,7 @@ public class GestureOverlay extends View {
mPointBuffer.add(new GesturePoint(x, y, event.getEventTime())); mPointBuffer.add(new GesturePoint(x, y, event.getEventTime()));
// pass the event to handlers // pass the event to handlers
final ArrayList<GestureListener> listeners = mGestureListeners; final ArrayList<OnGestureListener> listeners = mOnGestureListeners;
final int count = listeners.size(); final int count = listeners.size();
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
listeners.get(i).onGesture(this, event); listeners.get(i).onGesture(this, event);
@@ -377,14 +396,24 @@ public class GestureOverlay extends View {
mGesturePaint.setMaskFilter(null); mGesturePaint.setMaskFilter(null);
// pass the event to handlers // pass the event to handlers
final ArrayList<GestureListener> listeners = mGestureListeners; final ArrayList<OnGestureListener> listeners = mOnGestureListeners;
final int count = listeners.size(); final int count = listeners.size();
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
listeners.get(i).onFinishGesture(this, event); listeners.get(i).onGestureEnded(this, event);
} }
mPath = null; mPath = null;
mPointBuffer = null; mPointBuffer = null;
} }
/**
* An interface for processing gesture events
*/
public static interface OnGestureListener {
public void onGestureStarted(GestureOverlayView overlay, MotionEvent event);
public void onGesture(GestureOverlayView overlay, MotionEvent event);
public void onGestureEnded(GestureOverlayView overlay, MotionEvent event);
}
} }

View File

@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package com.android.gesture; package android.gesture;
import java.io.DataInputStream; import java.io.DataInputStream;
import java.io.IOException; import java.io.IOException;

View File

@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package com.android.gesture; package android.gesture;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.graphics.Matrix; import android.graphics.Matrix;
@@ -126,16 +126,16 @@ public class GestureStroke {
* @param width the width of the bounding box of the target path * @param width the width of the bounding box of the target path
* @param height the height of the bounding box of the target path * @param height the height of the bounding box of the target path
* @param numSample the number of points needed * @param numSample the number of points needed
*
* @return the path * @return the path
*/ */
public Path toPath(float width, float height, int numSample) { public Path toPath(float width, float height, int numSample) {
final float[] pts = GestureUtilities.temporalSampling(this, numSample); final float[] pts = GestureUtilities.temporalSampling(this, numSample);
final RectF rect = boundingBox; final RectF rect = boundingBox;
final float scale = height / rect.height();
final Matrix matrix = new Matrix(); final Matrix matrix = new Matrix();
matrix.setTranslate(-rect.left, -rect.top); matrix.setTranslate(-rect.left, -rect.top);
matrix.postScale(scale, scale); matrix.postScale(width / rect.width(), height / rect.height());
matrix.mapPoints(pts); matrix.mapPoints(pts);
float mX = 0; float mX = 0;
@@ -156,7 +156,8 @@ public class GestureStroke {
} else { } else {
float dx = Math.abs(x - mX); float dx = Math.abs(x - mX);
float dy = Math.abs(y - mY); float dy = Math.abs(y - mY);
if (dx >= GestureOverlay.TOUCH_TOLERANCE || dy >= GestureOverlay.TOUCH_TOLERANCE) { if (dx >= GestureOverlayView.TOUCH_TOLERANCE ||
dy >= GestureOverlayView.TOUCH_TOLERANCE) {
path.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2); path.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2);
mX = x; mX = x;
mY = y; mY = y;
@@ -198,29 +199,10 @@ public class GestureStroke {
} }
/** /**
* Convert the stroke to string * Invalidate the cached path that is used to render the stroke
*/ */
@Override public void clearPath() {
public String toString() { if (mCachedPath != null) mCachedPath.rewind();
final StringBuilder str = new StringBuilder(GestureConstants.STROKE_STRING_BUFFER_SIZE);
final float[] pts = points;
final long[] times = timestamps;
final int count = points.length;
for (int i = 0; i < count; i += 2) {
str.append(pts[i]).append(GestureConstants.STRING_STROKE_DELIIMITER);
str.append(pts[i + 1]).append(GestureConstants.STRING_STROKE_DELIIMITER);
str.append(times[i / 2]).append(GestureConstants.STRING_STROKE_DELIIMITER);
}
return str.toString();
}
/**
* Invalidate the cached path that is used for rendering the stroke
*/
public void invalidate() {
mCachedPath = null;
} }
/** /**

View File

@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package com.android.gesture; package android.gesture;
import android.graphics.RectF; import android.graphics.RectF;
import android.graphics.Matrix; import android.graphics.Matrix;
@@ -25,7 +25,7 @@ import java.util.Arrays;
import java.io.Closeable; import java.io.Closeable;
import java.io.IOException; import java.io.IOException;
import static com.android.gesture.GestureConstants.*; import static android.gesture.GestureConstants.*;
final class GestureUtilities { final class GestureUtilities {
private static final int TEMPORAL_SAMPLING_RATE = 16; private static final int TEMPORAL_SAMPLING_RATE = 16;

View File

@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package com.android.gesture; package android.gesture;
import android.graphics.Matrix; import android.graphics.Matrix;

View File

@@ -14,10 +14,11 @@
* limitations under the License. * limitations under the License.
*/ */
package com.android.gesture; package android.gesture;
import android.util.Config; import android.util.Config;
import android.util.Log; import android.util.Log;
import static android.gesture.GestureConstants.*;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
@@ -30,9 +31,6 @@ import java.util.TreeMap;
*/ */
class InstanceLearner extends Learner { class InstanceLearner extends Learner {
private static final String LOGTAG = "InstanceLearner";
@Override @Override
ArrayList<Prediction> classify(int gestureType, float[] vector) { ArrayList<Prediction> classify(int gestureType, float[] vector) {
ArrayList<Prediction> predictions = new ArrayList<Prediction>(); ArrayList<Prediction> predictions = new ArrayList<Prediction>();
@@ -63,19 +61,15 @@ class InstanceLearner extends Learner {
} }
double sum = 0; double sum = 0;
Iterator<String> lableIterator = label2score.keySet().iterator(); for (String name : label2score.keySet()) {
while (lableIterator.hasNext()) {
String name = lableIterator.next();
double score = label2score.get(name); double score = label2score.get(name);
sum += score; sum += score;
predictions.add(new Prediction(name, score)); predictions.add(new Prediction(name, score));
} }
// normalize // normalize
Iterator<Prediction> predictionIterator = predictions.iterator(); for (Prediction prediction : predictions) {
while (predictionIterator.hasNext()) { prediction.score /= sum;
Prediction name = predictionIterator.next();
name.score /= sum;
} }
Collections.sort(predictions, new Comparator<Prediction>() { Collections.sort(predictions, new Comparator<Prediction>() {
@@ -92,14 +86,6 @@ class InstanceLearner extends Learner {
} }
}); });
if (Config.DEBUG) {
predictionIterator = predictions.iterator();
while (predictionIterator.hasNext()) {
Prediction name = predictionIterator.next();
Log.v(LOGTAG, "prediction [" + name.name + " = " + name.score + "]");
}
}
return predictions; return predictions;
} }
} }

View File

@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package com.android.gesture; package android.gesture;
import java.util.ArrayList; import java.util.ArrayList;

View File

@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package com.android.gesture; package android.gesture;
import android.content.Context; import android.content.Context;
import android.content.res.Resources; import android.content.res.Resources;
@@ -28,10 +28,13 @@ import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
import static com.android.gesture.GestureConstants.LOG_TAG; import static android.gesture.GestureConstants.LOG_TAG;
public class LetterRecognizer { public class LetterRecognizer {
public final static int LATIN_LOWERCASE = 0; public final static int RECOGNIZER_LATIN_LOWERCASE = 0;
static final String GESTURE_FILE_NAME = "letters.gestures";
private final static int ADJUST_RANGE = 3;
private SigmoidUnit[] mHiddenLayer; private SigmoidUnit[] mHiddenLayer;
private SigmoidUnit[] mOutputLayer; private SigmoidUnit[] mOutputLayer;
@@ -40,59 +43,57 @@ public class LetterRecognizer {
private final int mPatchSize; private final int mPatchSize;
static final String GESTURE_FILE_NAME = "letters.gestures"; private GestureLibrary mGestureLibrary;
private GestureLibrary mGestureLibrary;
private final static int ADJUST_RANGE = 3;
private static class SigmoidUnit { private static class SigmoidUnit {
final float[] mWeights; final float[] mWeights;
private boolean mComputed;
private float mResult;
SigmoidUnit(float[] weights) { SigmoidUnit(float[] weights) {
mWeights = weights; mWeights = weights;
} }
private float compute(float[] inputs) { private float compute(float[] inputs) {
float sum = 0; if (!mComputed) {
float sum = 0;
final int count = inputs.length; final int count = inputs.length;
final float[] weights = mWeights; final float[] weights = mWeights;
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
sum += inputs[i] * weights[i]; sum += inputs[i] * weights[i];
}
sum += weights[weights.length - 1];
mResult = 1.0f / (float) (1 + Math.exp(-sum));
mComputed = true;
} }
sum += weights[weights.length - 1]; return mResult;
return 1.0f / (float) (1 + Math.exp(-sum));
} }
} }
private LetterRecognizer(int numOfInput, int numOfHidden, String[] classes) {
mPatchSize = (int)Math.sqrt(numOfInput);
mHiddenLayer = new SigmoidUnit[numOfHidden];
mClasses = classes;
mOutputLayer = new SigmoidUnit[classes.length];
}
public void save() {
mGestureLibrary.save();
}
public static LetterRecognizer getLetterRecognizer(Context context, int type) { public static LetterRecognizer getLetterRecognizer(Context context, int type) {
switch (type) { switch (type) {
case LATIN_LOWERCASE: { case RECOGNIZER_LATIN_LOWERCASE: {
return createFromResource(context, com.android.internal.R.raw.latin_lowercase); return createFromResource(context, com.android.internal.R.raw.latin_lowercase);
} }
} }
return null; return null;
} }
private LetterRecognizer(int numOfInput, int numOfHidden, String[] classes) {
mPatchSize = (int) Math.sqrt(numOfInput);
mHiddenLayer = new SigmoidUnit[numOfHidden];
mClasses = classes;
mOutputLayer = new SigmoidUnit[classes.length];
}
public ArrayList<Prediction> recognize(Gesture gesture) { public ArrayList<Prediction> recognize(Gesture gesture) {
float[] query = GestureUtilities.spatialSampling(gesture, mPatchSize); float[] query = GestureUtilities.spatialSampling(gesture, mPatchSize);
ArrayList<Prediction> predictions = classify(query); ArrayList<Prediction> predictions = classify(query);
if (mGestureLibrary != null) { adjustPrediction(gesture, predictions);
adjustPrediction(gesture, predictions);
}
return predictions; return predictions;
} }
@@ -153,38 +154,14 @@ public class LetterRecognizer {
in = new DataInputStream(new BufferedInputStream(resources.openRawResource(resourceID), in = new DataInputStream(new BufferedInputStream(resources.openRawResource(resourceID),
GestureConstants.IO_BUFFER_SIZE)); GestureConstants.IO_BUFFER_SIZE));
final int iCount = in.readInt(); final int version = in.readShort();
final int hCount = in.readInt();
final int oCount = in.readInt();
final String[] classes = new String[oCount]; switch (version) {
for (int i = 0; i < classes.length; i++) { case 1:
classes[i] = in.readUTF(); classifier = readV1(in);
break;
} }
classifier = new LetterRecognizer(iCount, hCount, classes);
SigmoidUnit[] hiddenLayer = new SigmoidUnit[hCount];
SigmoidUnit[] outputLayer = new SigmoidUnit[oCount];
for (int i = 0; i < hCount; i++) {
float[] weights = new float[iCount + 1];
for (int j = 0; j <= iCount; j++) {
weights[j] = in.readFloat();
}
hiddenLayer[i] = new SigmoidUnit(weights);
}
for (int i = 0; i < oCount; i++) {
float[] weights = new float[hCount + 1];
for (int j = 0; j <= hCount; j++) {
weights[j] = in.readFloat();
}
outputLayer[i] = new SigmoidUnit(weights);
}
classifier.mHiddenLayer = hiddenLayer;
classifier.mOutputLayer = outputLayer;
} catch (IOException e) { } catch (IOException e) {
Log.d(LOG_TAG, "Failed to load handwriting data:", e); Log.d(LOG_TAG, "Failed to load handwriting data:", e);
} finally { } finally {
@@ -193,9 +170,65 @@ public class LetterRecognizer {
return classifier; return classifier;
} }
public void enablePersonalization(boolean enable) { private static LetterRecognizer readV1(DataInputStream in) throws IOException {
if (enable) {
final int iCount = in.readInt();
final int hCount = in.readInt();
final int oCount = in.readInt();
final String[] classes = new String[oCount];
for (int i = 0; i < classes.length; i++) {
classes[i] = in.readUTF();
}
final LetterRecognizer classifier = new LetterRecognizer(iCount, hCount, classes);
final SigmoidUnit[] hiddenLayer = new SigmoidUnit[hCount];
final SigmoidUnit[] outputLayer = new SigmoidUnit[oCount];
for (int i = 0; i < hCount; i++) {
final float[] weights = new float[iCount + 1];
for (int j = 0; j <= iCount; j++) {
weights[j] = in.readFloat();
}
hiddenLayer[i] = new SigmoidUnit(weights);
}
for (int i = 0; i < oCount; i++) {
final float[] weights = new float[hCount + 1];
for (int j = 0; j <= hCount; j++) {
weights[j] = in.readFloat();
}
outputLayer[i] = new SigmoidUnit(weights);
}
classifier.mHiddenLayer = hiddenLayer;
classifier.mOutputLayer = outputLayer;
return classifier;
}
/**
* TODO: Publish this API once we figure out where we should save the personzlied
* gestures, and how to do so across all apps
*
* @hide
*/
public boolean save() {
if (mGestureLibrary != null) {
return mGestureLibrary.save();
}
return false;
}
/**
* TODO: Publish this API once we figure out where we should save the personzlied
* gestures, and how to do so across all apps
*
* @hide
*/
public void setPersonalizationEnabled(boolean enabled) {
if (enabled) {
mGestureLibrary = new GestureLibrary(GESTURE_FILE_NAME); mGestureLibrary = new GestureLibrary(GESTURE_FILE_NAME);
mGestureLibrary.setSequenceType(GestureLibrary.SEQUENCE_INVARIANT); mGestureLibrary.setSequenceType(GestureLibrary.SEQUENCE_INVARIANT);
mGestureLibrary.load(); mGestureLibrary.load();
@@ -204,24 +237,36 @@ public class LetterRecognizer {
} }
} }
/**
* TODO: Publish this API once we figure out where we should save the personzlied
* gestures, and how to do so across all apps
*
* @hide
*/
public void addExample(String letter, Gesture example) { public void addExample(String letter, Gesture example) {
mGestureLibrary.addGesture(letter, example); if (mGestureLibrary != null) {
mGestureLibrary.addGesture(letter, example);
}
} }
private void adjustPrediction(Gesture query, ArrayList<Prediction> predictions) { private void adjustPrediction(Gesture query, ArrayList<Prediction> predictions) {
ArrayList<Prediction> results = mGestureLibrary.recognize(query); if (mGestureLibrary != null) {
HashMap<String, Prediction> topNList = new HashMap<String, Prediction>(); final ArrayList<Prediction> results = mGestureLibrary.recognize(query);
for (int j = 0; j < ADJUST_RANGE; j++) { final HashMap<String, Prediction> topNList = new HashMap<String, Prediction>();
Prediction prediction = predictions.remove(0);
topNList.put(prediction.name, prediction); for (int j = 0; j < ADJUST_RANGE; j++) {
} Prediction prediction = predictions.remove(0);
int count = results.size(); topNList.put(prediction.name, prediction);
for (int j = count - 1; j >= 0 && !topNList.isEmpty(); j--) { }
Prediction item = results.get(j);
Prediction original = topNList.get(item.name); final int count = results.size();
if (original != null) { for (int j = count - 1; j >= 0 && !topNList.isEmpty(); j--) {
predictions.add(0, original); final Prediction item = results.get(j);
topNList.remove(item.name); final Prediction original = topNList.get(item.name);
if (original != null) {
predictions.add(0, original);
topNList.remove(item.name);
}
} }
} }
} }

View File

@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package com.android.gesture; package android.gesture;
import android.graphics.Matrix; import android.graphics.Matrix;
import android.graphics.Path; import android.graphics.Path;
@@ -47,6 +47,11 @@ public class OrientedBoundingBox {
} }
} }
/**
* Currently used for debugging purpose only.
*
* @hide
*/
public Path toPath() { public Path toPath() {
Path path = new Path(); Path path = new Path();
float[] point = new float[2]; float[] point = new float[2];

View File

@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package com.android.gesture; package android.gesture;
public class Prediction { public class Prediction {
public final String name; public final String name;

View File

@@ -14,13 +14,13 @@
* limitations under the License. * limitations under the License.
*/ */
package com.android.gesture; package android.gesture;
import android.graphics.Color;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.View; import android.view.View;
import java.util.ArrayList; import java.util.ArrayList;
import java.lang.ref.WeakReference;
/** /**
* TouchThroughGesturing implements the interaction behavior that allows a user * TouchThroughGesturing implements the interaction behavior that allows a user
@@ -28,19 +28,15 @@ import java.util.ArrayList;
* still allows a user to perform basic interactions (clicking, scrolling and panning) * still allows a user to perform basic interactions (clicking, scrolling and panning)
* with the underlying widget. * with the underlying widget.
*/ */
public class TouchThroughGestureListener implements GestureOverlayView.OnGestureListener {
public class TouchThroughGesturing implements GestureListener {
public static final int SINGLE_STROKE = 0; public static final int SINGLE_STROKE = 0;
public static final int MULTIPLE_STROKE = 1; public static final int MULTIPLE_STROKE = 1;
// TODO: Add properties for all these
private static final float STROKE_LENGTH_THRESHOLD = 30; private static final float STROKE_LENGTH_THRESHOLD = 30;
private static final float SQUARENESS_THRESHOLD = 0.275f; private static final float SQUARENESS_THRESHOLD = 0.275f;
private static final float ANGLE_THRESHOLD = 40; private static final float ANGLE_THRESHOLD = 40;
private static final boolean STEAL_EVENTS = false;
public static final int DEFAULT_UNCERTAIN_GESTURE_COLOR = Color.argb(60, 255, 255, 0);
private boolean mIsGesturing = false; private boolean mIsGesturing = false;
private float mTotalLength; private float mTotalLength;
@@ -48,18 +44,23 @@ public class TouchThroughGesturing implements GestureListener {
private float mX; private float mX;
private float mY; private float mY;
// TODO: Use WeakReference? private WeakReference<View> mModel;
private View mModel;
private int mGestureType = SINGLE_STROKE; private int mGestureType = SINGLE_STROKE;
private int mUncertainGestureColor = DEFAULT_UNCERTAIN_GESTURE_COLOR;
// TODO: Use WeakReferences // TODO: Use WeakReferences
private final ArrayList<GestureActionListener> mActionListeners = private final ArrayList<OnGesturePerformedListener> mPerformedListeners =
new ArrayList<GestureActionListener>(); new ArrayList<OnGesturePerformedListener>();
public TouchThroughGesturing(View model) { private boolean mStealEvents = false;
mModel = model;
public TouchThroughGestureListener(View model) {
this(model, false);
}
public TouchThroughGestureListener(View model, boolean stealEvents) {
mModel = new WeakReference<View>(model);
mStealEvents = stealEvents;
} }
/** /**
@@ -70,11 +71,7 @@ public class TouchThroughGesturing implements GestureListener {
mGestureType = type; mGestureType = type;
} }
public void setUncertainGestureColor(int color) { public void onGestureStarted(GestureOverlayView overlay, MotionEvent event) {
mUncertainGestureColor = color;
}
public void onStartGesture(GestureOverlay overlay, MotionEvent event) {
if (mGestureType == MULTIPLE_STROKE) { if (mGestureType == MULTIPLE_STROKE) {
overlay.cancelFadingOut(); overlay.cancelFadingOut();
} }
@@ -86,16 +83,21 @@ public class TouchThroughGesturing implements GestureListener {
if (mGestureType == SINGLE_STROKE || overlay.getCurrentGesture() == null if (mGestureType == SINGLE_STROKE || overlay.getCurrentGesture() == null
|| overlay.getCurrentGesture().getStrokesCount() == 0) { || overlay.getCurrentGesture().getStrokesCount() == 0) {
overlay.setGestureColor(mUncertainGestureColor); overlay.setGestureDrawingColor(overlay.getUncertainGestureColor());
} }
mModel.dispatchTouchEvent(event); dispatchEventToModel(event);
} }
public void onGesture(GestureOverlay overlay, MotionEvent event) { private void dispatchEventToModel(MotionEvent event) {
View v = mModel.get();
if (v != null) v.dispatchTouchEvent(event);
}
public void onGesture(GestureOverlayView overlay, MotionEvent event) {
//noinspection PointlessBooleanExpression //noinspection PointlessBooleanExpression
if (!STEAL_EVENTS) { if (!mStealEvents) {
mModel.dispatchTouchEvent(event); dispatchEventToModel(event);
} }
if (mIsGesturing) { if (mIsGesturing) {
@@ -107,7 +109,7 @@ public class TouchThroughGesturing implements GestureListener {
final float dx = x - mX; final float dx = x - mX;
final float dy = y - mY; final float dy = y - mY;
mTotalLength += (float)Math.sqrt(dx * dx + dy * dy); mTotalLength += (float) Math.sqrt(dx * dx + dy * dy);
mX = x; mX = x;
mY = y; mY = y;
@@ -120,8 +122,8 @@ public class TouchThroughGesturing implements GestureListener {
} }
if (box.squareness > SQUARENESS_THRESHOLD || angle < ANGLE_THRESHOLD) { if (box.squareness > SQUARENESS_THRESHOLD || angle < ANGLE_THRESHOLD) {
mIsGesturing = true; mIsGesturing = true;
overlay.setGestureColor(GestureOverlay.DEFAULT_GESTURE_COLOR); overlay.setGestureDrawingColor(overlay.getGestureColor());
if (STEAL_EVENTS) { if (mStealEvents) {
event = MotionEvent.obtain(event.getDownTime(), System.currentTimeMillis(), event = MotionEvent.obtain(event.getDownTime(), System.currentTimeMillis(),
MotionEvent.ACTION_UP, x, y, event.getPressure(), event.getSize(), MotionEvent.ACTION_UP, x, y, event.getPressure(), event.getSize(),
event.getMetaState(), event.getXPrecision(), event.getYPrecision(), event.getMetaState(), event.getXPrecision(), event.getYPrecision(),
@@ -130,36 +132,40 @@ public class TouchThroughGesturing implements GestureListener {
} }
} }
if (STEAL_EVENTS) { if (mStealEvents) {
mModel.dispatchTouchEvent(event); dispatchEventToModel(event);
} }
} }
public void onFinishGesture(GestureOverlay overlay, MotionEvent event) { public void onGestureEnded(GestureOverlayView overlay, MotionEvent event) {
if (mIsGesturing) { if (mIsGesturing) {
overlay.clear(true); overlay.clear(true);
final ArrayList<GestureActionListener> listeners = mActionListeners; final ArrayList<OnGesturePerformedListener> listeners = mPerformedListeners;
final int count = listeners.size(); final int count = listeners.size();
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
listeners.get(i).onGesturePerformed(overlay, overlay.getCurrentGesture()); listeners.get(i).onGesturePerformed(overlay, overlay.getCurrentGesture());
} }
} else { } else {
mModel.dispatchTouchEvent(event); dispatchEventToModel(event);
overlay.clear(false); overlay.clear(false);
} }
} }
public void addGestureActionListener(GestureActionListener listener) { public void addOnGestureActionListener(OnGesturePerformedListener listener) {
mActionListeners.add(listener); mPerformedListeners.add(listener);
} }
public void removeGestureActionListener(GestureActionListener listener) { public void removeOnGestureActionListener(OnGesturePerformedListener listener) {
mActionListeners.remove(listener); mPerformedListeners.remove(listener);
} }
public boolean isGesturing() { public boolean isGesturing() {
return mIsGesturing; return mIsGesturing;
} }
public static interface OnGesturePerformedListener {
public void onGesturePerformed(GestureOverlayView overlay, Gesture gesture);
}
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 28 KiB

View File

@@ -14,9 +14,7 @@
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:versionName="1.0.0">
<uses-permission android:name="android.permission.READ_CONTACTS" /> <uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.WRITE_SDCARD" /> <uses-permission android:name="android.permission.WRITE_SDCARD" />

View File

@@ -1,21 +0,0 @@
/*
* 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;
public interface GestureActionListener {
public void onGesturePerformed(GestureOverlay overlay, Gesture gesture);
}

View File

@@ -1,30 +0,0 @@
/*
* 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.view.MotionEvent;
/**
* An interface for processing gesture events
*/
public interface GestureListener {
public void onStartGesture(GestureOverlay overlay, MotionEvent event);
public void onGesture(GestureOverlay overlay, MotionEvent event);
public void onFinishGesture(GestureOverlay overlay, MotionEvent event);
}

View File

@@ -30,12 +30,11 @@ import android.view.Window;
import android.widget.AdapterView; import android.widget.AdapterView;
import android.widget.ListView; import android.widget.ListView;
import com.android.gesture.Gesture; import android.gesture.Gesture;
import com.android.gesture.GestureActionListener; import android.gesture.GestureOverlayView;
import com.android.gesture.GestureOverlay; import android.gesture.LetterRecognizer;
import com.android.gesture.LetterRecognizer; import android.gesture.Prediction;
import com.android.gesture.Prediction; import android.gesture.TouchThroughGestureListener;
import com.android.gesture.TouchThroughGesturing;
import java.util.ArrayList; import java.util.ArrayList;
@@ -52,7 +51,7 @@ public class ContactListGestureOverlay extends Activity {
private ContactAdapter mContactAdapter; private ContactAdapter mContactAdapter;
private TouchThroughGesturing mGestureProcessor; private TouchThroughGestureListener mGestureProcessor;
private LetterRecognizer mRecognizer; private LetterRecognizer mRecognizer;
@@ -67,7 +66,7 @@ public class ContactListGestureOverlay extends Activity {
setProgressBarIndeterminateVisibility(true); setProgressBarIndeterminateVisibility(true);
// create a letter recognizer // create a letter recognizer
mRecognizer = LetterRecognizer.getLetterRecognizer(this, LetterRecognizer.LATIN_LOWERCASE); mRecognizer = LetterRecognizer.getLetterRecognizer(this, LetterRecognizer.RECOGNIZER_LATIN_LOWERCASE);
// load the contact list // load the contact list
mContactList = (ListView) findViewById(R.id.list); mContactList = (ListView) findViewById(R.id.list);
@@ -95,11 +94,11 @@ public class ContactListGestureOverlay extends Activity {
setProgressBarIndeterminateVisibility(false); setProgressBarIndeterminateVisibility(false);
// add a gesture overlay on top of the ListView // add a gesture overlay on top of the ListView
GestureOverlay overlay = new GestureOverlay(this); GestureOverlayView overlay = new GestureOverlayView(this);
mGestureProcessor = new TouchThroughGesturing(mContactList); mGestureProcessor = new TouchThroughGestureListener(mContactList);
mGestureProcessor.setGestureType(TouchThroughGesturing.MULTIPLE_STROKE); mGestureProcessor.setGestureType(TouchThroughGestureListener.MULTIPLE_STROKE);
mGestureProcessor.addGestureActionListener(new GestureActionListener() { mGestureProcessor.addOnGestureActionListener(new TouchThroughGestureListener.OnGesturePerformedListener() {
public void onGesturePerformed(GestureOverlay overlay, Gesture gesture) { public void onGesturePerformed(GestureOverlayView overlay, Gesture gesture) {
ArrayList<Prediction> predictions = mRecognizer.recognize(gesture); ArrayList<Prediction> predictions = mRecognizer.recognize(gesture);
if (!predictions.isEmpty()) { if (!predictions.isEmpty()) {
Log.v(LOGTAG, "1st Prediction : " + predictions.get(0).name); Log.v(LOGTAG, "1st Prediction : " + predictions.get(0).name);
@@ -112,7 +111,7 @@ public class ContactListGestureOverlay extends Activity {
} }
} }
}); });
overlay.addGestureListener(mGestureProcessor); overlay.addOnGestureListener(mGestureProcessor);
ViewGroup.LayoutParams params = new ViewGroup.LayoutParams( ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT); ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT);
this.addContentView(overlay, params); this.addContentView(overlay, params);

View File

@@ -34,12 +34,11 @@ import android.widget.ArrayAdapter;
import android.widget.EditText; import android.widget.EditText;
import android.widget.Spinner; import android.widget.Spinner;
import android.widget.AdapterView.OnItemSelectedListener; import android.widget.AdapterView.OnItemSelectedListener;
import android.gesture.Gesture;
import com.android.gesture.Gesture; import android.gesture.GestureLibrary;
import com.android.gesture.GestureLibrary; import android.gesture.GestureOverlayView;
import com.android.gesture.GestureListener; import android.gesture.Prediction;
import com.android.gesture.GestureOverlay;
import com.android.gesture.Prediction;
import java.io.File; import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
@@ -57,7 +56,7 @@ public class GestureEntry extends Activity {
private static final int VIEW_ID = Menu.FIRST + 1; private static final int VIEW_ID = Menu.FIRST + 1;
private GestureOverlay mGesturePad; private GestureOverlayView mGesturePad;
private Spinner mRecognitionResult; private Spinner mRecognitionResult;
@@ -96,17 +95,17 @@ public class GestureEntry extends Activity {
}); });
// create the area for drawing a gesture // create the area for drawing a gesture
mGesturePad = (GestureOverlay) findViewById(R.id.drawingpad); mGesturePad = (GestureOverlayView) findViewById(R.id.drawingpad);
mGesturePad.setBackgroundColor(Color.BLACK); mGesturePad.setBackgroundColor(Color.BLACK);
mGesturePad.addGestureListener(new GestureListener() { mGesturePad.addOnGestureListener(new GestureOverlayView.OnGestureListener() {
public void onFinishGesture(GestureOverlay overlay, MotionEvent event) { public void onGestureEnded(GestureOverlayView overlay, MotionEvent event) {
recognize(overlay.getCurrentGesture()); recognize(overlay.getCurrentGesture());
} }
public void onGesture(GestureOverlay overlay, MotionEvent event) { public void onGesture(GestureOverlayView overlay, MotionEvent event) {
} }
public void onStartGesture(GestureOverlay overlay, MotionEvent event) { public void onGestureStarted(GestureOverlayView overlay, MotionEvent event) {
overlay.clear(false); overlay.clear(false);
} }
}); });

View File

@@ -26,10 +26,10 @@ import android.widget.ArrayAdapter;
import android.widget.Button; import android.widget.Button;
import android.widget.Spinner; import android.widget.Spinner;
import android.widget.AdapterView.OnItemSelectedListener; import android.widget.AdapterView.OnItemSelectedListener;
import android.gesture.Gesture;
import com.android.gesture.Gesture; import android.gesture.GestureLibrary;
import com.android.gesture.GestureLibrary; import android.gesture.GestureOverlayView;
import com.android.gesture.GestureOverlay;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
@@ -41,7 +41,7 @@ import java.util.Collections;
public class GestureLibViewer extends Activity { public class GestureLibViewer extends Activity {
private GestureOverlay mGesturePad; private GestureOverlayView mGesturePad;
private Spinner mGestureCategory; private Spinner mGestureCategory;
@@ -90,7 +90,7 @@ public class GestureLibViewer extends Activity {
setContentView(R.layout.gestureviewer); setContentView(R.layout.gestureviewer);
// create the area for drawing a gesture // create the area for drawing a gesture
mGesturePad = (GestureOverlay) findViewById(R.id.drawingpad); mGesturePad = (GestureOverlayView) findViewById(R.id.drawingpad);
mGesturePad.setEnabled(false); mGesturePad.setEnabled(false);
// init the gesture library // init the gesture library

View File

@@ -15,6 +15,7 @@ import java.io.BufferedInputStream;
*/ */
public class Converter { public class Converter {
private final File mFile; private final File mFile;
private static final short VERSION_NUMBER = 1;
Converter(File file) { Converter(File file) {
mFile = file; mFile = file;
@@ -63,10 +64,10 @@ public class Converter {
iWeights = new float[hCount][]; iWeights = new float[hCount][];
for (int i = 0; i < hCount; i++) { for (int i = 0; i < hCount; i++) {
iWeights[i] = new float[iCount]; iWeights[i] = new float[iCount + 1];
line = reader.readLine(); line = reader.readLine();
startIndex = 0; startIndex = 0;
for (int j = 0; j < iCount; j++) { for (int j = 0; j <= iCount; j++) {
endIndex = line.indexOf(" ", startIndex); endIndex = line.indexOf(" ", startIndex);
iWeights[i][j] = Float.parseFloat(line.substring(startIndex, endIndex)); iWeights[i][j] = Float.parseFloat(line.substring(startIndex, endIndex));
startIndex = endIndex + 1; startIndex = endIndex + 1;
@@ -75,10 +76,10 @@ public class Converter {
oWeights = new float[oCount][]; oWeights = new float[oCount][];
for (int i = 0; i < oCount; i++) { for (int i = 0; i < oCount; i++) {
oWeights[i] = new float[hCount]; oWeights[i] = new float[hCount + 1];
line = reader.readLine(); line = reader.readLine();
startIndex = 0; startIndex = 0;
for (int j = 0; j < hCount; j++) { for (int j = 0; j <= hCount; j++) {
endIndex = line.indexOf(" ", startIndex); endIndex = line.indexOf(" ", startIndex);
oWeights[i][j] = Float.parseFloat(line.substring(startIndex, endIndex)); oWeights[i][j] = Float.parseFloat(line.substring(startIndex, endIndex));
startIndex = endIndex + 1; startIndex = endIndex + 1;
@@ -105,6 +106,7 @@ public class Converter {
try { try {
out = new DataOutputStream(new FileOutputStream(mFile)); out = new DataOutputStream(new FileOutputStream(mFile));
out.writeShort(VERSION_NUMBER);
out.writeInt(iCount); out.writeInt(iCount);
out.writeInt(hCount); out.writeInt(hCount);
out.writeInt(oCount); out.writeInt(oCount);