Loading java/src/com/android/inputmethod/keyboard/PointerTracker.java +44 −1 Original line number Diff line number Diff line Loading @@ -22,8 +22,10 @@ import android.view.MotionEvent; import android.view.View; import android.widget.TextView; import com.android.inputmethod.keyboard.internal.GestureStroke; import com.android.inputmethod.keyboard.internal.GestureTracker; import com.android.inputmethod.keyboard.internal.PointerTrackerQueue; import com.android.inputmethod.latin.InputPointers; import com.android.inputmethod.latin.LatinImeLogger; import com.android.inputmethod.latin.ResearchLogger; import com.android.inputmethod.latin.define.ProductionFlag; Loading Loading @@ -165,6 +167,8 @@ public class PointerTracker { // Gesture tracker singleton instance private static final GestureTracker sGestureTracker = GestureTracker.getInstance(); private final GestureStroke mGestureStroke; public static void init(boolean hasDistinctMultitouch, boolean needsPhantomSuddenMoveEventHack) { if (hasDistinctMultitouch) { Loading Loading @@ -222,10 +226,43 @@ public class PointerTracker { } } public PointerTracker(int id, KeyEventHandler handler) { // The working and returning object of the following methods, // {@link #getIncrementalBatchPoints()} and {@link #getAllBatchPoints()}. private static final InputPointers mAggregatedPointers = new InputPointers(); // TODO: This method is called only from GestureTracker and should address the thread-safty // issue soon. public static InputPointers getIncrementalBatchPoints() { final InputPointers pointers = mAggregatedPointers; pointers.reset(); for (final PointerTracker tracker : sTrackers) { tracker.getGestureStroke().appendIncrementalBatchPoints(pointers); } return pointers; } // TODO: This method is called only from GestureTracker and should address the thread-safety // issue soon. public static InputPointers getAllBatchPoints() { final InputPointers pointers = mAggregatedPointers; pointers.reset(); for (final PointerTracker tracker : sTrackers) { tracker.getGestureStroke().appendAllBatchPoints(pointers); } return pointers; } public static void clearBatchInputPoints() { for (final PointerTracker tracker : sTrackers) { tracker.getGestureStroke().reset(); } } private PointerTracker(int id, KeyEventHandler handler) { if (handler == null) throw new NullPointerException(); mPointerId = id; mGestureStroke = new GestureStroke(id); setKeyDetectorInner(handler.getKeyDetector()); mListener = handler.getKeyboardActionListener(); mDrawingProxy = handler.getDrawingProxy(); Loading @@ -237,6 +274,10 @@ public class PointerTracker { return mKeyPreviewText; } public GestureStroke getGestureStroke() { return mGestureStroke; } // Returns true if keyboard has been changed by this callback. private boolean callListenerOnPressAndCheckKeyboardLayoutChange(Key key) { if (sGestureTracker.isInGesture()) { Loading Loading @@ -328,6 +369,8 @@ public class PointerTracker { private void setKeyDetectorInner(KeyDetector keyDetector) { mKeyDetector = keyDetector; mKeyboard = keyDetector.getKeyboard(); mGestureStroke.setGestureSampleLength( mKeyboard.mMostCommonKeyWidth, mKeyboard.mMostCommonKeyHeight); final Key newKey = mKeyDetector.detectHitKey(mKeyX, mKeyY); if (newKey != mCurrentKey) { if (mDrawingProxy != null) { Loading java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java 0 → 100644 +149 −0 Original line number Diff line number Diff line /* * Copyright (C) 2012 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.inputmethod.keyboard.internal; import android.util.FloatMath; import com.android.inputmethod.latin.InputPointers; public class GestureStroke { private final int mPointerId; private final InputPointers mInputPointers = new InputPointers(); private float mLength; private float mAngle; private int mIncrementalRecognitionPoint; private boolean mHasSharpCorner; private long mLastPointTime; private int mLastPointX; private int mLastPointY; private int mMinGestureLength; private int mMinGestureSampleLength; // TODO: Tune these parameters. private static final float MIN_GESTURE_DETECTION_RATIO_TO_KEY_WIDTH = 1.0f / 4.0f; private static final float MIN_GESTURE_SAMPLING_RATIO_TO_KEY_HEIGHT = 1.0f / 6.0f; private static final int MIN_GESTURE_DURATION = 100; // msec private static final float GESTURE_RECOG_SPEED_THRESHOLD = 0.4f; // dip/msec private static final float GESTURE_RECOG_CURVATURE_THRESHOLD = (float)(Math.PI / 4.0f); private static final float DOUBLE_PI = (float)(2 * Math.PI); public GestureStroke(int pointerId) { mPointerId = pointerId; reset(); } public void setGestureSampleLength(final int keyWidth, final int keyHeight) { mMinGestureLength = (int)(keyWidth * MIN_GESTURE_DETECTION_RATIO_TO_KEY_WIDTH); mMinGestureSampleLength = (int)(keyHeight * MIN_GESTURE_SAMPLING_RATIO_TO_KEY_HEIGHT); } public boolean isStartOfAGesture(int downDuration) { return downDuration > MIN_GESTURE_DURATION && mLength > mMinGestureLength; } public void reset() { mLength = 0; mAngle = 0; mIncrementalRecognitionPoint = 0; mHasSharpCorner = false; mLastPointTime = 0; mInputPointers.reset(); } private void updateLastPoint(final int x, final int y, final int time) { mLastPointTime = time; mLastPointX = x; mLastPointY = y; } public void addPoint(final int x, final int y, final int time, final boolean isHistorical) { final int size = mInputPointers.getPointerSize(); if (size == 0) { mInputPointers.addPointer(x, y, mPointerId, time); if (!isHistorical) { updateLastPoint(x, y, time); } return; } final int[] xCoords = mInputPointers.getXCoordinates(); final int[] yCoords = mInputPointers.getYCoordinates(); final int lastX = xCoords[size - 1]; final int lastY = yCoords[size - 1]; final float dist = getDistance(lastX, lastY, x, y); if (dist > mMinGestureSampleLength) { mInputPointers.addPointer(x, y, mPointerId, time); mLength += dist; final float angle = getAngle(lastX, lastY, x, y); if (size > 1) { float curvature = getAngleDiff(angle, mAngle); if (curvature > GESTURE_RECOG_CURVATURE_THRESHOLD) { if (size > mIncrementalRecognitionPoint) { mIncrementalRecognitionPoint = size; } mHasSharpCorner = true; } if (!mHasSharpCorner) { mIncrementalRecognitionPoint = size; } } mAngle = angle; } if (!isHistorical) { final int duration = (int)(time - mLastPointTime); if (mLastPointTime != 0 && duration > 0) { final float speed = getDistance(mLastPointX, mLastPointY, x, y) / duration; if (speed < GESTURE_RECOG_SPEED_THRESHOLD) { mIncrementalRecognitionPoint = size; } } updateLastPoint(x, y, time); } } public void appendAllBatchPoints(final InputPointers out) { out.append(mInputPointers, 0, mInputPointers.getPointerSize()); } public void appendIncrementalBatchPoints(final InputPointers out) { out.append(mInputPointers, 0, mIncrementalRecognitionPoint); } private static float getDistance(final int p1x, final int p1y, final int p2x, final int p2y) { final float dx = p1x - p2x; final float dy = p1y - p2y; // TODO: Optimize out this {@link FloatMath#sqrt(float)} call. return FloatMath.sqrt(dx * dx + dy * dy); } private static float getAngle(final int p1x, final int p1y, final int p2x, final int p2y) { final int dx = p1x - p2x; final int dy = p1y - p2y; if (dx == 0 && dy == 0) return 0; return (float)Math.atan2(dy, dx); } private static float getAngleDiff(final float a1, final float a2) { final float diff = Math.abs(a1 - a2); if (diff > Math.PI) { return DOUBLE_PI - diff; } return diff; } } java/src/com/android/inputmethod/keyboard/internal/GestureTracker.java +6 −182 Original line number Diff line number Diff line Loading @@ -15,7 +15,6 @@ package com.android.inputmethod.keyboard.internal; import android.util.Log; import android.util.SparseArray; import com.android.inputmethod.keyboard.Key; import com.android.inputmethod.keyboard.Keyboard; Loading @@ -35,12 +34,6 @@ public class GestureTracker { private static final GestureTracker sInstance = new GestureTracker(); private static final int MIN_RECOGNITION_TIME = 100; private static final int MIN_GESTURE_DURATION = 200; private static final float GESTURE_RECOG_SPEED_THRESHOLD = 0.4f; private static final float SQUARED_GESTURE_RECOG_SPEED_THRESHOLD = GESTURE_RECOG_SPEED_THRESHOLD * GESTURE_RECOG_SPEED_THRESHOLD; private static final float GESTURE_RECOG_CURVATURE_THRESHOLD = (float) (Math.PI / 4); private boolean mIsAlphabetKeyboard; private boolean mIsPossibleGesture = false; Loading @@ -49,8 +42,6 @@ public class GestureTracker { private KeyboardActionListener mListener; private SuggestedWords mSuggestions; private final SparseArray<GestureStroke> mGestureStrokes = new SparseArray<GestureStroke>(); private int mLastRecognitionPointSize = 0; private long mLastRecognitionTime = 0; Loading @@ -67,8 +58,6 @@ public class GestureTracker { public void setKeyboard(Keyboard keyboard) { mIsAlphabetKeyboard = keyboard.mId.isAlphabetKeyboard(); GestureStroke.setGestureSampleLength(keyboard.mMostCommonKeyWidth / 2, keyboard.mMostCommonKeyHeight / 6); } private void startBatchInput() { Loading Loading @@ -107,7 +96,7 @@ public class GestureTracker { // A gesture should start only from the letter key. if (GESTURE_ON && mIsAlphabetKeyboard && key != null && Keyboard.isLetterCode(key.mCode)) { mIsPossibleGesture = true; addPointToStroke(x, y, 0, tracker.mPointerId, false); tracker.getGestureStroke().addPoint(x, y, 0, false); } } Loading @@ -115,15 +104,15 @@ public class GestureTracker { boolean isHistorical, Key key) { final int gestureTime = (int)(eventTime - tracker.getDownTime()); if (GESTURE_ON && mIsPossibleGesture) { final GestureStroke stroke = addPointToStroke(x, y, gestureTime, tracker.mPointerId, isHistorical); final GestureStroke stroke = tracker.getGestureStroke(); stroke.addPoint(x, y, gestureTime, isHistorical); if (!isInGesture() && stroke.isStartOfAGesture(gestureTime)) { startBatchInput(); } } if (key != null && isInGesture()) { final InputPointers batchPoints = getIncrementalBatchPoints(); final InputPointers batchPoints = PointerTracker.getIncrementalBatchPoints(); if (updateBatchInputRecognitionState(eventTime, batchPoints.getPointerSize())) { if (DEBUG_LISTENER) { Log.d(TAG, "onUpdateBatchInput: batchPoints=" + batchPoints.getPointerSize()); Loading @@ -135,7 +124,7 @@ public class GestureTracker { public void onUpEvent(PointerTracker tracker, int x, int y, long eventTime) { if (isInGesture()) { final InputPointers batchPoints = getAllBatchPoints(); final InputPointers batchPoints = PointerTracker.getAllBatchPoints(); if (DEBUG_LISTENER) { Log.d(TAG, "onUpdateBatchInput: batchPoints=" + batchPoints.getPointerSize()); } Loading @@ -143,49 +132,8 @@ public class GestureTracker { } } private GestureStroke addPointToStroke(int x, int y, int time, int pointerId, boolean isHistorical) { GestureStroke stroke = mGestureStrokes.get(pointerId); if (stroke == null) { stroke = new GestureStroke(pointerId); mGestureStrokes.put(pointerId, stroke); } stroke.addPoint(x, y, time, isHistorical); return stroke; } // The working and return object of the following methods, {@link #getIncrementalBatchPoints()} // and {@link #getAllBatchPoints()}. private final InputPointers mAggregatedPointers = new InputPointers(); private InputPointers getIncrementalBatchPoints() { final InputPointers pointers = mAggregatedPointers; pointers.reset(); final int strokeSize = mGestureStrokes.size(); for (int index = 0; index < strokeSize; index++) { final GestureStroke stroke = mGestureStrokes.valueAt(index); stroke.appendIncrementalBatchPoints(pointers); } return pointers; } private InputPointers getAllBatchPoints() { final InputPointers pointers = mAggregatedPointers; pointers.reset(); final int strokeSize = mGestureStrokes.size(); for (int index = 0; index < strokeSize; index++) { final GestureStroke stroke = mGestureStrokes.valueAt(index); stroke.appendAllBatchPoints(pointers); } return pointers; } private void clearBatchInputPoints() { final int strokeSize = mGestureStrokes.size(); for (int index = 0; index < strokeSize; index++) { final GestureStroke stroke = mGestureStrokes.valueAt(index); stroke.reset(); } PointerTracker.clearBatchInputPoints(); mLastRecognitionPointSize = 0; mLastRecognitionTime = 0; } Loading @@ -199,128 +147,4 @@ public class GestureTracker { } return false; } private static class GestureStroke { private final int mPointerId; private final InputPointers mInputPointers = new InputPointers(); private float mLength; private float mAngle; private int mIncrementalRecognitionPoint; private boolean mHasSharpCorner; private long mLastPointTime; private int mLastPointX; private int mLastPointY; private static int sMinGestureLength; private static int sSquaredGestureSampleLength; private static final float DOUBLE_PI = (float)(2 * Math.PI); public static void setGestureSampleLength(final int minGestureLength, final int sampleLength) { sMinGestureLength = minGestureLength; sSquaredGestureSampleLength = sampleLength * sampleLength; } public GestureStroke(int pointerId) { mPointerId = pointerId; reset(); } public boolean isStartOfAGesture(int downDuration) { return downDuration > MIN_GESTURE_DURATION / 2 && mLength > sMinGestureLength / 2; } public void reset() { mLength = 0; mAngle = 0; mIncrementalRecognitionPoint = 0; mHasSharpCorner = false; mLastPointTime = 0; mInputPointers.reset(); } private void updateLastPoint(final int x, final int y, final int time) { mLastPointTime = time; mLastPointX = x; mLastPointY = y; } public void addPoint(final int x, final int y, final int time, final boolean isHistorical) { final int size = mInputPointers.getPointerSize(); if (size == 0) { mInputPointers.addPointer(x, y, mPointerId, time); if (!isHistorical) { updateLastPoint(x, y, time); } return; } final int[] xCoords = mInputPointers.getXCoordinates(); final int[] yCoords = mInputPointers.getYCoordinates(); final int lastX = xCoords[size - 1]; final int lastY = yCoords[size - 1]; final float dist = squaredDistance(lastX, lastY, x, y); if (dist > sSquaredGestureSampleLength) { mInputPointers.addPointer(x, y, mPointerId, time); mLength += dist; final float angle = angle(lastX, lastY, x, y); if (size > 1) { float curvature = getAngleDiff(angle, mAngle); if (curvature > GESTURE_RECOG_CURVATURE_THRESHOLD) { if (size > mIncrementalRecognitionPoint) { mIncrementalRecognitionPoint = size; } mHasSharpCorner = true; } if (!mHasSharpCorner) { mIncrementalRecognitionPoint = size; } } mAngle = angle; } if (!isHistorical) { final int duration = (int)(time - mLastPointTime); if (mLastPointTime != 0 && duration > 0) { final int squaredDuration = duration * duration; final float squaredSpeed = squaredDistance(mLastPointX, mLastPointY, x, y) / squaredDuration; if (squaredSpeed < SQUARED_GESTURE_RECOG_SPEED_THRESHOLD) { mIncrementalRecognitionPoint = size; } } updateLastPoint(x, y, time); } } private float getAngleDiff(float a1, float a2) { final float diff = Math.abs(a1 - a2); if (diff > Math.PI) { return DOUBLE_PI - diff; } return diff; } public void appendAllBatchPoints(InputPointers out) { out.append(mInputPointers, 0, mInputPointers.getPointerSize()); } public void appendIncrementalBatchPoints(InputPointers out) { out.append(mInputPointers, 0, mIncrementalRecognitionPoint); } } static float squaredDistance(int p1x, int p1y, int p2x, int p2y) { final float dx = p1x - p2x; final float dy = p1y - p2y; return dx * dx + dy * dy; } static float angle(int p1x, int p1y, int p2x, int p2y) { final int dx = p1x - p2x; final int dy = p1y - p2y; if (dx == 0 && dy == 0) return 0; return (float)Math.atan2(dy, dx); } } Loading
java/src/com/android/inputmethod/keyboard/PointerTracker.java +44 −1 Original line number Diff line number Diff line Loading @@ -22,8 +22,10 @@ import android.view.MotionEvent; import android.view.View; import android.widget.TextView; import com.android.inputmethod.keyboard.internal.GestureStroke; import com.android.inputmethod.keyboard.internal.GestureTracker; import com.android.inputmethod.keyboard.internal.PointerTrackerQueue; import com.android.inputmethod.latin.InputPointers; import com.android.inputmethod.latin.LatinImeLogger; import com.android.inputmethod.latin.ResearchLogger; import com.android.inputmethod.latin.define.ProductionFlag; Loading Loading @@ -165,6 +167,8 @@ public class PointerTracker { // Gesture tracker singleton instance private static final GestureTracker sGestureTracker = GestureTracker.getInstance(); private final GestureStroke mGestureStroke; public static void init(boolean hasDistinctMultitouch, boolean needsPhantomSuddenMoveEventHack) { if (hasDistinctMultitouch) { Loading Loading @@ -222,10 +226,43 @@ public class PointerTracker { } } public PointerTracker(int id, KeyEventHandler handler) { // The working and returning object of the following methods, // {@link #getIncrementalBatchPoints()} and {@link #getAllBatchPoints()}. private static final InputPointers mAggregatedPointers = new InputPointers(); // TODO: This method is called only from GestureTracker and should address the thread-safty // issue soon. public static InputPointers getIncrementalBatchPoints() { final InputPointers pointers = mAggregatedPointers; pointers.reset(); for (final PointerTracker tracker : sTrackers) { tracker.getGestureStroke().appendIncrementalBatchPoints(pointers); } return pointers; } // TODO: This method is called only from GestureTracker and should address the thread-safety // issue soon. public static InputPointers getAllBatchPoints() { final InputPointers pointers = mAggregatedPointers; pointers.reset(); for (final PointerTracker tracker : sTrackers) { tracker.getGestureStroke().appendAllBatchPoints(pointers); } return pointers; } public static void clearBatchInputPoints() { for (final PointerTracker tracker : sTrackers) { tracker.getGestureStroke().reset(); } } private PointerTracker(int id, KeyEventHandler handler) { if (handler == null) throw new NullPointerException(); mPointerId = id; mGestureStroke = new GestureStroke(id); setKeyDetectorInner(handler.getKeyDetector()); mListener = handler.getKeyboardActionListener(); mDrawingProxy = handler.getDrawingProxy(); Loading @@ -237,6 +274,10 @@ public class PointerTracker { return mKeyPreviewText; } public GestureStroke getGestureStroke() { return mGestureStroke; } // Returns true if keyboard has been changed by this callback. private boolean callListenerOnPressAndCheckKeyboardLayoutChange(Key key) { if (sGestureTracker.isInGesture()) { Loading Loading @@ -328,6 +369,8 @@ public class PointerTracker { private void setKeyDetectorInner(KeyDetector keyDetector) { mKeyDetector = keyDetector; mKeyboard = keyDetector.getKeyboard(); mGestureStroke.setGestureSampleLength( mKeyboard.mMostCommonKeyWidth, mKeyboard.mMostCommonKeyHeight); final Key newKey = mKeyDetector.detectHitKey(mKeyX, mKeyY); if (newKey != mCurrentKey) { if (mDrawingProxy != null) { Loading
java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java 0 → 100644 +149 −0 Original line number Diff line number Diff line /* * Copyright (C) 2012 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.inputmethod.keyboard.internal; import android.util.FloatMath; import com.android.inputmethod.latin.InputPointers; public class GestureStroke { private final int mPointerId; private final InputPointers mInputPointers = new InputPointers(); private float mLength; private float mAngle; private int mIncrementalRecognitionPoint; private boolean mHasSharpCorner; private long mLastPointTime; private int mLastPointX; private int mLastPointY; private int mMinGestureLength; private int mMinGestureSampleLength; // TODO: Tune these parameters. private static final float MIN_GESTURE_DETECTION_RATIO_TO_KEY_WIDTH = 1.0f / 4.0f; private static final float MIN_GESTURE_SAMPLING_RATIO_TO_KEY_HEIGHT = 1.0f / 6.0f; private static final int MIN_GESTURE_DURATION = 100; // msec private static final float GESTURE_RECOG_SPEED_THRESHOLD = 0.4f; // dip/msec private static final float GESTURE_RECOG_CURVATURE_THRESHOLD = (float)(Math.PI / 4.0f); private static final float DOUBLE_PI = (float)(2 * Math.PI); public GestureStroke(int pointerId) { mPointerId = pointerId; reset(); } public void setGestureSampleLength(final int keyWidth, final int keyHeight) { mMinGestureLength = (int)(keyWidth * MIN_GESTURE_DETECTION_RATIO_TO_KEY_WIDTH); mMinGestureSampleLength = (int)(keyHeight * MIN_GESTURE_SAMPLING_RATIO_TO_KEY_HEIGHT); } public boolean isStartOfAGesture(int downDuration) { return downDuration > MIN_GESTURE_DURATION && mLength > mMinGestureLength; } public void reset() { mLength = 0; mAngle = 0; mIncrementalRecognitionPoint = 0; mHasSharpCorner = false; mLastPointTime = 0; mInputPointers.reset(); } private void updateLastPoint(final int x, final int y, final int time) { mLastPointTime = time; mLastPointX = x; mLastPointY = y; } public void addPoint(final int x, final int y, final int time, final boolean isHistorical) { final int size = mInputPointers.getPointerSize(); if (size == 0) { mInputPointers.addPointer(x, y, mPointerId, time); if (!isHistorical) { updateLastPoint(x, y, time); } return; } final int[] xCoords = mInputPointers.getXCoordinates(); final int[] yCoords = mInputPointers.getYCoordinates(); final int lastX = xCoords[size - 1]; final int lastY = yCoords[size - 1]; final float dist = getDistance(lastX, lastY, x, y); if (dist > mMinGestureSampleLength) { mInputPointers.addPointer(x, y, mPointerId, time); mLength += dist; final float angle = getAngle(lastX, lastY, x, y); if (size > 1) { float curvature = getAngleDiff(angle, mAngle); if (curvature > GESTURE_RECOG_CURVATURE_THRESHOLD) { if (size > mIncrementalRecognitionPoint) { mIncrementalRecognitionPoint = size; } mHasSharpCorner = true; } if (!mHasSharpCorner) { mIncrementalRecognitionPoint = size; } } mAngle = angle; } if (!isHistorical) { final int duration = (int)(time - mLastPointTime); if (mLastPointTime != 0 && duration > 0) { final float speed = getDistance(mLastPointX, mLastPointY, x, y) / duration; if (speed < GESTURE_RECOG_SPEED_THRESHOLD) { mIncrementalRecognitionPoint = size; } } updateLastPoint(x, y, time); } } public void appendAllBatchPoints(final InputPointers out) { out.append(mInputPointers, 0, mInputPointers.getPointerSize()); } public void appendIncrementalBatchPoints(final InputPointers out) { out.append(mInputPointers, 0, mIncrementalRecognitionPoint); } private static float getDistance(final int p1x, final int p1y, final int p2x, final int p2y) { final float dx = p1x - p2x; final float dy = p1y - p2y; // TODO: Optimize out this {@link FloatMath#sqrt(float)} call. return FloatMath.sqrt(dx * dx + dy * dy); } private static float getAngle(final int p1x, final int p1y, final int p2x, final int p2y) { final int dx = p1x - p2x; final int dy = p1y - p2y; if (dx == 0 && dy == 0) return 0; return (float)Math.atan2(dy, dx); } private static float getAngleDiff(final float a1, final float a2) { final float diff = Math.abs(a1 - a2); if (diff > Math.PI) { return DOUBLE_PI - diff; } return diff; } }
java/src/com/android/inputmethod/keyboard/internal/GestureTracker.java +6 −182 Original line number Diff line number Diff line Loading @@ -15,7 +15,6 @@ package com.android.inputmethod.keyboard.internal; import android.util.Log; import android.util.SparseArray; import com.android.inputmethod.keyboard.Key; import com.android.inputmethod.keyboard.Keyboard; Loading @@ -35,12 +34,6 @@ public class GestureTracker { private static final GestureTracker sInstance = new GestureTracker(); private static final int MIN_RECOGNITION_TIME = 100; private static final int MIN_GESTURE_DURATION = 200; private static final float GESTURE_RECOG_SPEED_THRESHOLD = 0.4f; private static final float SQUARED_GESTURE_RECOG_SPEED_THRESHOLD = GESTURE_RECOG_SPEED_THRESHOLD * GESTURE_RECOG_SPEED_THRESHOLD; private static final float GESTURE_RECOG_CURVATURE_THRESHOLD = (float) (Math.PI / 4); private boolean mIsAlphabetKeyboard; private boolean mIsPossibleGesture = false; Loading @@ -49,8 +42,6 @@ public class GestureTracker { private KeyboardActionListener mListener; private SuggestedWords mSuggestions; private final SparseArray<GestureStroke> mGestureStrokes = new SparseArray<GestureStroke>(); private int mLastRecognitionPointSize = 0; private long mLastRecognitionTime = 0; Loading @@ -67,8 +58,6 @@ public class GestureTracker { public void setKeyboard(Keyboard keyboard) { mIsAlphabetKeyboard = keyboard.mId.isAlphabetKeyboard(); GestureStroke.setGestureSampleLength(keyboard.mMostCommonKeyWidth / 2, keyboard.mMostCommonKeyHeight / 6); } private void startBatchInput() { Loading Loading @@ -107,7 +96,7 @@ public class GestureTracker { // A gesture should start only from the letter key. if (GESTURE_ON && mIsAlphabetKeyboard && key != null && Keyboard.isLetterCode(key.mCode)) { mIsPossibleGesture = true; addPointToStroke(x, y, 0, tracker.mPointerId, false); tracker.getGestureStroke().addPoint(x, y, 0, false); } } Loading @@ -115,15 +104,15 @@ public class GestureTracker { boolean isHistorical, Key key) { final int gestureTime = (int)(eventTime - tracker.getDownTime()); if (GESTURE_ON && mIsPossibleGesture) { final GestureStroke stroke = addPointToStroke(x, y, gestureTime, tracker.mPointerId, isHistorical); final GestureStroke stroke = tracker.getGestureStroke(); stroke.addPoint(x, y, gestureTime, isHistorical); if (!isInGesture() && stroke.isStartOfAGesture(gestureTime)) { startBatchInput(); } } if (key != null && isInGesture()) { final InputPointers batchPoints = getIncrementalBatchPoints(); final InputPointers batchPoints = PointerTracker.getIncrementalBatchPoints(); if (updateBatchInputRecognitionState(eventTime, batchPoints.getPointerSize())) { if (DEBUG_LISTENER) { Log.d(TAG, "onUpdateBatchInput: batchPoints=" + batchPoints.getPointerSize()); Loading @@ -135,7 +124,7 @@ public class GestureTracker { public void onUpEvent(PointerTracker tracker, int x, int y, long eventTime) { if (isInGesture()) { final InputPointers batchPoints = getAllBatchPoints(); final InputPointers batchPoints = PointerTracker.getAllBatchPoints(); if (DEBUG_LISTENER) { Log.d(TAG, "onUpdateBatchInput: batchPoints=" + batchPoints.getPointerSize()); } Loading @@ -143,49 +132,8 @@ public class GestureTracker { } } private GestureStroke addPointToStroke(int x, int y, int time, int pointerId, boolean isHistorical) { GestureStroke stroke = mGestureStrokes.get(pointerId); if (stroke == null) { stroke = new GestureStroke(pointerId); mGestureStrokes.put(pointerId, stroke); } stroke.addPoint(x, y, time, isHistorical); return stroke; } // The working and return object of the following methods, {@link #getIncrementalBatchPoints()} // and {@link #getAllBatchPoints()}. private final InputPointers mAggregatedPointers = new InputPointers(); private InputPointers getIncrementalBatchPoints() { final InputPointers pointers = mAggregatedPointers; pointers.reset(); final int strokeSize = mGestureStrokes.size(); for (int index = 0; index < strokeSize; index++) { final GestureStroke stroke = mGestureStrokes.valueAt(index); stroke.appendIncrementalBatchPoints(pointers); } return pointers; } private InputPointers getAllBatchPoints() { final InputPointers pointers = mAggregatedPointers; pointers.reset(); final int strokeSize = mGestureStrokes.size(); for (int index = 0; index < strokeSize; index++) { final GestureStroke stroke = mGestureStrokes.valueAt(index); stroke.appendAllBatchPoints(pointers); } return pointers; } private void clearBatchInputPoints() { final int strokeSize = mGestureStrokes.size(); for (int index = 0; index < strokeSize; index++) { final GestureStroke stroke = mGestureStrokes.valueAt(index); stroke.reset(); } PointerTracker.clearBatchInputPoints(); mLastRecognitionPointSize = 0; mLastRecognitionTime = 0; } Loading @@ -199,128 +147,4 @@ public class GestureTracker { } return false; } private static class GestureStroke { private final int mPointerId; private final InputPointers mInputPointers = new InputPointers(); private float mLength; private float mAngle; private int mIncrementalRecognitionPoint; private boolean mHasSharpCorner; private long mLastPointTime; private int mLastPointX; private int mLastPointY; private static int sMinGestureLength; private static int sSquaredGestureSampleLength; private static final float DOUBLE_PI = (float)(2 * Math.PI); public static void setGestureSampleLength(final int minGestureLength, final int sampleLength) { sMinGestureLength = minGestureLength; sSquaredGestureSampleLength = sampleLength * sampleLength; } public GestureStroke(int pointerId) { mPointerId = pointerId; reset(); } public boolean isStartOfAGesture(int downDuration) { return downDuration > MIN_GESTURE_DURATION / 2 && mLength > sMinGestureLength / 2; } public void reset() { mLength = 0; mAngle = 0; mIncrementalRecognitionPoint = 0; mHasSharpCorner = false; mLastPointTime = 0; mInputPointers.reset(); } private void updateLastPoint(final int x, final int y, final int time) { mLastPointTime = time; mLastPointX = x; mLastPointY = y; } public void addPoint(final int x, final int y, final int time, final boolean isHistorical) { final int size = mInputPointers.getPointerSize(); if (size == 0) { mInputPointers.addPointer(x, y, mPointerId, time); if (!isHistorical) { updateLastPoint(x, y, time); } return; } final int[] xCoords = mInputPointers.getXCoordinates(); final int[] yCoords = mInputPointers.getYCoordinates(); final int lastX = xCoords[size - 1]; final int lastY = yCoords[size - 1]; final float dist = squaredDistance(lastX, lastY, x, y); if (dist > sSquaredGestureSampleLength) { mInputPointers.addPointer(x, y, mPointerId, time); mLength += dist; final float angle = angle(lastX, lastY, x, y); if (size > 1) { float curvature = getAngleDiff(angle, mAngle); if (curvature > GESTURE_RECOG_CURVATURE_THRESHOLD) { if (size > mIncrementalRecognitionPoint) { mIncrementalRecognitionPoint = size; } mHasSharpCorner = true; } if (!mHasSharpCorner) { mIncrementalRecognitionPoint = size; } } mAngle = angle; } if (!isHistorical) { final int duration = (int)(time - mLastPointTime); if (mLastPointTime != 0 && duration > 0) { final int squaredDuration = duration * duration; final float squaredSpeed = squaredDistance(mLastPointX, mLastPointY, x, y) / squaredDuration; if (squaredSpeed < SQUARED_GESTURE_RECOG_SPEED_THRESHOLD) { mIncrementalRecognitionPoint = size; } } updateLastPoint(x, y, time); } } private float getAngleDiff(float a1, float a2) { final float diff = Math.abs(a1 - a2); if (diff > Math.PI) { return DOUBLE_PI - diff; } return diff; } public void appendAllBatchPoints(InputPointers out) { out.append(mInputPointers, 0, mInputPointers.getPointerSize()); } public void appendIncrementalBatchPoints(InputPointers out) { out.append(mInputPointers, 0, mIncrementalRecognitionPoint); } } static float squaredDistance(int p1x, int p1y, int p2x, int p2y) { final float dx = p1x - p2x; final float dy = p1y - p2y; return dx * dx + dy * dy; } static float angle(int p1x, int p1y, int p2x, int p2y) { final int dx = p1x - p2x; final int dy = p1y - p2y; if (dx == 0 && dy == 0) return 0; return (float)Math.atan2(dy, dx); } }