diff options
author | Yang Li <liyang@google.com> | 2009-05-15 13:55:56 -0700 |
---|---|---|
committer | Yang Li <liyang@google.com> | 2009-05-15 13:55:56 -0700 |
commit | f8173411cc612017ecfa115d71fb823b0714d982 (patch) | |
tree | acf4f4d47b2958668b730df339bb31674d6fedf2 /tests/sketch/src | |
parent | abc25e3679a9de2625eadf3e3050a6973f7a6c71 (diff) | |
download | frameworks_base-f8173411cc612017ecfa115d71fb823b0714d982.zip frameworks_base-f8173411cc612017ecfa115d71fb823b0714d982.tar.gz frameworks_base-f8173411cc612017ecfa115d71fb823b0714d982.tar.bz2 |
Squashed commit of the following:
commit db1a75ffc2d5a811a8d983289dd4d0d9f2eeba5c
Author: Yang Li <liyang@google.com>
Date: Fri May 1 17:49:29 2009 -0700
Fixed a few more styles issues and made some methods package private
commit f9735c3f1bbe6183337df226aa730b4be1225d55
Author: Yang Li <liyang@google.com>
Date: Fri May 1 16:38:16 2009 -0700
- Refactored the code for Android coding styles and performance
- Renamed GesturePad to GestureOverlay
- Added GestureAdapter for default touch-through interactions of GestureOverlay
- Added a new exmaple, ContactListGestureOverlay, to show how to gesture over an arbitary view
commit 31050183c6d85091612f53155126c2488b920e32
Author: Yang Li <liyang@google.com>
Date: Wed Apr 29 18:41:54 2009 -0700
Refactored the Gesture Library (An initial check-in).
Diffstat (limited to 'tests/sketch/src')
23 files changed, 3028 insertions, 703 deletions
diff --git a/tests/sketch/src/com/android/gesture/Gesture.java b/tests/sketch/src/com/android/gesture/Gesture.java index 29c07ad..a5e7a25 100755 --- a/tests/sketch/src/com/android/gesture/Gesture.java +++ b/tests/sketch/src/com/android/gesture/Gesture.java @@ -18,343 +18,290 @@ package com.android.gesture; import android.graphics.Bitmap; import android.graphics.Canvas; -import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Path; -import android.graphics.PointF; import android.graphics.RectF; import android.os.Parcel; import android.os.Parcelable; -import com.android.gesture.recognizer.RecognitionUtil; - import org.xmlpull.v1.XmlSerializer; import java.io.IOException; import java.util.ArrayList; -import java.util.Iterator; -import java.util.StringTokenizer; /** - * A single stroke gesture. + * A gesture can have a single or multiple strokes */ public class Gesture implements Parcelable { - private RectF mBBX; - private float mLength = 0; - private int mColor; - private float mWidth; - private ArrayList<PointF> mPtsBuffer = new ArrayList<PointF>(); - private long mTimestamp = 0; - private long mID; - - private static final long systemStartupTime = System.currentTimeMillis(); - private static int instanceCount = 0; + private static final long GESTURE_ID_BASE = System.currentTimeMillis(); + + private static final int BITMAP_RENDERING_WIDTH = 2; + + private static final boolean BITMAP_RENDERING_ANTIALIAS = true; + + private static final boolean BITMAP_RENDERING_DITHER = true; + + private static int sGestureCount = 0; + + private RectF mBoundingBox; + + // the same as its instance ID + private long mGestureID; + + private ArrayList<GestureStroke> mStrokes = new ArrayList<GestureStroke>(); public Gesture() { - mID = systemStartupTime + instanceCount++; + mGestureID = GESTURE_ID_BASE + sGestureCount++; } - public void setColor(int c) { - mColor = c; - } - - public void setStrokeWidth(float w) { - mWidth = w; - } - - public int getColor() { - return mColor; - } - - public float getStrokeWidth() { - return mWidth; - } - - public ArrayList<PointF> getPoints() { - return this.mPtsBuffer; + /** + * @return all the strokes of the gesture + */ + public ArrayList<GestureStroke> getStrokes() { + return mStrokes; } - - public int numOfPoints() { - return this.mPtsBuffer.size(); + + /** + * @return the number of strokes included by this gesture + */ + public int getStrokesCount() { + return mStrokes.size(); } - public void addPoint(float x, float y) { - mPtsBuffer.add(new PointF(x, y)); - if (mBBX == null) { - mBBX = new RectF(); - mBBX.top = y; - mBBX.left = x; - mBBX.right = x; - mBBX.bottom = y; - mLength = 0; - } - else { - PointF lst = mPtsBuffer.get(mPtsBuffer.size()-2); - mLength += Math.sqrt(Math.pow(x-lst.x, 2)+Math.pow(y-lst.y, 2)); - mBBX.union(x, y); + /** + * Add a stroke to the gesture + * + * @param stroke + */ + public void addStroke(GestureStroke stroke) { + mStrokes.add(stroke); + + if (mBoundingBox == null) { + mBoundingBox = new RectF(stroke.boundingBox); + } else { + mBoundingBox.union(stroke.boundingBox); } - mTimestamp = System.currentTimeMillis(); } /** + * Get the total length of the gesture. When there are multiple strokes in + * the gesture, this returns the sum of the lengths of all the strokes + * * @return the length of the gesture */ public float getLength() { - return this.mLength; + int len = 0; + ArrayList<GestureStroke> strokes = mStrokes; + int count = strokes.size(); + for (int i = 0; i < count; i++) { + GestureStroke stroke = strokes.get(i); + len += stroke.length; + } + return len; } - - public RectF getBBX() { - return mBBX; + + /** + * @return the bounding box of the gesture + */ + public RectF getBoundingBox() { + return mBoundingBox; } - - public void setID(long id) { - mID = id; + + /** + * Set the id of the gesture + * + * @param id + */ + void setID(long id) { + mGestureID = id; } - + + /** + * @return the id of the gesture + */ public long getID() { - return mID; + return mGestureID; } - - public long getTimeStamp() { - return mTimestamp; - } - - public void setTimestamp(long t) { - this.mTimestamp = t; - } - + /** * draw the gesture + * * @param canvas */ - public void draw(Canvas canvas) { - Paint paint = new Paint(); - paint.setAntiAlias(true); - paint.setDither(true); - paint.setColor(mColor); - paint.setStyle(Paint.Style.STROKE); - paint.setStrokeJoin(Paint.Join.ROUND); - paint.setStrokeCap(Paint.Cap.ROUND); - paint.setStrokeWidth(mWidth); - - Path path = null; - float mX = 0, mY = 0; - Iterator<PointF> it = mPtsBuffer.iterator(); - while (it.hasNext()) { - PointF p = it.next(); - float x = p.x; - float y = p.y; - if (path == null) { - path = new Path(); - path.moveTo(x, y); - mX = x; - mY = y; - } else { - float dx = Math.abs(x - mX); - float dy = Math.abs(y - mY); - if (dx >= 3 || dy >= 3) { - path.quadTo(mX, mY, (x + mX)/2, (y + mY)/2); - mX = x; - mY = y; - } - } + void draw(Canvas canvas, Paint paint) { + ArrayList<GestureStroke> strokes = mStrokes; + int count = strokes.size(); + for (int i = 0; i < count; i++) { + GestureStroke stroke = strokes.get(i); + stroke.draw(canvas, paint); } - - canvas.drawPath(path, paint); } - + /** - * convert the gesture to a 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 numSample the num of points needed - * @return the path + * Create a bitmap of the gesture with a transparent background + * + * @param width width of the target bitmap + * @param height height of the target bitmap + * @param edge the edge + * @param numSample + * @param color + * @return the bitmap */ - public Path toPath(float width, float height, int numSample) { - float[] pts = RecognitionUtil.resample(this, numSample); - RectF rect = this.getBBX(); - float scale = height / rect.height(); - Matrix matrix = new Matrix(); - matrix.setTranslate(-rect.left, -rect.top); - Matrix scalem = new Matrix(); - scalem.setScale(scale, scale); - matrix.postConcat(scalem); - Matrix translate = new Matrix(); - matrix.postConcat(translate); - matrix.mapPoints(pts); - - Path path = null; - float mX = 0, mY = 0; - for (int i=0; i<pts.length-1; i+=2) { - float x = pts[i]; - float y = pts[i+1]; - if (path == null) { - path = new Path(); - path.moveTo(x, y); - mX = x; - mY = y; - } else { - float dx = Math.abs(x - mX); - float dy = Math.abs(y - mY); - if (dx >= 3 || dy >= 3) { - path.quadTo(mX, mY, (x + mX)/2, (y + mY)/2); - mX = x; - mY = y; - } - } + public Bitmap toBitmap(int width, int height, int edge, int numSample, int color) { + RectF bbx = getBoundingBox(); + Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + canvas.translate(edge, edge); + Paint paint = new Paint(); + paint.setAntiAlias(BITMAP_RENDERING_ANTIALIAS); + paint.setDither(BITMAP_RENDERING_DITHER); + paint.setColor(color); + paint.setStyle(Paint.Style.STROKE); + paint.setStrokeJoin(Paint.Join.ROUND); + paint.setStrokeCap(Paint.Cap.ROUND); + paint.setStrokeWidth(BITMAP_RENDERING_WIDTH); + ArrayList<GestureStroke> strokes = mStrokes; + int count = strokes.size(); + for (int i = 0; i < count; i++) { + GestureStroke stroke = strokes.get(i); + Path path = stroke.toPath(width - 2 * edge, height - 2 * edge, numSample); + canvas.drawPath(path, paint); } - return path; + + return bitmap; } - + /** - * get a bitmap thumbnail of the gesture with a transparent background - * @param w - * @param h + * Create a bitmap of the gesture with a transparent background + * + * @param width + * @param height * @param edge - * @param numSample - * @param foreground - * @return + * @param color + * @return the bitmap */ - public Bitmap toBitmap(int w, int h, - int edge, int numSample) { - RectF bbx = this.getBBX(); - Bitmap bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); - Path path = this.toPath(w - 2 * edge, h - 2 * edge, numSample); - Canvas c = new Canvas(bitmap); - //c.drawColor(background); - c.translate(edge, edge); + public Bitmap toBitmap(int width, int height, int edge, int color) { + RectF bbx = getBoundingBox(); + Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + canvas.translate(edge, edge); Paint paint = new Paint(); - paint.setAntiAlias(true); - paint.setDither(true); - paint.setColor(mColor); + paint.setAntiAlias(BITMAP_RENDERING_ANTIALIAS); + paint.setDither(BITMAP_RENDERING_DITHER); + paint.setColor(color); paint.setStyle(Paint.Style.STROKE); paint.setStrokeJoin(Paint.Join.ROUND); paint.setStrokeCap(Paint.Cap.ROUND); - paint.setStrokeWidth(2); - c.drawPath(path, paint); + paint.setStrokeWidth(BITMAP_RENDERING_WIDTH); + ArrayList<GestureStroke> strokes = mStrokes; + int count = strokes.size(); + for (int i = 0; i < count; i++) { + GestureStroke stroke = strokes.get(i); + stroke.draw(canvas, paint); + } + return bitmap; } - + /** - * save the gesture as XML + * Save the gesture as XML + * * @param namespace * @param serializer * @throws IOException */ - public void toXML(String namespace, XmlSerializer serializer) throws IOException { - serializer.startTag(namespace, "stroke"); - serializer.attribute(namespace, "timestamp", Long.toString(mTimestamp)); - serializer.attribute(namespace, "id", Long.toString(mID)); - serializer.attribute(namespace, "color", Integer.toString(mColor)); - serializer.attribute(namespace, "width", Float.toString(mWidth)); - Iterator it = this.mPtsBuffer.iterator(); - String pts = ""; - while (it.hasNext()) { - PointF fp = (PointF)it.next(); - if (pts.length() > 0) - pts += ","; - pts += fp.x + "," + fp.y; + void toXML(String namespace, XmlSerializer serializer) throws IOException { + 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++) { + GestureStroke stroke = strokes.get(i); + stroke.toXML(namespace, serializer); } - serializer.text(pts); - serializer.endTag(namespace, "stroke"); + serializer.endTag(namespace, GestureConstants.XML_TAG_GESTURE); } - - + + /** + * Create the gesture from a string + * + * @param str + */ public void createFromString(String str) { - StringTokenizer st = new StringTokenizer(str, "#"); - - String para = st.nextToken(); - StringTokenizer innerst = new StringTokenizer(para, ","); - this.mBBX = new RectF(); - this.mBBX.left = Float.parseFloat(innerst.nextToken()); - this.mBBX.top = Float.parseFloat(innerst.nextToken()); - this.mBBX.right = Float.parseFloat(innerst.nextToken()); - this.mBBX.bottom = Float.parseFloat(innerst.nextToken()); - - para = st.nextToken(); - innerst = new StringTokenizer(para, ","); - while (innerst.hasMoreTokens()) { - String s = innerst.nextToken().trim(); - if (s.length()==0) - break; - float x = Float.parseFloat(s); - float y = Float.parseFloat(innerst.nextToken()); - this.mPtsBuffer.add(new PointF(x, y)); + int startIndex = 0; + int endIndex; + while ((endIndex = str.indexOf(GestureConstants.STRING_GESTURE_DELIIMITER, startIndex + 1)) != -1) { + 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; } - - para = st.nextToken(); - this.mColor = Integer.parseInt(para); - - para = st.nextToken(); - this.mWidth = Float.parseFloat(para); - - para = st.nextToken(); - this.mLength = Float.parseFloat(para); - - para = st.nextToken(); - this.mTimestamp = Long.parseLong(para); } - + + /** + * Convert the gesture to string + */ @Override public String toString() { - String str = ""; - - str += "#" + this.mBBX.left + "," + this.mBBX.top + "," + - this.mBBX.right + "," + this.mBBX.bottom; - - str += "#"; - Iterator<PointF> it = this.mPtsBuffer.iterator(); - while (it.hasNext()) { - PointF fp = it.next(); - str += fp.x + "," + fp.y + ","; + StringBuilder str = new StringBuilder(); + str.append(mGestureID); + ArrayList<GestureStroke> strokes = mStrokes; + int count = strokes.size(); + for (int i = 0; i < count; i++) { + GestureStroke stroke = strokes.get(i); + str.append(GestureConstants.STRING_GESTURE_DELIIMITER); + str.append(stroke.toString()); } - str += "#"; - str += this.mColor; - - str += "#"; - str += this.mWidth; - - str += "#"; - str += this.mLength; - - str += "#"; - str += this.mTimestamp; - - return str; + return str.toString(); } - - public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + + public static final Parcelable.Creator<Gesture> CREATOR = new Parcelable.Creator<Gesture>() { public Gesture createFromParcel(Parcel in) { String str = in.readString(); - Gesture stk = new Gesture(); - stk.createFromString(str); - return stk; + Gesture gesture = new Gesture(); + gesture.createFromString(str); + return gesture; } - + public Gesture[] newArray(int size) { return new Gesture[size]; } }; - - public static Gesture buildFromArray(byte[] bytes) { + + /** + * Build a gesture from a byte array + * + * @param bytes + * @return the gesture + */ + static Gesture buildFromArray(byte[] bytes) { String str = new String(bytes); - Gesture stk = new Gesture(); - stk.createFromString(str); - return stk; + Gesture gesture = new Gesture(); + gesture.createFromString(str); + return gesture; } - - public static byte[] saveToArray(Gesture stk) { - String str = stk.toString(); + + /** + * 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) { - out.writeString(this.toString()); + out.writeString(toString()); } - + public int describeContents() { return CONTENTS_FILE_DESCRIPTOR; } diff --git a/tests/sketch/src/com/android/gesture/GestureActionListener.java b/tests/sketch/src/com/android/gesture/GestureActionListener.java new file mode 100644 index 0000000..130ac19 --- /dev/null +++ b/tests/sketch/src/com/android/gesture/GestureActionListener.java @@ -0,0 +1,22 @@ +/* + * 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); +} diff --git a/tests/sketch/src/com/android/gesture/GestureAdapter.java b/tests/sketch/src/com/android/gesture/GestureAdapter.java new file mode 100644 index 0000000..3cf9b4c --- /dev/null +++ b/tests/sketch/src/com/android/gesture/GestureAdapter.java @@ -0,0 +1,124 @@ +/* + * 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.graphics.Color; +import android.view.MotionEvent; +import android.view.View; + +import java.util.ArrayList; + +public class GestureAdapter implements GestureListener { + + public static final int SINGLE_STROKE = 0; + + public static final int MULTIPLE_STROKE = 1; + + private static final float STROKE_LENGTH_THRESHOLD = 100; + + private static final float SQUARENESS_THRESHOLD = 0.24f; + + private static final int UNCERTAIN_GESTURE_COLOR = Color.argb(60, 255, 255, 0); + + private boolean mIsGesturing = false; + + private float mTotalLength; + + private float mX, mY; + + private View mModel; + + private int mGestureType = SINGLE_STROKE; + + private ArrayList<GestureActionListener> mActionListeners = new ArrayList<GestureActionListener>(); + + public GestureAdapter(View model) { + mModel = model; + } + + public void setGestureType(int type) { + mGestureType = type; + } + + public void onStartGesture(GestureOverlay overlay, MotionEvent event) { + if (mGestureType == MULTIPLE_STROKE) { + overlay.cancelFadingOut(); + } + mX = event.getX(); + mY = event.getY(); + mTotalLength = 0; + mIsGesturing = false; + if (mGestureType == SINGLE_STROKE || overlay.getCurrentGesture() == null + || overlay.getCurrentGesture().getStrokesCount() == 0) { + overlay.setGestureColor(UNCERTAIN_GESTURE_COLOR); + } + mModel.dispatchTouchEvent(event); + } + + public void onGesture(GestureOverlay overlay, MotionEvent event) { + if (mIsGesturing) { + return; + } + float x = event.getX(); + float y = event.getY(); + float dx = x - mX; + float dy = y - mY; + mTotalLength += (float)Math.sqrt(dx * dx + dy * dy); + mX = x; + mY = y; + + if (mTotalLength > STROKE_LENGTH_THRESHOLD) { + OrientedBoundingBox bbx = GestureUtils.computeOrientedBBX(overlay.getCurrentStroke()); + if (bbx.squareness > SQUARENESS_THRESHOLD) { + mIsGesturing = true; + overlay.setGestureColor(GestureOverlay.DEFAULT_GESTURE_COLOR); + event = MotionEvent.obtain(event.getDownTime(), System.currentTimeMillis(), + MotionEvent.ACTION_UP, x, y, event.getPressure(), event.getSize(), event + .getMetaState(), event.getXPrecision(), event.getYPrecision(), + event.getDeviceId(), event.getEdgeFlags()); + } + } + mModel.dispatchTouchEvent(event); + } + + public void onFinishGesture(GestureOverlay overlay, MotionEvent event) { + if (mIsGesturing) { + overlay.clear(true); + ArrayList<GestureActionListener> listeners = mActionListeners; + int count = listeners.size(); + for (int i = 0; i < count; i++) { + GestureActionListener listener = listeners.get(i); + listener.onGesturePerformed(overlay, overlay.getCurrentGesture()); + } + } else { + mModel.dispatchTouchEvent(event); + overlay.clear(false); + } + } + + public void addGestureActionListener(GestureActionListener listener) { + mActionListeners.add(listener); + } + + public void removeGestureActionListener(GestureActionListener listener) { + mActionListeners.remove(listener); + } + + public boolean isGesturing() { + return mIsGesturing; + } +} diff --git a/tests/sketch/src/com/android/gesture/GestureConstants.java b/tests/sketch/src/com/android/gesture/GestureConstants.java new file mode 100644 index 0000000..0e17c8a --- /dev/null +++ b/tests/sketch/src/com/android/gesture/GestureConstants.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 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; + +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_STROKE_DELIIMITER = ","; + static final int STROKE_STRING_BUFFER_SIZE = 1024; + static final int STROKE_POINT_BUFFER_SIZE = 100; // number of points + static final int IO_BUFFER_SIZE = 8 * 1024; // 8K +} diff --git a/tests/sketch/src/com/android/gesture/GestureLibrary.java b/tests/sketch/src/com/android/gesture/GestureLibrary.java new file mode 100644 index 0000000..32c1ac4 --- /dev/null +++ b/tests/sketch/src/com/android/gesture/GestureLibrary.java @@ -0,0 +1,332 @@ +/* + * 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; + + public static final int SEQUENCE_SENSITIVE = 2; + + private int mSequenceType = SEQUENCE_SENSITIVE; + + public static final int ORIENTATION_INVARIANT = 1; + + 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); + } + 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) { + } + } +} diff --git a/tests/sketch/src/com/android/gesture/GestureListener.java b/tests/sketch/src/com/android/gesture/GestureListener.java index ebb4149..9b50714 100755 --- a/tests/sketch/src/com/android/gesture/GestureListener.java +++ b/tests/sketch/src/com/android/gesture/GestureListener.java @@ -18,8 +18,13 @@ package com.android.gesture; import android.view.MotionEvent; +/** + * An interface for processing gesture events + */ public interface GestureListener { - public void onStartGesture(GesturePad pad, MotionEvent event); - public void onGesture(GesturePad pad, MotionEvent event); - public void onFinishGesture(GesturePad pad, MotionEvent event); + public void onStartGesture(GestureOverlay overlay, MotionEvent event); + + public void onGesture(GestureOverlay overlay, MotionEvent event); + + public void onFinishGesture(GestureOverlay overlay, MotionEvent event); } diff --git a/tests/sketch/src/com/android/gesture/GestureOverlay.java b/tests/sketch/src/com/android/gesture/GestureOverlay.java new file mode 100755 index 0000000..5cef8c8 --- /dev/null +++ b/tests/sketch/src/com/android/gesture/GestureOverlay.java @@ -0,0 +1,340 @@ +/* + * 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.content.Context; +import android.graphics.Bitmap; +import android.graphics.BlurMaskFilter; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Path; +import android.os.Handler; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; +import java.util.ArrayList; + +/** + * A (transparent) overlay for gesture input that can be placed on top of other + * widgets. The view can also be opaque. + */ + +public class GestureOverlay extends View { + + static final float TOUCH_TOLERANCE = 3; + + private static final int TRANSPARENT_BACKGROUND = Color.argb(0, 0, 0, 0); + + private static final float FADING_ALPHA_CHANGE = 0.03f; + + private static final long FADING_REFRESHING_RATE = 100; + + private static final int GESTURE_STROKE_WIDTH = 12; + + private static final boolean GESTURE_RENDERING_ANTIALIAS = true; + + private static final int BLUR_MASK_RADIUS = 1; + + public static final int DEFAULT_GESTURE_COLOR = Color.argb(255, 255, 255, 0); + + // double buffering + private Paint mGesturePaint; + + private Bitmap mBitmap; // with transparent background + + private Canvas mBitmapCanvas; + + // for rendering immediate ink feedback + private Path mPath; + + private float mX; + + private float mY; + + // current gesture + private Gesture mCurrentGesture = null; + + // gesture event handlers + ArrayList<GestureListener> mGestureListeners = new ArrayList<GestureListener>(); + + private ArrayList<GesturePoint> mPointBuffer = null; + + // fading out effect + private boolean mIsFadingOut = false; + + private float mFadingAlpha = 1; + + private Handler mHandler = new Handler(); + + private Paint mBitmapPaint = new Paint(Paint.DITHER_FLAG); + + private Runnable mFadingOut = new Runnable() { + public void run() { + if (mIsFadingOut) { + mFadingAlpha -= FADING_ALPHA_CHANGE; + if (mFadingAlpha <= 0) { + mIsFadingOut = false; + mPath = null; + mCurrentGesture = null; + mBitmap.eraseColor(TRANSPARENT_BACKGROUND); + } else { + mHandler.postDelayed(this, FADING_REFRESHING_RATE); + } + invalidate(); + } + } + }; + + public GestureOverlay(Context context) { + super(context); + init(); + } + + public GestureOverlay(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + public ArrayList<GesturePoint> getCurrentStroke() { + return mPointBuffer; + } + + public Gesture getCurrentGesture() { + return mCurrentGesture; + } + + /** + * Set Gesture color + * + * @param color + */ + public void setGestureColor(int color) { + mGesturePaint.setColor(color); + if (mCurrentGesture != null) { + mBitmap.eraseColor(TRANSPARENT_BACKGROUND); + mCurrentGesture.draw(mBitmapCanvas, mGesturePaint); + } + } + + /** + * Set the gesture to be shown in the view + * + * @param gesture + */ + public void setCurrentGesture(Gesture gesture) { + if (mCurrentGesture != null) { + clear(false); + } + + mCurrentGesture = gesture; + + if (gesture != null) { + if (mBitmapCanvas != null) { + gesture.draw(mBitmapCanvas, mGesturePaint); + invalidate(); + } + } + } + + private void init() { + mGesturePaint = new Paint(); + mGesturePaint.setAntiAlias(GESTURE_RENDERING_ANTIALIAS); + mGesturePaint.setColor(DEFAULT_GESTURE_COLOR); + mGesturePaint.setStyle(Paint.Style.STROKE); + mGesturePaint.setStrokeJoin(Paint.Join.ROUND); + mGesturePaint.setStrokeCap(Paint.Cap.ROUND); + mGesturePaint.setStrokeWidth(GESTURE_STROKE_WIDTH); + mGesturePaint + .setMaskFilter(new BlurMaskFilter(BLUR_MASK_RADIUS, BlurMaskFilter.Blur.NORMAL)); + + mPath = null; + } + + @Override + protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) { + super.onSizeChanged(width, height, oldWidth, oldHeight); + if (width <= 0 || height <= 0) { + return; + } + int targetWidth = width > oldWidth ? width : oldWidth; + int targetHeight = height > oldHeight ? height : oldHeight; + mBitmap = Bitmap.createBitmap(targetWidth, targetHeight, Bitmap.Config.ARGB_8888); + mBitmapCanvas = new Canvas(mBitmap); + mBitmapCanvas.drawColor(TRANSPARENT_BACKGROUND); + if (mCurrentGesture != null) { + mCurrentGesture.draw(mBitmapCanvas, mGesturePaint); + } + } + + public void addGestureListener(GestureListener listener) { + mGestureListeners.add(listener); + } + + public void removeGestureListener(GestureListener listener) { + mGestureListeners.remove(listener); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + + // draw double buffer + if (mIsFadingOut) { + mBitmapPaint.setAlpha((int) (255 * mFadingAlpha)); + canvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint); + } else { + mBitmapPaint.setAlpha(255); + canvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint); + } + + // draw the current stroke + if (mPath != null) { + canvas.drawPath(mPath, mGesturePaint); + } + } + + /** + * Clear up the overlay + * + * @param fadeOut whether the gesture on the overlay should fade out + * gradually or disappear immediately + */ + public void clear(boolean fadeOut) { + if (fadeOut) { + mFadingAlpha = 1; + mIsFadingOut = true; + mHandler.removeCallbacks(mFadingOut); + mHandler.postDelayed(mFadingOut, FADING_REFRESHING_RATE); + } else { + mPath = null; + mCurrentGesture = null; + if (mBitmap != null) { + mBitmap.eraseColor(TRANSPARENT_BACKGROUND); + invalidate(); + } + } + } + + public void cancelFadingOut() { + mIsFadingOut = false; + mHandler.removeCallbacks(mFadingOut); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + + if (!isEnabled()) { + return true; + } + + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + touch_start(event); + invalidate(); + break; + case MotionEvent.ACTION_MOVE: + touch_move(event); + invalidate(); + break; + case MotionEvent.ACTION_UP: + touch_up(event); + invalidate(); + break; + } + + return true; + } + + private void touch_start(MotionEvent event) { + // pass the event to handlers + ArrayList<GestureListener> listeners = mGestureListeners; + int count = listeners.size(); + for (int i = 0; i < count; i++) { + GestureListener listener = listeners.get(i); + listener.onStartGesture(this, event); + } + + // if there is fading out going on, stop it. + if (mIsFadingOut) { + mIsFadingOut = false; + mHandler.removeCallbacks(mFadingOut); + mBitmap.eraseColor(TRANSPARENT_BACKGROUND); + mCurrentGesture = null; + } + + float x = event.getX(); + float y = event.getY(); + + mX = x; + mY = y; + + if (mCurrentGesture == null) { + mCurrentGesture = new Gesture(); + } + + mPointBuffer = new ArrayList<GesturePoint>(); + mPointBuffer.add(new GesturePoint(x, y, event.getEventTime())); + + mPath = new Path(); + mPath.moveTo(x, y); + } + + private void touch_move(MotionEvent event) { + float x = event.getX(); + float y = event.getY(); + + float dx = Math.abs(x - mX); + float dy = Math.abs(y - mY); + if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) { + mPath.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2); + mX = x; + mY = y; + } + + mPointBuffer.add(new GesturePoint(x, y, event.getEventTime())); + + // pass the event to handlers + ArrayList<GestureListener> listeners = mGestureListeners; + int count = listeners.size(); + for (int i = 0; i < count; i++) { + GestureListener listener = listeners.get(i); + listener.onGesture(this, event); + } + } + + private void touch_up(MotionEvent event) { + // add the stroke to the current gesture + mCurrentGesture.addStroke(new GestureStroke(mPointBuffer)); + mPointBuffer = null; + + // add the stroke to the double buffer + mGesturePaint.setDither(true); + mBitmapCanvas.drawPath(mPath, mGesturePaint); + mGesturePaint.setDither(false); + mPath = null; + + // pass the event to handlers + ArrayList<GestureListener> listeners = mGestureListeners; + int count = listeners.size(); + for (int i = 0; i < count; i++) { + GestureListener listener = listeners.get(i); + listener.onFinishGesture(this, event); + } + } + +} diff --git a/tests/sketch/src/com/android/gesture/GesturePad.java b/tests/sketch/src/com/android/gesture/GesturePad.java index 45a09e6..04dbe3a 100755 --- a/tests/sketch/src/com/android/gesture/GesturePad.java +++ b/tests/sketch/src/com/android/gesture/GesturePad.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008 The Android Open Source Project + * Copyright (C) 2008-2009 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,64 +18,67 @@ package com.android.gesture; import android.content.Context; import android.graphics.Bitmap; +import android.graphics.BlurMaskFilter; import android.graphics.Canvas; import android.graphics.Color; -import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Path; -import android.graphics.PointF; import android.os.Handler; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import java.util.ArrayList; -import java.util.Iterator; + /** - * A view for rendering and processing gestures + * A (transparent) view for gesture input that can be placed on top of other + * widgets. The background of the view is customizable. + * + * @author liyang@google.com (Yang Li) + * */ public class GesturePad extends View { - public static final float TOUCH_TOLERANCE = 4; - public static final int default_foreground = Color.argb(255, 255, 255, 0); - private int background = Color.argb(0, 0, 0, 0); - private int foreground = default_foreground; - private int uncertain_foreground = Color.argb(55, 255, 255, 0); - private Bitmap mBitmap; - private Canvas mCanvas; - private Path mPath; - private Paint mBitmapPaint; - private Paint mPaint; - private Paint mDebugPaint; - private float mX, mY; - private boolean mEnableInput = true; - private boolean mEnableRendering = true; - private boolean mCacheGesture = true; - private Gesture mCurrentGesture = null; - ArrayList<GestureListener> mGestureListeners = new ArrayList<GestureListener>(); - - private boolean mShouldFadingOut = true; - private boolean mIsFadingOut = false; - private float mFadingAlpha = 1; + private static final float TOUCH_TOLERANCE = 4; + public static final int DEFAULT_GESTURE_COLOR = Color.argb(255, 255, 255, 0); - private boolean reconstruct = false; + // double buffering + private Paint mGesturePaint; + private Bitmap mBitmap; // with transparent background + private Canvas mBitmapCanvas; + + // for rendering immediate ink feedback + private Path mPath; + private float mX; + private float mY; + + // current gesture + private Gesture mCurrentGesture = null; - private ArrayList<Path> debug = new ArrayList<Path>(); + // gesture event handlers + ArrayList<GestureListener> mGestureListeners = + new ArrayList<GestureListener>(); + private ArrayList<GesturePoint> mPointBuffer = null; + + // fading out effect + private boolean mIsFadingOut = false; + private float mFadingAlpha = 1; private Handler mHandler = new Handler(); - private Runnable mFadingOut = new Runnable() { - public void run() { - mFadingAlpha -= 0.03f; - if (mFadingAlpha <= 0) { - mIsFadingOut = false; - mPath.reset(); - } else { - mHandler.postDelayed(this, 100); - } - invalidate(); - } - }; + public void run() { + mFadingAlpha -= 0.03f; + if (mFadingAlpha <= 0) { + mIsFadingOut = false; + mPath = null; + mCurrentGesture = null; + mBitmap.eraseColor(Color.argb(0, 0, 0, 0)); + } else { + mHandler.postDelayed(this, 100); + } + invalidate(); + } + }; public GesturePad(Context context) { super(context); @@ -87,82 +90,76 @@ public class GesturePad extends View { init(); } - public boolean isEnableRendering() { - return this.mEnableRendering; + public ArrayList<GesturePoint> getCurrentStroke() { + return this.mPointBuffer; } public Gesture getCurrentGesture() { return mCurrentGesture; } - public Paint getPaint() { - return mPaint; - } - - public void setColor(int c) { - this.foreground = c; - } - - public void setFadingAlpha(float f) { - mFadingAlpha = f; + /** + * Set Gesture color + * @param c + */ + public void setGestureColor(int c) { + this.mGesturePaint.setColor(c); + if (mCurrentGesture != null) { + mBitmap.eraseColor(Color.argb(0, 0, 0, 0)); + mCurrentGesture.draw(mBitmapCanvas, mGesturePaint); + } } - public void setCurrentGesture(Gesture stk) { - this.mCurrentGesture = stk; - reconstruct = true; + /** + * Set the gesture to be shown in the view + * @param gesture + */ + public void setCurrentGesture(Gesture gesture) { + if (this.mCurrentGesture != null) { + clear(false); + } + + this.mCurrentGesture = gesture; + + if (this.mCurrentGesture != null) { + if (mBitmapCanvas != null) { + this.mCurrentGesture.draw(mBitmapCanvas, mGesturePaint); + this.invalidate(); + } + } } private void init() { - mDebugPaint = new Paint(); - mDebugPaint.setColor(Color.WHITE); - mDebugPaint.setStrokeWidth(4); - mDebugPaint.setAntiAlias(true); - mDebugPaint.setStyle(Paint.Style.STROKE); - - mPaint = new Paint(); - mPaint.setAntiAlias(true); - mPaint.setDither(true); - mPaint.setColor(foreground); - mPaint.setStyle(Paint.Style.STROKE); - mPaint.setStrokeJoin(Paint.Join.ROUND); - mPaint.setStrokeCap(Paint.Cap.ROUND); - mPaint.setStrokeWidth(12); + mGesturePaint = new Paint(); + mGesturePaint.setAntiAlias(true); + mGesturePaint.setDither(true); + mGesturePaint.setColor(DEFAULT_GESTURE_COLOR); + mGesturePaint.setStyle(Paint.Style.STROKE); + mGesturePaint.setStrokeJoin(Paint.Join.ROUND); + mGesturePaint.setStrokeCap(Paint.Cap.ROUND); + mGesturePaint.setStrokeWidth(12); + mGesturePaint.setMaskFilter( + new BlurMaskFilter(1, BlurMaskFilter.Blur.NORMAL)); - mBitmapPaint = new Paint(Paint.DITHER_FLAG); - mPath = new Path(); - - reconstruct = false; - } - - public void cacheGesture(boolean b) { - mCacheGesture = b; - } - - public void enableRendering(boolean b) { - mEnableRendering = b; + mPath = null; } - @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { // TODO Auto-generated method stub super.onSizeChanged(w, h, oldw, oldh); - - if (w <=0 || h <=0) + if (w <= 0 || h <= 0) { return; - - int width = w>oldw? w : oldw; - int height = h>oldh? h : oldh; - Bitmap newBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); - mCanvas = new Canvas(newBitmap); - - if (mBitmap != null) { - mCanvas.drawColor(background); - mCanvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint); - mCanvas.drawPath(mPath, mPaint); } - - mBitmap = newBitmap; + int width = w > oldw? w : oldw; + int height = h > oldh? h : oldh; + mBitmap = Bitmap.createBitmap( + width, height, Bitmap.Config.ARGB_8888); + mBitmapCanvas = new Canvas(mBitmap); + mBitmapCanvas.drawColor(Color.argb(0, 0, 0, 0)); + if (mCurrentGesture != null) { + mCurrentGesture.draw(mBitmapCanvas, mGesturePaint); + } } public void addGestureListener(GestureListener l) { @@ -175,111 +172,50 @@ public class GesturePad extends View { @Override protected void onDraw(Canvas canvas) { - canvas.drawColor(background); - - if (mCacheGesture) - canvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint); - + super.onDraw(canvas); + + // draw double buffer + Paint paint = new Paint(Paint.DITHER_FLAG); if (mIsFadingOut) { - int color = foreground; - int alpha = (int)(Color.alpha(color) * mFadingAlpha); - mPaint.setColor(Color.argb(alpha, - Color.red(color), - Color.green(color), - Color.blue(color))); - } else if (mEnableRendering == false) { - mPaint.setColor(uncertain_foreground); + paint.setAlpha((int)(255 * mFadingAlpha)); + canvas.drawBitmap(mBitmap, 0, 0, paint); } else { - mPaint.setColor(foreground); - } - - if (reconstruct) { - - if (this.mCurrentGesture != null) { - float xedge = 30; - float yedge = 30; - float w = this.getWidth() - 2 * xedge; - float h = this.getHeight() - 2 * yedge; - float sx = w / this.mCurrentGesture.getBBX().width(); - float sy = h / mCurrentGesture.getBBX().height(); - float scale = sx>sy?sy:sx; - convertFromStroke(mCurrentGesture); - Matrix matrix = new Matrix(); - matrix.preTranslate(-mCurrentGesture.getBBX().centerX(), -mCurrentGesture.getBBX().centerY()); - matrix.postScale(scale, scale); - matrix.postTranslate(this.getWidth()/2, this.getHeight()/2); - this.mPath.transform(matrix); - } else { - mPath.reset(); - } - - reconstruct = false; + canvas.drawBitmap(mBitmap, 0, 0, paint); } - canvas.drawPath(mPath, mPaint); - - Iterator<Path> it = debug.iterator(); - while (it.hasNext()) { - Path path = it.next(); - canvas.drawPath(path, mDebugPaint); + // draw the current stroke + if (mPath != null) { + canvas.drawPath(mPath, mGesturePaint); } } - - public void clearDebugPath() { - debug.clear(); - } - - public void addDebugPath(Path path) { - debug.add(path); - } - - public void addDebugPath(ArrayList<Path> paths) { - debug.addAll(paths); - } - - public void clear() { - mPath = new Path(); - this.mCurrentGesture = null; - mCanvas.drawColor(background); - this.invalidate(); - } - - private void convertFromStroke(Gesture stk) { - mPath = null; - Iterator it = stk.getPoints().iterator(); - while (it.hasNext()) { - PointF p = (PointF) it.next(); - if (mPath == null) { - mPath = new Path(); - mPath.moveTo(p.x, p.y); - mX = p.x; - mY = p.y; - } else { - float dx = Math.abs(p.x - mX); - float dy = Math.abs(p.y - mY); - if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) { - mPath.quadTo(mX, mY, (p.x + mX)/2, (p.y + mY)/2); - mX = p.x; - mY = p.y; - } + + /** + * Clear up the gesture pad + * @param fadeOut whether the gesture on the pad should fade out gradually + * or disappear immediately + */ + public void clear(boolean fadeOut) { + if (fadeOut) { + mFadingAlpha = 1; + mIsFadingOut = true; + mHandler.removeCallbacks(mFadingOut); + mHandler.postDelayed(mFadingOut, 100); + } else { + mPath = null; + this.mCurrentGesture = null; + if (mBitmap != null) { + mBitmap.eraseColor(Color.argb(0, 0, 0, 0)); + this.invalidate(); } } - mPath.lineTo(mX, mY); - } - - public void setEnableInput(boolean b) { - mEnableInput = b; - } - - public boolean isEnableInput() { - return mEnableInput; } @Override public boolean onTouchEvent(MotionEvent event) { - if(mEnableInput == false) + if(this.isEnabled() == false) { return true; + } switch (event.getAction()) { case MotionEvent.ACTION_DOWN: @@ -295,28 +231,41 @@ public class GesturePad extends View { invalidate(); break; } + return true; } private void touch_start(MotionEvent event) { - mIsFadingOut = false; - mHandler.removeCallbacks(mFadingOut); + // if there is fading-out effect, stop it. + if (mIsFadingOut) { + mIsFadingOut = false; + mHandler.removeCallbacks(mFadingOut); + mBitmap.eraseColor(Color.argb(0, 0, 0, 0)); + this.mCurrentGesture = null; + } float x = event.getX(); float y = event.getY(); - mCurrentGesture = new Gesture(); - mCurrentGesture.addPoint(x, y); - - mPath.reset(); - mPath.moveTo(x, y); mX = x; mY = y; - Iterator<GestureListener> it = mGestureListeners.iterator(); - while (it.hasNext()) { - it.next().onStartGesture(this, event); + // pass the event to handlers + int count = mGestureListeners.size(); + for (int i = 0; i < count; i++) { + GestureListener listener = mGestureListeners.get(i); + listener.onStartGesture(this, event); } + + if (mCurrentGesture == null) { + mCurrentGesture = new Gesture(); + } + + mPointBuffer = new ArrayList<GesturePoint>(); + mPointBuffer.add(new GesturePoint(x, y, event.getEventTime())); + + mPath = new Path(); + mPath.moveTo(x, y); } private void touch_move(MotionEvent event) { @@ -331,41 +280,32 @@ public class GesturePad extends View { mY = y; } - mCurrentGesture.addPoint(x, y); - - Iterator<GestureListener> it = mGestureListeners.iterator(); - while (it.hasNext()) { - it.next().onGesture(this, event); + mPointBuffer.add(new GesturePoint(x, y, event.getEventTime())); + + // pass the event to handlers + int count = mGestureListeners.size(); + for (int i = 0; i < count; i++) { + GestureListener listener = mGestureListeners.get(i); + listener.onGesture(this, event); } } - - public void setFadingOut(boolean b) { - mShouldFadingOut = b; - mIsFadingOut = false; - } - - public boolean shouldFadingOut() { - return mShouldFadingOut; - } + private void touch_up(MotionEvent event) { - mPath.lineTo(mX, mY); - - if (mCacheGesture) - mCanvas.drawPath(mPath, mPaint); - - // kill this so we don't double draw - if (shouldFadingOut()) { - mFadingAlpha = 1; - mIsFadingOut = true; - mHandler.removeCallbacks(mFadingOut); - mHandler.postDelayed(mFadingOut, 100); - } + // add the stroke to the current gesture + mCurrentGesture.addStroke(new GestureStroke(mPointBuffer)); + mPointBuffer = null; + + // add the stroke to the double buffer + mBitmapCanvas.drawPath(mPath, mGesturePaint); + mPath = null; - Iterator<GestureListener> it = mGestureListeners.iterator(); - while (it.hasNext()) { - it.next().onFinishGesture(this, event); + // pass the event to handlers + int count = mGestureListeners.size(); + for (int i = 0; i < count; i++) { + GestureListener listener = mGestureListeners.get(i); + listener.onFinishGesture(this, event); } } - + } diff --git a/tests/sketch/src/com/android/gesture/GesturePoint.java b/tests/sketch/src/com/android/gesture/GesturePoint.java new file mode 100644 index 0000000..d06eff47 --- /dev/null +++ b/tests/sketch/src/com/android/gesture/GesturePoint.java @@ -0,0 +1,35 @@ +/* + * 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; + +/** + * A timed point of a gesture stroke + */ + +public class GesturePoint { + public final float xpos; + + public final float ypos; + + public final long timestamp; + + public GesturePoint(float x, float y, long t) { + xpos = x; + ypos = y; + timestamp = t; + } +} diff --git a/tests/sketch/src/com/android/gesture/GestureProcessor.java b/tests/sketch/src/com/android/gesture/GestureProcessor.java new file mode 100644 index 0000000..feddead --- /dev/null +++ b/tests/sketch/src/com/android/gesture/GestureProcessor.java @@ -0,0 +1,128 @@ +/* + * 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.graphics.Color; +import android.view.MotionEvent; +import android.view.View; + +import java.util.ArrayList; + +public class GestureProcessor implements GestureListener { + + public static final int SINGLE_STROKE = 0; + + public static final int MULTIPLE_STROKE = 1; + + private static final float STROKE_LENGTH_THRESHOLD = 100; + + private static final float SQUARENESS_THRESHOLD = 0.24f; + + private static final int UNCERTAIN_GESTURE_COLOR = Color.argb(60, 255, 255, 0); + + private boolean mIsGesturing = false; + + private float mTotalLength; + + private float mX, mY; + + private View mModel; + + private int mGestureType = SINGLE_STROKE; + + private ArrayList<GestureActionListener> mActionListeners = new ArrayList<GestureActionListener>(); + + public GestureProcessor(View model) { + mModel = model; + } + + /** + * + * @param type SINGLE_STROKE or MULTIPLE_STROKE + */ + public void setGestureType(int type) { + mGestureType = type; + } + + public void onStartGesture(GestureOverlay overlay, MotionEvent event) { + if (mGestureType == MULTIPLE_STROKE) { + overlay.cancelFadingOut(); + } + mX = event.getX(); + mY = event.getY(); + mTotalLength = 0; + mIsGesturing = false; + if (mGestureType == SINGLE_STROKE || overlay.getCurrentGesture() == null + || overlay.getCurrentGesture().getStrokesCount() == 0) { + overlay.setGestureColor(UNCERTAIN_GESTURE_COLOR); + } + mModel.dispatchTouchEvent(event); + } + + public void onGesture(GestureOverlay overlay, MotionEvent event) { + if (mIsGesturing) { + return; + } + float x = event.getX(); + float y = event.getY(); + float dx = x - mX; + float dy = y - mY; + mTotalLength += (float)Math.sqrt(dx * dx + dy * dy); + mX = x; + mY = y; + + if (mTotalLength > STROKE_LENGTH_THRESHOLD) { + OrientedBoundingBox bbx = GestureUtils.computeOrientedBBX(overlay.getCurrentStroke()); + if (bbx.squareness > SQUARENESS_THRESHOLD) { + mIsGesturing = true; + overlay.setGestureColor(GestureOverlay.DEFAULT_GESTURE_COLOR); + event = MotionEvent.obtain(event.getDownTime(), System.currentTimeMillis(), + MotionEvent.ACTION_UP, x, y, event.getPressure(), event.getSize(), event + .getMetaState(), event.getXPrecision(), event.getYPrecision(), + event.getDeviceId(), event.getEdgeFlags()); + } + } + mModel.dispatchTouchEvent(event); + } + + public void onFinishGesture(GestureOverlay overlay, MotionEvent event) { + if (mIsGesturing) { + overlay.clear(true); + ArrayList<GestureActionListener> listeners = mActionListeners; + int count = listeners.size(); + for (int i = 0; i < count; i++) { + GestureActionListener listener = listeners.get(i); + listener.onGesturePerformed(overlay, overlay.getCurrentGesture()); + } + } else { + mModel.dispatchTouchEvent(event); + overlay.clear(false); + } + } + + public void addGestureActionListener(GestureActionListener listener) { + mActionListeners.add(listener); + } + + public void removeGestureActionListener(GestureActionListener listener) { + mActionListeners.remove(listener); + } + + public boolean isGesturing() { + return mIsGesturing; + } +} diff --git a/tests/sketch/src/com/android/gesture/GestureStroke.java b/tests/sketch/src/com/android/gesture/GestureStroke.java new file mode 100644 index 0000000..b1081e5 --- /dev/null +++ b/tests/sketch/src/com/android/gesture/GestureStroke.java @@ -0,0 +1,244 @@ +/* + * 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.graphics.Canvas; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.RectF; + +import org.xmlpull.v1.XmlSerializer; + +import java.io.IOException; +import java.util.ArrayList; + +/** + * A gesture stroke started on a touch down and ended on a touch up. + */ +public class GestureStroke { + public final RectF boundingBox; + + public final float length; + + public final float[] xPoints; + + public final float[] yPoints; + + public final long[] timestamps; + + private Path mCachedPath; + + /** + * Construct a gesture stroke from a list of gesture points + * + * @param pts + */ + public GestureStroke(ArrayList<GesturePoint> pts) { + xPoints = new float[pts.size()]; + yPoints = new float[pts.size()]; + timestamps = new long[pts.size()]; + + RectF bx = null; + float len = 0; + int index = 0; + int count = pts.size(); + float[] xpts = xPoints; + float[] ypts = yPoints; + long[] times = timestamps; + + for (int i = 0; i < count; i++) { + GesturePoint p = pts.get(i); + xpts[index] = p.xpos; + ypts[index] = p.ypos; + times[index] = p.timestamp; + + if (bx == null) { + bx = new RectF(); + bx.top = p.ypos; + bx.left = p.xpos; + bx.right = p.xpos; + bx.bottom = p.ypos; + len = 0; + } else { + len += Math.sqrt(Math.pow(p.xpos - xpts[index - 1], 2) + + Math.pow(p.ypos - ypts[index - 1], 2)); + bx.union(p.xpos, p.ypos); + } + index++; + } + + boundingBox = bx; + length = len; + } + + /** + * Draw the gesture with a given canvas and paint + * + * @param canvas + */ + void draw(Canvas canvas, Paint paint) { + if (mCachedPath == null) { + float[] xpts = xPoints; + float[] ypts = yPoints; + int count = xpts.length; + Path path = null; + float mX = 0, mY = 0; + for (int i = 0; i < count; i++) { + float x = xpts[i]; + float y = ypts[i]; + if (path == null) { + path = new Path(); + path.moveTo(x, y); + mX = x; + mY = y; + } else { + float dx = Math.abs(x - mX); + float dy = Math.abs(y - mY); + if (dx >= 3 || dy >= 3) { + path.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2); + mX = x; + mY = y; + } + } + } + + mCachedPath = path; + } + + canvas.drawPath(mCachedPath, paint); + } + + /** + * Convert the stroke to a Path based on the number of points + * + * @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 numSample the number of points needed + * @return the path + */ + public Path toPath(float width, float height, int numSample) { + float[] pts = GestureUtils.sequentialFeaturize(this, numSample); + RectF rect = boundingBox; + float scale = height / rect.height(); + Matrix matrix = new Matrix(); + matrix.setTranslate(-rect.left, -rect.top); + Matrix scaleMatrix = new Matrix(); + scaleMatrix.setScale(scale, scale); + matrix.postConcat(scaleMatrix); + Matrix translate = new Matrix(); + matrix.postConcat(translate); + matrix.mapPoints(pts); + + Path path = null; + float mX = 0; + float mY = 0; + int count = pts.length; + for (int i = 0; i < count; i += 2) { + float x = pts[i]; + float y = pts[i + 1]; + if (path == null) { + path = new Path(); + path.moveTo(x, y); + mX = x; + mY = y; + } else { + float dx = Math.abs(x - mX); + float dy = Math.abs(y - mY); + if (dx >= GestureOverlay.TOUCH_TOLERANCE || dy >= GestureOverlay.TOUCH_TOLERANCE) { + path.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2); + mX = x; + mY = y; + } + } + } + return path; + } + + /** + * Save the gesture stroke as XML + * + * @param namespace + * @param serializer + * @throws IOException + */ + void toXML(String namespace, XmlSerializer serializer) throws IOException { + serializer.startTag(namespace, GestureConstants.XML_TAG_STROKE); + serializer.text(toString()); + serializer.endTag(namespace, GestureConstants.XML_TAG_STROKE); + } + + /** + * Create a gesture stroke from a string + * + * @param str + * @return the gesture stroke + */ + public static GestureStroke createFromString(String str) { + ArrayList<GesturePoint> points = new ArrayList<GesturePoint>( + GestureConstants.STROKE_POINT_BUFFER_SIZE); + int endIndex; + int startIndex = 0; + 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); + } + + /** + * Convert the stroke to string + */ + @Override + public String toString() { + StringBuilder str = new StringBuilder(GestureConstants.STROKE_STRING_BUFFER_SIZE); + float[] xpts = xPoints; + float[] ypts = yPoints; + long[] times = timestamps; + int count = xpts.length; + for (int i = 0; i < count; i++) { + str.append(xpts[i] + GestureConstants.STRING_STROKE_DELIIMITER + ypts[i] + + GestureConstants.STRING_STROKE_DELIIMITER + times[i] + + GestureConstants.STRING_STROKE_DELIIMITER); + } + return str.toString(); + } + + /** + * Invalidate the cached path that is used for rendering the stroke + */ + public void invalidate() { + mCachedPath = null; + } +} diff --git a/tests/sketch/src/com/android/gesture/GestureUtils.java b/tests/sketch/src/com/android/gesture/GestureUtils.java new file mode 100755 index 0000000..7c3237a --- /dev/null +++ b/tests/sketch/src/com/android/gesture/GestureUtils.java @@ -0,0 +1,358 @@ +/* + * 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.graphics.RectF; + +import java.util.ArrayList; +import java.util.Arrays; + +public class GestureUtils { + + private static final int SEQUENCE_SAMPLE_SIZE = 16; + + protected static float[] spatialFeaturize(Gesture gesture, int sampleSize) { + float[] sample = new float[sampleSize * sampleSize]; + Arrays.fill(sample, 0); + + RectF rect = gesture.getBoundingBox(); + float sx = sampleSize / rect.width(); + float sy = sampleSize / rect.height(); + 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(sampleSize / 2, sampleSize / 2); + trans.postConcat(translate2); + + ArrayList<GestureStroke> strokes = gesture.getStrokes(); + int count = strokes.size(); + int size; + for (int index = 0; index < count; index++) { + GestureStroke stroke = strokes.get(index); + float[] pts = sequentialFeaturize(stroke, SEQUENCE_SAMPLE_SIZE); + trans.mapPoints(pts); + + size = pts.length; + for (int i = 0; i < size; i += 2) { + float x = pts[i]; + int xFloor = (int) Math.floor(x); + int xCeiling = (int) Math.ceil(x); + float y = pts[i + 1]; + int yFloor = (int) Math.floor(y); + int yCeiling = (int) Math.ceil(y); + + if (yFloor >= 0 && yFloor < sampleSize && xFloor >= 0 && xFloor < sampleSize) { + int pos = yFloor * sampleSize + xFloor; + float value = (1 - x + xFloor) * (1 - y + yFloor); + if (sample[pos] < value) { + sample[pos] = value; + } + } + + if (yFloor >= 0 && yFloor < sampleSize && xCeiling >= 0 && xCeiling < sampleSize) { + int pos = yFloor * sampleSize + xCeiling; + float value = (1 - xCeiling + x) * (1 - y + yFloor); + if (sample[pos] < value) { + sample[pos] = value; + } + } + + if (yCeiling >= 0 && yCeiling < sampleSize && xFloor >= 0 && xFloor < sampleSize) { + int pos = yCeiling * sampleSize + xFloor; + float value = (1 - x + xFloor) * (1 - yCeiling + y); + if (sample[pos] < value) { + sample[pos] = value; + } + + } + + if (yCeiling >= 0 && yCeiling < sampleSize && xCeiling >= 0 + && xCeiling < sampleSize) { + int pos = yCeiling * sampleSize + xCeiling; + float value = (1 - xCeiling + x) * (1 - yCeiling + y); + if (sample[pos] < value) { + sample[pos] = value; + } + } + } + } + + return sample; + } + + /** + * Featurize a stroke into a vector of a given number of elements + * + * @param stroke + * @param sampleSize + * @return a float array + */ + protected static float[] sequentialFeaturize(GestureStroke stroke, int sampleSize) { + final float increment = stroke.length / (sampleSize - 1); + int vectorLength = sampleSize * 2; + float[] vector = new float[vectorLength]; + float distanceSoFar = 0; + float[] xpts = stroke.xPoints; + float[] ypts = stroke.yPoints; + float lstPointX = xpts[0]; + float lstPointY = ypts[0]; + int index = 0; + float currentPointX = Float.MIN_VALUE; + float currentPointY = Float.MIN_VALUE; + vector[index] = lstPointX; + index++; + vector[index] = lstPointY; + index++; + int i = 0; + int count = xpts.length; + while (i < count) { + if (currentPointX == Float.MIN_VALUE) { + i++; + if (i >= count) { + break; + } + currentPointX = xpts[i]; + currentPointY = ypts[i]; + } + float deltaX = currentPointX - lstPointX; + float deltaY = currentPointY - lstPointY; + float distance = (float) Math.sqrt(deltaX * deltaX + deltaY * deltaY); + if (distanceSoFar + distance >= increment) { + float ratio = (increment - distanceSoFar) / distance; + float nx = lstPointX + ratio * deltaX; + float ny = lstPointY + ratio * deltaY; + vector[index] = nx; + index++; + vector[index] = ny; + index++; + lstPointX = nx; + lstPointY = ny; + distanceSoFar = 0; + } else { + lstPointX = currentPointX; + lstPointY = currentPointY; + currentPointX = Float.MIN_VALUE; + currentPointY = Float.MIN_VALUE; + distanceSoFar += distance; + } + } + + for (i = index; i < vectorLength; i += 2) { + vector[i] = lstPointX; + vector[i + 1] = lstPointY; + } + return vector; + } + + /** + * Calculate the centroid + * + * @param points + * @return the centroid + */ + public static float[] computeCentroid(float[] points) { + float centerX = 0; + float centerY = 0; + int count = points.length; + for (int i = 0; i < count; i++) { + centerX += points[i]; + i++; + centerY += points[i]; + } + float[] center = new float[2]; + center[0] = 2 * centerX / count; + center[1] = 2 * centerY / count; + + return center; + } + + /** + * calculate the variance-covariance matrix, treat each point as a sample + * + * @param points + * @return the covariance matrix + */ + protected static double[][] computeCoVariance(float[] points) { + double[][] array = new double[2][2]; + array[0][0] = 0; + array[0][1] = 0; + array[1][0] = 0; + array[1][1] = 0; + int count = points.length; + for (int i = 0; i < count; i++) { + float x = points[i]; + i++; + float y = points[i]; + array[0][0] += x * x; + array[0][1] += x * y; + array[1][0] = array[0][1]; + array[1][1] += y * y; + } + array[0][0] /= (count / 2); + array[0][1] /= (count / 2); + array[1][0] /= (count / 2); + array[1][1] /= (count / 2); + + return array; + } + + public static float computeTotalLength(float[] points) { + float sum = 0; + int count = points.length - 4; + for (int i = 0; i < count; i += 2) { + float dx = points[i + 2] - points[i]; + float dy = points[i + 3] - points[i + 1]; + sum += Math.sqrt(dx * dx + dy * dy); + } + return sum; + } + + public static double computeStraightness(float[] points) { + float totalLen = computeTotalLength(points); + float dx = points[2] - points[0]; + float dy = points[3] - points[1]; + return Math.sqrt(dx * dx + dy * dy) / totalLen; + } + + public static double computeStraightness(float[] points, float totalLen) { + float dx = points[2] - points[0]; + float dy = points[3] - points[1]; + return Math.sqrt(dx * dx + dy * dy) / totalLen; + } + + /** + * Calculate the squared Euclidean distance between two vectors + * + * @param vector1 + * @param vector2 + * @return the distance + */ + protected static double euclideanDistance(float[] vector1, float[] vector2) { + double squaredDistance = 0; + int size = vector1.length; + for (int i = 0; i < size; i++) { + float difference = vector1[i] - vector2[i]; + squaredDistance += difference * difference; + } + return squaredDistance / size; + } + + /** + * Calculate the cosine distance between two instances + * + * @param in1 + * @param in2 + * @return the distance between 0 and Math.PI + */ + protected static double cosineDistance(Instance in1, Instance in2) { + float sum = 0; + float[] vector1 = in1.vector; + float[] vector2 = in2.vector; + int len = vector1.length; + for (int i = 0; i < len; i++) { + sum += vector1[i] * vector2[i]; + } + return Math.acos(sum / (in1.magnitude * in2.magnitude)); + } + + public static OrientedBoundingBox computeOrientedBBX(ArrayList<GesturePoint> pts) { + GestureStroke stroke = new GestureStroke(pts); + float[] points = sequentialFeaturize(stroke, SEQUENCE_SAMPLE_SIZE); + return computeOrientedBBX(points); + } + + public static OrientedBoundingBox computeOrientedBBX(float[] points) { + float[] meanVector = computeCentroid(points); + return computeOrientedBBX(points, meanVector); + } + + public static OrientedBoundingBox computeOrientedBBX(float[] points, float[] centroid) { + + android.graphics.Matrix tr = new android.graphics.Matrix(); + tr.setTranslate(-centroid[0], -centroid[1]); + tr.mapPoints(points); + + double[][] array = computeCoVariance(points); + double[] targetVector = computeOrientation(array); + + float angle; + if (targetVector[0] == 0 && targetVector[1] == 0) { + angle = -90; + } else { // -PI<alpha<PI + angle = (float) Math.atan2(targetVector[1], targetVector[0]); + angle = (float) (180 * angle / Math.PI); + android.graphics.Matrix trans = new android.graphics.Matrix(); + trans.setRotate(-angle); + trans.mapPoints(points); + } + + float minx = Float.MAX_VALUE; + float miny = Float.MAX_VALUE; + float maxx = Float.MIN_VALUE; + float maxy = Float.MIN_VALUE; + int count = points.length; + for (int i = 0; i < count; i++) { + if (points[i] < minx) { + minx = points[i]; + } + if (points[i] > maxx) { + maxx = points[i]; + } + i++; + if (points[i] < miny) { + miny = points[i]; + } + if (points[i] > maxy) { + maxy = points[i]; + } + } + + OrientedBoundingBox bbx = new OrientedBoundingBox(angle, centroid[0], centroid[1], maxx + - minx, maxy - miny); + return bbx; + } + + private static double[] computeOrientation(double[][] covarianceMatrix) { + double[] targetVector = new double[2]; + if (covarianceMatrix[0][1] == 0 || covarianceMatrix[1][0] == 0) { + targetVector[0] = 1; + targetVector[1] = 0; + } + + // lamda^2 + a * lamda + b = 0 + double a = -covarianceMatrix[0][0] - covarianceMatrix[1][1]; + double b = covarianceMatrix[0][0] * covarianceMatrix[1][1] - covarianceMatrix[0][1] + * covarianceMatrix[1][0]; + double value = a / 2; + double rightside = Math.sqrt(Math.pow(value, 2) - b); + double lambda1 = -value + rightside; + double lambda2 = -value - rightside; + if (lambda1 == lambda2) { + targetVector[0] = 0; + targetVector[1] = 0; + } else { + double lambda = lambda1 > lambda2 ? lambda1 : lambda2; + targetVector[0] = 1; + targetVector[1] = (lambda - covarianceMatrix[0][0]) / covarianceMatrix[0][1]; + } + return targetVector; + } +} diff --git a/tests/sketch/src/com/android/gesture/Instance.java b/tests/sketch/src/com/android/gesture/Instance.java new file mode 100755 index 0000000..4eb10f9 --- /dev/null +++ b/tests/sketch/src/com/android/gesture/Instance.java @@ -0,0 +1,106 @@ +/* + * 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; + +/** + * An instance represents a sample if the label is available or a query if the + * label is null. + */ +public class Instance { + + private static final int SEQUENCE_SAMPLE_SIZE = 16; + + private static final int PATCH_SAMPLE_SIZE = 8; + + private final static float[] ORIENTATIONS = { + 0, 45, 90, 135, 180, -0, -45, -90, -135, -180 + }; + + // the feature vector + public final float[] vector; + + // the label can be null + public final String label; + + // the length of the vector + public final float magnitude; + + // the id of the instance + public final long instanceID; + + private Instance(long id, float[] sample, String sampleName) { + instanceID = id; + vector = sample; + label = sampleName; + float sum = 0; + int size = sample.length; + for (int i = 0; i < size; i++) { + sum += sample[i] * sample[i]; + } + magnitude = (float) Math.sqrt(sum); + } + + /** + * create a learning instance for a single stroke gesture + * + * @param gesture + * @param label + * @return the instance + */ + public static Instance createInstance(GestureLibrary gesturelib, Gesture gesture, String label) { + float[] pts; + if (gesturelib.getGestureType() == GestureLibrary.SEQUENCE_SENSITIVE) { + pts = sequenceSampler(gesturelib, gesture); + } else { + pts = spatialSampler(gesture); + } + return new Instance(gesture.getID(), pts, label); + } + + private static float[] spatialSampler(Gesture gesture) { + float[] pts = GestureUtils.spatialFeaturize(gesture, PATCH_SAMPLE_SIZE); + return pts; + } + + private static float[] sequenceSampler(GestureLibrary gesturelib, Gesture gesture) { + float[] pts = GestureUtils.sequentialFeaturize(gesture.getStrokes().get(0), + SEQUENCE_SAMPLE_SIZE); + float[] center = GestureUtils.computeCentroid(pts); + float orientation = (float) Math.atan2(pts[1] - center[1], pts[0] - center[0]); + orientation *= 180 / Math.PI; + + float adjustment = -orientation; + if (gesturelib.getOrientationStyle() == GestureLibrary.ORIENTATION_SENSITIVE) { + int count = ORIENTATIONS.length; + for (int i = 0; i < count; i++) { + float delta = ORIENTATIONS[i] - orientation; + if (Math.abs(delta) < Math.abs(adjustment)) { + adjustment = delta; + } + } + } + + android.graphics.Matrix m = new android.graphics.Matrix(); + m.setTranslate(-center[0], -center[1]); + android.graphics.Matrix rotation = new android.graphics.Matrix(); + rotation.setRotate(adjustment); + m.postConcat(rotation); + m.mapPoints(pts); + return pts; + } + +} diff --git a/tests/sketch/src/com/android/gesture/InstanceLearner.java b/tests/sketch/src/com/android/gesture/InstanceLearner.java new file mode 100644 index 0000000..3bd875b --- /dev/null +++ b/tests/sketch/src/com/android/gesture/InstanceLearner.java @@ -0,0 +1,105 @@ +/* + * 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 java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.TreeMap; + +/** + * An implementation of an instance-based learner + */ + +class InstanceLearner extends Learner { + + private static final String LOGTAG = "InstanceLearner"; + + @Override + ArrayList<Prediction> classify(GestureLibrary lib, Instance instance) { + ArrayList<Prediction> predictions = new ArrayList<Prediction>(); + ArrayList<Instance> instances = getInstances(); + int count = instances.size(); + TreeMap<String, Double> label2score = new TreeMap<String, Double>(); + for (int i = 0; i < count; i++) { + Instance sample = instances.get(i); + if (sample.vector.length != instance.vector.length) { + continue; + } + double distance; + if (lib.getGestureType() == GestureLibrary.SEQUENCE_SENSITIVE) { + distance = GestureUtils.cosineDistance(sample, instance); + } else { + distance = GestureUtils.euclideanDistance(sample.vector, instance.vector); + } + double weight; + if (distance == 0) { + weight = Double.MAX_VALUE; + } else { + weight = 1 / distance; + } + Double score = label2score.get(sample.label); + if (score == null || weight > score) { + label2score.put(sample.label, weight); + } + } + + double sum = 0; + Iterator<String> lableIterator = label2score.keySet().iterator(); + while (lableIterator.hasNext()) { + String name = lableIterator.next(); + double score = label2score.get(name); + sum += score; + predictions.add(new Prediction(name, score)); + } + + // normalize + Iterator<Prediction> predictionIterator = predictions.iterator(); + while (predictionIterator.hasNext()) { + Prediction name = predictionIterator.next(); + name.predictionScore /= sum; + } + + Collections.sort(predictions, new Comparator<Prediction>() { + public int compare(Prediction object1, Prediction object2) { + double score1 = object1.predictionScore; + double score2 = object2.predictionScore; + if (score1 > score2) { + return -1; + } else if (score1 < score2) { + return 1; + } else { + return 0; + } + } + }); + + if (Config.DEBUG) { + predictionIterator = predictions.iterator(); + while (predictionIterator.hasNext()) { + Prediction name = predictionIterator.next(); + Log.v(LOGTAG, "prediction [" + name.gestureName + " = " + name.predictionScore + "]"); + } + } + + return predictions; + } +} diff --git a/tests/sketch/src/com/android/gesture/Learner.java b/tests/sketch/src/com/android/gesture/Learner.java new file mode 100755 index 0000000..63f3156 --- /dev/null +++ b/tests/sketch/src/com/android/gesture/Learner.java @@ -0,0 +1,82 @@ +/* + * 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 java.util.ArrayList; + +/** + * The abstract class of a gesture learner + */ +abstract class Learner { + + private final ArrayList<Instance> mInstances = new ArrayList<Instance>(); + + /** + * Add an instance to the learner + * + * @param instance + */ + void addInstance(Instance instance) { + mInstances.add(instance); + } + + /** + * Retrieve all the instances + * + * @return instances + */ + ArrayList<Instance> getInstances() { + return mInstances; + } + + /** + * Remove an instance based on its id + * + * @param id + */ + void removeInstance(long id) { + ArrayList<Instance> instances = mInstances; + int count = instances.size(); + for (int i = 0; i < count; i++) { + Instance instance = instances.get(i); + if (id == instance.instanceID) { + instances.remove(instance); + return; + } + } + } + + /** + * Remove all the instances of a category + * + * @param name the category name + */ + void removeInstances(String name) { + ArrayList<Instance> toDelete = new ArrayList<Instance>(); + ArrayList<Instance> instances = mInstances; + int count = instances.size(); + for (int i = 0; i < count; i++) { + Instance instance = instances.get(i); + if (instance.label.equals(name)) { + toDelete.add(instance); + } + } + mInstances.removeAll(toDelete); + } + + abstract ArrayList<Prediction> classify(GestureLibrary library, Instance instance); +} diff --git a/tests/sketch/src/com/android/gesture/OrientedBoundingBox.java b/tests/sketch/src/com/android/gesture/OrientedBoundingBox.java new file mode 100644 index 0000000..fe1984c --- /dev/null +++ b/tests/sketch/src/com/android/gesture/OrientedBoundingBox.java @@ -0,0 +1,81 @@ +/* + * 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.graphics.Matrix; +import android.graphics.Path; + +/** + * An oriented bounding box + */ +public class OrientedBoundingBox { + + public final float squareness; + + public final float width; + public final float height; + + public final float orientation; // -PI<alpha<PI + + public final float centerX; + public final float centerY; + + OrientedBoundingBox(float angle, float cx, float cy, float w, float h) { + orientation = angle; + width = w; + height = h; + centerX = cx; + centerY = cy; + float ratio = w / h; + if (ratio > 1) { + squareness = 1 / ratio; + } else { + squareness = ratio; + } + } + + public Path toPath() { + Path path = new Path(); + float[] point = new float[2]; + point[0] = -width / 2; + point[1] = height / 2; + Matrix matrix = new Matrix(); + matrix.setRotate(orientation); + matrix.postTranslate(centerX, centerY); + matrix.mapPoints(point); + path.moveTo(point[0], point[1]); + + point[0] = -width / 2; + point[1] = -height / 2; + matrix.mapPoints(point); + path.lineTo(point[0], point[1]); + + point[0] = width / 2; + point[1] = -height / 2; + matrix.mapPoints(point); + path.lineTo(point[0], point[1]); + + point[0] = width / 2; + point[1] = height / 2; + matrix.mapPoints(point); + path.lineTo(point[0], point[1]); + + path.close(); + + return path; + } +} diff --git a/tests/sketch/src/com/android/gesture/Prediction.java b/tests/sketch/src/com/android/gesture/Prediction.java new file mode 100755 index 0000000..60cca8b --- /dev/null +++ b/tests/sketch/src/com/android/gesture/Prediction.java @@ -0,0 +1,33 @@ +/* + * 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 class Prediction { + public final String gestureName; + + public double predictionScore; + + Prediction(String label, double score) { + gestureName = label; + predictionScore = score; + } + + @Override + public String toString() { + return gestureName; + } +} diff --git a/tests/sketch/src/com/android/gesture/example/ContactAdapter.java b/tests/sketch/src/com/android/gesture/example/ContactAdapter.java new file mode 100644 index 0000000..008a972 --- /dev/null +++ b/tests/sketch/src/com/android/gesture/example/ContactAdapter.java @@ -0,0 +1,100 @@ +/* + * 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.example; + +import android.app.Activity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.TextView; + +import java.util.ArrayList; + +class ContactAdapter extends ArrayAdapter<ContactItem> { + + private LayoutInflater mInflater; + + public ContactAdapter(Activity activity, ArrayList<ContactItem> contacts) { + super(activity, 0, contacts); + mInflater = activity.getLayoutInflater(); + } + + @Override + public ContactItem getItem(int position) { + return super.getItem(position); + } + + @Override + public long getItemId(int position) { + return getItem(position).itemID; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + final ContactItem info = getItem(position); + + View view = convertView; + if (view == null) { + view = mInflater.inflate(android.R.layout.simple_list_item_1, parent, false); + view.setTag(view.findViewById(android.R.id.text1)); + } + + final TextView textView = (TextView)view.getTag(); + textView.setText(info.toString()); + + return view; + } + + public int search(String query) { + if (query != null && query.length() > 0) { + int start = 0; + int end = getCount() - 1; + int index = binarySearch(query, start, end); + for (index = index - 1; index >= 0; index--) { + String str = getItem(index).toString().toLowerCase(); + if (!str.startsWith(query)) { + return index + 1; + } + if (index == 0) { + return 0; + } + } + return -1; + } else { + return -1; + } + } + + private int binarySearch(String prefix, int start, int end) { + if (start > end) { + return -1; + } + int mid = (start + end) / 2; + String str = getItem(mid).toString().toLowerCase(); + if (prefix.compareTo(str) <= 0) { + if (str.startsWith(prefix)) { + return mid; + } else { + return binarySearch(prefix, start, mid - 1); + } + } else { + return binarySearch(prefix, mid + 1, end); + } + } + +} diff --git a/tests/sketch/src/com/android/gesture/example/ContactItem.java b/tests/sketch/src/com/android/gesture/example/ContactItem.java new file mode 100644 index 0000000..557c4d9 --- /dev/null +++ b/tests/sketch/src/com/android/gesture/example/ContactItem.java @@ -0,0 +1,34 @@ +/* + * 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.example; + + +class ContactItem { + final String itemName; + + final long itemID; + + public ContactItem(long id, String name) { + itemID = id; + itemName = name; + } + + @Override + public String toString() { + return itemName; + } +} diff --git a/tests/sketch/src/com/android/gesture/example/ContactListGestureOverlay.java b/tests/sketch/src/com/android/gesture/example/ContactListGestureOverlay.java new file mode 100644 index 0000000..50d349a --- /dev/null +++ b/tests/sketch/src/com/android/gesture/example/ContactListGestureOverlay.java @@ -0,0 +1,122 @@ +/* + * 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.example; + +import android.app.Activity; +import android.content.ContentResolver; +import android.content.ContentUris; +import android.content.Intent; +import android.database.Cursor; +import android.os.Bundle; +import android.os.Environment; +import android.provider.Contacts.People; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.widget.AdapterView; +import android.widget.ListView; + +import com.android.gesture.Gesture; +import com.android.gesture.GestureActionListener; +import com.android.gesture.GestureProcessor; +import com.android.gesture.GestureLibrary; +import com.android.gesture.GestureOverlay; +import com.android.gesture.Prediction; + +import java.io.File; +import java.util.ArrayList; + +public class ContactListGestureOverlay extends Activity { + + static final String GESTURE_FILE_NAME = Environment.getExternalStorageDirectory().getAbsolutePath() + + File.separator + "gestureOverlay.xml"; + + private static final String SORT_ORDER = People.DISPLAY_NAME + " COLLATE LOCALIZED ASC"; + + private static final String[] CONTACTS_PROJECTION = new String[] { + People._ID, // 0 + People.DISPLAY_NAME, // 1 + }; + + private GestureOverlay mOverlay; + + private ContactAdapter mContactAdapter; + + private GestureProcessor mGestureProcessor; + + private GestureLibrary mLibrary; + + private ListView mContactList; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); + setContentView(R.layout.overlaydemo); + + setProgressBarIndeterminateVisibility(true); + + // load the gesture library + mLibrary = new GestureLibrary(GESTURE_FILE_NAME); + mLibrary.load(); + + // load the contact list + mContactList = (ListView)this.findViewById(R.id.list); + registerForContextMenu(mContactList); + mContactList.setTextFilterEnabled(true); + mContactList.setVerticalScrollBarEnabled(true); + mContactList.setOnItemClickListener(new AdapterView.OnItemClickListener() { + public void onItemClick(AdapterView<?> parent, View v, int position, long id) { + if (!mGestureProcessor.isGesturing()) { + Intent intent = new Intent(Intent.ACTION_VIEW, ContentUris.withAppendedId( + People.CONTENT_URI, id)); + startActivity(intent); + } + } + }); + ContentResolver resolver = getContentResolver(); + Cursor cursor = resolver.query(People.CONTENT_URI, CONTACTS_PROJECTION, null, null, + SORT_ORDER); + ArrayList<ContactItem> list = new ArrayList<ContactItem>(); + while (cursor.moveToNext()) { + list.add(new ContactItem(cursor.getLong(0), cursor.getString(1))); + } + mContactAdapter = new ContactAdapter(this, list); + mContactList.setAdapter(mContactAdapter); + + setProgressBarIndeterminateVisibility(false); + + // add a gesture overlay on top of the ListView + mOverlay = new GestureOverlay(this); + mGestureProcessor = new GestureProcessor(mContactList); + mGestureProcessor.addGestureActionListener(new GestureActionListener() { + public void onGesturePerformed(GestureOverlay overlay, Gesture gesture) { + ArrayList<Prediction> predictions = mLibrary.recognize(gesture); + if (!predictions.isEmpty()) { + int index = mContactAdapter.search(predictions.get(0).gestureName); + if (index != -1) { + mContactList.setSelection(index); + } + } + } + }); + mOverlay.addGestureListener(mGestureProcessor); + ViewGroup.LayoutParams params = new ViewGroup.LayoutParams( + ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT); + this.addContentView(mOverlay, params); + } +} diff --git a/tests/sketch/src/com/android/gesture/example/GestureEntry.java b/tests/sketch/src/com/android/gesture/example/GestureEntry.java new file mode 100644 index 0000000..6a54b49 --- /dev/null +++ b/tests/sketch/src/com/android/gesture/example/GestureEntry.java @@ -0,0 +1,209 @@ +/* + * 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.example; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.DialogInterface; +import android.content.Intent; +import android.graphics.Color; +import android.os.Bundle; +import android.os.Environment; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.MotionEvent; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.EditText; +import android.widget.Spinner; +import android.widget.AdapterView.OnItemSelectedListener; + +import com.android.gesture.Gesture; +import com.android.gesture.GestureLibrary; +import com.android.gesture.GestureListener; +import com.android.gesture.GestureOverlay; +import com.android.gesture.Prediction; + +import java.io.File; +import java.util.ArrayList; + +public class GestureEntry extends Activity { + + private static final String PARCEL_KEY = "gesture"; + + static final String GESTURE_FILE_NAME = Environment.getExternalStorageDirectory().getAbsolutePath() + + File.separator + "gestureEntry.xml"; + + private static final int DIALOG_NEW_ENTRY = 1; + + private static final int NEW_ID = Menu.FIRST; + + private static final int VIEW_ID = Menu.FIRST + 1; + + private GestureOverlay mGesturePad; + + private Spinner mRecognitionResult; + + private GestureLibrary mGestureLibrary; + + private boolean mChangedByRecognizer = false; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.demo); + + // init the gesture library + mGestureLibrary = new GestureLibrary(GESTURE_FILE_NAME); + mGestureLibrary.load(); + + // create the spinner for showing the recognition results + // the spinner also allows a user to correct a prediction + mRecognitionResult = (Spinner) findViewById(R.id.spinner); + mRecognitionResult.setOnItemSelectedListener(new OnItemSelectedListener() { + + public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { + // correct the recognition result by adding the new example + if (!mChangedByRecognizer) { + mGestureLibrary.addGesture(parent.getSelectedItem().toString(), mGesturePad + .getCurrentGesture()); + } else { + mChangedByRecognizer = false; + } + } + + public void onNothingSelected(AdapterView<?> parent) { + + } + + }); + + // create the area for drawing a gesture + mGesturePad = (GestureOverlay) findViewById(R.id.drawingpad); + mGesturePad.setBackgroundColor(Color.BLACK); + mGesturePad.addGestureListener(new GestureListener() { + public void onFinishGesture(GestureOverlay pad, MotionEvent event) { + recognize(pad.getCurrentGesture()); + pad.clear(true); + } + + public void onGesture(GestureOverlay pad, MotionEvent event) { + } + + public void onStartGesture(GestureOverlay pad, MotionEvent event) { + } + }); + + if (savedInstanceState != null) { + Gesture g = (Gesture) savedInstanceState.getParcelable(PARCEL_KEY); + if (g != null) { + mGesturePad.setCurrentGesture(g); + } + } + } + + @Override + protected Dialog onCreateDialog(int id) { + LayoutInflater factory = LayoutInflater.from(this); + final View textEntryView = factory.inflate(R.layout.newgesture_dialog, null); + return new AlertDialog.Builder(GestureEntry.this).setTitle( + R.string.newgesture_text_entry).setView(textEntryView).setPositiveButton( + R.string.newgesture_dialog_ok, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) { + EditText edittext = (EditText) ((AlertDialog) dialog) + .findViewById(R.id.gesturename_edit); + String text = edittext.getText().toString().trim(); + if (text.length() > 0) { + mGestureLibrary.addGesture(text, mGesturePad.getCurrentGesture()); + } + } + }).setNegativeButton(R.string.newgesture_dialog_cancel, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) { + } + }).create(); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + super.onCreateOptionsMenu(menu); + menu.add(0, NEW_ID, 0, R.string.newgesture).setShortcut('0', 'n').setIcon( + android.R.drawable.ic_menu_add); + menu.add(0, VIEW_ID, 0, R.string.viewgesture).setShortcut('1', 'v').setIcon( + android.R.drawable.ic_menu_view); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case NEW_ID: + if (mGesturePad.getCurrentGesture() != null) { + showDialog(DIALOG_NEW_ENTRY); + } + break; + + case VIEW_ID: + startActivityForResult(new Intent(this, GestureLibViewer.class), VIEW_ID); + break; + } + + return super.onOptionsItemSelected(item); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + mGestureLibrary.load(); + mGesturePad.clear(false); + } + + @Override + protected void onPause() { + super.onPause(); + mGestureLibrary.save(); + } + + @Override + protected void onPrepareDialog(int id, Dialog dialog) { + super.onPrepareDialog(id, dialog); + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + Gesture gesture = mGesturePad.getCurrentGesture(); + if (gesture != null) { + outState.putParcelable(PARCEL_KEY, gesture); + } + mGestureLibrary.save(); + } + + private void recognize(Gesture ink) { + mChangedByRecognizer = true; + ArrayList<Prediction> predictions = mGestureLibrary.recognize(ink); + ArrayAdapter<Prediction> adapter = new ArrayAdapter<Prediction>(this, + android.R.layout.simple_spinner_item, predictions); + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + mRecognitionResult.setAdapter(adapter); + } + +} diff --git a/tests/sketch/src/com/android/gesture/example/GestureEntryDemo.java b/tests/sketch/src/com/android/gesture/example/GestureEntryDemo.java index 8fee21a..52a2ba8 100755..100644 --- a/tests/sketch/src/com/android/gesture/example/GestureEntryDemo.java +++ b/tests/sketch/src/com/android/gesture/example/GestureEntryDemo.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008 The Android Open Source Project + * Copyright (C) 2009 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,29 +21,34 @@ import android.app.AlertDialog; import android.app.Dialog; import android.content.DialogInterface; import android.content.Intent; +import android.graphics.Color; import android.os.Bundle; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; +import android.view.View.OnClickListener; import android.widget.AdapterView; import android.widget.ArrayAdapter; +import android.widget.Button; import android.widget.EditText; import android.widget.Spinner; import android.widget.AdapterView.OnItemSelectedListener; import com.android.gesture.Gesture; -import com.android.gesture.GestureLib; +import com.android.gesture.GestureLibrary; import com.android.gesture.GestureListener; import com.android.gesture.GesturePad; -import com.android.gesture.R; -import com.android.gesture.recognizer.Prediction; +import com.android.gesture.Prediction; import java.util.ArrayList; /** * The demo shows how to construct a gesture-based user interface on Android. + * + * @author liyang@google.com (Yang Li) + * */ public class GestureEntryDemo extends Activity { @@ -52,31 +57,35 @@ public class GestureEntryDemo extends Activity { private static final int NEW_ID = Menu.FIRST; private static final int VIEW_ID = Menu.FIRST + 1; - GesturePad mView; - Spinner mResult; - GestureLib mRecognizer; - boolean mChangedByRecognizer = false; + private GesturePad mGesturePad; + private Spinner mRecognitionResult; + private GestureLibrary mGestureLibrary; + private boolean mChangedByRecognizer = false; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.demo); - // init the recognizer - mRecognizer = new GestureLib("/sdcard/gestureentry"); - mRecognizer.load(); + // init the gesture library + mGestureLibrary = new GestureLibrary( + "/sdcard/gestureentry/gestures.xml"); + mGestureLibrary.load(); // create the spinner for showing the recognition results // the spinner also allows a user to correct a prediction - mResult = (Spinner) findViewById(R.id.spinner); - mResult.setOnItemSelectedListener(new OnItemSelectedListener() { + mRecognitionResult = (Spinner) findViewById(R.id.spinner); + mRecognitionResult.setOnItemSelectedListener( + new OnItemSelectedListener() { - public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { + public void onItemSelected( + AdapterView<?> parent, View view, int position, long id) { // TODO Auto-generated method stub // correct the recognition result by adding the new example if (mChangedByRecognizer == false) { - mRecognizer.addGesture(parent.getSelectedItem().toString(), - mView.getCurrentGesture()); + mGestureLibrary.addGesture( + parent.getSelectedItem().toString(), + mGesturePad.getCurrentGesture()); } else { mChangedByRecognizer = false; } @@ -90,28 +99,35 @@ public class GestureEntryDemo extends Activity { }); // create the area for drawing a gesture - mView = (GesturePad)this.findViewById(R.id.drawingpad); - mView.cacheGesture(false); - mView.setFadingOut(false); - mView.addGestureListener(new GestureListener() { - public void onFinishGesture(GesturePad patch, MotionEvent event) { + mGesturePad = (GesturePad)this.findViewById(R.id.drawingpad); + mGesturePad.setBackgroundColor(Color.BLACK); + mGesturePad.addGestureListener(new GestureListener() { + public void onFinishGesture(GesturePad pad, MotionEvent event) { // TODO Auto-generated method stub - recognize(patch.getCurrentGesture()); + recognize(pad.getCurrentGesture()); } - public void onGesture(GesturePad patch, MotionEvent event) { + public void onGesture(GesturePad pad, MotionEvent event) { // TODO Auto-generated method stub - } - public void onStartGesture(GesturePad patch, MotionEvent event) { - // TODO Auto-generated method stub - + public void onStartGesture(GesturePad pad, MotionEvent event) { + // TODO Auto-generated method stub } }); + Button clear = (Button)this.findViewById(R.id.clear); + clear.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + // TODO Auto-generated method stub + mGesturePad.clear(false); + mGesturePad.invalidate(); + } + }); if (savedInstanceState != null) { - mView.setCurrentGesture( - (Gesture)savedInstanceState.getParcelable("gesture")); + Gesture g = (Gesture)savedInstanceState.getParcelable("gesture"); + if (g != null) { + mGesturePad.setCurrentGesture(g); + } } } @@ -129,10 +145,12 @@ public class GestureEntryDemo extends Activity { public void onClick(DialogInterface dialog, int whichButton) { /* User clicked OK so do some stuff */ EditText edittext = - (EditText)((AlertDialog)dialog).findViewById(R.id.gesturename_edit); + (EditText)((AlertDialog)dialog).findViewById( + R.id.gesturename_edit); String text = edittext.getText().toString().trim(); if (text.length() > 0) { - mRecognizer.addGesture(text, mView.getCurrentGesture()); + mGestureLibrary.addGesture( + text, mGesturePad.getCurrentGesture()); } } }) @@ -164,7 +182,7 @@ public class GestureEntryDemo extends Activity { switch (item.getItemId()) { case NEW_ID: // if there has been a gesture on the canvas - if (mView.getCurrentGesture() != null) { + if (mGesturePad.getCurrentGesture() != null) { showDialog(DIALOG_NEW_ENTRY); } break; @@ -180,16 +198,17 @@ public class GestureEntryDemo extends Activity { @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - mRecognizer.load(); - mView.clear(); + protected void onActivityResult( + int requestCode, int resultCode, Intent data) { + mGestureLibrary.load(); + mGesturePad.clear(false); } @Override protected void onPause() { // TODO Auto-generated method stub super.onPause(); - mRecognizer.save(); + mGestureLibrary.save(); } @@ -203,18 +222,20 @@ public class GestureEntryDemo extends Activity { protected void onSaveInstanceState(Bundle outState) { // TODO Auto-generated method stub super.onSaveInstanceState(outState); - outState.putParcelable("gesture", mView.getCurrentGesture()); - mRecognizer.save(); + Gesture gesture = mGesturePad.getCurrentGesture(); + if (gesture != null) + outState.putParcelable("gesture", gesture); + mGestureLibrary.save(); } public void recognize(Gesture ink) { mChangedByRecognizer = true; - ArrayList<Prediction> predictions = mRecognizer.recognize(ink); - ArrayAdapter adapter = new ArrayAdapter(this, + ArrayList<Prediction> predictions = mGestureLibrary.recognize(ink); + ArrayAdapter<Prediction> adapter = new ArrayAdapter<Prediction>(this, android.R.layout.simple_spinner_item, predictions); adapter.setDropDownViewResource( android.R.layout.simple_spinner_dropdown_item); - mResult.setAdapter(adapter); + mRecognitionResult.setAdapter(adapter); } } diff --git a/tests/sketch/src/com/android/gesture/example/GestureLibViewer.java b/tests/sketch/src/com/android/gesture/example/GestureLibViewer.java index 7ae7fc5..ca54110 100755..100644 --- a/tests/sketch/src/com/android/gesture/example/GestureLibViewer.java +++ b/tests/sketch/src/com/android/gesture/example/GestureLibViewer.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008 The Android Open Source Project + * 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. @@ -17,8 +17,6 @@ package com.android.gesture.example; import android.app.Activity; -import android.graphics.Matrix; -import android.graphics.Path; import android.os.Bundle; import android.view.KeyEvent; import android.view.View; @@ -30,228 +28,156 @@ import android.widget.Spinner; import android.widget.AdapterView.OnItemSelectedListener; import com.android.gesture.Gesture; -import com.android.gesture.GestureLib; -import com.android.gesture.GesturePad; -import com.android.gesture.R; -import com.android.gesture.recognizer.Instance; +import com.android.gesture.GestureLibrary; +import com.android.gesture.GestureOverlay; import java.util.ArrayList; import java.util.Collections; /** - * GestureLibViewer is for viewing existing gestures and + * GestureLibViewer gives an example on how to browse existing gestures and * removing unwanted gestures. */ -public class GestureLibViewer extends Activity { - - GesturePad mView; - Spinner mResult; - GestureLib mRecognizer; - ArrayList<Gesture> mSamples; - int mCurrentGestureIndex; +public class GestureLibViewer extends Activity { + + private GestureOverlay mGesturePad; + + private Spinner mGestureCategory; + + private GestureLibrary mGesureLibrary; + + private ArrayList<Gesture> mGestures; + + private int mCurrentGestureIndex; + + private class RemoveGestureListener implements OnClickListener { + public void onClick(View v) { + if (mGestures.isEmpty()) { + return; + } + + String name = (String) mGestureCategory.getSelectedItem(); + Gesture gesture = mGestures.get(mCurrentGestureIndex); + mGesureLibrary.removeGesture(name, gesture); + + mGestures = mGesureLibrary.getGestures(name); + + if (mGestures == null) { + // delete the entire entry + mCurrentGestureIndex = 0; + ArrayList<String> list = new ArrayList<String>(); + list.addAll(mGesureLibrary.getGestureEntries()); + Collections.sort(list); + ArrayAdapter<String> adapter = new ArrayAdapter<String>(GestureLibViewer.this, + android.R.layout.simple_spinner_item, list); + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + mGestureCategory.setAdapter(adapter); + } else { + if (mCurrentGestureIndex > mGestures.size() - 1) { + mCurrentGestureIndex--; + } + gesture = mGestures.get(mCurrentGestureIndex); + mGesturePad.setCurrentGesture(gesture); + mGesturePad.invalidate(); + } + } + } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.gestureviewer); - - // create the area for drawing a glyph - mView = (GesturePad)this.findViewById(R.id.drawingpad); - mView.cacheGesture(false); - mView.setFadingOut(false); - mView.setEnableInput(false); - - // init the recognizer - mRecognizer = new GestureLib("/sdcard/gestureentry"); - mRecognizer.load(); - mResult = (Spinner) findViewById(R.id.spinner); + // create the area for drawing a gesture + mGesturePad = (GestureOverlay) findViewById(R.id.drawingpad); + mGesturePad.setEnabled(false); + + // init the gesture library + mGesureLibrary = new GestureLibrary(GestureEntry.GESTURE_FILE_NAME); + mGesureLibrary.load(); + + mGestureCategory = (Spinner) findViewById(R.id.spinner); ArrayList<String> list = new ArrayList<String>(); - list.addAll(mRecognizer.getLabels()); - Collections.sort(list); - ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, - android.R.layout.simple_spinner_item, - list); - adapter.setDropDownViewResource( - android.R.layout.simple_spinner_dropdown_item); - mResult.setAdapter(adapter); - mSamples = mRecognizer.getGestures(list.get(0)); - if (mSamples.isEmpty() == false) { + if (!mGesureLibrary.getGestureEntries().isEmpty()) { + list.addAll(mGesureLibrary.getGestureEntries()); + Collections.sort(list); + ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, + android.R.layout.simple_spinner_item, list); + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + mGestureCategory.setAdapter(adapter); + mGestures = mGesureLibrary.getGestures(list.get(0)); mCurrentGestureIndex = 0; - Gesture gesture = mSamples.get(mCurrentGestureIndex); - mView.setCurrentGesture(gesture); - mView.clearDebugPath(); - mView.addDebugPath( - toPath(mRecognizer.getClassifier().getInstance(gesture.getID()))); + Gesture gesture = mGestures.get(mCurrentGestureIndex); + mGesturePad.setCurrentGesture(gesture); } - - mResult.setOnItemSelectedListener(new OnItemSelectedListener() { + + mGestureCategory.setOnItemSelectedListener(new OnItemSelectedListener() { public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { - // TODO Auto-generated method stub - mSamples = mRecognizer.getGestures( - (String)mResult.getSelectedItem()); - if (mSamples.isEmpty() == false) { + mGestures = mGesureLibrary.getGestures((String) mGestureCategory.getSelectedItem()); + if (!mGestures.isEmpty()) { mCurrentGestureIndex = 0; - Gesture gesture = mSamples.get(mCurrentGestureIndex); - mView.setCurrentGesture(gesture); - mView.clearDebugPath(); - mView.addDebugPath( - toPath(mRecognizer.getClassifier().getInstance(gesture.getID()))); + Gesture gesture = mGestures.get(mCurrentGestureIndex); + mGesturePad.setCurrentGesture(gesture); } - mView.invalidate(); + mGesturePad.invalidate(); } - + public void onNothingSelected(AdapterView<?> parent) { - // TODO Auto-generated method stub - - } - - }); - - Button remove = (Button)this.findViewById(R.id.remove); - remove.setOnClickListener(new OnClickListener() { - public void onClick(View v) { - // TODO Auto-generated method stub - if (mSamples.isEmpty()) - return; - - String name = (String)mResult.getSelectedItem(); - Gesture gesture = mSamples.get(mCurrentGestureIndex); - mRecognizer.removeGesture(name, gesture); - - mSamples = mRecognizer.getGestures(name); - - if (mSamples == null) { - // delete the entire entry - mCurrentGestureIndex = 0; - ArrayList<String> list = new ArrayList<String>(); - list.addAll(mRecognizer.getLabels()); - Collections.sort(list); - ArrayAdapter<String> adapter = new ArrayAdapter<String>( - GestureLibViewer.this, - android.R.layout.simple_spinner_item, - list); - adapter.setDropDownViewResource( - android.R.layout.simple_spinner_dropdown_item); - mResult.setAdapter(adapter); - } else { - if (mCurrentGestureIndex > mSamples.size()-1) { - mCurrentGestureIndex--; - } - gesture = mSamples.get(mCurrentGestureIndex); - mView.setCurrentGesture(gesture); - mView.clearDebugPath(); - mView.addDebugPath( - toPath(mRecognizer.getClassifier().getInstance(gesture.getID()))); - mView.invalidate(); - } } + }); - - Button next = (Button)this.findViewById(R.id.next); + + Button remove = (Button) findViewById(R.id.remove); + remove.setOnClickListener(new RemoveGestureListener()); + + Button next = (Button) findViewById(R.id.next); next.setOnClickListener(new OnClickListener() { public void onClick(View v) { - // TODO Auto-generated method stub - if (mCurrentGestureIndex >= mSamples.size()-1) + if (mCurrentGestureIndex >= mGestures.size() - 1) { return; - + } mCurrentGestureIndex++; - Gesture gesture = mSamples.get(mCurrentGestureIndex); - mView.setCurrentGesture(gesture); - mView.clearDebugPath(); - mView.addDebugPath( - toPath(mRecognizer.getClassifier().getInstance(gesture.getID()))); - mView.invalidate(); + Gesture gesture = mGestures.get(mCurrentGestureIndex); + mGesturePad.setCurrentGesture(gesture); + mGesturePad.invalidate(); } }); - Button previous = (Button)this.findViewById(R.id.previous); + Button previous = (Button) findViewById(R.id.previous); previous.setOnClickListener(new OnClickListener() { public void onClick(View v) { - // TODO Auto-generated method stub - if (mCurrentGestureIndex >= 1 && - mSamples.isEmpty() == false) { + if (mCurrentGestureIndex >= 1 && !mGestures.isEmpty()) { mCurrentGestureIndex--; - Gesture gesture = mSamples.get(mCurrentGestureIndex); - mView.setCurrentGesture(gesture); - mView.clearDebugPath(); - mView.addDebugPath( - toPath(mRecognizer.getClassifier().getInstance(gesture.getID()))); - mView.invalidate(); + Gesture gesture = mGestures.get(mCurrentGestureIndex); + mGesturePad.setCurrentGesture(gesture); + mGesturePad.invalidate(); } } }); } - - public static ArrayList<Path> toPath(Instance instance) { - ArrayList<Path> paths = new ArrayList(); - Path path = null; - float minx = 0, miny = 0; - float mX = 0, mY = 0; - for (int i=0; i<instance.vector.length; i+=2) { - float x = instance.vector[i]; - float y = instance.vector[i+1]; - if (x < minx) - minx = x; - if (y < miny) - miny = y; - if (path == null) { - path = new Path(); - path.moveTo(x, y); - mX = x; - mY = y; - } else { - float dx = Math.abs(x - mX); - float dy = Math.abs(y - mY); - if (dx >= 3 || dy >= 3) { - path.quadTo(mX, mY, (x + mX)/2, (y + mY)/2); - mX = x; - mY = y; - } - } - } - Matrix matrix = new Matrix(); - matrix.setTranslate(-minx + 10, -miny + 10); - path.transform(matrix); - paths.add(path); - - path = new Path(); - path.moveTo(instance.vector[0]-5, instance.vector[1]-5); - path.lineTo(instance.vector[0]-5, instance.vector[1]+5); - path.lineTo(instance.vector[0]+5, instance.vector[1]+5); - path.lineTo(instance.vector[0]+5, instance.vector[1]-5); - path.close(); - path.transform(matrix); - paths.add(path); - - return paths; - } - + @Override public boolean onKeyUp(int keyCode, KeyEvent event) { - // TODO Auto-generated method stub - if (keyCode == KeyEvent.KEYCODE_BACK) { - mRecognizer.save(); - this.setResult(RESULT_OK); - finish(); - return true; - } - else - return false; + if (keyCode == KeyEvent.KEYCODE_BACK) { + mGesureLibrary.save(); + setResult(RESULT_OK); + finish(); + return true; + } else { + return false; + } } - + @Override protected void onPause() { - // TODO Auto-generated method stub super.onPause(); - mRecognizer.save(); + mGesureLibrary.save(); } @Override protected void onSaveInstanceState(Bundle outState) { - // TODO Auto-generated method stub super.onSaveInstanceState(outState); - mRecognizer.save(); + mGesureLibrary.save(); } } |