diff --git a/java/res/values/config.xml b/java/res/values/config.xml
index 32b54447cf631de7d5aab1b7ea04fe3fdb817d70..cb1358726ffdbbc56dbdf8d373bec70d784fbcc1 100644
--- a/java/res/values/config.xml
+++ b/java/res/values/config.xml
@@ -71,7 +71,7 @@
          false -->
     <bool name="config_show_more_keys_keyboard_at_touched_point">false</bool>
     <!-- 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) -->
     <fraction name="config_gesture_detect_fast_move_speed_threshold">150%</fraction>
     <!-- Dynamic threshold for gesture after fast typing (msec) -->
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 291b658494965daca1e46b685b664ef8aca272ed..ccf6932ca106dde6ca9c76b84edde109ecdbe708 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<PointerTracker> 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;
         }
     }