diff --git a/java/res/values/config.xml b/java/res/values/config.xml
index 32b54447c..cb1358726 100644
--- a/java/res/values/config.xml
+++ b/java/res/values/config.xml
@@ -71,7 +71,7 @@
false -->
false
- 1000
+ 500
150%
diff --git a/java/res/values/phantom_sudden_move_event_device_list.xml b/java/res/values/phantom-sudden-move-event-device-list.xml
similarity index 100%
rename from java/res/values/phantom_sudden_move_event_device_list.xml
rename to java/res/values/phantom-sudden-move-event-device-list.xml
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
index 291b65849..ccf6932ca 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
@@ -40,7 +40,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
private static final boolean DEBUG_EVENT = false;
private static final boolean DEBUG_MOVE_EVENT = false;
private static final boolean DEBUG_LISTENER = false;
- private static boolean DEBUG_MODE = LatinImeLogger.sDBG;
+ private static boolean DEBUG_MODE = LatinImeLogger.sDBG || DEBUG_EVENT;
/** True if {@link PointerTracker}s should handle gesture events. */
private static boolean sShouldHandleGesture = false;
@@ -121,8 +121,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
static final class PointerTrackerParams {
public final boolean mSlidingKeyInputEnabled;
public final int mTouchNoiseThresholdTime;
- public final float mTouchNoiseThresholdDistance;
- public final int mTouchNoiseThresholdDistanceSquared;
+ public final int mTouchNoiseThresholdDistance;
public final int mSuppressKeyPreviewAfterBatchInputDuration;
public static final PointerTrackerParams DEFAULT = new PointerTrackerParams();
@@ -130,8 +129,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
private PointerTrackerParams() {
mSlidingKeyInputEnabled = false;
mTouchNoiseThresholdTime = 0;
- mTouchNoiseThresholdDistance = 0.0f;
- mTouchNoiseThresholdDistanceSquared = 0;
+ mTouchNoiseThresholdDistance = 0;
mSuppressKeyPreviewAfterBatchInputDuration = 0;
}
@@ -140,11 +138,8 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
R.styleable.MainKeyboardView_slidingKeyInputEnable, false);
mTouchNoiseThresholdTime = mainKeyboardViewAttr.getInt(
R.styleable.MainKeyboardView_touchNoiseThresholdTime, 0);
- final float touchNouseThresholdDistance = mainKeyboardViewAttr.getDimension(
+ mTouchNoiseThresholdDistance = mainKeyboardViewAttr.getDimensionPixelSize(
R.styleable.MainKeyboardView_touchNoiseThresholdDistance, 0);
- mTouchNoiseThresholdDistance = touchNouseThresholdDistance;
- mTouchNoiseThresholdDistanceSquared =
- (int)(touchNouseThresholdDistance * touchNouseThresholdDistance);
mSuppressKeyPreviewAfterBatchInputDuration = mainKeyboardViewAttr.getInt(
R.styleable.MainKeyboardView_suppressKeyPreviewAfterBatchInputDuration, 0);
}
@@ -154,6 +149,11 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
private static PointerTrackerParams sParams;
private static GestureStrokeParams sGestureStrokeParams;
private static boolean sNeedsPhantomSuddenMoveEventHack;
+ // Move this threshold to resource.
+ // TODO: Device specific parameter would be better for device specific hack?
+ private static final float PHANTOM_SUDDEN_MOVE_THRESHOLD = 0.25f; // in keyWidth
+ // This hack might be device specific.
+ private static final boolean sNeedsProximateBogusDownMoveUpEventHack = true;
private static final ArrayList sTrackers = CollectionUtils.newArrayList();
private static PointerTrackerQueue sPointerTrackerQueue;
@@ -166,7 +166,8 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
private KeyboardActionListener mListener = EMPTY_LISTENER;
private Keyboard mKeyboard;
- private int mKeyQuarterWidthSquared;
+ private int mPhantonSuddenMoveThreshold;
+ private final BogusMoveEventDetector mBogusMoveEventDetector = new BogusMoveEventDetector();
private boolean mIsDetectingGesture = false; // per PointerTracker.
private static boolean sInGesture = false;
@@ -177,6 +178,51 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
private static int sLastRecognitionPointSize = 0; // synchronized using sAggregratedPointers
private static long sLastRecognitionTime = 0; // synchronized using sAggregratedPointers
+ static final class BogusMoveEventDetector {
+ // Move these thresholds to resource.
+ private static final float BOGUS_MOVE_ACCUMULATED_DISTANCE_THRESHOLD = 0.70f; // in keyWidth
+ private static final float BOGUS_MOVE_RADIUS_THRESHOLD = 1.50f; // in keyWidth
+
+ private int mAccumulatedDistanceThreshold;
+ private int mRadiusThreshold;
+
+ // Accumulated distance from actual and artificial down keys.
+ /* package */ int mAccumulatedDistanceFromDownKey;
+ private int mActualDownX;
+ private int mActualDownY;
+
+ public void setKeyboardGeometry(final int keyWidth) {
+ mAccumulatedDistanceThreshold = (int)(
+ keyWidth * BOGUS_MOVE_ACCUMULATED_DISTANCE_THRESHOLD);
+ mRadiusThreshold = (int)(keyWidth * BOGUS_MOVE_RADIUS_THRESHOLD);
+ }
+
+ public void onActualDownEvent(final int x, final int y) {
+ mActualDownX = x;
+ mActualDownY = y;
+ }
+
+ public void onDownKey() {
+ mAccumulatedDistanceFromDownKey = 0;
+ }
+
+ public void onMoveKey(final int distance) {
+ mAccumulatedDistanceFromDownKey += distance;
+ }
+
+ public boolean hasTraveledLongDistance() {
+ return mAccumulatedDistanceFromDownKey >= mAccumulatedDistanceThreshold;
+ }
+
+ /* package */ int getDistanceFromDownEvent(final int x, final int y) {
+ return getDistance(x, y, mActualDownX, mActualDownY);
+ }
+
+ public boolean isCloseToActualDownEvent(final int x, final int y) {
+ return getDistanceFromDownEvent(x, y) < mRadiusThreshold;
+ }
+ }
+
static final class TimeRecorder {
private final int mSuppressKeyPreviewAfterBatchInputDuration;
private final int mStaticTimeThresholdAfterFastTyping; // msec
@@ -192,6 +238,11 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
gestureStrokeParams.mStaticTimeThresholdAfterFastTyping;
}
+ public boolean isInFastTyping(final long eventTime) {
+ final long elapsedTimeSinceLastLetterTyping = eventTime - mLastLetterTypingTime;
+ return elapsedTimeSinceLastLetterTyping < mStaticTimeThresholdAfterFastTyping;
+ }
+
private boolean wasLastInputTyping() {
return mLastTypingTime >= mLastBatchInputTime;
}
@@ -471,8 +522,9 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
}
// Keep {@link #mCurrentKey} that comes from previous keyboard.
}
- final int keyQuarterWidth = mKeyboard.mMostCommonKeyWidth / 4;
- mKeyQuarterWidthSquared = keyQuarterWidth * keyQuarterWidth;
+ final int keyWidth = mKeyboard.mMostCommonKeyWidth;
+ mPhantonSuddenMoveThreshold = (int)(keyWidth * PHANTOM_SUDDEN_MOVE_THRESHOLD);
+ mBogusMoveEventDetector.setKeyboardGeometry(keyWidth);
}
@Override
@@ -596,10 +648,16 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
private Key onDownKey(final int x, final int y, final long eventTime) {
mDownTime = eventTime;
+ mBogusMoveEventDetector.onDownKey();
return onMoveToNewKey(onMoveKeyInternal(x, y), x, y);
}
+ static int getDistance(final int x1, final int y1, final int x2, final int y2) {
+ return (int)Math.hypot(x1 - x2, y1 - y2);
+ }
+
private Key onMoveKeyInternal(final int x, final int y) {
+ mBogusMoveEventDetector.onMoveKey(getDistance(x, y, mLastX, mLastY));
mLastX = x;
mLastY = y;
return mKeyDetector.detectHitKey(x, y);
@@ -713,15 +771,14 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
// Naive up-to-down noise filter.
final long deltaT = eventTime - mUpTime;
if (deltaT < sParams.mTouchNoiseThresholdTime) {
- final int dx = x - mLastX;
- final int dy = y - mLastY;
- final int distanceSquared = (dx * dx + dy * dy);
- if (distanceSquared < sParams.mTouchNoiseThresholdDistanceSquared) {
+ final int distance = getDistance(x, y, mLastX, mLastY);
+ if (distance < sParams.mTouchNoiseThresholdDistance) {
if (DEBUG_MODE)
- Log.w(TAG, "onDownEvent: ignore potential noise: time=" + deltaT
- + " distance=" + distanceSquared);
+ Log.w(TAG, String.format("[%d] onDownEvent:"
+ + " ignore potential noise: time=%d distance=%d",
+ mPointerId, deltaT, distance));
if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.pointerTracker_onDownEvent(deltaT, distanceSquared);
+ ResearchLogger.pointerTracker_onDownEvent(deltaT, distance * distance);
}
mKeyAlreadyProcessed = true;
return;
@@ -729,6 +786,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
}
final Key key = getKeyOn(x, y);
+ mBogusMoveEventDetector.onActualDownEvent(x, y);
final PointerTrackerQueue queue = sPointerTrackerQueue;
if (queue != null) {
if (key != null && key.isModifier()) {
@@ -856,7 +914,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
onMoveToNewKey(key, x, y);
startLongPressTimer(key);
setPressedKeyGraphics(key, eventTime);
- } else if (isMajorEnoughMoveToBeOnNewKey(x, y, key)) {
+ } else if (isMajorEnoughMoveToBeOnNewKey(x, y, eventTime, key)) {
// The pointer has been slid in to the new key from the previous key, we must call
// onRelease() first to notify that the previous key has been released, then call
// onPress() to notify that the new key is being pressed.
@@ -876,20 +934,19 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
startLongPressTimer(key);
setPressedKeyGraphics(key, eventTime);
} else {
- // HACK: On some devices, quick successive touches may be translated to sudden
- // move by touch panel firmware. This hack detects the case and translates the
+ // HACK: On some devices, quick successive touches may be reported as a sudden
+ // move by touch panel firmware. This hack detects such cases and translates the
// move event to successive up and down events.
- final int dx = x - lastX;
- final int dy = y - lastY;
- final int lastMoveSquared = dx * dx + dy * dy;
// TODO: Should find a way to balance gesture detection and this hack.
if (sNeedsPhantomSuddenMoveEventHack
- && lastMoveSquared >= mKeyQuarterWidthSquared
- && !mIsDetectingGesture) {
+ && getDistance(x, y, lastX, lastY) >= mPhantonSuddenMoveThreshold) {
if (DEBUG_MODE) {
- Log.w(TAG, String.format("onMoveEvent:"
- + " phantom sudden move event is translated to "
- + "up[%d,%d]/down[%d,%d] events", lastX, lastY, x, y));
+ Log.w(TAG, String.format("[%d] onMoveEvent:"
+ + " phantom sudden move event (distance=%d) is translated to "
+ + "up[%d,%d,%s]/down[%d,%d,%s] events", mPointerId,
+ getDistance(x, y, lastX, lastY),
+ lastX, lastY, Keyboard.printableCode(oldKey.mCode),
+ x, y, Keyboard.printableCode(key.mCode)));
}
// TODO: This should be moved to outside of this nested if-clause?
if (ProductionFlag.IS_EXPERIMENTAL) {
@@ -897,6 +954,26 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
}
onUpEventInternal(eventTime);
onDownEventInternal(x, y, eventTime);
+ }
+ // HACK: On some devices, quick successive proximate touches may be reported as
+ // a bogus down-move-up event by touch panel firmware. This hack detects such
+ // cases and breaks these events into separate up and down events.
+ else if (sNeedsProximateBogusDownMoveUpEventHack
+ && sTimeRecorder.isInFastTyping(eventTime)
+ && mBogusMoveEventDetector.isCloseToActualDownEvent(x, y)) {
+ if (DEBUG_MODE) {
+ final float radiusRatio =
+ (float)mBogusMoveEventDetector.getDistanceFromDownEvent(x, y)
+ / mKeyboard.mMostCommonKeyWidth;
+ Log.w(TAG, String.format("[%d] onMoveEvent:"
+ + " bogus down-move-up event (raidus=%.2f keyWidth) is "
+ + " translated to up[%d,%d,%s]/down[%d,%d,%s] events",
+ mPointerId, radiusRatio,
+ lastX, lastY, Keyboard.printableCode(oldKey.mCode),
+ x, y, Keyboard.printableCode(key.mCode)));
+ }
+ onUpEventInternal(eventTime);
+ onDownEventInternal(x, y, eventTime);
} else {
// HACK: If there are currently multiple touches, register the key even if
// the finger slides off the key. This defends against noise from some
@@ -905,7 +982,13 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
// this hack.
if (getActivePointerTrackerCount() > 1 && sPointerTrackerQueue != null
&& !sPointerTrackerQueue.hasModifierKeyOlderThan(this)) {
- onUpEventInternal(eventTime);
+ if (DEBUG_MODE) {
+ Log.w(TAG, String.format("[%d] onMoveEvent:"
+ + " detected sliding finger while multi touching",
+ mPointerId));
+ }
+ onUpEvent(x, y, eventTime);
+ mKeyAlreadyProcessed = true;
}
if (!mIsDetectingGesture) {
mKeyAlreadyProcessed = true;
@@ -915,7 +998,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
}
}
} else {
- if (oldKey != null && isMajorEnoughMoveToBeOnNewKey(x, y, key)) {
+ if (oldKey != null && isMajorEnoughMoveToBeOnNewKey(x, y, eventTime, key)) {
// The pointer has been slid out from the previous key, we must call onRelease() to
// notify that the previous key has been released.
setReleasedKeyGraphics(oldKey);
@@ -1049,7 +1132,8 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
}
}
- private boolean isMajorEnoughMoveToBeOnNewKey(final int x, final int y, final Key newKey) {
+ private boolean isMajorEnoughMoveToBeOnNewKey(final int x, final int y, final long eventTime,
+ final Key newKey) {
if (mKeyDetector == null) {
throw new NullPointerException("keyboard and/or key detector not set");
}
@@ -1059,8 +1143,33 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
} else if (curKey != null) {
final int keyHysteresisDistanceSquared = mKeyDetector.getKeyHysteresisDistanceSquared(
mIsInSlidingKeyInputFromModifier);
- return curKey.squaredDistanceToEdge(x, y) >= keyHysteresisDistanceSquared;
- } else {
+ final int distanceFromKeyEdgeSquared = curKey.squaredDistanceToEdge(x, y);
+ if (distanceFromKeyEdgeSquared >= keyHysteresisDistanceSquared) {
+ if (DEBUG_MODE) {
+ final int keyWidth = mKeyboard.mMostCommonKeyWidth;
+ final float distanceToEdgeRatio = (float)distanceFromKeyEdgeSquared
+ / (keyWidth * keyWidth);
+ Log.d(TAG, String.format("[%d] isMajorEnoughMoveToBeOnNewKey:"
+ +" %.2f keyWidth from key edge",
+ mPointerId, distanceToEdgeRatio));
+ }
+ return true;
+ }
+ if (sNeedsProximateBogusDownMoveUpEventHack && !mIsAllowedSlidingKeyInput
+ && sTimeRecorder.isInFastTyping(eventTime)
+ && mBogusMoveEventDetector.hasTraveledLongDistance()) {
+ if (DEBUG_MODE) {
+ final float lengthFromDownRatio =
+ (float)mBogusMoveEventDetector.mAccumulatedDistanceFromDownKey
+ / mKeyboard.mMostCommonKeyWidth;
+ Log.d(TAG, String.format("[%d] isMajorEnoughMoveToBeOnNewKey:"
+ + " %.2f keyWidth from virtual down point",
+ mPointerId, lengthFromDownRatio));
+ }
+ return true;
+ }
+ return false;
+ } else { // curKey == null && newKey != null
return true;
}
}