diff options
author | Android (Google) Code Review <android-gerrit@google.com> | 2009-05-19 10:32:01 -0700 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2009-05-19 10:32:01 -0700 |
commit | 12bb9471cb9ce2eb48d8e9817841f43e7220566f (patch) | |
tree | 3259d62ef49d58ff47dd7c0ff0e76af420569212 /tests | |
parent | c3320dbe1c7acf040a3ec895129d8aae09c570ea (diff) | |
parent | 35aa84b1f9f5e42dd00cb66df993ed1628c8963b (diff) | |
download | frameworks_base-12bb9471cb9ce2eb48d8e9817841f43e7220566f.zip frameworks_base-12bb9471cb9ce2eb48d8e9817841f43e7220566f.tar.gz frameworks_base-12bb9471cb9ce2eb48d8e9817841f43e7220566f.tar.bz2 |
Merge change 1936 into donut
* changes:
Recovered the code of the gesture library
Diffstat (limited to 'tests')
34 files changed, 3016 insertions, 1732 deletions
diff --git a/tests/sketch/AndroidManifest.xml b/tests/sketch/AndroidManifest.xml index c44b54e..1f4333c 100755 --- a/tests/sketch/AndroidManifest.xml +++ b/tests/sketch/AndroidManifest.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<!-- 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. @@ -14,11 +14,12 @@ limitations under the License. --> <manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.android.gesture" + package="com.android.gesture.example" android:versionCode="1" android:versionName="1.0.0"> + <uses-permission android:name="android.permission.READ_CONTACTS" /> <application android:icon="@drawable/icon" android:label="@string/app_name"> - <activity android:name="com.android.gesture.example.GestureEntryDemo" + <activity android:name="com.android.gesture.example.GestureEntry" android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.MAIN" /> @@ -26,5 +27,12 @@ </intent-filter> </activity> <activity android:name="com.android.gesture.example.GestureLibViewer"/> + <activity android:name="com.android.gesture.example.ContactListGestureOverlay" + android:label="@string/overlay_name"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> </application> </manifest> diff --git a/tests/sketch/res/layout/demo.xml b/tests/sketch/res/layout/demo.xml index e516229..8c9161a 100755 --- a/tests/sketch/res/layout/demo.xml +++ b/tests/sketch/res/layout/demo.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2008 The Android Open Source Project +<!-- 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. @@ -13,6 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. --> + <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" @@ -24,7 +25,7 @@ android:drawSelectorOnTop="true" android:prompt="@string/recognition_result"/> - <com.android.gesture.GesturePad + <com.android.gesture.GestureOverlay android:id="@+id/drawingpad" android:layout_width="fill_parent" android:layout_height="wrap_content" diff --git a/tests/sketch/res/layout/gestureviewer.xml b/tests/sketch/res/layout/gestureviewer.xml index 5302d34..73d6a35 100755 --- a/tests/sketch/res/layout/gestureviewer.xml +++ b/tests/sketch/res/layout/gestureviewer.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2008 The Android Open Source Project +<!-- 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. @@ -13,6 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. --> + <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" @@ -25,7 +26,7 @@ android:drawSelectorOnTop="true" android:prompt="@string/recognition_result"/> - <com.android.gesture.GesturePad + <com.android.gesture.GestureOverlay android:id="@+id/drawingpad" android:layout_width="fill_parent" android:layout_height="wrap_content" diff --git a/tests/sketch/res/layout/newgesture_dialog.xml b/tests/sketch/res/layout/newgesture_dialog.xml index 6e45d81..91e7645 100755 --- a/tests/sketch/res/layout/newgesture_dialog.xml +++ b/tests/sketch/res/layout/newgesture_dialog.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2008 The Android Open Source Project +<!-- 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. diff --git a/tests/sketch/res/layout/overlaydemo.xml b/tests/sketch/res/layout/overlaydemo.xml new file mode 100644 index 0000000..b6bbab3 --- /dev/null +++ b/tests/sketch/res/layout/overlaydemo.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + > + <ListView + android:id="@+id/list" + android:layout_width="fill_parent" + android:layout_height="0dip" + android:layout_weight="1"/> +</LinearLayout> diff --git a/tests/sketch/res/values/strings.xml b/tests/sketch/res/values/strings.xml index 4c6aa20..42f14da 100755 --- a/tests/sketch/res/values/strings.xml +++ b/tests/sketch/res/values/strings.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2008 The Android Open Source Project +<!-- 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. @@ -15,6 +15,7 @@ --> <resources> <string name="app_name">Gesture Demo</string> + <string name="overlay_name">Overlay Demo</string> <string name="recognition_result">Recognition Result</string> <string name="clear">Clear</string> <string name="newgesture">Add</string> 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/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/GestureLib.java b/tests/sketch/src/com/android/gesture/GestureLib.java deleted file mode 100755 index d0a25f2..0000000 --- a/tests/sketch/src/com/android/gesture/GestureLib.java +++ /dev/null @@ -1,280 +0,0 @@ -/* - * Copyright (C) 2008-2009 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.gesture; - -import android.util.Log; -import android.util.Xml; -import android.util.Xml.Encoding; - -import com.android.gesture.recognizer.Classifier; -import com.android.gesture.recognizer.Instance; -import com.android.gesture.recognizer.NearestNeighbor; -import com.android.gesture.recognizer.Prediction; - -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; -import java.util.StringTokenizer; - -public class GestureLib { - - private static final String LOGTAG = "GestureLib"; - private static String namespace = "ink"; - private final String datapath; - private HashMap<String, ArrayList<Gesture>> name2gestures = - new HashMap<String, ArrayList<Gesture>>(); - private Classifier mClassifier; - - public GestureLib(String path) { - datapath = path; - mClassifier = new NearestNeighbor(); - } - - public Classifier getClassifier() { - return mClassifier; - } - - /** - * get all the labels in the library - * @return a set of strings - */ - public Set<String> getLabels() { - return name2gestures.keySet(); - } - - public ArrayList<Prediction> recognize(Gesture gesture) { - Instance instance = Instance.createInstance(gesture, null); - return mClassifier.classify(instance); - } - - public void addGesture(String name, Gesture gesture) { - Log.v(LOGTAG, "add an example for gesture: " + name); - ArrayList<Gesture> gestures = name2gestures.get(name); - if (gestures == null) { - gestures = new ArrayList<Gesture>(); - name2gestures.put(name, gestures); - } - gestures.add(gesture); - mClassifier.addInstance( - Instance.createInstance(gesture, name)); - } - - public void removeGesture(String name, Gesture gesture) { - ArrayList<Gesture> gestures = name2gestures.get(name); - if (gestures == null) { - return; - } - - gestures.remove(gesture); - - // if there are no more samples, remove the entry automatically - if (gestures.isEmpty()) { - name2gestures.remove(name); - } - - mClassifier.removeInstance(gesture.getID()); - } - - public ArrayList<Gesture> getGestures(String label) { - ArrayList<Gesture> gestures = name2gestures.get(label); - if (gestures != null) - return (ArrayList<Gesture>)gestures.clone(); - else - return null; - } - - public void load() { - String filename = datapath - + File.separator + "gestures.xml"; - File f = new File(filename); - if (f.exists()) { - try { - loadInk(filename, null); - } - catch (SAXException ex) { - ex.printStackTrace(); - } catch (IOException ex) { - ex.printStackTrace(); - } - } - } - - public void save() { - try { - compactSave(); - } catch (IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } - - private void compactSave() throws IOException { - File f = new File(datapath); - if (f.exists() == false) { - f.mkdirs(); - } - String filename = datapath + File.separator + "gestures.xml"; - Log.v(LOGTAG, "save to " + filename); - BufferedOutputStream fos = new BufferedOutputStream( - new FileOutputStream(filename)); - - PrintWriter writer = new PrintWriter(fos); - XmlSerializer serializer = Xml.newSerializer(); - serializer.setOutput(writer); - serializer.startDocument(Encoding.ISO_8859_1.name(), null); - serializer.startTag(namespace, "gestures"); - Iterator<String> it = name2gestures.keySet().iterator(); - while (it.hasNext()) { - String key = it.next(); - ArrayList<Gesture> gestures = name2gestures.get(key); - saveGestures(serializer, key, gestures); - } - - serializer.endTag(namespace, "gestures"); - serializer.endDocument(); - serializer.flush(); - writer.close(); - fos.close(); - } - - private static void saveGestures(XmlSerializer serializer, - String name, ArrayList<Gesture> strokes) throws IOException { - serializer.startTag(namespace, "gesture"); - serializer.startTag(namespace, "name"); - serializer.text(name); - serializer.endTag(namespace, "name"); - Iterator<Gesture> it = strokes.iterator(); - while (it.hasNext()) { - Gesture stk = it.next(); - stk.toXML(namespace, serializer); - } - serializer.endTag(namespace, "gesture"); - } - - private void loadInk(String filename, String label) throws SAXException, IOException { - Log.v(LOGTAG, "load from " + filename); - BufferedInputStream in = new BufferedInputStream( - new FileInputStream(filename)); - Xml.parse(in, Encoding.ISO_8859_1, new CompactInkHandler()); - in.close(); - } - - class CompactInkHandler implements ContentHandler { - - Gesture currentGesture = null; - StringBuffer buffer = null; - String gestureName; - ArrayList<Gesture> gestures; - - CompactInkHandler() { - } - - // Receive notification of character data. - public void characters(char[] ch, int start, int length) { - buffer.append(ch, start, length); - } - - //Receive notification of the end of a document. - public void endDocument() { - } - - // Receive notification of the end of an element. - public void endElement(String uri, String localName, String qName) { - if (localName.equals("gesture")) { - name2gestures.put(gestureName, gestures); - gestures = null; - } else if (localName.equals("name")) { - gestureName = buffer.toString(); - } else if (localName.equals("stroke")) { - StringTokenizer tokenizer = new StringTokenizer(buffer.toString(), ","); - while (tokenizer.hasMoreTokens()) { - String str = tokenizer.nextToken(); - float x = Float.parseFloat(str); - str = tokenizer.nextToken(); - float y = Float.parseFloat(str); - try - { - currentGesture.addPoint(x, y); - } - catch(Exception ex) { - ex.printStackTrace(); - } - } - gestures.add(currentGesture); - mClassifier.addInstance( - Instance.createInstance(currentGesture, gestureName)); - currentGesture = null; - } - } - - // End the scope of a prefix-URI mapping. - public void endPrefixMapping(String prefix) { - } - - //Receive notification of ignorable whitespace in element content. - public void ignorableWhitespace(char[] ch, int start, int length) { - } - - //Receive notification of a processing instruction. - public void processingInstruction(String target, String data) { - } - - // Receive an object for locating the origin of SAX document events. - public void setDocumentLocator(Locator locator) { - } - - // Receive notification of a skipped entity. - public void skippedEntity(String name) { - } - - // Receive notification of the beginning of a document. - public void startDocument() { - } - - // Receive notification of the beginning of an element. - public void startElement(String uri, String localName, String qName, Attributes atts) { - if (localName.equals("gesture")) { - gestures = new ArrayList<Gesture>(); - } else if (localName.equals("name")) { - buffer = new StringBuffer(); - } else if (localName.equals("stroke")) { - currentGesture = new Gesture(); - currentGesture.setTimestamp(Long.parseLong(atts.getValue(namespace, "timestamp"))); - currentGesture.setColor(Integer.parseInt(atts.getValue(namespace, "color"))); - currentGesture.setStrokeWidth(Float.parseFloat(atts.getValue(namespace, "width"))); - buffer = new StringBuffer(); - } - } - - public void startPrefixMapping(String prefix, String uri) { - } - } -} 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..c89aa16 --- /dev/null +++ b/tests/sketch/src/com/android/gesture/GestureLibrary.java @@ -0,0 +1,337 @@ +/* + * Copyright (C) 2008-2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.gesture; + +import android.util.Config; +import android.util.Log; +import android.util.Xml; +import android.util.Xml.Encoding; + +import org.xml.sax.Attributes; +import org.xml.sax.ContentHandler; +import org.xml.sax.Locator; +import org.xml.sax.SAXException; +import org.xmlpull.v1.XmlSerializer; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Set; + +/** + * GestureLibrary maintains gesture examples and makes predictions on a new + * gesture + */ +public class GestureLibrary { + + public static final int SEQUENCE_INVARIANT = 1; + + // when SEQUENCE_SENSITIVE is used, only single stroke gestures are allowed + public static final int SEQUENCE_SENSITIVE = 2; + + private int mSequenceType = SEQUENCE_SENSITIVE; + + public static final int ORIENTATION_INVARIANT = 1; + + // ORIENTATION_SENSITIVE is only available for single stroke gestures + public static final int ORIENTATION_SENSITIVE = 2; + + private int mOrientationStyle = ORIENTATION_SENSITIVE; + + private static final String LOGTAG = "GestureLibrary"; + + private static final String NAMESPACE = ""; + + private final String mGestureFileName; + + private HashMap<String, ArrayList<Gesture>> mEntryName2gestures = new HashMap<String, ArrayList<Gesture>>(); + + private Learner mClassifier; + + private boolean mChanged = false; + + /** + * @param path where gesture data is stored + */ + public GestureLibrary(String path) { + mGestureFileName = path; + mClassifier = new InstanceLearner(); + } + + /** + * Specify whether the gesture library will handle orientation sensitive + * gestures. Use ORIENTATION_INVARIANT or ORIENTATION_SENSITIVE + * + * @param style + */ + public void setOrientationStyle(int style) { + mOrientationStyle = style; + } + + public int getOrientationStyle() { + return mOrientationStyle; + } + + public void setGestureType(int type) { + mSequenceType = type; + } + + public int getGestureType() { + return mSequenceType; + } + + /** + * Get all the gesture entry names in the library + * + * @return a set of strings + */ + public Set<String> getGestureEntries() { + return mEntryName2gestures.keySet(); + } + + /** + * Recognize a gesture + * + * @param gesture the query + * @return a list of predictions of possible entries for a given gesture + */ + public ArrayList<Prediction> recognize(Gesture gesture) { + Instance instance = Instance.createInstance(this, gesture, null); + return mClassifier.classify(this, instance); + } + + /** + * Add a gesture for the entry + * + * @param entryName entry name + * @param gesture + */ + public void addGesture(String entryName, Gesture gesture) { + if (Config.DEBUG) { + Log.v(LOGTAG, "Add an example for gesture: " + entryName); + } + if (entryName == null || entryName.length() == 0) { + return; + } + ArrayList<Gesture> gestures = mEntryName2gestures.get(entryName); + if (gestures == null) { + gestures = new ArrayList<Gesture>(); + mEntryName2gestures.put(entryName, gestures); + } + gestures.add(gesture); + mClassifier.addInstance(Instance.createInstance(this, gesture, entryName)); + mChanged = true; + } + + /** + * Remove a gesture from the library. If there are no more gestures for the + * given entry, the gesture entry will be removed. + * + * @param entryName entry name + * @param gesture + */ + public void removeGesture(String entryName, Gesture gesture) { + ArrayList<Gesture> gestures = mEntryName2gestures.get(entryName); + if (gestures == null) { + return; + } + + gestures.remove(gesture); + + // if there are no more samples, remove the entry automatically + if (gestures.isEmpty()) { + mEntryName2gestures.remove(entryName); + } + + mClassifier.removeInstance(gesture.getID()); + + mChanged = true; + } + + /** + * Remove a entry of gestures + * + * @param entryName the entry name + */ + public void removeEntireEntry(String entryName) { + mEntryName2gestures.remove(entryName); + mClassifier.removeInstances(entryName); + mChanged = true; + } + + /** + * Get all the gestures of an entry + * + * @param entryName + * @return the list of gestures that is under this name + */ + @SuppressWarnings("unchecked") + public ArrayList<Gesture> getGestures(String entryName) { + ArrayList<Gesture> gestures = mEntryName2gestures.get(entryName); + if (gestures != null) { + return (ArrayList<Gesture>)gestures.clone(); + } else { + return null; + } + } + + /** + * Save the gesture library + */ + public void save() { + if (!mChanged) + return; + + try { + File file = new File(mGestureFileName); + if (!file.getParentFile().exists()) { + file.getParentFile().mkdirs(); + } + if (Config.DEBUG) { + Log.v(LOGTAG, "Save to " + mGestureFileName); + } + BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream( + mGestureFileName), GestureConstants.IO_BUFFER_SIZE); + + PrintWriter writer = new PrintWriter(outputStream); + XmlSerializer serializer = Xml.newSerializer(); + serializer.setOutput(writer); + serializer.startDocument(Encoding.ISO_8859_1.name(), null); + serializer.startTag(NAMESPACE, GestureConstants.XML_TAG_LIBRARY); + HashMap<String, ArrayList<Gesture>> maps = mEntryName2gestures; + Iterator<String> it = maps.keySet().iterator(); + while (it.hasNext()) { + String key = it.next(); + ArrayList<Gesture> examples = maps.get(key); + // save an entry + serializer.startTag(NAMESPACE, GestureConstants.XML_TAG_ENTRY); + serializer.attribute(NAMESPACE, GestureConstants.XML_TAG_NAME, key); + int count = examples.size(); + for (int i = 0; i < count; i++) { + Gesture gesture = examples.get(i); + // save each gesture in the entry + gesture.toXML(NAMESPACE, serializer); + } + serializer.endTag(NAMESPACE, GestureConstants.XML_TAG_ENTRY); + } + serializer.endTag(NAMESPACE, GestureConstants.XML_TAG_LIBRARY); + serializer.endDocument(); + serializer.flush(); + writer.close(); + outputStream.close(); + mChanged = false; + } catch (IOException ex) { + Log.d(LOGTAG, "Failed to save gestures:", ex); + } + } + + /** + * Load the gesture library + */ + public void load() { + File file = new File(mGestureFileName); + if (file.exists()) { + try { + if (Config.DEBUG) { + Log.v(LOGTAG, "Load from " + mGestureFileName); + } + BufferedInputStream in = new BufferedInputStream(new FileInputStream( + mGestureFileName), GestureConstants.IO_BUFFER_SIZE); + Xml.parse(in, Encoding.ISO_8859_1, new CompactInkHandler()); + in.close(); + } catch (SAXException ex) { + Log.d(LOGTAG, "Failed to load gestures:", ex); + } catch (IOException ex) { + Log.d(LOGTAG, "Failed to load gestures:", ex); + } + } + } + + private class CompactInkHandler implements ContentHandler { + Gesture currentGesture = null; + + StringBuilder buffer = new StringBuilder(GestureConstants.STROKE_STRING_BUFFER_SIZE); + + String entryName; + + ArrayList<Gesture> gestures; + + CompactInkHandler() { + } + + public void characters(char[] ch, int start, int length) { + buffer.append(ch, start, length); + } + + public void endDocument() { + } + + public void endElement(String uri, String localName, String qName) { + if (localName.equals(GestureConstants.XML_TAG_ENTRY)) { + mEntryName2gestures.put(entryName, gestures); + gestures = null; + } else if (localName.equals(GestureConstants.XML_TAG_GESTURE)) { + gestures.add(currentGesture); + mClassifier.addInstance(Instance.createInstance(GestureLibrary.this, + currentGesture, entryName)); + currentGesture = null; + } else if (localName.equals(GestureConstants.XML_TAG_STROKE)) { + currentGesture.addStroke(GestureStroke.createFromString(buffer.toString())); + buffer.setLength(0); + } + } + + public void endPrefixMapping(String prefix) { + } + + public void ignorableWhitespace(char[] ch, int start, int length) { + } + + public void processingInstruction(String target, String data) { + } + + public void setDocumentLocator(Locator locator) { + } + + public void skippedEntity(String name) { + } + + public void startDocument() { + } + + public void startElement(String uri, String localName, String qName, Attributes attributes) { + if (localName.equals(GestureConstants.XML_TAG_ENTRY)) { + gestures = new ArrayList<Gesture>(); + entryName = attributes.getValue(NAMESPACE, GestureConstants.XML_TAG_NAME); + } else if (localName.equals(GestureConstants.XML_TAG_GESTURE)) { + currentGesture = new Gesture(); + currentGesture.setID(Long.parseLong(attributes.getValue(NAMESPACE, + GestureConstants.XML_TAG_ID))); + } + } + + public void startPrefixMapping(String prefix, String uri) { + } + } +} 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..9907831 --- /dev/null +++ b/tests/sketch/src/com/android/gesture/GestureOverlay.java @@ -0,0 +1,386 @@ +/* + * 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.graphics.Rect; +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 BlurMaskFilter BLUR_MASK_FILTER = new BlurMaskFilter(1, BlurMaskFilter.Blur.NORMAL); + + private static final boolean DITHER_FLAG = true; + + private static final int REFRESH_RANGE = 10; + + 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 Rect mInvalidRect = new Rect(); + + private Path mPath; + + private float mX; + + private float mY; + + private float mCurveEndX; + + private float mCurveEndY; + + // 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.setDither(DITHER_FLAG); + + 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: + Rect rect = touchStart(event); + invalidate(rect); + break; + case MotionEvent.ACTION_MOVE: + rect = touchMove(event); + if (rect != null) { + invalidate(rect); + } + break; + case MotionEvent.ACTION_UP: + touchUp(event); + invalidate(); + break; + } + + return true; + } + + private Rect touchStart(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); + + mInvalidRect.set((int) x - REFRESH_RANGE, (int) y - REFRESH_RANGE, (int) x + REFRESH_RANGE, + (int) y + REFRESH_RANGE); + + mCurveEndX = x; + mCurveEndY = y; + + return mInvalidRect; + } + + private Rect touchMove(MotionEvent event) { + Rect areaToRefresh = null; + + 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) { + + // start with the curve end + mInvalidRect.set((int) mCurveEndX - REFRESH_RANGE, (int) mCurveEndY - REFRESH_RANGE, + (int) mCurveEndX + REFRESH_RANGE, (int) mCurveEndY + REFRESH_RANGE); + + mCurveEndX = (x + mX) / 2; + mCurveEndY = (y + mY) / 2; + mPath.quadTo(mX, mY, mCurveEndX, mCurveEndY); + + // union with the control point of the new curve + mInvalidRect.union((int) mX - REFRESH_RANGE, (int) mY - REFRESH_RANGE, + (int) mX + REFRESH_RANGE, (int) mY + REFRESH_RANGE); + + // union with the end point of the new curve + mInvalidRect.union((int) mCurveEndX - REFRESH_RANGE, (int) mCurveEndY - REFRESH_RANGE, + (int) mCurveEndX + REFRESH_RANGE, (int) mCurveEndY + REFRESH_RANGE); + + areaToRefresh = mInvalidRect; + + 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); + } + + return areaToRefresh; + } + + private void touchUp(MotionEvent event) { + // add the stroke to the current gesture + mCurrentGesture.addStroke(new GestureStroke(mPointBuffer)); + + // add the stroke to the double buffer + mGesturePaint.setMaskFilter(BLUR_MASK_FILTER); + mBitmapCanvas.drawPath(mPath, mGesturePaint); + mGesturePaint.setMaskFilter(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); + } + + mPath = null; + mPointBuffer = null; + } + +} diff --git a/tests/sketch/src/com/android/gesture/GesturePad.java b/tests/sketch/src/com/android/gesture/GesturePad.java deleted file mode 100755 index 45a09e6..0000000 --- a/tests/sketch/src/com/android/gesture/GesturePad.java +++ /dev/null @@ -1,371 +0,0 @@ -/* - * Copyright (C) 2008 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.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 - */ - -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 boolean reconstruct = false; - - private ArrayList<Path> debug = new ArrayList<Path>(); - 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 GesturePad(Context context) { - super(context); - init(); - } - - public GesturePad(Context context, AttributeSet attrs) { - super(context, attrs); - init(); - } - - public boolean isEnableRendering() { - return this.mEnableRendering; - } - - 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; - } - - public void setCurrentGesture(Gesture stk) { - this.mCurrentGesture = stk; - reconstruct = true; - } - - 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); - - 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; - } - - - @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) - 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; - } - - public void addGestureListener(GestureListener l) { - this.mGestureListeners.add(l); - } - - public void removeGestureListener(GestureListener l) { - this.mGestureListeners.remove(l); - } - - @Override - protected void onDraw(Canvas canvas) { - canvas.drawColor(background); - - if (mCacheGesture) - canvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint); - - 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); - } 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.drawPath(mPath, mPaint); - - Iterator<Path> it = debug.iterator(); - while (it.hasNext()) { - Path path = it.next(); - canvas.drawPath(path, mDebugPaint); - } - } - - 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; - } - } - } - 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) - 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) { - mIsFadingOut = false; - mHandler.removeCallbacks(mFadingOut); - - 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); - } - } - - 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; - } - - mCurrentGesture.addPoint(x, y); - - Iterator<GestureListener> it = mGestureListeners.iterator(); - while (it.hasNext()) { - it.next().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); - } - - Iterator<GestureListener> it = mGestureListeners.iterator(); - while (it.hasNext()) { - it.next().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/GestureStroke.java b/tests/sketch/src/com/android/gesture/GestureStroke.java new file mode 100644 index 0000000..b5e38b7 --- /dev/null +++ b/tests/sketch/src/com/android/gesture/GestureStroke.java @@ -0,0 +1,238 @@ +/* + * 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[] points; + + private final long[] timestamps; + + private Path mCachedPath; + + /** + * Construct a gesture stroke from a list of gesture points + * + * @param pts + */ + public GestureStroke(ArrayList<GesturePoint> pts) { + float[] tmpPoints = new float[pts.size() * 2]; + long[] times = new long[pts.size()]; + + RectF bx = null; + float len = 0; + int index = 0; + int count = pts.size(); + + for (int i = 0; i < count; i++) { + GesturePoint p = pts.get(i); + tmpPoints[i * 2] = p.xpos; + tmpPoints[i * 2 + 1] = 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 - tmpPoints[(i - 1) * 2], 2) + + Math.pow(p.ypos - tmpPoints[(i -1 ) * 2 + 1], 2)); + bx.union(p.xpos, p.ypos); + } + index++; + } + + timestamps = times; + points = tmpPoints; + 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[] pts = points; + int count = pts.length; + Path path = null; + float mX = 0, mY = 0; + 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 >= 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.temporalSampling(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[] pts = points; + long[] times = timestamps; + int count = points.length; + for (int i = 0; i < count; i += 2) { + str.append(points[i] + GestureConstants.STRING_STROKE_DELIIMITER + points[i + 1] + + GestureConstants.STRING_STROKE_DELIIMITER + times[i / 2] + + 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..09d2625 --- /dev/null +++ b/tests/sketch/src/com/android/gesture/GestureUtils.java @@ -0,0 +1,426 @@ +/* + * 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 TEMPORAL_SAMPLING_RATE = 16; + + protected static float[] spatialSampling(Gesture gesture, int sampleMatrixDimension) { + final float targetPatchSize = sampleMatrixDimension - 1; // edge inclusive + float[] sample = new float[sampleMatrixDimension * sampleMatrixDimension]; + Arrays.fill(sample, 0); + + RectF rect = gesture.getBoundingBox(); + float sx = targetPatchSize / rect.width(); + float sy = targetPatchSize / 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(targetPatchSize / 2, targetPatchSize / 2); + trans.postConcat(translate2); + + ArrayList<GestureStroke> strokes = gesture.getStrokes(); + int count = strokes.size(); + int size; + float xpos; + float ypos; + for (int index = 0; index < count; index++) { + GestureStroke stroke = strokes.get(index); + size = stroke.points.length; + float[] pts = new float[size]; + trans.mapPoints(pts, 0, stroke.points, 0, size / 2); + float segmentEndX = -1; + float segmentEndY = -1; + + for (int i = 0; i < size; i += 2) { + + float segmentStartX = pts[i] < 0 ? 0 : pts[i]; + float segmentStartY = pts[i + 1] < 0 ? 0 : pts[i + 1]; + + if (segmentStartX > targetPatchSize) { + segmentStartX = targetPatchSize; + } + + if (segmentStartY > targetPatchSize) { + segmentStartY = targetPatchSize; + } + + plot(segmentStartX, segmentStartY, sample, sampleMatrixDimension); + + if (segmentEndX != -1) { + // evaluate horizontally + if (segmentEndX > segmentStartX) { + xpos = (float) Math.ceil(segmentStartX); + float slope = (segmentEndY - segmentStartY) / (segmentEndX - segmentStartX); + while (xpos < segmentEndX) { + ypos = slope * (xpos - segmentStartX) + segmentStartY; + plot(xpos, ypos, sample, sampleMatrixDimension); + xpos++; + } + } else if (segmentEndX < segmentStartX){ + xpos = (float) Math.ceil(segmentEndX); + float slope = (segmentEndY - segmentStartY) / (segmentEndX - segmentStartX); + while (xpos < segmentStartX) { + ypos = slope * (xpos - segmentStartX) + segmentStartY; + plot(xpos, ypos, sample, sampleMatrixDimension); + xpos++; + } + } + + // evaluating vertically + if (segmentEndY > segmentStartY) { + ypos = (float) Math.ceil(segmentStartY); + float invertSlope = (segmentEndX - segmentStartX) / (segmentEndY - segmentStartY); + while (ypos < segmentEndY) { + xpos = invertSlope * (ypos - segmentStartY) + segmentStartX; + plot(xpos, ypos, sample, sampleMatrixDimension); + ypos++; + } + } else if (segmentEndY < segmentStartY) { + ypos = (float) Math.ceil(segmentEndY); + float invertSlope = (segmentEndX - segmentStartX) / (segmentEndY - segmentStartY); + while (ypos < segmentStartY) { + xpos = invertSlope * (ypos - segmentStartY) + segmentStartX; + plot(xpos, ypos, sample, sampleMatrixDimension); + ypos++; + } + } + } + + segmentEndX = segmentStartX; + segmentEndY = segmentStartY; + } + } + + + return sample; + } + + + private static void plot(float x, float y, float[] sample, int sampleSize) { + x = x < 0 ? 0 : x; + y = y < 0 ? 0 : y; + int xFloor = (int) Math.floor(x); + int xCeiling = (int) Math.ceil(x); + int yFloor = (int) Math.floor(y); + int yCeiling = (int) Math.ceil(y); + + // if it's an integer + if (x == xFloor && y == yFloor) { + int index = yCeiling * sampleSize + xCeiling; + if (sample[index] < 1){ + sample[index] = 1; + } + } else { + double topLeft = Math.sqrt(Math.pow(xFloor - x, 2) + Math.pow(yFloor - y, 2)); + double topRight = Math.sqrt(Math.pow(xCeiling - x, 2) + Math.pow(yFloor - y, 2)); + double btmLeft = Math.sqrt(Math.pow(xFloor - x, 2) + Math.pow(yCeiling - y, 2)); + double btmRight = Math.sqrt(Math.pow(xCeiling - x, 2) + Math.pow(yCeiling - y, 2)); + double sum = topLeft + topRight + btmLeft + btmRight; + + double value = topLeft / sum; + int index = yFloor * sampleSize + xFloor; + if (value > sample[index]){ + sample[index] = (float) value; + } + + value = topRight / sum; + index = yFloor * sampleSize + xCeiling; + if (value > sample[index]){ + sample[index] = (float) value; + } + + value = btmLeft / sum; + index = yCeiling * sampleSize + xFloor; + if (value > sample[index]){ + sample[index] = (float) value; + } + + value = btmRight / sum; + index = yCeiling * sampleSize + xCeiling; + if (value > sample[index]){ + sample[index] = (float) value; + } + } + } + + /** + * Featurize a stroke into a vector of a given number of elements + * + * @param stroke + * @param sampleSize + * @return a float array + */ + protected static float[] temporalSampling(GestureStroke stroke, int sampleSize) { + final float increment = stroke.length / (sampleSize - 1); + int vectorLength = sampleSize * 2; + float[] vector = new float[vectorLength]; + float distanceSoFar = 0; + float[] pts = stroke.points; + float lstPointX = pts[0]; + float lstPointY = pts[1]; + 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 = pts.length / 2; + while (i < count) { + if (currentPointX == Float.MIN_VALUE) { + i++; + if (i >= count) { + break; + } + currentPointX = pts[i * 2]; + currentPointY = pts[i * 2 + 1]; + } + 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 + */ + private 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 squaredEuclideanDistance(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 = temporalSampling(stroke, TEMPORAL_SAMPLING_RATE); + 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; + } + + 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..4fbebf9 --- /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. + */ +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 + final float[] vector; + + // the label can be null + final String label; + + // the length of the vector + final float magnitude; + + // the id of the instance + 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 + */ + static Instance createInstance(GestureLibrary gesturelib, Gesture gesture, String label) { + float[] pts; + if (gesturelib.getGestureType() == GestureLibrary.SEQUENCE_SENSITIVE) { + pts = temporalSampler(gesturelib, gesture); + } else { + pts = spatialSampler(gesture); + } + return new Instance(gesture.getID(), pts, label); + } + + private static float[] spatialSampler(Gesture gesture) { + float[] pts = GestureUtils.spatialSampling(gesture, PATCH_SAMPLE_SIZE); + return pts; + } + + private static float[] temporalSampler(GestureLibrary gesturelib, Gesture gesture) { + float[] pts = GestureUtils.temporalSampling(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..95241d4 --- /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.squaredEuclideanDistance(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.score /= sum; + } + + Collections.sort(predictions, new Comparator<Prediction>() { + public int compare(Prediction object1, Prediction object2) { + double score1 = object1.score; + double score2 = object2.score; + 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.name + " = " + name.score + "]"); + } + } + + 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/LetterRecognizer.java b/tests/sketch/src/com/android/gesture/LetterRecognizer.java new file mode 100644 index 0000000..1c15c7d --- /dev/null +++ b/tests/sketch/src/com/android/gesture/LetterRecognizer.java @@ -0,0 +1,198 @@ +/* + * 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; + +import android.content.Context; +import android.content.res.Resources; +import android.util.Log; + +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; + +public class LetterRecognizer { + + private static final String LOGTAG = "LetterRecognizer"; + + public final static int LATTIN_LOWERCASE = 0; + + private SigmoidUnit[] mHiddenLayer; + + private SigmoidUnit[] mOutputLayer; + + private final String[] mClasses; + + private final int mInputCount; + + private class SigmoidUnit { + + private float[] mWeights; + + private SigmoidUnit(float[] weights) { + mWeights = weights; + } + + private float compute(float[] inputs) { + float sum = 0; + int count = inputs.length; + float[] weights = mWeights; + for (int i = 0; i < count; i++) { + sum += inputs[i] * weights[i]; + } + sum += weights[weights.length - 1]; + return 1 / (float)(1 + Math.exp(-sum)); + } + } + + private LetterRecognizer(int numOfInput, int numOfHidden, String[] classes) { + mInputCount = (int)Math.sqrt(numOfInput); + mHiddenLayer = new SigmoidUnit[numOfHidden]; + mClasses = classes; + mOutputLayer = new SigmoidUnit[classes.length]; + } + + public static LetterRecognizer getLetterRecognizer(Context context, int type) { + switch (type) { + case LATTIN_LOWERCASE: { + return createFromResource(context, com.android.internal.R.raw.lattin_lowercase); + } + } + return null; + } + + public ArrayList<Prediction> recognize(Gesture gesture) { + return this.classify(GestureUtils.spatialSampling(gesture, mInputCount)); + } + + private ArrayList<Prediction> classify(float[] vector) { + float[] intermediateOutput = compute(mHiddenLayer, vector); + float[] output = compute(mOutputLayer, intermediateOutput); + ArrayList<Prediction> predictions = new ArrayList<Prediction>(); + double sum = 0; + int count = mClasses.length; + for (int i = 0; i < count; i++) { + String name = mClasses[i]; + double score = output[i]; + sum += score; + predictions.add(new Prediction(name, score)); + } + + for (int i = 0; i < count; i++) { + Prediction name = predictions.get(i); + name.score /= sum; + } + + Collections.sort(predictions, new Comparator<Prediction>() { + public int compare(Prediction object1, Prediction object2) { + double score1 = object1.score; + double score2 = object2.score; + if (score1 > score2) { + return -1; + } else if (score1 < score2) { + return 1; + } else { + return 0; + } + } + }); + return predictions; + } + + private float[] compute(SigmoidUnit[] layer, float[] input) { + float[] output = new float[layer.length]; + int count = layer.length; + for (int i = 0; i < count; i++) { + output[i] = layer[i].compute(input); + } + return output; + } + + private static LetterRecognizer createFromResource(Context context, int resourceID) { + Resources resources = context.getResources(); + InputStream stream = resources.openRawResource(resourceID); + try { + BufferedReader reader = new BufferedReader(new InputStreamReader(stream)); + + String line = reader.readLine(); + int startIndex = 0; + int endIndex = -1; + endIndex = line.indexOf(" ", startIndex); + int iCount = Integer.parseInt(line.substring(startIndex, endIndex)); + + startIndex = endIndex + 1; + endIndex = line.indexOf(" ", startIndex); + int hCount = Integer.parseInt(line.substring(startIndex, endIndex)); + + startIndex = endIndex + 1; + endIndex = line.length(); + int oCount = Integer.parseInt(line.substring(startIndex, endIndex)); + + String[] classes = new String[oCount]; + line = reader.readLine(); + startIndex = 0; + endIndex = -1; + for (int i = 0; i < oCount; i++) { + endIndex = line.indexOf(" ", startIndex); + classes[i] = line.substring(startIndex, endIndex); + startIndex = endIndex + 1; + } + + LetterRecognizer classifier = new LetterRecognizer(iCount, hCount, classes); + SigmoidUnit[] hiddenLayer = new SigmoidUnit[hCount]; + SigmoidUnit[] outputLayer = new SigmoidUnit[oCount]; + + for (int i = 0; i < hCount; i++) { + float[] weights = new float[iCount]; + line = reader.readLine(); + startIndex = 0; + for (int j = 0; j < iCount; j++) { + endIndex = line.indexOf(" ", startIndex); + weights[j] = Float.parseFloat(line.substring(startIndex, endIndex)); + startIndex = endIndex + 1; + } + hiddenLayer[i] = classifier.new SigmoidUnit(weights); + } + + for (int i = 0; i < oCount; i++) { + float[] weights = new float[hCount]; + line = reader.readLine(); + startIndex = 0; + for (int j = 0; j < hCount; j++) { + endIndex = line.indexOf(" ", startIndex); + weights[j] = Float.parseFloat(line.substring(startIndex, endIndex)); + startIndex = endIndex + 1; + } + outputLayer[i] = classifier.new SigmoidUnit(weights); + } + + reader.close(); + + classifier.mHiddenLayer = hiddenLayer; + classifier.mOutputLayer = outputLayer; + + return classifier; + + } catch (IOException ex) { + Log.d(LOGTAG, "Failed to save gestures:", ex); + } + return null; + } +} 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..90c3969 --- /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; + + 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/recognizer/Prediction.java b/tests/sketch/src/com/android/gesture/Prediction.java index c318754..92d3ba4 100755 --- a/tests/sketch/src/com/android/gesture/recognizer/Prediction.java +++ b/tests/sketch/src/com/android/gesture/Prediction.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. @@ -14,23 +14,20 @@ * limitations under the License. */ -package com.android.gesture.recognizer; +package com.android.gesture; -/** - * - * A recognition result that includes the label and its score - */ public class Prediction { - public final String label; - public double score; - - public Prediction(String l, double s) { - label = l; - score = s; - } - - @Override + public final String name; + + public double score; + + Prediction(String label, double predictionScore) { + name = label; + score = predictionScore; + } + + @Override public String toString() { - return label; - } + return name; + } } diff --git a/tests/sketch/src/com/android/gesture/TouchThroughGesturing.java b/tests/sketch/src/com/android/gesture/TouchThroughGesturing.java new file mode 100644 index 0000000..0ffc370 --- /dev/null +++ b/tests/sketch/src/com/android/gesture/TouchThroughGesturing.java @@ -0,0 +1,147 @@ +/* + * 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; + +/** + * TouchThroughGesturing implements the interaction behavior that allows a user + * to gesture over a regular UI widget such as ListView and at the same time, + * still allows a user to perform basic interactions (clicking, scrolling and panning) + * with the underlying widget. + */ + +public class TouchThroughGesturing implements GestureListener { + + public static final int SINGLE_STROKE = 0; + + public static final int MULTIPLE_STROKE = 1; + + private static final float STROKE_LENGTH_THRESHOLD = 30; + + private static final float SQUARENESS_THRESHOLD = 0.275f; + + private static final float ANGLE_THRESHOLD = 40; + + public static final int DEFAULT_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 int mUncertainGestureColor = DEFAULT_UNCERTAIN_GESTURE_COLOR; + + private ArrayList<GestureActionListener> mActionListeners = new ArrayList<GestureActionListener>(); + + public TouchThroughGesturing(View model) { + mModel = model; + } + + /** + * + * @param type SINGLE_STROKE or MULTIPLE_STROKE + */ + public void setGestureType(int type) { + mGestureType = type; + } + + public void setUncertainGestureColor(int color) { + mUncertainGestureColor = color; + } + + 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(mUncertainGestureColor); + } + 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()); + float angle = Math.abs(bbx.orientation); + if (angle > 90) { + angle = 180 - angle; + } + if (bbx.squareness > SQUARENESS_THRESHOLD || angle < ANGLE_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/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..1d3fdf3 --- /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.provider.Contacts.People; +import android.util.Log; +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.GestureOverlay; +import com.android.gesture.LetterRecognizer; +import com.android.gesture.Prediction; +import com.android.gesture.TouchThroughGesturing; + +import java.util.ArrayList; + +public class ContactListGestureOverlay extends Activity { + + private static final String LOGTAG = "ContactListGestureOverlay"; + + 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 TouchThroughGesturing mGestureProcessor; + + private LetterRecognizer mRecognizer; + + private ListView mContactList; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); + setContentView(R.layout.overlaydemo); + + setProgressBarIndeterminateVisibility(true); + + // create a letter recognizer + mRecognizer = LetterRecognizer.getLetterRecognizer(this, LetterRecognizer.LATTIN_LOWERCASE); + + // load the contact list + mContactList = (ListView) findViewById(R.id.list); + registerForContextMenu(mContactList); + mContactList.setTextFilterEnabled(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 TouchThroughGesturing(mContactList); + mGestureProcessor.setGestureType(TouchThroughGesturing.MULTIPLE_STROKE); + mGestureProcessor.addGestureActionListener(new GestureActionListener() { + public void onGesturePerformed(GestureOverlay overlay, Gesture gesture) { + ArrayList<Prediction> predictions = mRecognizer.recognize(gesture); + if (!predictions.isEmpty()) { + Log.v(LOGTAG, "1st Prediction : " + predictions.get(0).name); + Log.v(LOGTAG, "2nd Prediction : " + predictions.get(1).name); + Log.v(LOGTAG, "3rd Prediction : " + predictions.get(2).name); + int index = mContactAdapter.search(predictions.get(0).name); + 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..03a26da --- /dev/null +++ b/tests/sketch/src/com/android/gesture/example/GestureEntry.java @@ -0,0 +1,207 @@ +/* + * 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.widget.AdapterView; +import android.widget.ArrayAdapter; +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 overlay, MotionEvent event) { + recognize(overlay.getCurrentGesture()); + } + + public void onGesture(GestureOverlay overlay, MotionEvent event) { + } + + public void onStartGesture(GestureOverlay overlay, MotionEvent event) { + overlay.clear(false); + } + }); + + if (savedInstanceState != null) { + Gesture gesture = (Gesture) savedInstanceState.getParcelable(PARCEL_KEY); + if (gesture != null) { + mGesturePad.setCurrentGesture(gesture); + } + } + } + + @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 gesture) { + mChangedByRecognizer = true; + ArrayList<Prediction> predictions = mGestureLibrary.recognize(gesture); + 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 deleted file mode 100755 index 8fee21a..0000000 --- a/tests/sketch/src/com/android/gesture/example/GestureEntryDemo.java +++ /dev/null @@ -1,220 +0,0 @@ -/* - * Copyright (C) 2008 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.os.Bundle; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuItem; -import android.view.MotionEvent; -import android.view.View; -import android.widget.AdapterView; -import android.widget.ArrayAdapter; -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.GestureListener; -import com.android.gesture.GesturePad; -import com.android.gesture.R; -import com.android.gesture.recognizer.Prediction; - -import java.util.ArrayList; - -/** - * The demo shows how to construct a gesture-based user interface on Android. - */ - -public class GestureEntryDemo extends Activity { - - 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; - - GesturePad mView; - Spinner mResult; - GestureLib mRecognizer; - 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(); - - // 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() { - - 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()); - } else { - mChangedByRecognizer = false; - } - } - - public void onNothingSelected(AdapterView<?> parent) { - // TODO Auto-generated method stub - - } - - }); - - // 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) { - // TODO Auto-generated method stub - recognize(patch.getCurrentGesture()); - } - public void onGesture(GesturePad patch, MotionEvent event) { - // TODO Auto-generated method stub - - } - public void onStartGesture(GesturePad patch, MotionEvent event) { - // TODO Auto-generated method stub - - } - }); - - - if (savedInstanceState != null) { - mView.setCurrentGesture( - (Gesture)savedInstanceState.getParcelable("gesture")); - } - } - - @Override - protected Dialog onCreateDialog(int id) { - // create the dialog for adding a new entry - LayoutInflater factory = LayoutInflater.from(this); - final View textEntryView = - factory.inflate(R.layout.newgesture_dialog, null); - return new AlertDialog.Builder(GestureEntryDemo.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) { - /* User clicked OK so do some stuff */ - EditText edittext = - (EditText)((AlertDialog)dialog).findViewById(R.id.gesturename_edit); - String text = edittext.getText().toString().trim(); - if (text.length() > 0) { - mRecognizer.addGesture(text, mView.getCurrentGesture()); - } - } - }) - .setNegativeButton(R.string.newgesture_dialog_cancel, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int whichButton) { - /* User clicked cancel so do some stuff */ - } - }) - .create(); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - // TODO Auto-generated method stub - 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) { - // Handle all of the possible menu actions. - switch (item.getItemId()) { - case NEW_ID: - // if there has been a gesture on the canvas - if (mView.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) { - mRecognizer.load(); - mView.clear(); - } - - @Override - protected void onPause() { - // TODO Auto-generated method stub - super.onPause(); - mRecognizer.save(); - } - - - @Override - protected void onPrepareDialog(int id, Dialog dialog) { - // TODO Auto-generated method stub - super.onPrepareDialog(id, dialog); - } - - @Override - protected void onSaveInstanceState(Bundle outState) { - // TODO Auto-generated method stub - super.onSaveInstanceState(outState); - outState.putParcelable("gesture", mView.getCurrentGesture()); - mRecognizer.save(); - } - - public void recognize(Gesture ink) { - mChangedByRecognizer = true; - ArrayList<Prediction> predictions = mRecognizer.recognize(ink); - ArrayAdapter adapter = new ArrayAdapter(this, - android.R.layout.simple_spinner_item, predictions); - adapter.setDropDownViewResource( - android.R.layout.simple_spinner_dropdown_item); - mResult.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 --- 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(); } } diff --git a/tests/sketch/src/com/android/gesture/recognizer/Classifier.java b/tests/sketch/src/com/android/gesture/recognizer/Classifier.java deleted file mode 100755 index 584e0a5..0000000 --- a/tests/sketch/src/com/android/gesture/recognizer/Classifier.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2008 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.recognizer; - -import java.util.ArrayList; -import java.util.HashMap; - -/** - * The abstract class of Classifier - */ -public abstract class Classifier { - - HashMap<Long, Instance> mInstances = new HashMap<Long, Instance>(); - - public void addInstance(Instance instance) { - mInstances.put(instance.id, instance); - } - - public Instance getInstance(long id) { - return mInstances.get(id); - } - - public void removeInstance(long id) { - mInstances.remove(id); - } - - public abstract ArrayList<Prediction> classify(Instance instance); -} diff --git a/tests/sketch/src/com/android/gesture/recognizer/Instance.java b/tests/sketch/src/com/android/gesture/recognizer/Instance.java deleted file mode 100755 index 2eaa1c2..0000000 --- a/tests/sketch/src/com/android/gesture/recognizer/Instance.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (C) 2008 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.recognizer; - -import android.graphics.PointF; - -import com.android.gesture.Gesture; - -/** - * An instance represents a sample if the label is available or a query if - * the label is null. - */ -public class Instance { - - private final static float[] targetOrientations = { - 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 length; - // the id of the instance - public final long id; - - Instance(long d, float[] v, String l) { - id = d; - vector = v; - label = l; - float sum = 0; - for (int i = 0; i < vector.length; i++) { - sum += vector[i] * vector[i]; - } - length = (float)Math.sqrt(sum); - } - - public static Instance createInstance(Gesture gesture, String label) { - float[] pts = RecognitionUtil.resample(gesture, 64); - PointF center = RecognitionUtil.computeCentroid(pts); - float inductiveOrientation = (float)Math.atan2(pts[1] - center.y, - pts[0] - center.x); - inductiveOrientation *= 180 / Math.PI; - - float minDeviation = Float.MAX_VALUE; - for (int i=0; i<targetOrientations.length; i++) { - float delta = targetOrientations[i] - inductiveOrientation; - if (Math.abs(delta) < Math.abs(minDeviation)) { - minDeviation = delta; - } - } - - android.graphics.Matrix m = new android.graphics.Matrix(); - m.setTranslate(-center.x, -center.y); - android.graphics.Matrix rotation = new android.graphics.Matrix(); - rotation.setRotate(minDeviation); - m.postConcat(rotation); - m.mapPoints(pts); - - return new Instance(gesture.getID(), pts, label); - } -} diff --git a/tests/sketch/src/com/android/gesture/recognizer/NearestNeighbor.java b/tests/sketch/src/com/android/gesture/recognizer/NearestNeighbor.java deleted file mode 100755 index cb8a9d3..0000000 --- a/tests/sketch/src/com/android/gesture/recognizer/NearestNeighbor.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (C) 2008 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.recognizer; - -import android.util.Log; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.Iterator; -import java.util.TreeMap; - -public class NearestNeighbor extends Classifier { - - private static final String LOGTAG = "NearestNeighbor"; - private static final double variance = 0.25; // std = 0.5 - - public ArrayList<Prediction> classify(Instance instance) { - - ArrayList<Prediction> list = new ArrayList<Prediction>(); - Iterator<Instance> it = mInstances.values().iterator(); - Log.v(LOGTAG, mInstances.size() + " instances found"); - TreeMap<String, Double> label2score = new TreeMap<String, Double>(); - while (it.hasNext()) { - Instance sample = it.next(); - double dis = RecognitionUtil.cosineDistance(sample, instance); - double weight = Math.exp(-dis*dis/(2 * variance)); - Log.v(LOGTAG, sample.label + " = " + dis + " weight = " + weight); - Double score = label2score.get(sample.label); - if (score == null) { - score = weight; - } - else { - score += weight; - } - label2score.put(sample.label, score); - } - - double sum = 0; - Iterator it2 = label2score.keySet().iterator(); - while (it2.hasNext()) { - String name = (String)it2.next(); - double score = label2score.get(name); - sum += score; - list.add(new Prediction(name, score)); - } - - it2 = list.iterator(); - while (it2.hasNext()) { - Prediction name = (Prediction)it2.next(); - name.score /= sum; - } - - - Collections.sort(list, new Comparator<Prediction>() { - public int compare(Prediction object1, Prediction object2) { - // TODO Auto-generated method stub - double score1 = object1.score; - double score2 = object2.score; - if (score1 > score2) - return -1; - else if (score1 < score2) - return 1; - else - return 0; - } - }); - - it2 = list.iterator(); - while (it2.hasNext()) { - Prediction name = (Prediction)it2.next(); - Log.v(LOGTAG, "prediction [" + name.label + " = " + name.score + "]"); - } - - return list; - } -} diff --git a/tests/sketch/src/com/android/gesture/recognizer/RecognitionUtil.java b/tests/sketch/src/com/android/gesture/recognizer/RecognitionUtil.java deleted file mode 100755 index 9146b95..0000000 --- a/tests/sketch/src/com/android/gesture/recognizer/RecognitionUtil.java +++ /dev/null @@ -1,190 +0,0 @@ -/* - * Copyright (C) 2008-2009 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.gesture.recognizer; - -import android.graphics.PointF; - -import com.android.gesture.Gesture; - -import java.util.Iterator; - -/** - * - * Utilities for recognition. - */ - -public class RecognitionUtil { - - /** - * Re-sample a list of points to a given number - * @param stk - * @param num - * @return - */ - public static float[] resample(Gesture gesture, int num) { - final float increment = gesture.getLength()/(num - 1); - float[] newstk = new float[num*2]; - float distanceSoFar = 0; - Iterator<PointF> it = gesture.getPoints().iterator(); - PointF lstPoint = it.next(); - int index = 0; - PointF currentPoint = null; - try - { - newstk[index] = lstPoint.x; - index++; - newstk[index] = lstPoint.y; - index++; - while (it.hasNext()) { - if (currentPoint == null) - currentPoint = it.next(); - float deltaX = currentPoint.x - lstPoint.x; - float deltaY = currentPoint.y - lstPoint.y; - float distance = (float)Math.sqrt(deltaX*deltaX+deltaY*deltaY); - if (distanceSoFar+distance >= increment) { - float ratio = (increment - distanceSoFar) / distance; - float nx = lstPoint.x + ratio * deltaX; - float ny = lstPoint.y + ratio * deltaY; - newstk[index] = nx; - index++; - newstk[index] = ny; - index++; - lstPoint = new PointF(nx, ny); - distanceSoFar = 0; - } - else { - lstPoint = currentPoint; - currentPoint = null; - distanceSoFar += distance; - } - } - } - catch(Exception ex) { - ex.printStackTrace(); - } - - for(int i = index; i < newstk.length -1; i+=2) { - newstk[i] = lstPoint.x; - newstk[i+1] = lstPoint.y; - } - return newstk; - } - - /** - * Calculate the centroid of a list of points - * @param points - * @return the centroid - */ - public static PointF computeCentroid(float[] points) { - float centerX = 0; - float centerY = 0; - for(int i=0; i<points.length; i++) - { - centerX += points[i]; - i++; - centerY += points[i]; - } - centerX = 2 * centerX/points.length; - centerY = 2 * centerY/points.length; - return new PointF(centerX, centerY); - } - - /** - * calculate the variance-covariance matrix, treat each point as a sample - * @param points - * @return - */ - public 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; - for(int i=0; i<points.length; 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] /= (points.length/2); - array[0][1] /= (points.length/2); - array[1][0] /= (points.length/2); - array[1][1] /= (points.length/2); - - return array; - } - - - public static float computeTotalLength(float[] points) { - float sum = 0; - for (int i=0; i<points.length - 4; 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; - } - - public static double averageEuclidDistance(float[] stk1, float[] stk2) { - double distance = 0; - for (int i = 0; i < stk1.length; i += 2) { - distance += PointF.length(stk1[i] - stk2[i], stk1[i+1] - stk2[i+1]); - } - return distance/stk1.length; - } - - public static double squaredEuclidDistance(float[] stk1, float[] stk2) { - double squaredDistance = 0; - for (int i = 0; i < stk1.length; i++) { - float difference = stk1[i] - stk2[i]; - squaredDistance += difference * difference; - } - return squaredDistance/stk1.length; - } - - /** - * Calculate the cosine distance between two instances - * @param in1 - * @param in2 - * @return the angle between 0 and Math.PI - */ - public static double cosineDistance(Instance in1, Instance in2) { - float sum = 0; - for (int i = 0; i < in1.vector.length; i++) { - sum += in1.vector[i] * in2.vector[i]; - } - return Math.acos(sum / (in1.length * in2.length)); - } - -} |