From f39fccbd0fd63647c52e8eabcb60df69f97492b5 Mon Sep 17 00:00:00 2001 From: "Tadashi G. Takaoka" Date: Wed, 18 Jul 2012 14:27:51 +0900 Subject: [PATCH] Make GestureStroke as top level class And make PointerTracker object has GestureStroke object. Change-Id: Ibf5cfd593c4f13468368e01acb847589b0ab12e7 --- .../inputmethod/keyboard/PointerTracker.java | 45 ++++- .../keyboard/internal/GestureStroke.java | 149 ++++++++++++++ .../keyboard/internal/GestureTracker.java | 188 +----------------- 3 files changed, 199 insertions(+), 183 deletions(-) create mode 100644 java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java index 733d3b09b..437bbf06b 100644 --- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java +++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java @@ -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; @@ -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) { @@ -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(); @@ -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()) { @@ -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) { diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java b/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java new file mode 100644 index 000000000..14e99487d --- /dev/null +++ b/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java @@ -0,0 +1,149 @@ +/* + * 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; + } +} diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureTracker.java b/java/src/com/android/inputmethod/keyboard/internal/GestureTracker.java index dfd697a7a..0f14dcef4 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/GestureTracker.java +++ b/java/src/com/android/inputmethod/keyboard/internal/GestureTracker.java @@ -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; @@ -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; @@ -49,8 +42,6 @@ public class GestureTracker { private KeyboardActionListener mListener; private SuggestedWords mSuggestions; - private final SparseArray mGestureStrokes = new SparseArray(); - private int mLastRecognitionPointSize = 0; private long mLastRecognitionTime = 0; @@ -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() { @@ -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); } } @@ -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()); @@ -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()); } @@ -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; } @@ -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); - } }