Detect bogus down-move-up event and add workaround

Bug: 7032858
Change-Id: I2e76bf49f1e07b436d05c19881e2f2c4fed21621
main
Tadashi G. Takaoka 2012-10-10 18:37:46 +09:00
parent 9d42353611
commit b0952888eb
3 changed files with 145 additions and 36 deletions

View File

@ -71,7 +71,7 @@
false --> false -->
<bool name="config_show_more_keys_keyboard_at_touched_point">false</bool> <bool name="config_show_more_keys_keyboard_at_touched_point">false</bool>
<!-- Static threshold for gesture after fast typing (msec) --> <!-- Static threshold for gesture after fast typing (msec) -->
<integer name="config_gesture_static_time_threshold_after_fast_typing">1000</integer> <integer name="config_gesture_static_time_threshold_after_fast_typing">500</integer>
<!-- Static threshold for starting gesture detection (keyWidth%/sec) --> <!-- Static threshold for starting gesture detection (keyWidth%/sec) -->
<fraction name="config_gesture_detect_fast_move_speed_threshold">150%</fraction> <fraction name="config_gesture_detect_fast_move_speed_threshold">150%</fraction>
<!-- Dynamic threshold for gesture after fast typing (msec) --> <!-- Dynamic threshold for gesture after fast typing (msec) -->

View File

@ -40,7 +40,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
private static final boolean DEBUG_EVENT = false; private static final boolean DEBUG_EVENT = false;
private static final boolean DEBUG_MOVE_EVENT = false; private static final boolean DEBUG_MOVE_EVENT = false;
private static final boolean DEBUG_LISTENER = 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. */ /** True if {@link PointerTracker}s should handle gesture events. */
private static boolean sShouldHandleGesture = false; private static boolean sShouldHandleGesture = false;
@ -121,8 +121,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
static final class PointerTrackerParams { static final class PointerTrackerParams {
public final boolean mSlidingKeyInputEnabled; public final boolean mSlidingKeyInputEnabled;
public final int mTouchNoiseThresholdTime; public final int mTouchNoiseThresholdTime;
public final float mTouchNoiseThresholdDistance; public final int mTouchNoiseThresholdDistance;
public final int mTouchNoiseThresholdDistanceSquared;
public final int mSuppressKeyPreviewAfterBatchInputDuration; public final int mSuppressKeyPreviewAfterBatchInputDuration;
public static final PointerTrackerParams DEFAULT = new PointerTrackerParams(); public static final PointerTrackerParams DEFAULT = new PointerTrackerParams();
@ -130,8 +129,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
private PointerTrackerParams() { private PointerTrackerParams() {
mSlidingKeyInputEnabled = false; mSlidingKeyInputEnabled = false;
mTouchNoiseThresholdTime = 0; mTouchNoiseThresholdTime = 0;
mTouchNoiseThresholdDistance = 0.0f; mTouchNoiseThresholdDistance = 0;
mTouchNoiseThresholdDistanceSquared = 0;
mSuppressKeyPreviewAfterBatchInputDuration = 0; mSuppressKeyPreviewAfterBatchInputDuration = 0;
} }
@ -140,11 +138,8 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
R.styleable.MainKeyboardView_slidingKeyInputEnable, false); R.styleable.MainKeyboardView_slidingKeyInputEnable, false);
mTouchNoiseThresholdTime = mainKeyboardViewAttr.getInt( mTouchNoiseThresholdTime = mainKeyboardViewAttr.getInt(
R.styleable.MainKeyboardView_touchNoiseThresholdTime, 0); R.styleable.MainKeyboardView_touchNoiseThresholdTime, 0);
final float touchNouseThresholdDistance = mainKeyboardViewAttr.getDimension( mTouchNoiseThresholdDistance = mainKeyboardViewAttr.getDimensionPixelSize(
R.styleable.MainKeyboardView_touchNoiseThresholdDistance, 0); R.styleable.MainKeyboardView_touchNoiseThresholdDistance, 0);
mTouchNoiseThresholdDistance = touchNouseThresholdDistance;
mTouchNoiseThresholdDistanceSquared =
(int)(touchNouseThresholdDistance * touchNouseThresholdDistance);
mSuppressKeyPreviewAfterBatchInputDuration = mainKeyboardViewAttr.getInt( mSuppressKeyPreviewAfterBatchInputDuration = mainKeyboardViewAttr.getInt(
R.styleable.MainKeyboardView_suppressKeyPreviewAfterBatchInputDuration, 0); R.styleable.MainKeyboardView_suppressKeyPreviewAfterBatchInputDuration, 0);
} }
@ -154,6 +149,11 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
private static PointerTrackerParams sParams; private static PointerTrackerParams sParams;
private static GestureStrokeParams sGestureStrokeParams; private static GestureStrokeParams sGestureStrokeParams;
private static boolean sNeedsPhantomSuddenMoveEventHack; 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<PointerTracker> sTrackers = CollectionUtils.newArrayList(); private static final ArrayList<PointerTracker> sTrackers = CollectionUtils.newArrayList();
private static PointerTrackerQueue sPointerTrackerQueue; private static PointerTrackerQueue sPointerTrackerQueue;
@ -166,7 +166,8 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
private KeyboardActionListener mListener = EMPTY_LISTENER; private KeyboardActionListener mListener = EMPTY_LISTENER;
private Keyboard mKeyboard; private Keyboard mKeyboard;
private int mKeyQuarterWidthSquared; private int mPhantonSuddenMoveThreshold;
private final BogusMoveEventDetector mBogusMoveEventDetector = new BogusMoveEventDetector();
private boolean mIsDetectingGesture = false; // per PointerTracker. private boolean mIsDetectingGesture = false; // per PointerTracker.
private static boolean sInGesture = false; 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 int sLastRecognitionPointSize = 0; // synchronized using sAggregratedPointers
private static long sLastRecognitionTime = 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 { static final class TimeRecorder {
private final int mSuppressKeyPreviewAfterBatchInputDuration; private final int mSuppressKeyPreviewAfterBatchInputDuration;
private final int mStaticTimeThresholdAfterFastTyping; // msec private final int mStaticTimeThresholdAfterFastTyping; // msec
@ -192,6 +238,11 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
gestureStrokeParams.mStaticTimeThresholdAfterFastTyping; gestureStrokeParams.mStaticTimeThresholdAfterFastTyping;
} }
public boolean isInFastTyping(final long eventTime) {
final long elapsedTimeSinceLastLetterTyping = eventTime - mLastLetterTypingTime;
return elapsedTimeSinceLastLetterTyping < mStaticTimeThresholdAfterFastTyping;
}
private boolean wasLastInputTyping() { private boolean wasLastInputTyping() {
return mLastTypingTime >= mLastBatchInputTime; return mLastTypingTime >= mLastBatchInputTime;
} }
@ -471,8 +522,9 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
} }
// Keep {@link #mCurrentKey} that comes from previous keyboard. // Keep {@link #mCurrentKey} that comes from previous keyboard.
} }
final int keyQuarterWidth = mKeyboard.mMostCommonKeyWidth / 4; final int keyWidth = mKeyboard.mMostCommonKeyWidth;
mKeyQuarterWidthSquared = keyQuarterWidth * keyQuarterWidth; mPhantonSuddenMoveThreshold = (int)(keyWidth * PHANTOM_SUDDEN_MOVE_THRESHOLD);
mBogusMoveEventDetector.setKeyboardGeometry(keyWidth);
} }
@Override @Override
@ -596,10 +648,16 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
private Key onDownKey(final int x, final int y, final long eventTime) { private Key onDownKey(final int x, final int y, final long eventTime) {
mDownTime = eventTime; mDownTime = eventTime;
mBogusMoveEventDetector.onDownKey();
return onMoveToNewKey(onMoveKeyInternal(x, y), x, y); 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) { private Key onMoveKeyInternal(final int x, final int y) {
mBogusMoveEventDetector.onMoveKey(getDistance(x, y, mLastX, mLastY));
mLastX = x; mLastX = x;
mLastY = y; mLastY = y;
return mKeyDetector.detectHitKey(x, y); return mKeyDetector.detectHitKey(x, y);
@ -713,15 +771,14 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
// Naive up-to-down noise filter. // Naive up-to-down noise filter.
final long deltaT = eventTime - mUpTime; final long deltaT = eventTime - mUpTime;
if (deltaT < sParams.mTouchNoiseThresholdTime) { if (deltaT < sParams.mTouchNoiseThresholdTime) {
final int dx = x - mLastX; final int distance = getDistance(x, y, mLastX, mLastY);
final int dy = y - mLastY; if (distance < sParams.mTouchNoiseThresholdDistance) {
final int distanceSquared = (dx * dx + dy * dy);
if (distanceSquared < sParams.mTouchNoiseThresholdDistanceSquared) {
if (DEBUG_MODE) if (DEBUG_MODE)
Log.w(TAG, "onDownEvent: ignore potential noise: time=" + deltaT Log.w(TAG, String.format("[%d] onDownEvent:"
+ " distance=" + distanceSquared); + " ignore potential noise: time=%d distance=%d",
mPointerId, deltaT, distance));
if (ProductionFlag.IS_EXPERIMENTAL) { if (ProductionFlag.IS_EXPERIMENTAL) {
ResearchLogger.pointerTracker_onDownEvent(deltaT, distanceSquared); ResearchLogger.pointerTracker_onDownEvent(deltaT, distance * distance);
} }
mKeyAlreadyProcessed = true; mKeyAlreadyProcessed = true;
return; return;
@ -729,6 +786,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
} }
final Key key = getKeyOn(x, y); final Key key = getKeyOn(x, y);
mBogusMoveEventDetector.onActualDownEvent(x, y);
final PointerTrackerQueue queue = sPointerTrackerQueue; final PointerTrackerQueue queue = sPointerTrackerQueue;
if (queue != null) { if (queue != null) {
if (key != null && key.isModifier()) { if (key != null && key.isModifier()) {
@ -856,7 +914,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
onMoveToNewKey(key, x, y); onMoveToNewKey(key, x, y);
startLongPressTimer(key); startLongPressTimer(key);
setPressedKeyGraphics(key, eventTime); 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 // 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 // onRelease() first to notify that the previous key has been released, then call
// onPress() to notify that the new key is being pressed. // onPress() to notify that the new key is being pressed.
@ -876,20 +934,19 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
startLongPressTimer(key); startLongPressTimer(key);
setPressedKeyGraphics(key, eventTime); setPressedKeyGraphics(key, eventTime);
} else { } else {
// HACK: On some devices, quick successive touches may be translated to sudden // HACK: On some devices, quick successive touches may be reported as a sudden
// move by touch panel firmware. This hack detects the case and translates the // move by touch panel firmware. This hack detects such cases and translates the
// move event to successive up and down events. // 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. // TODO: Should find a way to balance gesture detection and this hack.
if (sNeedsPhantomSuddenMoveEventHack if (sNeedsPhantomSuddenMoveEventHack
&& lastMoveSquared >= mKeyQuarterWidthSquared && getDistance(x, y, lastX, lastY) >= mPhantonSuddenMoveThreshold) {
&& !mIsDetectingGesture) {
if (DEBUG_MODE) { if (DEBUG_MODE) {
Log.w(TAG, String.format("onMoveEvent:" Log.w(TAG, String.format("[%d] onMoveEvent:"
+ " phantom sudden move event is translated to " + " phantom sudden move event (distance=%d) is translated to "
+ "up[%d,%d]/down[%d,%d] events", lastX, lastY, x, y)); + "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? // TODO: This should be moved to outside of this nested if-clause?
if (ProductionFlag.IS_EXPERIMENTAL) { if (ProductionFlag.IS_EXPERIMENTAL) {
@ -897,6 +954,26 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
} }
onUpEventInternal(eventTime); onUpEventInternal(eventTime);
onDownEventInternal(x, y, 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 { } else {
// HACK: If there are currently multiple touches, register the key even if // HACK: If there are currently multiple touches, register the key even if
// the finger slides off the key. This defends against noise from some // 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. // this hack.
if (getActivePointerTrackerCount() > 1 && sPointerTrackerQueue != null if (getActivePointerTrackerCount() > 1 && sPointerTrackerQueue != null
&& !sPointerTrackerQueue.hasModifierKeyOlderThan(this)) { && !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) { if (!mIsDetectingGesture) {
mKeyAlreadyProcessed = true; mKeyAlreadyProcessed = true;
@ -915,7 +998,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
} }
} }
} else { } 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 // The pointer has been slid out from the previous key, we must call onRelease() to
// notify that the previous key has been released. // notify that the previous key has been released.
setReleasedKeyGraphics(oldKey); 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) { if (mKeyDetector == null) {
throw new NullPointerException("keyboard and/or key detector not set"); 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) { } else if (curKey != null) {
final int keyHysteresisDistanceSquared = mKeyDetector.getKeyHysteresisDistanceSquared( final int keyHysteresisDistanceSquared = mKeyDetector.getKeyHysteresisDistanceSquared(
mIsInSlidingKeyInputFromModifier); mIsInSlidingKeyInputFromModifier);
return curKey.squaredDistanceToEdge(x, y) >= keyHysteresisDistanceSquared; final int distanceFromKeyEdgeSquared = curKey.squaredDistanceToEdge(x, y);
} else { 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; return true;
} }
} }