M7350v1_en_gpl

This commit is contained in:
T
2024-09-09 08:52:07 +00:00
commit f9cc65cfda
65988 changed files with 26357421 additions and 0 deletions

View File

@ -0,0 +1,338 @@
/*
* 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 android.gesture;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;
import java.io.IOException;
import java.io.DataOutputStream;
import java.io.DataInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ByteArrayInputStream;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicInteger;
/**
* A gesture is a hand-drawn shape on a touch screen. It can have one or multiple strokes.
* Each stroke is a sequence of timed points. A user-defined gesture can be recognized by
* a GestureLibrary and a built-in alphabet gesture can be recognized by a LetterRecognizer.
*/
public class Gesture implements Parcelable {
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 final AtomicInteger sGestureCount = new AtomicInteger(0);
private final RectF mBoundingBox = new RectF();
// the same as its instance ID
private long mGestureID;
private final ArrayList<GestureStroke> mStrokes = new ArrayList<GestureStroke>();
public Gesture() {
mGestureID = GESTURE_ID_BASE + sGestureCount.incrementAndGet();
}
@Override
public Object clone() {
Gesture gesture = new Gesture();
gesture.mBoundingBox.set(mBoundingBox.left, mBoundingBox.top,
mBoundingBox.right, mBoundingBox.bottom);
final int count = mStrokes.size();
for (int i = 0; i < count; i++) {
GestureStroke stroke = mStrokes.get(i);
gesture.mStrokes.add((GestureStroke)stroke.clone());
}
return gesture;
}
/**
* @return all the strokes of the gesture
*/
public ArrayList<GestureStroke> getStrokes() {
return mStrokes;
}
/**
* @return the number of strokes included by this gesture
*/
public int getStrokesCount() {
return mStrokes.size();
}
/**
* Adds a stroke to the gesture.
*
* @param stroke
*/
public void addStroke(GestureStroke stroke) {
mStrokes.add(stroke);
mBoundingBox.union(stroke.boundingBox);
}
/**
* Calculates 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() {
int len = 0;
final ArrayList<GestureStroke> strokes = mStrokes;
final int count = strokes.size();
for (int i = 0; i < count; i++) {
len += strokes.get(i).length;
}
return len;
}
/**
* @return the bounding box of the gesture
*/
public RectF getBoundingBox() {
return mBoundingBox;
}
public Path toPath() {
return toPath(null);
}
public Path toPath(Path path) {
if (path == null) path = new Path();
final ArrayList<GestureStroke> strokes = mStrokes;
final int count = strokes.size();
for (int i = 0; i < count; i++) {
path.addPath(strokes.get(i).getPath());
}
return path;
}
public Path toPath(int width, int height, int edge, int numSample) {
return toPath(null, width, height, edge, numSample);
}
public Path toPath(Path path, int width, int height, int edge, int numSample) {
if (path == null) path = new Path();
final ArrayList<GestureStroke> strokes = mStrokes;
final int count = strokes.size();
for (int i = 0; i < count; i++) {
path.addPath(strokes.get(i).toPath(width - 2 * edge, height - 2 * edge, numSample));
}
return path;
}
/**
* Sets the id of the gesture.
*
* @param id
*/
void setID(long id) {
mGestureID = id;
}
/**
* @return the id of the gesture
*/
public long getID() {
return mGestureID;
}
/**
* Creates 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 Bitmap toBitmap(int width, int height, int edge, int numSample, int color) {
final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
final Canvas canvas = new Canvas(bitmap);
canvas.translate(edge, edge);
final 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);
final ArrayList<GestureStroke> strokes = mStrokes;
final int count = strokes.size();
for (int i = 0; i < count; i++) {
Path path = strokes.get(i).toPath(width - 2 * edge, height - 2 * edge, numSample);
canvas.drawPath(path, paint);
}
return bitmap;
}
/**
* Creates a bitmap of the gesture with a transparent background.
*
* @param width
* @param height
* @param inset
* @param color
* @return the bitmap
*/
public Bitmap toBitmap(int width, int height, int inset, int color) {
final Bitmap bitmap = Bitmap.createBitmap(width, height,
Bitmap.Config.ARGB_8888);
final Canvas canvas = new Canvas(bitmap);
final 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);
final Path path = toPath();
final RectF bounds = new RectF();
path.computeBounds(bounds, true);
final float sx = (width - 2 * inset) / bounds.width();
final float sy = (height - 2 * inset) / bounds.height();
final float scale = sx > sy ? sy : sx;
paint.setStrokeWidth(2.0f / scale);
path.offset(-bounds.left + (width - bounds.width() * scale) / 2.0f,
-bounds.top + (height - bounds.height() * scale) / 2.0f);
canvas.translate(inset, inset);
canvas.scale(scale, scale);
canvas.drawPath(path, paint);
return bitmap;
}
void serialize(DataOutputStream out) throws IOException {
final ArrayList<GestureStroke> strokes = mStrokes;
final int count = strokes.size();
// Write gesture ID
out.writeLong(mGestureID);
// Write number of strokes
out.writeInt(count);
for (int i = 0; i < count; i++) {
strokes.get(i).serialize(out);
}
}
static Gesture deserialize(DataInputStream in) throws IOException {
final Gesture gesture = new Gesture();
// Gesture ID
gesture.mGestureID = in.readLong();
// Number of strokes
final int count = in.readInt();
for (int i = 0; i < count; i++) {
gesture.addStroke(GestureStroke.deserialize(in));
}
return gesture;
}
public static final Parcelable.Creator<Gesture> CREATOR = new Parcelable.Creator<Gesture>() {
public Gesture createFromParcel(Parcel in) {
Gesture gesture = null;
final long gestureID = in.readLong();
final DataInputStream inStream = new DataInputStream(
new ByteArrayInputStream(in.createByteArray()));
try {
gesture = deserialize(inStream);
} catch (IOException e) {
Log.e(GestureConstants.LOG_TAG, "Error reading Gesture from parcel:", e);
} finally {
GestureUtils.closeStream(inStream);
}
if (gesture != null) {
gesture.mGestureID = gestureID;
}
return gesture;
}
public Gesture[] newArray(int size) {
return new Gesture[size];
}
};
public void writeToParcel(Parcel out, int flags) {
out.writeLong(mGestureID);
boolean result = false;
final ByteArrayOutputStream byteStream =
new ByteArrayOutputStream(GestureConstants.IO_BUFFER_SIZE);
final DataOutputStream outStream = new DataOutputStream(byteStream);
try {
serialize(outStream);
result = true;
} catch (IOException e) {
Log.e(GestureConstants.LOG_TAG, "Error writing Gesture to parcel:", e);
} finally {
GestureUtils.closeStream(outStream);
GestureUtils.closeStream(byteStream);
}
if (result) {
out.writeByteArray(byteStream.toByteArray());
}
}
public int describeContents() {
return 0;
}
}

View File

@ -0,0 +1,26 @@
/*
* 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 android.gesture;
interface GestureConstants {
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 = 32 * 1024; // 32K
static final String LOG_TAG = "Gestures";
}

View File

@ -0,0 +1,143 @@
/*
* 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 android.gesture;
import android.util.Log;
import static android.gesture.GestureConstants.*;
import android.content.Context;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.FileInputStream;
import java.io.InputStream;
import java.lang.ref.WeakReference;
public final class GestureLibraries {
private GestureLibraries() {
}
public static GestureLibrary fromFile(String path) {
return fromFile(new File(path));
}
public static GestureLibrary fromFile(File path) {
return new FileGestureLibrary(path);
}
public static GestureLibrary fromPrivateFile(Context context, String name) {
return fromFile(context.getFileStreamPath(name));
}
public static GestureLibrary fromRawResource(Context context, int resourceId) {
return new ResourceGestureLibrary(context, resourceId);
}
private static class FileGestureLibrary extends GestureLibrary {
private final File mPath;
public FileGestureLibrary(File path) {
mPath = path;
}
@Override
public boolean isReadOnly() {
return !mPath.canWrite();
}
public boolean save() {
if (!mStore.hasChanged()) return true;
final File file = mPath;
final File parentFile = file.getParentFile();
if (!parentFile.exists()) {
if (!parentFile.mkdirs()) {
return false;
}
}
boolean result = false;
try {
//noinspection ResultOfMethodCallIgnored
file.createNewFile();
mStore.save(new FileOutputStream(file), true);
result = true;
} catch (FileNotFoundException e) {
Log.d(LOG_TAG, "Could not save the gesture library in " + mPath, e);
} catch (IOException e) {
Log.d(LOG_TAG, "Could not save the gesture library in " + mPath, e);
}
return result;
}
public boolean load() {
boolean result = false;
final File file = mPath;
if (file.exists() && file.canRead()) {
try {
mStore.load(new FileInputStream(file), true);
result = true;
} catch (FileNotFoundException e) {
Log.d(LOG_TAG, "Could not load the gesture library from " + mPath, e);
} catch (IOException e) {
Log.d(LOG_TAG, "Could not load the gesture library from " + mPath, e);
}
}
return result;
}
}
private static class ResourceGestureLibrary extends GestureLibrary {
private final WeakReference<Context> mContext;
private final int mResourceId;
public ResourceGestureLibrary(Context context, int resourceId) {
mContext = new WeakReference<Context>(context);
mResourceId = resourceId;
}
@Override
public boolean isReadOnly() {
return true;
}
public boolean save() {
return false;
}
public boolean load() {
boolean result = false;
final Context context = mContext.get();
if (context != null) {
final InputStream in = context.getResources().openRawResource(mResourceId);
try {
mStore.load(in, true);
result = true;
} catch (IOException e) {
Log.d(LOG_TAG, "Could not load the gesture library from raw resource " +
context.getResources().getResourceName(mResourceId), e);
}
}
return result;
}
}
}

View File

@ -0,0 +1,82 @@
/*
* 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 android.gesture;
import java.util.Set;
import java.util.ArrayList;
public abstract class GestureLibrary {
protected final GestureStore mStore;
protected GestureLibrary() {
mStore = new GestureStore();
}
public abstract boolean save();
public abstract boolean load();
public boolean isReadOnly() {
return false;
}
/** @hide */
public Learner getLearner() {
return mStore.getLearner();
}
public void setOrientationStyle(int style) {
mStore.setOrientationStyle(style);
}
public int getOrientationStyle() {
return mStore.getOrientationStyle();
}
public void setSequenceType(int type) {
mStore.setSequenceType(type);
}
public int getSequenceType() {
return mStore.getSequenceType();
}
public Set<String> getGestureEntries() {
return mStore.getGestureEntries();
}
public ArrayList<Prediction> recognize(Gesture gesture) {
return mStore.recognize(gesture);
}
public void addGesture(String entryName, Gesture gesture) {
mStore.addGesture(entryName, gesture);
}
public void removeGesture(String entryName, Gesture gesture) {
mStore.removeGesture(entryName, gesture);
}
public void removeEntry(String entryName) {
mStore.removeEntry(entryName);
}
public ArrayList<Gesture> getGestures(String entryName) {
return mStore.getGestures(entryName);
}
}

View File

@ -0,0 +1,795 @@
/*
* 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 android.gesture;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.animation.AnimationUtils;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.widget.FrameLayout;
import android.os.SystemClock;
import android.annotation.Widget;
import com.android.internal.R;
import java.util.ArrayList;
/**
* A transparent overlay for gesture input that can be placed on top of other
* widgets or contain other widgets.
*
* @attr ref android.R.styleable#GestureOverlayView_eventsInterceptionEnabled
* @attr ref android.R.styleable#GestureOverlayView_fadeDuration
* @attr ref android.R.styleable#GestureOverlayView_fadeOffset
* @attr ref android.R.styleable#GestureOverlayView_fadeEnabled
* @attr ref android.R.styleable#GestureOverlayView_gestureStrokeWidth
* @attr ref android.R.styleable#GestureOverlayView_gestureStrokeAngleThreshold
* @attr ref android.R.styleable#GestureOverlayView_gestureStrokeLengthThreshold
* @attr ref android.R.styleable#GestureOverlayView_gestureStrokeSquarenessThreshold
* @attr ref android.R.styleable#GestureOverlayView_gestureStrokeType
* @attr ref android.R.styleable#GestureOverlayView_gestureColor
* @attr ref android.R.styleable#GestureOverlayView_orientation
* @attr ref android.R.styleable#GestureOverlayView_uncertainGestureColor
*/
@Widget
public class GestureOverlayView extends FrameLayout {
public static final int GESTURE_STROKE_TYPE_SINGLE = 0;
public static final int GESTURE_STROKE_TYPE_MULTIPLE = 1;
public static final int ORIENTATION_HORIZONTAL = 0;
public static final int ORIENTATION_VERTICAL = 1;
private static final int FADE_ANIMATION_RATE = 16;
private static final boolean GESTURE_RENDERING_ANTIALIAS = true;
private static final boolean DITHER_FLAG = true;
private final Paint mGesturePaint = new Paint();
private long mFadeDuration = 150;
private long mFadeOffset = 420;
private long mFadingStart;
private boolean mFadingHasStarted;
private boolean mFadeEnabled = true;
private int mCurrentColor;
private int mCertainGestureColor = 0xFFFFFF00;
private int mUncertainGestureColor = 0x48FFFF00;
private float mGestureStrokeWidth = 12.0f;
private int mInvalidateExtraBorder = 10;
private int mGestureStrokeType = GESTURE_STROKE_TYPE_SINGLE;
private float mGestureStrokeLengthThreshold = 50.0f;
private float mGestureStrokeSquarenessTreshold = 0.275f;
private float mGestureStrokeAngleThreshold = 40.0f;
private int mOrientation = ORIENTATION_VERTICAL;
private final Rect mInvalidRect = new Rect();
private final Path mPath = new Path();
private boolean mGestureVisible = true;
private float mX;
private float mY;
private float mCurveEndX;
private float mCurveEndY;
private float mTotalLength;
private boolean mIsGesturing = false;
private boolean mPreviousWasGesturing = false;
private boolean mInterceptEvents = true;
private boolean mIsListeningForGestures;
private boolean mResetGesture;
// current gesture
private Gesture mCurrentGesture;
private final ArrayList<GesturePoint> mStrokeBuffer = new ArrayList<GesturePoint>(100);
// TODO: Make this a list of WeakReferences
private final ArrayList<OnGestureListener> mOnGestureListeners =
new ArrayList<OnGestureListener>();
// TODO: Make this a list of WeakReferences
private final ArrayList<OnGesturePerformedListener> mOnGesturePerformedListeners =
new ArrayList<OnGesturePerformedListener>();
// TODO: Make this a list of WeakReferences
private final ArrayList<OnGesturingListener> mOnGesturingListeners =
new ArrayList<OnGesturingListener>();
private boolean mHandleGestureActions;
// fading out effect
private boolean mIsFadingOut = false;
private float mFadingAlpha = 1.0f;
private final AccelerateDecelerateInterpolator mInterpolator =
new AccelerateDecelerateInterpolator();
private final FadeOutRunnable mFadingOut = new FadeOutRunnable();
public GestureOverlayView(Context context) {
super(context);
init();
}
public GestureOverlayView(Context context, AttributeSet attrs) {
this(context, attrs, com.android.internal.R.attr.gestureOverlayViewStyle);
}
public GestureOverlayView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.GestureOverlayView, defStyle, 0);
mGestureStrokeWidth = a.getFloat(R.styleable.GestureOverlayView_gestureStrokeWidth,
mGestureStrokeWidth);
mInvalidateExtraBorder = Math.max(1, ((int) mGestureStrokeWidth) - 1);
mCertainGestureColor = a.getColor(R.styleable.GestureOverlayView_gestureColor,
mCertainGestureColor);
mUncertainGestureColor = a.getColor(R.styleable.GestureOverlayView_uncertainGestureColor,
mUncertainGestureColor);
mFadeDuration = a.getInt(R.styleable.GestureOverlayView_fadeDuration, (int) mFadeDuration);
mFadeOffset = a.getInt(R.styleable.GestureOverlayView_fadeOffset, (int) mFadeOffset);
mGestureStrokeType = a.getInt(R.styleable.GestureOverlayView_gestureStrokeType,
mGestureStrokeType);
mGestureStrokeLengthThreshold = a.getFloat(
R.styleable.GestureOverlayView_gestureStrokeLengthThreshold,
mGestureStrokeLengthThreshold);
mGestureStrokeAngleThreshold = a.getFloat(
R.styleable.GestureOverlayView_gestureStrokeAngleThreshold,
mGestureStrokeAngleThreshold);
mGestureStrokeSquarenessTreshold = a.getFloat(
R.styleable.GestureOverlayView_gestureStrokeSquarenessThreshold,
mGestureStrokeSquarenessTreshold);
mInterceptEvents = a.getBoolean(R.styleable.GestureOverlayView_eventsInterceptionEnabled,
mInterceptEvents);
mFadeEnabled = a.getBoolean(R.styleable.GestureOverlayView_fadeEnabled,
mFadeEnabled);
mOrientation = a.getInt(R.styleable.GestureOverlayView_orientation, mOrientation);
a.recycle();
init();
}
private void init() {
setWillNotDraw(false);
final Paint gesturePaint = mGesturePaint;
gesturePaint.setAntiAlias(GESTURE_RENDERING_ANTIALIAS);
gesturePaint.setColor(mCertainGestureColor);
gesturePaint.setStyle(Paint.Style.STROKE);
gesturePaint.setStrokeJoin(Paint.Join.ROUND);
gesturePaint.setStrokeCap(Paint.Cap.ROUND);
gesturePaint.setStrokeWidth(mGestureStrokeWidth);
gesturePaint.setDither(DITHER_FLAG);
mCurrentColor = mCertainGestureColor;
setPaintAlpha(255);
}
public ArrayList<GesturePoint> getCurrentStroke() {
return mStrokeBuffer;
}
public int getOrientation() {
return mOrientation;
}
public void setOrientation(int orientation) {
mOrientation = orientation;
}
public void setGestureColor(int color) {
mCertainGestureColor = color;
}
public void setUncertainGestureColor(int color) {
mUncertainGestureColor = color;
}
public int getUncertainGestureColor() {
return mUncertainGestureColor;
}
public int getGestureColor() {
return mCertainGestureColor;
}
public float getGestureStrokeWidth() {
return mGestureStrokeWidth;
}
public void setGestureStrokeWidth(float gestureStrokeWidth) {
mGestureStrokeWidth = gestureStrokeWidth;
mInvalidateExtraBorder = Math.max(1, ((int) gestureStrokeWidth) - 1);
mGesturePaint.setStrokeWidth(gestureStrokeWidth);
}
public int getGestureStrokeType() {
return mGestureStrokeType;
}
public void setGestureStrokeType(int gestureStrokeType) {
mGestureStrokeType = gestureStrokeType;
}
public float getGestureStrokeLengthThreshold() {
return mGestureStrokeLengthThreshold;
}
public void setGestureStrokeLengthThreshold(float gestureStrokeLengthThreshold) {
mGestureStrokeLengthThreshold = gestureStrokeLengthThreshold;
}
public float getGestureStrokeSquarenessTreshold() {
return mGestureStrokeSquarenessTreshold;
}
public void setGestureStrokeSquarenessTreshold(float gestureStrokeSquarenessTreshold) {
mGestureStrokeSquarenessTreshold = gestureStrokeSquarenessTreshold;
}
public float getGestureStrokeAngleThreshold() {
return mGestureStrokeAngleThreshold;
}
public void setGestureStrokeAngleThreshold(float gestureStrokeAngleThreshold) {
mGestureStrokeAngleThreshold = gestureStrokeAngleThreshold;
}
public boolean isEventsInterceptionEnabled() {
return mInterceptEvents;
}
public void setEventsInterceptionEnabled(boolean enabled) {
mInterceptEvents = enabled;
}
public boolean isFadeEnabled() {
return mFadeEnabled;
}
public void setFadeEnabled(boolean fadeEnabled) {
mFadeEnabled = fadeEnabled;
}
public Gesture getGesture() {
return mCurrentGesture;
}
public void setGesture(Gesture gesture) {
if (mCurrentGesture != null) {
clear(false);
}
setCurrentColor(mCertainGestureColor);
mCurrentGesture = gesture;
final Path path = mCurrentGesture.toPath();
final RectF bounds = new RectF();
path.computeBounds(bounds, true);
// TODO: The path should also be scaled to fit inside this view
mPath.rewind();
mPath.addPath(path, -bounds.left + (getWidth() - bounds.width()) / 2.0f,
-bounds.top + (getHeight() - bounds.height()) / 2.0f);
mResetGesture = true;
invalidate();
}
public Path getGesturePath() {
return mPath;
}
public Path getGesturePath(Path path) {
path.set(mPath);
return path;
}
public boolean isGestureVisible() {
return mGestureVisible;
}
public void setGestureVisible(boolean visible) {
mGestureVisible = visible;
}
public long getFadeOffset() {
return mFadeOffset;
}
public void setFadeOffset(long fadeOffset) {
mFadeOffset = fadeOffset;
}
public void addOnGestureListener(OnGestureListener listener) {
mOnGestureListeners.add(listener);
}
public void removeOnGestureListener(OnGestureListener listener) {
mOnGestureListeners.remove(listener);
}
public void removeAllOnGestureListeners() {
mOnGestureListeners.clear();
}
public void addOnGesturePerformedListener(OnGesturePerformedListener listener) {
mOnGesturePerformedListeners.add(listener);
if (mOnGesturePerformedListeners.size() > 0) {
mHandleGestureActions = true;
}
}
public void removeOnGesturePerformedListener(OnGesturePerformedListener listener) {
mOnGesturePerformedListeners.remove(listener);
if (mOnGesturePerformedListeners.size() <= 0) {
mHandleGestureActions = false;
}
}
public void removeAllOnGesturePerformedListeners() {
mOnGesturePerformedListeners.clear();
mHandleGestureActions = false;
}
public void addOnGesturingListener(OnGesturingListener listener) {
mOnGesturingListeners.add(listener);
}
public void removeOnGesturingListener(OnGesturingListener listener) {
mOnGesturingListeners.remove(listener);
}
public void removeAllOnGesturingListeners() {
mOnGesturingListeners.clear();
}
public boolean isGesturing() {
return mIsGesturing;
}
private void setCurrentColor(int color) {
mCurrentColor = color;
if (mFadingHasStarted) {
setPaintAlpha((int) (255 * mFadingAlpha));
} else {
setPaintAlpha(255);
}
invalidate();
}
/**
* @hide
*/
public Paint getGesturePaint() {
return mGesturePaint;
}
@Override
public void draw(Canvas canvas) {
super.draw(canvas);
if (mCurrentGesture != null && mGestureVisible) {
canvas.drawPath(mPath, mGesturePaint);
}
}
private void setPaintAlpha(int alpha) {
alpha += alpha >> 7;
final int baseAlpha = mCurrentColor >>> 24;
final int useAlpha = baseAlpha * alpha >> 8;
mGesturePaint.setColor((mCurrentColor << 8 >>> 8) | (useAlpha << 24));
}
public void clear(boolean animated) {
clear(animated, false, true);
}
private void clear(boolean animated, boolean fireActionPerformed, boolean immediate) {
setPaintAlpha(255);
removeCallbacks(mFadingOut);
mResetGesture = false;
mFadingOut.fireActionPerformed = fireActionPerformed;
mFadingOut.resetMultipleStrokes = false;
if (animated && mCurrentGesture != null) {
mFadingAlpha = 1.0f;
mIsFadingOut = true;
mFadingHasStarted = false;
mFadingStart = AnimationUtils.currentAnimationTimeMillis() + mFadeOffset;
postDelayed(mFadingOut, mFadeOffset);
} else {
mFadingAlpha = 1.0f;
mIsFadingOut = false;
mFadingHasStarted = false;
if (immediate) {
mCurrentGesture = null;
mPath.rewind();
invalidate();
} else if (fireActionPerformed) {
postDelayed(mFadingOut, mFadeOffset);
} else if (mGestureStrokeType == GESTURE_STROKE_TYPE_MULTIPLE) {
mFadingOut.resetMultipleStrokes = true;
postDelayed(mFadingOut, mFadeOffset);
} else {
mCurrentGesture = null;
mPath.rewind();
invalidate();
}
}
}
public void cancelClearAnimation() {
setPaintAlpha(255);
mIsFadingOut = false;
mFadingHasStarted = false;
removeCallbacks(mFadingOut);
mPath.rewind();
mCurrentGesture = null;
}
public void cancelGesture() {
mIsListeningForGestures = false;
// add the stroke to the current gesture
mCurrentGesture.addStroke(new GestureStroke(mStrokeBuffer));
// pass the event to handlers
final long now = SystemClock.uptimeMillis();
final MotionEvent event = MotionEvent.obtain(now, now,
MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
final ArrayList<OnGestureListener> listeners = mOnGestureListeners;
int count = listeners.size();
for (int i = 0; i < count; i++) {
listeners.get(i).onGestureCancelled(this, event);
}
event.recycle();
clear(false);
mIsGesturing = false;
mPreviousWasGesturing = false;
mStrokeBuffer.clear();
final ArrayList<OnGesturingListener> otherListeners = mOnGesturingListeners;
count = otherListeners.size();
for (int i = 0; i < count; i++) {
otherListeners.get(i).onGesturingEnded(this);
}
}
@Override
protected void onDetachedFromWindow() {
cancelClearAnimation();
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
if (isEnabled()) {
final boolean cancelDispatch = (mIsGesturing || (mCurrentGesture != null &&
mCurrentGesture.getStrokesCount() > 0 && mPreviousWasGesturing)) &&
mInterceptEvents;
processEvent(event);
if (cancelDispatch) {
event.setAction(MotionEvent.ACTION_CANCEL);
}
super.dispatchTouchEvent(event);
return true;
}
return super.dispatchTouchEvent(event);
}
private boolean processEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
touchDown(event);
invalidate();
return true;
case MotionEvent.ACTION_MOVE:
if (mIsListeningForGestures) {
Rect rect = touchMove(event);
if (rect != null) {
invalidate(rect);
}
return true;
}
break;
case MotionEvent.ACTION_UP:
if (mIsListeningForGestures) {
touchUp(event, false);
invalidate();
return true;
}
break;
case MotionEvent.ACTION_CANCEL:
if (mIsListeningForGestures) {
touchUp(event, true);
invalidate();
return true;
}
}
return false;
}
private void touchDown(MotionEvent event) {
mIsListeningForGestures = true;
float x = event.getX();
float y = event.getY();
mX = x;
mY = y;
mTotalLength = 0;
mIsGesturing = false;
if (mGestureStrokeType == GESTURE_STROKE_TYPE_SINGLE || mResetGesture) {
if (mHandleGestureActions) setCurrentColor(mUncertainGestureColor);
mResetGesture = false;
mCurrentGesture = null;
mPath.rewind();
} else if (mCurrentGesture == null || mCurrentGesture.getStrokesCount() == 0) {
if (mHandleGestureActions) setCurrentColor(mUncertainGestureColor);
}
// if there is fading out going on, stop it.
if (mFadingHasStarted) {
cancelClearAnimation();
} else if (mIsFadingOut) {
setPaintAlpha(255);
mIsFadingOut = false;
mFadingHasStarted = false;
removeCallbacks(mFadingOut);
}
if (mCurrentGesture == null) {
mCurrentGesture = new Gesture();
}
mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime()));
mPath.moveTo(x, y);
final int border = mInvalidateExtraBorder;
mInvalidRect.set((int) x - border, (int) y - border, (int) x + border, (int) y + border);
mCurveEndX = x;
mCurveEndY = y;
// pass the event to handlers
final ArrayList<OnGestureListener> listeners = mOnGestureListeners;
final int count = listeners.size();
for (int i = 0; i < count; i++) {
listeners.get(i).onGestureStarted(this, event);
}
}
private Rect touchMove(MotionEvent event) {
Rect areaToRefresh = null;
final float x = event.getX();
final float y = event.getY();
final float previousX = mX;
final float previousY = mY;
final float dx = Math.abs(x - previousX);
final float dy = Math.abs(y - previousY);
if (dx >= GestureStroke.TOUCH_TOLERANCE || dy >= GestureStroke.TOUCH_TOLERANCE) {
areaToRefresh = mInvalidRect;
// start with the curve end
final int border = mInvalidateExtraBorder;
areaToRefresh.set((int) mCurveEndX - border, (int) mCurveEndY - border,
(int) mCurveEndX + border, (int) mCurveEndY + border);
float cX = mCurveEndX = (x + previousX) / 2;
float cY = mCurveEndY = (y + previousY) / 2;
mPath.quadTo(previousX, previousY, cX, cY);
// union with the control point of the new curve
areaToRefresh.union((int) previousX - border, (int) previousY - border,
(int) previousX + border, (int) previousY + border);
// union with the end point of the new curve
areaToRefresh.union((int) cX - border, (int) cY - border,
(int) cX + border, (int) cY + border);
mX = x;
mY = y;
mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime()));
if (mHandleGestureActions && !mIsGesturing) {
mTotalLength += (float) Math.sqrt(dx * dx + dy * dy);
if (mTotalLength > mGestureStrokeLengthThreshold) {
final OrientedBoundingBox box =
GestureUtils.computeOrientedBoundingBox(mStrokeBuffer);
float angle = Math.abs(box.orientation);
if (angle > 90) {
angle = 180 - angle;
}
if (box.squareness > mGestureStrokeSquarenessTreshold ||
(mOrientation == ORIENTATION_VERTICAL ?
angle < mGestureStrokeAngleThreshold :
angle > mGestureStrokeAngleThreshold)) {
mIsGesturing = true;
setCurrentColor(mCertainGestureColor);
final ArrayList<OnGesturingListener> listeners = mOnGesturingListeners;
int count = listeners.size();
for (int i = 0; i < count; i++) {
listeners.get(i).onGesturingStarted(this);
}
}
}
}
// pass the event to handlers
final ArrayList<OnGestureListener> listeners = mOnGestureListeners;
final int count = listeners.size();
for (int i = 0; i < count; i++) {
listeners.get(i).onGesture(this, event);
}
}
return areaToRefresh;
}
private void touchUp(MotionEvent event, boolean cancel) {
mIsListeningForGestures = false;
// A gesture wasn't started or was cancelled
if (mCurrentGesture != null) {
// add the stroke to the current gesture
mCurrentGesture.addStroke(new GestureStroke(mStrokeBuffer));
if (!cancel) {
// pass the event to handlers
final ArrayList<OnGestureListener> listeners = mOnGestureListeners;
int count = listeners.size();
for (int i = 0; i < count; i++) {
listeners.get(i).onGestureEnded(this, event);
}
clear(mHandleGestureActions && mFadeEnabled, mHandleGestureActions && mIsGesturing,
false);
} else {
cancelGesture(event);
}
} else {
cancelGesture(event);
}
mStrokeBuffer.clear();
mPreviousWasGesturing = mIsGesturing;
mIsGesturing = false;
final ArrayList<OnGesturingListener> listeners = mOnGesturingListeners;
int count = listeners.size();
for (int i = 0; i < count; i++) {
listeners.get(i).onGesturingEnded(this);
}
}
private void cancelGesture(MotionEvent event) {
// pass the event to handlers
final ArrayList<OnGestureListener> listeners = mOnGestureListeners;
final int count = listeners.size();
for (int i = 0; i < count; i++) {
listeners.get(i).onGestureCancelled(this, event);
}
clear(false);
}
private void fireOnGesturePerformed() {
final ArrayList<OnGesturePerformedListener> actionListeners = mOnGesturePerformedListeners;
final int count = actionListeners.size();
for (int i = 0; i < count; i++) {
actionListeners.get(i).onGesturePerformed(GestureOverlayView.this, mCurrentGesture);
}
}
private class FadeOutRunnable implements Runnable {
boolean fireActionPerformed;
boolean resetMultipleStrokes;
public void run() {
if (mIsFadingOut) {
final long now = AnimationUtils.currentAnimationTimeMillis();
final long duration = now - mFadingStart;
if (duration > mFadeDuration) {
if (fireActionPerformed) {
fireOnGesturePerformed();
}
mPreviousWasGesturing = false;
mIsFadingOut = false;
mFadingHasStarted = false;
mPath.rewind();
mCurrentGesture = null;
setPaintAlpha(255);
} else {
mFadingHasStarted = true;
float interpolatedTime = Math.max(0.0f,
Math.min(1.0f, duration / (float) mFadeDuration));
mFadingAlpha = 1.0f - mInterpolator.getInterpolation(interpolatedTime);
setPaintAlpha((int) (255 * mFadingAlpha));
postDelayed(this, FADE_ANIMATION_RATE);
}
} else if (resetMultipleStrokes) {
mResetGesture = true;
} else {
fireOnGesturePerformed();
mFadingHasStarted = false;
mPath.rewind();
mCurrentGesture = null;
mPreviousWasGesturing = false;
setPaintAlpha(255);
}
invalidate();
}
}
public static interface OnGesturingListener {
void onGesturingStarted(GestureOverlayView overlay);
void onGesturingEnded(GestureOverlayView overlay);
}
public static interface OnGestureListener {
void onGestureStarted(GestureOverlayView overlay, MotionEvent event);
void onGesture(GestureOverlayView overlay, MotionEvent event);
void onGestureEnded(GestureOverlayView overlay, MotionEvent event);
void onGestureCancelled(GestureOverlayView overlay, MotionEvent event);
}
public static interface OnGesturePerformedListener {
void onGesturePerformed(GestureOverlayView overlay, Gesture gesture);
}
}

View File

@ -0,0 +1,51 @@
/*
* 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 android.gesture;
import java.io.DataInputStream;
import java.io.IOException;
/**
* A timed point of a gesture stroke. Multiple points form a stroke.
*/
public class GesturePoint {
public final float x;
public final float y;
public final long timestamp;
public GesturePoint(float x, float y, long t) {
this.x = x;
this.y = y;
timestamp = t;
}
static GesturePoint deserialize(DataInputStream in) throws IOException {
// Read X and Y
final float x = in.readFloat();
final float y = in.readFloat();
// Read timestamp
final long timeStamp = in.readLong();
return new GesturePoint(x, y, timeStamp);
}
@Override
public Object clone() {
return new GesturePoint(x, y, timestamp);
}
}

View File

@ -0,0 +1,335 @@
/*
* 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 android.gesture;
import android.util.Log;
import android.os.SystemClock;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.DataOutputStream;
import java.io.DataInputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Set;
import java.util.Map;
import static android.gesture.GestureConstants.LOG_TAG;
/**
* GestureLibrary maintains gesture examples and makes predictions on a new
* gesture
*/
//
// File format for GestureStore:
//
// Nb. bytes Java type Description
// -----------------------------------
// Header
// 2 bytes short File format version number
// 4 bytes int Number of entries
// Entry
// X bytes UTF String Entry name
// 4 bytes int Number of gestures
// Gesture
// 8 bytes long Gesture ID
// 4 bytes int Number of strokes
// Stroke
// 4 bytes int Number of points
// Point
// 4 bytes float X coordinate of the point
// 4 bytes float Y coordinate of the point
// 8 bytes long Time stamp
//
public class GestureStore {
public static final int SEQUENCE_INVARIANT = 1;
// when SEQUENCE_SENSITIVE is used, only single stroke gestures are currently allowed
public static final int SEQUENCE_SENSITIVE = 2;
// ORIENTATION_SENSITIVE and ORIENTATION_INVARIANT are only for SEQUENCE_SENSITIVE gestures
public static final int ORIENTATION_INVARIANT = 1;
// at most 2 directions can be recognized
public static final int ORIENTATION_SENSITIVE = 2;
// at most 4 directions can be recognized
static final int ORIENTATION_SENSITIVE_4 = 4;
// at most 8 directions can be recognized
static final int ORIENTATION_SENSITIVE_8 = 8;
private static final short FILE_FORMAT_VERSION = 1;
private static final boolean PROFILE_LOADING_SAVING = false;
private int mSequenceType = SEQUENCE_SENSITIVE;
private int mOrientationStyle = ORIENTATION_SENSITIVE;
private final HashMap<String, ArrayList<Gesture>> mNamedGestures =
new HashMap<String, ArrayList<Gesture>>();
private Learner mClassifier;
private boolean mChanged = false;
public GestureStore() {
mClassifier = new InstanceLearner();
}
/**
* Specify how the gesture library will handle orientation.
* Use ORIENTATION_INVARIANT or ORIENTATION_SENSITIVE
*
* @param style
*/
public void setOrientationStyle(int style) {
mOrientationStyle = style;
}
public int getOrientationStyle() {
return mOrientationStyle;
}
/**
* @param type SEQUENCE_INVARIANT or SEQUENCE_SENSITIVE
*/
public void setSequenceType(int type) {
mSequenceType = type;
}
/**
* @return SEQUENCE_INVARIANT or SEQUENCE_SENSITIVE
*/
public int getSequenceType() {
return mSequenceType;
}
/**
* Get all the gesture entry names in the library
*
* @return a set of strings
*/
public Set<String> getGestureEntries() {
return mNamedGestures.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(mSequenceType,
mOrientationStyle, gesture, null);
return mClassifier.classify(mSequenceType, mOrientationStyle, instance.vector);
}
/**
* Add a gesture for the entry
*
* @param entryName entry name
* @param gesture
*/
public void addGesture(String entryName, Gesture gesture) {
if (entryName == null || entryName.length() == 0) {
return;
}
ArrayList<Gesture> gestures = mNamedGestures.get(entryName);
if (gestures == null) {
gestures = new ArrayList<Gesture>();
mNamedGestures.put(entryName, gestures);
}
gestures.add(gesture);
mClassifier.addInstance(
Instance.createInstance(mSequenceType, mOrientationStyle, 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 = mNamedGestures.get(entryName);
if (gestures == null) {
return;
}
gestures.remove(gesture);
// if there are no more samples, remove the entry automatically
if (gestures.isEmpty()) {
mNamedGestures.remove(entryName);
}
mClassifier.removeInstance(gesture.getID());
mChanged = true;
}
/**
* Remove a entry of gestures
*
* @param entryName the entry name
*/
public void removeEntry(String entryName) {
mNamedGestures.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
*/
public ArrayList<Gesture> getGestures(String entryName) {
ArrayList<Gesture> gestures = mNamedGestures.get(entryName);
if (gestures != null) {
return new ArrayList<Gesture>(gestures);
} else {
return null;
}
}
public boolean hasChanged() {
return mChanged;
}
/**
* Save the gesture library
*/
public void save(OutputStream stream) throws IOException {
save(stream, false);
}
public void save(OutputStream stream, boolean closeStream) throws IOException {
DataOutputStream out = null;
try {
long start;
if (PROFILE_LOADING_SAVING) {
start = SystemClock.elapsedRealtime();
}
final HashMap<String, ArrayList<Gesture>> maps = mNamedGestures;
out = new DataOutputStream((stream instanceof BufferedOutputStream) ? stream :
new BufferedOutputStream(stream, GestureConstants.IO_BUFFER_SIZE));
// Write version number
out.writeShort(FILE_FORMAT_VERSION);
// Write number of entries
out.writeInt(maps.size());
for (Map.Entry<String, ArrayList<Gesture>> entry : maps.entrySet()) {
final String key = entry.getKey();
final ArrayList<Gesture> examples = entry.getValue();
final int count = examples.size();
// Write entry name
out.writeUTF(key);
// Write number of examples for this entry
out.writeInt(count);
for (int i = 0; i < count; i++) {
examples.get(i).serialize(out);
}
}
out.flush();
if (PROFILE_LOADING_SAVING) {
long end = SystemClock.elapsedRealtime();
Log.d(LOG_TAG, "Saving gestures library = " + (end - start) + " ms");
}
mChanged = false;
} finally {
if (closeStream) GestureUtils.closeStream(out);
}
}
/**
* Load the gesture library
*/
public void load(InputStream stream) throws IOException {
load(stream, false);
}
public void load(InputStream stream, boolean closeStream) throws IOException {
DataInputStream in = null;
try {
in = new DataInputStream((stream instanceof BufferedInputStream) ? stream :
new BufferedInputStream(stream, GestureConstants.IO_BUFFER_SIZE));
long start;
if (PROFILE_LOADING_SAVING) {
start = SystemClock.elapsedRealtime();
}
// Read file format version number
final short versionNumber = in.readShort();
switch (versionNumber) {
case 1:
readFormatV1(in);
break;
}
if (PROFILE_LOADING_SAVING) {
long end = SystemClock.elapsedRealtime();
Log.d(LOG_TAG, "Loading gestures library = " + (end - start) + " ms");
}
} finally {
if (closeStream) GestureUtils.closeStream(in);
}
}
private void readFormatV1(DataInputStream in) throws IOException {
final Learner classifier = mClassifier;
final HashMap<String, ArrayList<Gesture>> namedGestures = mNamedGestures;
namedGestures.clear();
// Number of entries in the library
final int entriesCount = in.readInt();
for (int i = 0; i < entriesCount; i++) {
// Entry name
final String name = in.readUTF();
// Number of gestures
final int gestureCount = in.readInt();
final ArrayList<Gesture> gestures = new ArrayList<Gesture>(gestureCount);
for (int j = 0; j < gestureCount; j++) {
final Gesture gesture = Gesture.deserialize(in);
gestures.add(gesture);
classifier.addInstance(
Instance.createInstance(mSequenceType, mOrientationStyle, gesture, name));
}
namedGestures.put(name, gestures);
}
}
Learner getLearner() {
return mClassifier;
}
}

View File

@ -0,0 +1,246 @@
/*
* 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 android.gesture;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import java.io.IOException;
import java.io.DataOutputStream;
import java.io.DataInputStream;
import java.util.ArrayList;
/**
* A gesture stroke started on a touch down and ended on a touch up. A stroke
* consists of a sequence of timed points. One or multiple strokes form a gesture.
*/
public class GestureStroke {
static final float TOUCH_TOLERANCE = 3;
public final RectF boundingBox;
public final float length;
public final float[] points;
private final long[] timestamps;
private Path mCachedPath;
/**
* A constructor that constructs a gesture stroke from a list of gesture points.
*
* @param points
*/
public GestureStroke(ArrayList<GesturePoint> points) {
final int count = points.size();
final float[] tmpPoints = new float[count * 2];
final long[] times = new long[count];
RectF bx = null;
float len = 0;
int index = 0;
for (int i = 0; i < count; i++) {
final GesturePoint p = points.get(i);
tmpPoints[i * 2] = p.x;
tmpPoints[i * 2 + 1] = p.y;
times[index] = p.timestamp;
if (bx == null) {
bx = new RectF();
bx.top = p.y;
bx.left = p.x;
bx.right = p.x;
bx.bottom = p.y;
len = 0;
} else {
len += Math.sqrt(Math.pow(p.x - tmpPoints[(i - 1) * 2], 2)
+ Math.pow(p.y - tmpPoints[(i -1 ) * 2 + 1], 2));
bx.union(p.x, p.y);
}
index++;
}
timestamps = times;
this.points = tmpPoints;
boundingBox = bx;
length = len;
}
/**
* A faster constructor specially for cloning a stroke.
*/
private GestureStroke(RectF bbx, float len, float[] pts, long[] times) {
boundingBox = new RectF(bbx.left, bbx.top, bbx.right, bbx.bottom);
length = len;
points = pts.clone();
timestamps = times.clone();
}
@Override
public Object clone() {
return new GestureStroke(boundingBox, length, points, timestamps);
}
/**
* Draws the stroke with a given canvas and paint.
*
* @param canvas
*/
void draw(Canvas canvas, Paint paint) {
if (mCachedPath == null) {
makePath();
}
canvas.drawPath(mCachedPath, paint);
}
public Path getPath() {
if (mCachedPath == null) {
makePath();
}
return mCachedPath;
}
private void makePath() {
final float[] localPoints = points;
final int count = localPoints.length;
Path path = null;
float mX = 0;
float mY = 0;
for (int i = 0; i < count; i += 2) {
float x = localPoints[i];
float y = localPoints[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 >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
path.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2);
mX = x;
mY = y;
}
}
}
mCachedPath = path;
}
/**
* Converts the stroke to a Path of a given 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) {
final float[] pts = GestureUtils.temporalSampling(this, numSample);
final RectF rect = boundingBox;
GestureUtils.translate(pts, -rect.left, -rect.top);
float sx = width / rect.width();
float sy = height / rect.height();
float scale = sx > sy ? sy : sx;
GestureUtils.scale(pts, scale, scale);
float mX = 0;
float mY = 0;
Path path = null;
final 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 >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
path.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2);
mX = x;
mY = y;
}
}
}
return path;
}
void serialize(DataOutputStream out) throws IOException {
final float[] pts = points;
final long[] times = timestamps;
final int count = points.length;
// Write number of points
out.writeInt(count / 2);
for (int i = 0; i < count; i += 2) {
// Write X
out.writeFloat(pts[i]);
// Write Y
out.writeFloat(pts[i + 1]);
// Write timestamp
out.writeLong(times[i / 2]);
}
}
static GestureStroke deserialize(DataInputStream in) throws IOException {
// Number of points
final int count = in.readInt();
final ArrayList<GesturePoint> points = new ArrayList<GesturePoint>(count);
for (int i = 0; i < count; i++) {
points.add(GesturePoint.deserialize(in));
}
return new GestureStroke(points);
}
/**
* Invalidates the cached path that is used to render the stroke.
*/
public void clearPath() {
if (mCachedPath != null) mCachedPath.rewind();
}
/**
* Computes an oriented bounding box of the stroke.
*
* @return OrientedBoundingBox
*/
public OrientedBoundingBox computeOrientedBoundingBox() {
return GestureUtils.computeOrientedBoundingBox(points);
}
}

View File

@ -0,0 +1,593 @@
/*
* 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 android.gesture;
import android.graphics.RectF;
import android.util.Log;
import java.util.ArrayList;
import java.util.Arrays;
import java.io.Closeable;
import java.io.IOException;
import static android.gesture.GestureConstants.*;
/**
* Utility functions for gesture processing & analysis, including methods for:
* <ul>
* <li>feature extraction (e.g., samplers and those for calculating bounding
* boxes and gesture path lengths);
* <li>geometric transformation (e.g., translation, rotation and scaling);
* <li>gesture similarity comparison (e.g., calculating Euclidean or Cosine
* distances between two gestures).
* </ul>
*/
public final class GestureUtils {
private static final float SCALING_THRESHOLD = 0.26f;
private static final float NONUNIFORM_SCALE = (float) Math.sqrt(2);
private GestureUtils() {
}
/**
* Closes the specified stream.
*
* @param stream The stream to close.
*/
static void closeStream(Closeable stream) {
if (stream != null) {
try {
stream.close();
} catch (IOException e) {
Log.e(LOG_TAG, "Could not close stream", e);
}
}
}
/**
* Samples the gesture spatially by rendering the gesture into a 2D
* grayscale bitmap. Scales the gesture to fit the size of the bitmap.
* The scaling does not necessarily keep the aspect ratio of the gesture.
*
* @param gesture the gesture to be sampled
* @param bitmapSize the size of the bitmap
* @return a bitmapSize x bitmapSize grayscale bitmap that is represented
* as a 1D array. The float at index i represents the grayscale
* value at pixel [i%bitmapSize, i/bitmapSize]
*/
public static float[] spatialSampling(Gesture gesture, int bitmapSize) {
return spatialSampling(gesture, bitmapSize, false);
}
/**
* Samples the gesture spatially by rendering the gesture into a 2D
* grayscale bitmap. Scales the gesture to fit the size of the bitmap.
*
* @param gesture the gesture to be sampled
* @param bitmapSize the size of the bitmap
* @param keepAspectRatio if the scaling should keep the gesture's
* aspect ratio
*
* @return a bitmapSize x bitmapSize grayscale bitmap that is represented
* as a 1D array. The float at index i represents the grayscale
* value at pixel [i%bitmapSize, i/bitmapSize]
*/
public static float[] spatialSampling(Gesture gesture, int bitmapSize,
boolean keepAspectRatio) {
final float targetPatchSize = bitmapSize - 1;
float[] sample = new float[bitmapSize * bitmapSize];
Arrays.fill(sample, 0);
RectF rect = gesture.getBoundingBox();
final float gestureWidth = rect.width();
final float gestureHeight = rect.height();
float sx = targetPatchSize / gestureWidth;
float sy = targetPatchSize / gestureHeight;
if (keepAspectRatio) {
float scale = sx < sy ? sx : sy;
sx = scale;
sy = scale;
} else {
float aspectRatio = gestureWidth / gestureHeight;
if (aspectRatio > 1) {
aspectRatio = 1 / aspectRatio;
}
if (aspectRatio < SCALING_THRESHOLD) {
float scale = sx < sy ? sx : sy;
sx = scale;
sy = scale;
} else {
if (sx > sy) {
float scale = sy * NONUNIFORM_SCALE;
if (scale < sx) {
sx = scale;
}
} else {
float scale = sx * NONUNIFORM_SCALE;
if (scale < sy) {
sy = scale;
}
}
}
}
float preDx = -rect.centerX();
float preDy = -rect.centerY();
float postDx = targetPatchSize / 2;
float postDy = targetPatchSize / 2;
final ArrayList<GestureStroke> strokes = gesture.getStrokes();
final int count = strokes.size();
int size;
float xpos;
float ypos;
for (int index = 0; index < count; index++) {
final GestureStroke stroke = strokes.get(index);
float[] strokepoints = stroke.points;
size = strokepoints.length;
final float[] pts = new float[size];
for (int i = 0; i < size; i += 2) {
pts[i] = (strokepoints[i] + preDx) * sx + postDx;
pts[i + 1] = (strokepoints[i + 1] + preDy) * sy + postDy;
}
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, bitmapSize);
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, bitmapSize);
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, bitmapSize);
xpos++;
}
}
// Evaluate 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, bitmapSize);
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, bitmapSize);
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 {
final double xFloorSq = Math.pow(xFloor - x, 2);
final double yFloorSq = Math.pow(yFloor - y, 2);
final double xCeilingSq = Math.pow(xCeiling - x, 2);
final double yCeilingSq = Math.pow(yCeiling - y, 2);
float topLeft = (float) Math.sqrt(xFloorSq + yFloorSq);
float topRight = (float) Math.sqrt(xCeilingSq + yFloorSq);
float btmLeft = (float) Math.sqrt(xFloorSq + yCeilingSq);
float btmRight = (float) Math.sqrt(xCeilingSq + yCeilingSq);
float sum = topLeft + topRight + btmLeft + btmRight;
float value = topLeft / sum;
int index = yFloor * sampleSize + xFloor;
if (value > sample[index]){
sample[index] = value;
}
value = topRight / sum;
index = yFloor * sampleSize + xCeiling;
if (value > sample[index]){
sample[index] = value;
}
value = btmLeft / sum;
index = yCeiling * sampleSize + xFloor;
if (value > sample[index]){
sample[index] = value;
}
value = btmRight / sum;
index = yCeiling * sampleSize + xCeiling;
if (value > sample[index]){
sample[index] = value;
}
}
}
/**
* Samples a stroke temporally into a given number of evenly-distributed
* points.
*
* @param stroke the gesture stroke to be sampled
* @param numPoints the number of points
* @return the sampled points in the form of [x1, y1, x2, y2, ..., xn, yn]
*/
public static float[] temporalSampling(GestureStroke stroke, int numPoints) {
final float increment = stroke.length / (numPoints - 1);
int vectorLength = numPoints * 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;
}
/**
* Calculates the centroid of a set of points.
*
* @param points the points in the form of [x1, y1, x2, y2, ..., xn, yn]
* @return the centroid
*/
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;
}
/**
* Calculates the variance-covariance matrix of a set of points.
*
* @param points the points in the form of [x1, y1, x2, y2, ..., xn, yn]
* @return the variance-covariance matrix
*/
private static float[][] computeCoVariance(float[] points) {
float[][] array = new float[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;
}
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;
}
static float computeStraightness(float[] points) {
float totalLen = computeTotalLength(points);
float dx = points[2] - points[0];
float dy = points[3] - points[1];
return (float) Math.sqrt(dx * dx + dy * dy) / totalLen;
}
static float computeStraightness(float[] points, float totalLen) {
float dx = points[2] - points[0];
float dy = points[3] - points[1];
return (float) Math.sqrt(dx * dx + dy * dy) / totalLen;
}
/**
* Calculates the squared Euclidean distance between two vectors.
*
* @param vector1
* @param vector2
* @return the distance
*/
static float squaredEuclideanDistance(float[] vector1, float[] vector2) {
float 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;
}
/**
* Calculates the cosine distance between two instances.
*
* @param vector1
* @param vector2
* @return the distance between 0 and Math.PI
*/
static float cosineDistance(float[] vector1, float[] vector2) {
float sum = 0;
int len = vector1.length;
for (int i = 0; i < len; i++) {
sum += vector1[i] * vector2[i];
}
return (float) Math.acos(sum);
}
/**
* Calculates the "minimum" cosine distance between two instances.
*
* @param vector1
* @param vector2
* @param numOrientations the maximum number of orientation allowed
* @return the distance between the two instances (between 0 and Math.PI)
*/
static float minimumCosineDistance(float[] vector1, float[] vector2, int numOrientations) {
final int len = vector1.length;
float a = 0;
float b = 0;
for (int i = 0; i < len; i += 2) {
a += vector1[i] * vector2[i] + vector1[i + 1] * vector2[i + 1];
b += vector1[i] * vector2[i + 1] - vector1[i + 1] * vector2[i];
}
if (a != 0) {
final float tan = b/a;
final double angle = Math.atan(tan);
if (numOrientations > 2 && Math.abs(angle) >= Math.PI / numOrientations) {
return (float) Math.acos(a);
} else {
final double cosine = Math.cos(angle);
final double sine = cosine * tan;
return (float) Math.acos(a * cosine + b * sine);
}
} else {
return (float) Math.PI / 2;
}
}
/**
* Computes an oriented, minimum bounding box of a set of points.
*
* @param originalPoints
* @return an oriented bounding box
*/
public static OrientedBoundingBox computeOrientedBoundingBox(ArrayList<GesturePoint> originalPoints) {
final int count = originalPoints.size();
float[] points = new float[count * 2];
for (int i = 0; i < count; i++) {
GesturePoint point = originalPoints.get(i);
int index = i * 2;
points[index] = point.x;
points[index + 1] = point.y;
}
float[] meanVector = computeCentroid(points);
return computeOrientedBoundingBox(points, meanVector);
}
/**
* Computes an oriented, minimum bounding box of a set of points.
*
* @param originalPoints
* @return an oriented bounding box
*/
public static OrientedBoundingBox computeOrientedBoundingBox(float[] originalPoints) {
int size = originalPoints.length;
float[] points = new float[size];
for (int i = 0; i < size; i++) {
points[i] = originalPoints[i];
}
float[] meanVector = computeCentroid(points);
return computeOrientedBoundingBox(points, meanVector);
}
private static OrientedBoundingBox computeOrientedBoundingBox(float[] points, float[] centroid) {
translate(points, -centroid[0], -centroid[1]);
float[][] array = computeCoVariance(points);
float[] targetVector = computeOrientation(array);
float angle;
if (targetVector[0] == 0 && targetVector[1] == 0) {
angle = (float) -Math.PI/2;
} else { // -PI<alpha<PI
angle = (float) Math.atan2(targetVector[1], targetVector[0]);
rotate(points, -angle);
}
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];
}
}
return new OrientedBoundingBox((float) (angle * 180 / Math.PI), centroid[0], centroid[1], maxx - minx, maxy - miny);
}
private static float[] computeOrientation(float[][] covarianceMatrix) {
float[] targetVector = new float[2];
if (covarianceMatrix[0][1] == 0 || covarianceMatrix[1][0] == 0) {
targetVector[0] = 1;
targetVector[1] = 0;
}
float a = -covarianceMatrix[0][0] - covarianceMatrix[1][1];
float b = covarianceMatrix[0][0] * covarianceMatrix[1][1] - covarianceMatrix[0][1]
* covarianceMatrix[1][0];
float value = a / 2;
float rightside = (float) Math.sqrt(Math.pow(value, 2) - b);
float lambda1 = -value + rightside;
float lambda2 = -value - rightside;
if (lambda1 == lambda2) {
targetVector[0] = 0;
targetVector[1] = 0;
} else {
float lambda = lambda1 > lambda2 ? lambda1 : lambda2;
targetVector[0] = 1;
targetVector[1] = (lambda - covarianceMatrix[0][0]) / covarianceMatrix[0][1];
}
return targetVector;
}
static float[] rotate(float[] points, float angle) {
float cos = (float) Math.cos(angle);
float sin = (float) Math.sin(angle);
int size = points.length;
for (int i = 0; i < size; i += 2) {
float x = points[i] * cos - points[i + 1] * sin;
float y = points[i] * sin + points[i + 1] * cos;
points[i] = x;
points[i + 1] = y;
}
return points;
}
static float[] translate(float[] points, float dx, float dy) {
int size = points.length;
for (int i = 0; i < size; i += 2) {
points[i] += dx;
points[i + 1] += dy;
}
return points;
}
static float[] scale(float[] points, float sx, float sy) {
int size = points.length;
for (int i = 0; i < size; i += 2) {
points[i] *= sx;
points[i + 1] *= sy;
}
return points;
}
}

View File

@ -0,0 +1,113 @@
/*
* 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 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 = 16;
private final static float[] ORIENTATIONS = {
0, (float) (Math.PI / 4), (float) (Math.PI / 2), (float) (Math.PI * 3 / 4),
(float) Math.PI, -0, (float) (-Math.PI / 4), (float) (-Math.PI / 2),
(float) (-Math.PI * 3 / 4), (float) -Math.PI
};
// the feature vector
final float[] vector;
// the label can be null
final String label;
// the id of the instance
final long id;
private Instance(long id, float[] sample, String sampleName) {
this.id = id;
vector = sample;
label = sampleName;
}
private void normalize() {
float[] sample = vector;
float sum = 0;
int size = sample.length;
for (int i = 0; i < size; i++) {
sum += sample[i] * sample[i];
}
float magnitude = (float)Math.sqrt(sum);
for (int i = 0; i < size; i++) {
sample[i] /= magnitude;
}
}
/**
* create a learning instance for a single stroke gesture
*
* @param gesture
* @param label
* @return the instance
*/
static Instance createInstance(int sequenceType, int orientationType, Gesture gesture, String label) {
float[] pts;
Instance instance;
if (sequenceType == GestureStore.SEQUENCE_SENSITIVE) {
pts = temporalSampler(orientationType, gesture);
instance = new Instance(gesture.getID(), pts, label);
instance.normalize();
} else {
pts = spatialSampler(gesture);
instance = new Instance(gesture.getID(), pts, label);
}
return instance;
}
private static float[] spatialSampler(Gesture gesture) {
return GestureUtils.spatialSampling(gesture, PATCH_SAMPLE_SIZE, false);
}
private static float[] temporalSampler(int orientationType, 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]);
float adjustment = -orientation;
if (orientationType != GestureStore.ORIENTATION_INVARIANT) {
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;
}
}
}
GestureUtils.translate(pts, -center[0], -center[1]);
GestureUtils.rotate(pts, adjustment);
return pts;
}
}

View File

@ -0,0 +1,88 @@
/*
* 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 android.gesture;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.TreeMap;
/**
* An implementation of an instance-based learner
*/
class InstanceLearner extends Learner {
private static final Comparator<Prediction> sComparator = 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;
}
}
};
@Override
ArrayList<Prediction> classify(int sequenceType, int orientationType, float[] vector) {
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 != vector.length) {
continue;
}
double distance;
if (sequenceType == GestureStore.SEQUENCE_SENSITIVE) {
distance = GestureUtils.minimumCosineDistance(sample.vector, vector, orientationType);
} else {
distance = GestureUtils.squaredEuclideanDistance(sample.vector, 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;
for (String name : label2score.keySet()) {
double score = label2score.get(name);
// sum += score;
predictions.add(new Prediction(name, score));
}
// normalize
// for (Prediction prediction : predictions) {
// prediction.score /= sum;
// }
Collections.sort(predictions, sComparator);
return predictions;
}
}

View File

@ -0,0 +1,84 @@
/*
* 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 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.id) {
instances.remove(instance);
return;
}
}
}
/**
* Remove all the instances of a category
*
* @param name the category name
*/
void removeInstances(String name) {
final ArrayList<Instance> toDelete = new ArrayList<Instance>();
final ArrayList<Instance> instances = mInstances;
final int count = instances.size();
for (int i = 0; i < count; i++) {
final Instance instance = instances.get(i);
// the label can be null, as specified in Instance
if ((instance.label == null && name == null)
|| (instance.label != null && instance.label.equals(name))) {
toDelete.add(instance);
}
}
instances.removeAll(toDelete);
}
abstract ArrayList<Prediction> classify(int sequenceType, int orientationType, float[] vector);
}

View File

@ -0,0 +1,85 @@
/*
* 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 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;
}
}
/**
* Currently used for debugging purpose only.
*
* @hide
*/
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;
}
}

View File

@ -0,0 +1,33 @@
/*
* Copyright (C) 2008-2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.gesture;
public class Prediction {
public final String name;
public double score;
Prediction(String label, double predictionScore) {
name = label;
score = predictionScore;
}
@Override
public String toString() {
return name;
}
}

View File

@ -0,0 +1,5 @@
<HTML>
<BODY>
Provides classes to create, recognize, load and save gestures.
</BODY>
</HTML>