diff --git a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
index f5c1b7a0c299cc281749f84e94b4a296a6e9d04f..f1fcfe78554e557bafefc1e4ecef80d1f63df6e5 100644
--- a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
@@ -318,12 +318,13 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
             return hasMessages(MSG_TYPING_STATE_EXPIRED);
         }
 
+        // TODO: Remove "gesture off while fast typing" related dead code.
         @Override
         public void startGestureOffWhileFastTypingTimer() {
-            removeMessages(MSG_DISABLE_GESTURE_EXPIRED);
-            PointerTracker.setGestureOffWhileFastTyping();
-            sendMessageDelayed(obtainMessage(MSG_DISABLE_GESTURE_EXPIRED),
-                    mDisableGestureWhileFastTypingTimeout);
+//            removeMessages(MSG_DISABLE_GESTURE_EXPIRED);
+//            PointerTracker.setGestureOffWhileFastTyping();
+//            sendMessageDelayed(obtainMessage(MSG_DISABLE_GESTURE_EXPIRED),
+//                    mDisableGestureWhileFastTypingTimeout);
         }
 
         @Override
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
index 0778ad902a807423090c8551f48596090ad7f05b..d6c567ef7555af713387ee09a6d26a4295770880 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
@@ -169,6 +169,7 @@ public class PointerTracker implements PointerTrackerQueue.Element {
     private boolean mIsDetectingGesture = false; // per PointerTracker.
     private static boolean sInGesture = false;
     private static long sGestureFirstDownTime;
+    private static long sLastLetterTypingUpTime;
     private static final InputPointers sAggregratedPointers = new InputPointers(
             GestureStroke.DEFAULT_CAPACITY);
     private static int sLastRecognitionPointSize = 0;
@@ -698,6 +699,7 @@ public class PointerTracker implements PointerTrackerQueue.Element {
 
     private void onGestureDownEvent(final int x, final int y, final long eventTime) {
         mIsDetectingGesture = true;
+        mGestureStrokeWithPreviewPoints.setLastLetterTypingTime(eventTime, sLastLetterTypingUpTime);
         final int elapsedTimeFromFirstDown = (int)(eventTime - sGestureFirstDownTime);
         mGestureStrokeWithPreviewPoints.addPoint(x, y, elapsedTimeFromFirstDown,
                 true /* isMajorEvent */);
@@ -842,7 +844,7 @@ public class PointerTracker implements PointerTrackerQueue.Element {
                         if (ProductionFlag.IS_EXPERIMENTAL) {
                             ResearchLogger.pointerTracker_onMoveEvent(x, y, lastX, lastY);
                         }
-                        onUpEventInternal();
+                        onUpEventInternal(eventTime);
                         onDownEventInternal(x, y, eventTime);
                     } else {
                         // HACK: If there are currently multiple touches, register the key even if
@@ -852,7 +854,7 @@ public class PointerTracker implements PointerTrackerQueue.Element {
                         // this hack.
                         if (getActivePointerTrackerCount() > 1 && sPointerTrackerQueue != null
                                 && !sPointerTrackerQueue.hasModifierKeyOlderThan(this)) {
-                            onUpEventInternal();
+                            onUpEventInternal(eventTime);
                         }
                         if (!mIsDetectingGesture) {
                             mKeyAlreadyProcessed = true;
@@ -897,7 +899,7 @@ public class PointerTracker implements PointerTrackerQueue.Element {
                 }
             }
         }
-        onUpEventInternal();
+        onUpEventInternal(eventTime);
         if (queue != null) {
             queue.remove(this);
         }
@@ -911,11 +913,11 @@ public class PointerTracker implements PointerTrackerQueue.Element {
         if (DEBUG_EVENT) {
             printTouchEvent("onPhntEvent:", getLastX(), getLastY(), eventTime);
         }
-        onUpEventInternal();
+        onUpEventInternal(eventTime);
         mKeyAlreadyProcessed = true;
     }
 
-    private void onUpEventInternal() {
+    private void onUpEventInternal(final long eventTime) {
         mTimerProxy.cancelKeyTimers();
         mIsInSlidingKeyInput = false;
         mIsDetectingGesture = false;
@@ -943,6 +945,10 @@ public class PointerTracker implements PointerTrackerQueue.Element {
         }
         if (currentKey != null && !currentKey.isRepeatable()) {
             detectAndSendKey(currentKey, mKeyX, mKeyY);
+            final int code = currentKey.mCode;
+            if (Keyboard.isLetterCode(code) && code != Keyboard.CODE_SPACE) {
+                sLastLetterTypingUpTime = eventTime;
+            }
         }
     }
 
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java b/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java
index 193c3a4bad119598438e0b0d0daf965dae072c04..9fe6fa3f87eabef57b8f153b150e3ba07ebc7f2e 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java
@@ -34,8 +34,10 @@ public class GestureStroke {
     private long mLastMajorEventTime;
     private int mLastMajorEventX;
     private int mLastMajorEventY;
+    private boolean mAfterFastTyping;
 
     private int mKeyWidth;
+    private int mStartGestureLengthThresholdAfterFastTyping; // pixel
     private int mStartGestureLengthThreshold; // pixel
     private int mMinGestureSamplingLength; // pixel
     private int mGestureRecognitionSpeedThreshold; // pixel / sec
@@ -45,7 +47,11 @@ public class GestureStroke {
     private int mDetectFastMoveY;
 
     // TODO: Move some of these to resource.
-    private static final float START_GESTURE_LENGTH_THRESHOLD_RATIO_TO_KEY_WIDTH = 0.60f;
+    private static final int GESTURE_AFTER_FAST_TYPING_DURATION_THRESHOLD = 350; // msec
+    private static final float START_GESTURE_LENGTH_THRESHOLD_AFTER_FAST_TYPING_RATIO_TO_KEY_WIDTH =
+            8.0f;
+    private static final int START_GESTURE_LENGTH_THRESHOLD_DECAY_DURATION = 400; // msec
+    private static final float START_GESTURE_LENGTH_THRESHOLD_RATIO_TO_KEY_WIDTH = 0.6f;
     private static final int START_GESTURE_DURATION_THRESHOLD = 70; // msec
     private static final int MIN_GESTURE_RECOGNITION_TIME = 100; // msec
     private static final float MIN_GESTURE_SAMPLING_RATIO_TO_KEY_WIDTH = 1.0f / 6.0f;
@@ -67,6 +73,8 @@ public class GestureStroke {
     public void setKeyboardGeometry(final int keyWidth) {
         mKeyWidth = keyWidth;
         // TODO: Find an appropriate base metric for these length. Maybe diagonal length of the key?
+        mStartGestureLengthThresholdAfterFastTyping = (int)(keyWidth
+                * START_GESTURE_LENGTH_THRESHOLD_AFTER_FAST_TYPING_RATIO_TO_KEY_WIDTH);
         mStartGestureLengthThreshold =
                 (int)(keyWidth * START_GESTURE_LENGTH_THRESHOLD_RATIO_TO_KEY_WIDTH);
         mMinGestureSamplingLength = (int)(keyWidth * MIN_GESTURE_SAMPLING_RATIO_TO_KEY_WIDTH);
@@ -75,10 +83,33 @@ public class GestureStroke {
         mDetectFastMoveSpeedThreshold =
                 (int)(keyWidth * DETECT_FAST_MOVE_SPEED_THRESHOLD_RATIO_TO_KEY_WIDTH);
         if (DEBUG) {
-            Log.d(TAG, "setKeyboardGeometry: keyWidth=" + keyWidth);
+            Log.d(TAG, "[" + mPointerId + "] setKeyboardGeometry: keyWidth=" + keyWidth
+                    + " tL0=" + mStartGestureLengthThresholdAfterFastTyping
+                    + " tL=" + mStartGestureLengthThreshold);
         }
     }
 
+    public void setLastLetterTypingTime(final long downTime, final long lastTypingTime) {
+        final long elpasedTimeAfterTyping = downTime - lastTypingTime;
+        if (elpasedTimeAfterTyping < GESTURE_AFTER_FAST_TYPING_DURATION_THRESHOLD) {
+            mAfterFastTyping = true;
+        }
+        if (DEBUG) {
+            Log.d(TAG, "[" + mPointerId + "] setLastTypingTime: dT=" + elpasedTimeAfterTyping
+                    + " afterFastTyping=" + mAfterFastTyping);
+        }
+    }
+
+    private int getStartGestureLengthThreshold(final int deltaTime) {
+        if (!mAfterFastTyping || deltaTime >= START_GESTURE_LENGTH_THRESHOLD_DECAY_DURATION) {
+            return mStartGestureLengthThreshold;
+        }
+        final int decayedThreshold =
+                (mStartGestureLengthThresholdAfterFastTyping - mStartGestureLengthThreshold)
+                * deltaTime / START_GESTURE_LENGTH_THRESHOLD_DECAY_DURATION;
+        return mStartGestureLengthThresholdAfterFastTyping - decayedThreshold;
+    }
+
     public boolean isStartOfAGesture() {
         if (mDetectFastMoveTime == 0) {
             return false;
@@ -92,10 +123,12 @@ public class GestureStroke {
         final int deltaLength = getDistance(
                 mXCoordinates.get(lastIndex), mYCoordinates.get(lastIndex),
                 mDetectFastMoveX, mDetectFastMoveY);
+        final int startGestureLengthThreshold = getStartGestureLengthThreshold(deltaTime);
         final boolean isStartOfAGesture = deltaTime > START_GESTURE_DURATION_THRESHOLD
-                && deltaLength > mStartGestureLengthThreshold;
+                && deltaLength > startGestureLengthThreshold;
         if (DEBUG) {
-            Log.d(TAG, "isStartOfAGesture: dT=" + deltaTime + " dL=" + deltaLength
+            Log.d(TAG, "[" + mPointerId + "] isStartOfAGesture: dT=" + deltaTime
+                    + " dL=" + deltaLength + " tL=" + startGestureLengthThreshold
                     + " points=" + size + (isStartOfAGesture ? " Detect start of a gesture" : ""));
         }
         return isStartOfAGesture;
@@ -109,6 +142,7 @@ public class GestureStroke {
         mYCoordinates.setLength(0);
         mLastMajorEventTime = 0;
         mDetectFastMoveTime = 0;
+        mAfterFastTyping = false;
     }
 
     private void appendPoint(final int x, final int y, final int time) {
@@ -135,12 +169,13 @@ public class GestureStroke {
             final int pixelsPerSec = pixels * MSEC_PER_SEC;
             if (DEBUG) {
                 final float speed = (float)pixelsPerSec / msecs / mKeyWidth;
-                Log.d(TAG, String.format("Speed=%.3f keyWidth/sec", speed));
+                Log.d(TAG, String.format("[" + mPointerId + "] speed=%.3f", speed));
             }
             // Equivalent to (pixels / msecs < mStartSpeedThreshold / MSEC_PER_SEC)
             if (mDetectFastMoveTime == 0 && pixelsPerSec > mDetectFastMoveSpeedThreshold * msecs) {
                 if (DEBUG) {
-                    Log.d(TAG, "Detect fast move: T=" + time + " points = " + size);
+                    Log.d(TAG, "[" + mPointerId + "] detect fast move: T="
+                            + time + " points = " + size);
                 }
                 mDetectFastMoveTime = time;
                 mDetectFastMoveX = x;