Make GestureStroke as top level class

And make PointerTracker object has GestureStroke object.

Change-Id: Ibf5cfd593c4f13468368e01acb847589b0ab12e7
This commit is contained in:
Tadashi G. Takaoka 2012-07-18 14:27:51 +09:00
parent 3ec31f4971
commit f39fccbd0f
3 changed files with 199 additions and 183 deletions

View file

@ -22,8 +22,10 @@ import android.view.MotionEvent;
import android.view.View; import android.view.View;
import android.widget.TextView; import android.widget.TextView;
import com.android.inputmethod.keyboard.internal.GestureStroke;
import com.android.inputmethod.keyboard.internal.GestureTracker; import com.android.inputmethod.keyboard.internal.GestureTracker;
import com.android.inputmethod.keyboard.internal.PointerTrackerQueue; import com.android.inputmethod.keyboard.internal.PointerTrackerQueue;
import com.android.inputmethod.latin.InputPointers;
import com.android.inputmethod.latin.LatinImeLogger; import com.android.inputmethod.latin.LatinImeLogger;
import com.android.inputmethod.latin.ResearchLogger; import com.android.inputmethod.latin.ResearchLogger;
import com.android.inputmethod.latin.define.ProductionFlag; import com.android.inputmethod.latin.define.ProductionFlag;
@ -165,6 +167,8 @@ public class PointerTracker {
// Gesture tracker singleton instance // Gesture tracker singleton instance
private static final GestureTracker sGestureTracker = GestureTracker.getInstance(); private static final GestureTracker sGestureTracker = GestureTracker.getInstance();
private final GestureStroke mGestureStroke;
public static void init(boolean hasDistinctMultitouch, public static void init(boolean hasDistinctMultitouch,
boolean needsPhantomSuddenMoveEventHack) { boolean needsPhantomSuddenMoveEventHack) {
if (hasDistinctMultitouch) { 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) if (handler == null)
throw new NullPointerException(); throw new NullPointerException();
mPointerId = id; mPointerId = id;
mGestureStroke = new GestureStroke(id);
setKeyDetectorInner(handler.getKeyDetector()); setKeyDetectorInner(handler.getKeyDetector());
mListener = handler.getKeyboardActionListener(); mListener = handler.getKeyboardActionListener();
mDrawingProxy = handler.getDrawingProxy(); mDrawingProxy = handler.getDrawingProxy();
@ -237,6 +274,10 @@ public class PointerTracker {
return mKeyPreviewText; return mKeyPreviewText;
} }
public GestureStroke getGestureStroke() {
return mGestureStroke;
}
// Returns true if keyboard has been changed by this callback. // Returns true if keyboard has been changed by this callback.
private boolean callListenerOnPressAndCheckKeyboardLayoutChange(Key key) { private boolean callListenerOnPressAndCheckKeyboardLayoutChange(Key key) {
if (sGestureTracker.isInGesture()) { if (sGestureTracker.isInGesture()) {
@ -328,6 +369,8 @@ public class PointerTracker {
private void setKeyDetectorInner(KeyDetector keyDetector) { private void setKeyDetectorInner(KeyDetector keyDetector) {
mKeyDetector = keyDetector; mKeyDetector = keyDetector;
mKeyboard = keyDetector.getKeyboard(); mKeyboard = keyDetector.getKeyboard();
mGestureStroke.setGestureSampleLength(
mKeyboard.mMostCommonKeyWidth, mKeyboard.mMostCommonKeyHeight);
final Key newKey = mKeyDetector.detectHitKey(mKeyX, mKeyY); final Key newKey = mKeyDetector.detectHitKey(mKeyX, mKeyY);
if (newKey != mCurrentKey) { if (newKey != mCurrentKey) {
if (mDrawingProxy != null) { if (mDrawingProxy != null) {

View file

@ -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;
}
}

View file

@ -15,7 +15,6 @@
package com.android.inputmethod.keyboard.internal; package com.android.inputmethod.keyboard.internal;
import android.util.Log; import android.util.Log;
import android.util.SparseArray;
import com.android.inputmethod.keyboard.Key; import com.android.inputmethod.keyboard.Key;
import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.keyboard.Keyboard;
@ -35,12 +34,6 @@ public class GestureTracker {
private static final GestureTracker sInstance = new GestureTracker(); private static final GestureTracker sInstance = new GestureTracker();
private static final int MIN_RECOGNITION_TIME = 100; 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 mIsAlphabetKeyboard;
private boolean mIsPossibleGesture = false; private boolean mIsPossibleGesture = false;
@ -49,8 +42,6 @@ public class GestureTracker {
private KeyboardActionListener mListener; private KeyboardActionListener mListener;
private SuggestedWords mSuggestions; private SuggestedWords mSuggestions;
private final SparseArray<GestureStroke> mGestureStrokes = new SparseArray<GestureStroke>();
private int mLastRecognitionPointSize = 0; private int mLastRecognitionPointSize = 0;
private long mLastRecognitionTime = 0; private long mLastRecognitionTime = 0;
@ -67,8 +58,6 @@ public class GestureTracker {
public void setKeyboard(Keyboard keyboard) { public void setKeyboard(Keyboard keyboard) {
mIsAlphabetKeyboard = keyboard.mId.isAlphabetKeyboard(); mIsAlphabetKeyboard = keyboard.mId.isAlphabetKeyboard();
GestureStroke.setGestureSampleLength(keyboard.mMostCommonKeyWidth / 2,
keyboard.mMostCommonKeyHeight / 6);
} }
private void startBatchInput() { private void startBatchInput() {
@ -107,7 +96,7 @@ public class GestureTracker {
// A gesture should start only from the letter key. // A gesture should start only from the letter key.
if (GESTURE_ON && mIsAlphabetKeyboard && key != null && Keyboard.isLetterCode(key.mCode)) { if (GESTURE_ON && mIsAlphabetKeyboard && key != null && Keyboard.isLetterCode(key.mCode)) {
mIsPossibleGesture = true; 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) { boolean isHistorical, Key key) {
final int gestureTime = (int)(eventTime - tracker.getDownTime()); final int gestureTime = (int)(eventTime - tracker.getDownTime());
if (GESTURE_ON && mIsPossibleGesture) { if (GESTURE_ON && mIsPossibleGesture) {
final GestureStroke stroke = addPointToStroke(x, y, gestureTime, tracker.mPointerId, final GestureStroke stroke = tracker.getGestureStroke();
isHistorical); stroke.addPoint(x, y, gestureTime, isHistorical);
if (!isInGesture() && stroke.isStartOfAGesture(gestureTime)) { if (!isInGesture() && stroke.isStartOfAGesture(gestureTime)) {
startBatchInput(); startBatchInput();
} }
} }
if (key != null && isInGesture()) { if (key != null && isInGesture()) {
final InputPointers batchPoints = getIncrementalBatchPoints(); final InputPointers batchPoints = PointerTracker.getIncrementalBatchPoints();
if (updateBatchInputRecognitionState(eventTime, batchPoints.getPointerSize())) { if (updateBatchInputRecognitionState(eventTime, batchPoints.getPointerSize())) {
if (DEBUG_LISTENER) { if (DEBUG_LISTENER) {
Log.d(TAG, "onUpdateBatchInput: batchPoints=" + batchPoints.getPointerSize()); 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) { public void onUpEvent(PointerTracker tracker, int x, int y, long eventTime) {
if (isInGesture()) { if (isInGesture()) {
final InputPointers batchPoints = getAllBatchPoints(); final InputPointers batchPoints = PointerTracker.getAllBatchPoints();
if (DEBUG_LISTENER) { if (DEBUG_LISTENER) {
Log.d(TAG, "onUpdateBatchInput: batchPoints=" + batchPoints.getPointerSize()); 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() { private void clearBatchInputPoints() {
final int strokeSize = mGestureStrokes.size(); PointerTracker.clearBatchInputPoints();
for (int index = 0; index < strokeSize; index++) {
final GestureStroke stroke = mGestureStrokes.valueAt(index);
stroke.reset();
}
mLastRecognitionPointSize = 0; mLastRecognitionPointSize = 0;
mLastRecognitionTime = 0; mLastRecognitionTime = 0;
} }
@ -199,128 +147,4 @@ public class GestureTracker {
} }
return false; 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);
}
} }