diff --git a/java/res/values/attrs.xml b/java/res/values/attrs.xml
index 88b4da3aeec394f157358b78f39fd9964f688878..72dcb0e35f0b9ce76f5cf8e7f1dc78b3421e3990 100644
--- a/java/res/values/attrs.xml
+++ b/java/res/values/attrs.xml
@@ -144,6 +144,8 @@
         <!-- Parameters for gesture recognition (msec) and (keyWidth%/sec) -->
         <attr name="gestureRecognitionMinimumTime" format="integer" />
         <attr name="gestureRecognitionSpeedThreshold" format="fraction" />
+        <!-- Suppress showing key preview duration after batch input in millisecond -->
+        <attr name="suppressKeyPreviewAfterBatchInputDuration" format="integer" />
     </declare-styleable>
 
     <declare-styleable name="SuggestionStripView">
diff --git a/java/res/values/config.xml b/java/res/values/config.xml
index da9a734015011a716b29f2b9b591d280311bc2c5..7c57f1f2bf3ba142fcfdce8dc758b9af16bdbb73 100644
--- a/java/res/values/config.xml
+++ b/java/res/values/config.xml
@@ -70,7 +70,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">350</integer>
+    <integer name="config_gesture_static_time_threshold_after_fast_typing">1000</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) -->
@@ -86,6 +86,8 @@
     <!-- Parameters for gesture recognition (msec) and (keyWidth%/sec) -->
     <integer name="config_gesture_recognition_minimum_time">100</integer>
     <fraction name="config_gesture_recognition_speed_threshold">550%</fraction>
+    <!-- Suppress showing key preview duration after batch input in millisecond -->
+    <integer name="config_suppress_key_preview_after_batch_input_duration">1000</integer>
     <!--
         Configuration for auto correction
      -->
diff --git a/java/res/values/styles.xml b/java/res/values/styles.xml
index 96d8c905b6ca465eb1370f0dd03ee1f4a61b4716..589962c27df822f3b952b504129e1385244d9fdb 100644
--- a/java/res/values/styles.xml
+++ b/java/res/values/styles.xml
@@ -105,6 +105,7 @@
         <item name="gestureSamplingMinimumDistance">@fraction/config_gesture_sampling_minimum_distance</item>
         <item name="gestureRecognitionMinimumTime">@integer/config_gesture_recognition_minimum_time</item>
         <item name="gestureRecognitionSpeedThreshold">@fraction/config_gesture_recognition_speed_threshold</item>
+        <item name="suppressKeyPreviewAfterBatchInputDuration">@integer/config_suppress_key_preview_after_batch_input_duration</item>
     </style>
     <style
         name="MainKeyboardView"
diff --git a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
index b41631e831117fc65966247bf53e897e5312303a..20d7847ff61e258a1b2707a3588365e5b14e8500 100644
--- a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
@@ -82,6 +82,17 @@ import java.util.WeakHashMap;
  * @attr ref R.styleable#MainKeyboardView_longPressShiftKeyTimeout
  * @attr ref R.styleable#MainKeyboardView_ignoreAltCodeKeyTimeout
  * @attr ref R.styleable#MainKeyboardView_showMoreKeysKeyboardAtTouchPoint
+ * @attr ref R.styleable#MainKeyboardView_gestureStaticTimeThresholdAfterFastTyping
+ * @attr ref R.styleable#MainKeyboardView_gestureDetectFastMoveSpeedThreshold
+ * @attr ref R.styleable#MainKeyboardView_gestureDynamicThresholdDecayDuration
+ * @attr ref R.styleable#MainKeyboardView_gestureDynamicTimeThresholdFrom
+ * @attr ref R.styleable#MainKeyboardView_gestureDynamicTimeThresholdTo
+ * @attr ref R.styleable#MainKeyboardView_gestureDynamicDistanceThresholdFrom
+ * @attr ref R.styleable#MainKeyboardView_gestureDynamicDistanceThresholdTo
+ * @attr ref R.styleable#MainKeyboardView_gestureSamplingMinimumDistance
+ * @attr ref R.styleable#MainKeyboardView_gestureRecognitionMinimumTime
+ * @attr ref R.styleable#MainKeyboardView_gestureRecognitionSpeedThreshold
+ * @attr ref R.styleable#MainKeyboardView_suppressKeyPreviewAfterBatchInputDuration
  */
 public final class MainKeyboardView extends KeyboardView implements PointerTracker.KeyEventHandler,
         SuddenJumpingTouchEventHandler.ProcessMotionEvent {
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
index fb623ea77f5e70564b14ae34d61018545bd3a158..7aeddc2798c3182efe361fde430917e0446fb751 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
@@ -48,9 +48,6 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
     private static boolean sGestureHandlingEnabledByInputField = false;
     private static boolean sGestureHandlingEnabledByUser = false;
 
-    // TODO: Move this to resource.
-    private static final int SUPPRESS_KEY_PREVIEW_AFTER_LAST_BATCH_INPUT_DURATION = 1000; // msec
-
     public interface KeyEventHandler {
         /**
          * Get KeyDetector object that is used for this PointerTracker.
@@ -126,6 +123,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
         public final int mTouchNoiseThresholdTime;
         public final float mTouchNoiseThresholdDistance;
         public final int mTouchNoiseThresholdDistanceSquared;
+        public final int mSuppressKeyPreviewAfterBatchInputDuration;
 
         public static final PointerTrackerParams DEFAULT = new PointerTrackerParams();
 
@@ -134,6 +132,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
             mTouchNoiseThresholdTime = 0;
             mTouchNoiseThresholdDistance = 0.0f;
             mTouchNoiseThresholdDistanceSquared = 0;
+            mSuppressKeyPreviewAfterBatchInputDuration = 0;
         }
 
         public PointerTrackerParams(final TypedArray mainKeyboardViewAttr) {
@@ -146,6 +145,8 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
             mTouchNoiseThresholdDistance = touchNouseThresholdDistance;
             mTouchNoiseThresholdDistanceSquared =
                     (int)(touchNouseThresholdDistance * touchNouseThresholdDistance);
+            mSuppressKeyPreviewAfterBatchInputDuration = mainKeyboardViewAttr.getInt(
+                    R.styleable.MainKeyboardView_suppressKeyPreviewAfterBatchInputDuration, 0);
         }
     }
 
@@ -170,13 +171,80 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
     private boolean mIsDetectingGesture = false; // per PointerTracker.
     private static boolean sInGesture = false;
     private static long sGestureFirstDownTime;
-    private static long sLastBatchInputTime;
-    private static long sLastLetterTypingUpTime;
+    private static TimeRecorder sTimeRecorder;
     private static final InputPointers sAggregratedPointers = new InputPointers(
             GestureStroke.DEFAULT_CAPACITY);
     private static int sLastRecognitionPointSize = 0; // synchronized using sAggregratedPointers
     private static long sLastRecognitionTime = 0; // synchronized using sAggregratedPointers
 
+    static final class TimeRecorder {
+        private final int mSuppressKeyPreviewAfterBatchInputDuration;
+        private final int mStaticTimeThresholdAfterFastTyping; // msec
+        private long mLastTypingTime;
+        private long mLastLetterTypingTime;
+        private long mLastBatchInputTime;
+
+        public TimeRecorder(final PointerTrackerParams pointerTrackerParams,
+                final GestureStrokeParams gestureStrokeParams) {
+            mSuppressKeyPreviewAfterBatchInputDuration =
+                    pointerTrackerParams.mSuppressKeyPreviewAfterBatchInputDuration;
+            mStaticTimeThresholdAfterFastTyping =
+                    gestureStrokeParams.mStaticTimeThresholdAfterFastTyping;
+        }
+
+        private void recordTyping(final long eventTime) {
+            mLastTypingTime = eventTime;
+        }
+
+        private void recordLetterTyping(final long eventTime) {
+            mLastLetterTypingTime = eventTime;
+            // Reset gesture typing time
+            mLastBatchInputTime = 0;
+        }
+
+        private void recordGestureTyping(final long eventTime) {
+            mLastBatchInputTime = eventTime;
+            // Reset typing time.
+            mLastTypingTime = 0;
+        }
+
+        private boolean isInTyping() {
+            return mLastTypingTime != 0;
+        }
+
+        private boolean isInBatchInput() {
+            return mLastBatchInputTime != 0;
+        }
+
+        public void onCodeInput(final int code, final long eventTime) {
+            if (Keyboard.isLetterCode(code) && code != Keyboard.CODE_SPACE) {
+                if (isInTyping()
+                        && eventTime - mLastTypingTime < mStaticTimeThresholdAfterFastTyping) {
+                    recordLetterTyping(eventTime);
+                }
+            } else {
+                if (eventTime - mLastLetterTypingTime < mStaticTimeThresholdAfterFastTyping) {
+                    // This non-letter typing should be treated as a part of fast typing.
+                    recordLetterTyping(eventTime);
+                }
+            }
+            recordTyping(eventTime);
+        }
+
+        public void onEndBatchInput(final long eventTime) {
+            recordGestureTyping(eventTime);
+        }
+
+        public long getLastLetterTypingTime() {
+            return mLastLetterTypingTime;
+        }
+
+        public boolean needsToSuppressKeyPreviewPopup(final long eventTime) {
+            return !isInTyping() && isInBatchInput()
+                    && eventTime - mLastBatchInputTime < mSuppressKeyPreviewAfterBatchInputDuration;
+        }
+    }
+
     // The position and time at which first down event occurred.
     private long mDownTime;
     private long mUpTime;
@@ -225,11 +293,13 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
         sNeedsPhantomSuddenMoveEventHack = needsPhantomSuddenMoveEventHack;
         sParams = PointerTrackerParams.DEFAULT;
         sGestureStrokeParams = GestureStrokeParams.DEFAULT;
+        sTimeRecorder = new TimeRecorder(sParams, sGestureStrokeParams);
     }
 
     public static void setParameters(final TypedArray mainKeyboardViewAttr) {
         sParams = new PointerTrackerParams(mainKeyboardViewAttr);
         sGestureStrokeParams = new GestureStrokeParams(mainKeyboardViewAttr);
+        sTimeRecorder = new TimeRecorder(sParams, sGestureStrokeParams);
     }
 
     private static void updateGestureHandlingMode() {
@@ -336,7 +406,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
     // Note that we need primaryCode argument because the keyboard may in shifted state and the
     // primaryCode is different from {@link Key#mCode}.
     private void callListenerOnCodeInput(final Key key, final int primaryCode, final int x,
-            final int y) {
+            final int y, final long eventTime) {
         final boolean ignoreModifierKey = mIgnoreModifierKey && key.isModifier();
         final boolean altersCode = key.altCodeWhileTyping() && mTimerProxy.isTypingState();
         final int code = altersCode ? key.getAltCode() : primaryCode;
@@ -356,7 +426,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
         }
         // Even if the key is disabled, it should respond if it is in the altCodeWhileTyping state.
         if (key.isEnabled() || altersCode) {
-            sLastBatchInputTime = 0; // reset time
+            sTimeRecorder.onCodeInput(code, eventTime);
             if (code == Keyboard.CODE_OUTPUT_TEXT) {
                 mListener.onTextInput(key.getOutputText());
             } else if (code != Keyboard.CODE_UNSPECIFIED) {
@@ -471,10 +541,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
 
     private static boolean needsToSuppressKeyPreviewPopup(final long eventTime) {
         if (!sShouldHandleGesture) return false;
-        if (sLastBatchInputTime == 0) return false;
-        final long elapsedTimeAfterTheLastBatchInput = eventTime - sLastBatchInputTime;
-        return elapsedTimeAfterTheLastBatchInput
-                < SUPPRESS_KEY_PREVIEW_AFTER_LAST_BATCH_INPUT_DURATION;
+        return sTimeRecorder.needsToSuppressKeyPreviewPopup(eventTime);
     }
 
     private void setPressedKeyGraphics(final Key key, final long eventTime) {
@@ -620,7 +687,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
                             mPointerId, sAggregratedPointers.getPointerSize()));
                 }
                 sInGesture = false;
-                sLastBatchInputTime = eventTime;
+                sTimeRecorder.onEndBatchInput(eventTime);
                 mListener.onEndBatchInput(sAggregratedPointers);
             }
         }
@@ -698,7 +765,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
                 sGestureFirstDownTime = eventTime;
             }
             mGestureStrokeWithPreviewPoints.onDownEvent(x, y, eventTime, sGestureFirstDownTime,
-                    sLastLetterTypingUpTime);
+                    sTimeRecorder.getLastLetterTypingTime());
         }
     }
 
@@ -939,11 +1006,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
             return;
         }
         if (currentKey != null && !currentKey.isRepeatable()) {
-            detectAndSendKey(currentKey, mKeyX, mKeyY);
-            final int code = currentKey.mCode;
-            if (Keyboard.isLetterCode(code) && code != Keyboard.CODE_SPACE) {
-                sLastLetterTypingUpTime = eventTime;
-            }
+            detectAndSendKey(currentKey, mKeyX, mKeyY, eventTime);
         }
     }
 
@@ -994,7 +1057,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
 
     public void onRegisterKey(final Key key) {
         if (key != null) {
-            detectAndSendKey(key, key.mX, key.mY);
+            detectAndSendKey(key, key.mX, key.mY, SystemClock.uptimeMillis());
             mTimerProxy.startTypingStateTimer(key);
         }
     }
@@ -1020,14 +1083,14 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
         }
     }
 
-    private void detectAndSendKey(final Key key, final int x, final int y) {
+    private void detectAndSendKey(final Key key, final int x, final int y, final long eventTime) {
         if (key == null) {
             callListenerOnCancelInput();
             return;
         }
 
         final int code = key.mCode;
-        callListenerOnCodeInput(key, code, x, y);
+        callListenerOnCodeInput(key, code, x, y, eventTime);
         callListenerOnRelease(key, code, false);
     }