Merge "Remove useless method call for spell checker."
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
index d4e3e29..5e02926 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
@@ -22,12 +22,13 @@
 import android.util.Log;
 import android.view.MotionEvent;
 
+import com.android.inputmethod.keyboard.internal.BatchInputArbiter;
+import com.android.inputmethod.keyboard.internal.BatchInputArbiter.BatchInputArbiterListener;
 import com.android.inputmethod.keyboard.internal.BogusMoveEventDetector;
 import com.android.inputmethod.keyboard.internal.GestureEnabler;
 import com.android.inputmethod.keyboard.internal.GestureStrokeDrawingParams;
 import com.android.inputmethod.keyboard.internal.GestureStrokeDrawingPoints;
 import com.android.inputmethod.keyboard.internal.GestureStrokeRecognitionParams;
-import com.android.inputmethod.keyboard.internal.GestureStrokeRecognitionPoints;
 import com.android.inputmethod.keyboard.internal.PointerTrackerQueue;
 import com.android.inputmethod.keyboard.internal.TypingTimeRecorder;
 import com.android.inputmethod.latin.Constants;
@@ -43,7 +44,8 @@
 
 import java.util.ArrayList;
 
-public final class PointerTracker implements PointerTrackerQueue.Element {
+public final class PointerTracker implements PointerTrackerQueue.Element,
+        BatchInputArbiterListener {
     private static final String TAG = PointerTracker.class.getSimpleName();
     private static final boolean DEBUG_EVENT = false;
     private static final boolean DEBUG_MOVE_EVENT = false;
@@ -160,12 +162,7 @@
 
     private boolean mIsDetectingGesture = false; // per PointerTracker.
     private static boolean sInGesture = false;
-    private static long sGestureFirstDownTime;
     private static TypingTimeRecorder sTypingTimeRecorder;
-    private static final InputPointers sAggregatedPointers = new InputPointers(
-            GestureStrokeRecognitionPoints.DEFAULT_CAPACITY);
-    private static int sLastRecognitionPointSize = 0; // synchronized using sAggregatedPointers
-    private static long sLastRecognitionTime = 0; // synchronized using sAggregatedPointers
 
     // The position and time at which first down event occurred.
     private long mDownTime;
@@ -203,7 +200,7 @@
     // true if dragging finger is allowed.
     private boolean mIsAllowedDraggingFinger;
 
-    private final GestureStrokeRecognitionPoints mGestureStrokeRecognitionPoints;
+    private final BatchInputArbiter mBatchInputArbiter;
     private final GestureStrokeDrawingPoints mGestureStrokeDrawingPoints;
 
     // TODO: Add PointerTrackerFactory singleton and move some class static methods into it.
@@ -287,8 +284,7 @@
 
     private PointerTracker(final int id) {
         mPointerId = id;
-        mGestureStrokeRecognitionPoints = new GestureStrokeRecognitionPoints(
-                id, sGestureStrokeRecognitionParams);
+        mBatchInputArbiter = new BatchInputArbiter(id, sGestureStrokeRecognitionParams);
         mGestureStrokeDrawingPoints = new GestureStrokeDrawingPoints(sGestureStrokeDrawingParams);
     }
 
@@ -410,7 +406,7 @@
         mKeyboardLayoutHasBeenChanged = true;
         final int keyWidth = mKeyboard.mMostCommonKeyWidth;
         final int keyHeight = mKeyboard.mMostCommonKeyHeight;
-        mGestureStrokeRecognitionPoints.setKeyboardGeometry(keyWidth, mKeyboard.mOccupiedHeight);
+        mBatchInputArbiter.setKeyboardGeometry(keyWidth, mKeyboard.mOccupiedHeight);
         final Key newKey = mKeyDetector.detectHitKey(mKeyX, mKeyY);
         if (newKey != mCurrentKey) {
             if (sDrawingProxy != null) {
@@ -578,26 +574,15 @@
         return sPointerTrackerQueue.getOldestElement() == this;
     }
 
-    /**
-     * Determines whether the batch input has started or not.
-     * @return true if the batch input has started successfully.
-     */
-    private boolean mayStartBatchInput() {
-        if (!mGestureStrokeRecognitionPoints.isStartOfAGesture()) {
-            return false;
-        }
+    // Implements {@link BatchInputArbiterListener}.
+    @Override
+    public void onStartBatchInput() {
         if (DEBUG_LISTENER) {
             Log.d(TAG, String.format("[%d] onStartBatchInput", mPointerId));
         }
-        synchronized (sAggregatedPointers) {
-            sAggregatedPointers.reset();
-            sLastRecognitionPointSize = 0;
-            sLastRecognitionTime = 0;
-            sListener.onStartBatchInput();
-            dismissAllMoreKeysPanels();
-            sTimerProxy.cancelLongPressTimerOf(this);
-        }
-        return true;
+        sListener.onStartBatchInput();
+        dismissAllMoreKeysPanels();
+        sTimerProxy.cancelLongPressTimerOf(this);
     }
 
     private void showGestureTrail() {
@@ -610,55 +595,38 @@
     }
 
     public void updateBatchInputByTimer(final long syntheticMoveEventTime) {
-        final int gestureTime = (int)(syntheticMoveEventTime - sGestureFirstDownTime);
-        mGestureStrokeRecognitionPoints.duplicateLastPointWith(gestureTime);
-        updateBatchInput(syntheticMoveEventTime);
+        mBatchInputArbiter.updateBatchInputByTimer(syntheticMoveEventTime, this);
     }
 
-    private void updateBatchInput(final long moveEventTime) {
-        synchronized (sAggregatedPointers) {
-            final GestureStrokeRecognitionPoints stroke = mGestureStrokeRecognitionPoints;
-            stroke.appendIncrementalBatchPoints(sAggregatedPointers);
-            final int size = sAggregatedPointers.getPointerSize();
-            if (size > sLastRecognitionPointSize
-                    && stroke.hasRecognitionTimePast(moveEventTime, sLastRecognitionTime)) {
-                if (DEBUG_LISTENER) {
-                    Log.d(TAG, String.format("[%d] onUpdateBatchInput: batchPoints=%d", mPointerId,
-                            size));
-                }
-                sTimerProxy.startUpdateBatchInputTimer(this);
-                sListener.onUpdateBatchInput(sAggregatedPointers);
-                // The listener may change the size of the pointers (when auto-committing
-                // for example), so we need to get the size from the pointers again.
-                sLastRecognitionPointSize = sAggregatedPointers.getPointerSize();
-                sLastRecognitionTime = moveEventTime;
-            }
+    // Implements {@link BatchInputArbiterListener}.
+    @Override
+    public void onUpdateBatchInput(final InputPointers aggregatedPointers, final long eventTime) {
+        if (DEBUG_LISTENER) {
+            Log.d(TAG, String.format("[%d] onUpdateBatchInput: batchPoints=%d", mPointerId,
+                    aggregatedPointers.getPointerSize()));
         }
+        sListener.onUpdateBatchInput(aggregatedPointers);
     }
 
-    /**
-     * Determines whether the batch input has ended successfully or continues.
-     * @param upEventTime the event time of this pointer up.
-     * @return true if the batch input has ended successfully, false if it continues.
-     */
-    private boolean mayEndBatchInput(final long upEventTime) {
-        boolean hasEndBatchInputSuccessfully = false;
-        synchronized (sAggregatedPointers) {
-            mGestureStrokeRecognitionPoints.appendAllBatchPoints(sAggregatedPointers);
-            if (getActivePointerTrackerCount() == 1) {
-                hasEndBatchInputSuccessfully = true;
-                sTypingTimeRecorder.onEndBatchInput(upEventTime);
-                sTimerProxy.cancelAllUpdateBatchInputTimers();
-                if (!mIsTrackingForActionDisabled) {
-                    if (DEBUG_LISTENER) {
-                        Log.d(TAG, String.format("[%d] onEndBatchInput   : batchPoints=%d",
-                                mPointerId, sAggregatedPointers.getPointerSize()));
-                    }
-                    sListener.onEndBatchInput(sAggregatedPointers);
-                }
-            }
+    // Implements {@link BatchInputArbiterListener}.
+    @Override
+    public void onStartUpdateBatchInputTimer() {
+        sTimerProxy.startUpdateBatchInputTimer(this);
+    }
+
+    // Implements {@link BatchInputArbiterListener}.
+    @Override
+    public void onEndBatchInput(final InputPointers aggregatedPointers, final long eventTime) {
+        sTypingTimeRecorder.onEndBatchInput(eventTime);
+        sTimerProxy.cancelAllUpdateBatchInputTimers();
+        if (mIsTrackingForActionDisabled) {
+            return;
         }
-        return hasEndBatchInputSuccessfully;
+        if (DEBUG_LISTENER) {
+            Log.d(TAG, String.format("[%d] onEndBatchInput   : batchPoints=%d",
+                    mPointerId, aggregatedPointers.getPointerSize()));
+        }
+        sListener.onEndBatchInput(aggregatedPointers);
     }
 
     private void cancelBatchInput() {
@@ -753,15 +721,10 @@
         mIsDetectingGesture = (mKeyboard != null) && mKeyboard.mId.isAlphabetKeyboard()
                 && key != null && !key.isModifier();
         if (mIsDetectingGesture) {
-            if (getActivePointerTrackerCount() == 1) {
-                sGestureFirstDownTime = eventTime;
-            }
-            final int elapsedTimeSinceFirstDown = (int)(eventTime - sGestureFirstDownTime);
-            final int elapsedTimeSinceLastTyping = (int)(
-                    eventTime - sTypingTimeRecorder.getLastLetterTypingTime());
-            mGestureStrokeRecognitionPoints.onDownEvent(x, y, elapsedTimeSinceFirstDown,
-                    elapsedTimeSinceLastTyping);
-            mGestureStrokeDrawingPoints.onDownEvent(x, y, elapsedTimeSinceFirstDown);
+            mBatchInputArbiter.addDownEventPoint(x, y, eventTime,
+                    sTypingTimeRecorder.getLastLetterTypingTime(), getActivePointerTrackerCount());
+            mGestureStrokeDrawingPoints.onDownEvent(
+                    x, y, mBatchInputArbiter.getElapsedTimeSinceFirstDown(eventTime));
         }
     }
 
@@ -820,31 +783,27 @@
         if (!mIsDetectingGesture) {
             return;
         }
-        final int beforeLength = mGestureStrokeRecognitionPoints.getLength();
-        final int elapsedTimeSinceFirstDown = (int)(eventTime - sGestureFirstDownTime);
-        final boolean onValidArea = mGestureStrokeRecognitionPoints.addPointOnKeyboard(
-                x, y, elapsedTimeSinceFirstDown, isMajorEvent);
-        if (mGestureStrokeRecognitionPoints.getLength() > beforeLength) {
-            sTimerProxy.startUpdateBatchInputTimer(this);
-        }
+        final boolean onValidArea = mBatchInputArbiter.addMoveEventPoint(
+                x, y, eventTime, isMajorEvent, this);
         // If the move event goes out from valid batch input area, cancel batch input.
         if (!onValidArea) {
             cancelBatchInput();
             return;
         }
-        mGestureStrokeDrawingPoints.onMoveEvent(x, y, elapsedTimeSinceFirstDown);
+        mGestureStrokeDrawingPoints.onMoveEvent(
+                x, y, mBatchInputArbiter.getElapsedTimeSinceFirstDown(eventTime));
         // If the MoreKeysPanel is showing then do not attempt to enter gesture mode. However,
         // the gestured touch points are still being recorded in case the panel is dismissed.
         if (isShowingMoreKeysPanel()) {
             return;
         }
         if (!sInGesture && key != null && Character.isLetter(key.getCode())
-                && mayStartBatchInput()) {
+                && mBatchInputArbiter.mayStartBatchInput(this)) {
             sInGesture = true;
         }
         if (sInGesture) {
             if (key != null) {
-                updateBatchInput(eventTime);
+                mBatchInputArbiter.updateBatchInput(eventTime, this);
             }
             showGestureTrail();
         }
@@ -1097,7 +1056,8 @@
             if (currentKey != null) {
                 callListenerOnRelease(currentKey, currentKey.getCode(), true /* withSliding */);
             }
-            if (mayEndBatchInput(eventTime)) {
+            if (mBatchInputArbiter.mayEndBatchInput(
+                    eventTime, getActivePointerTrackerCount(), this)) {
                 sInGesture = false;
             }
             showGestureTrail();
diff --git a/java/src/com/android/inputmethod/keyboard/internal/BatchInputArbiter.java b/java/src/com/android/inputmethod/keyboard/internal/BatchInputArbiter.java
new file mode 100644
index 0000000..cd98759
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/internal/BatchInputArbiter.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.keyboard.internal;
+
+import com.android.inputmethod.latin.Constants;
+import com.android.inputmethod.latin.InputPointers;
+
+/**
+ * This class arbitrates batch input.
+ * An instance of this class holds a {@link GestureStrokeRecognitionPoints}.
+ * And it arbitrates multiple strokes gestured by multiple fingers and aggregates those gesture
+ * points into one batch input.
+ */
+public class BatchInputArbiter {
+    public interface BatchInputArbiterListener {
+        public void onStartBatchInput();
+        public void onUpdateBatchInput(
+                final InputPointers aggregatedPointers, final long moveEventTime);
+        public void onStartUpdateBatchInputTimer();
+        public void onEndBatchInput(final InputPointers aggregatedPointers, final long upEventTime);
+    }
+
+    // The starting time of the first stroke of a gesture input.
+    private static long sGestureFirstDownTime;
+    // The {@link InputPointers} that includes all events of a gesture input.
+    private static final InputPointers sAggregatedPointers = new InputPointers(
+            Constants.DEFAULT_GESTURE_POINTS_CAPACITY);
+    private static int sLastRecognitionPointSize = 0; // synchronized using sAggregatedPointers
+    private static long sLastRecognitionTime = 0; // synchronized using sAggregatedPointers
+
+    private final GestureStrokeRecognitionPoints mRecognitionPoints;
+
+    public BatchInputArbiter(final int pointerId, final GestureStrokeRecognitionParams params) {
+        mRecognitionPoints = new GestureStrokeRecognitionPoints(pointerId, params);
+    }
+
+    public void setKeyboardGeometry(final int keyWidth, final int keyboardHeight) {
+        mRecognitionPoints.setKeyboardGeometry(keyWidth, keyboardHeight);
+    }
+
+    /**
+     * Calculate elapsed time since the first gesture down.
+     * @param eventTime the time of this event.
+     * @return the elapsed time in millisecond from the first gesture down.
+     */
+    public int getElapsedTimeSinceFirstDown(final long eventTime) {
+        return (int)(eventTime - sGestureFirstDownTime);
+    }
+
+    /**
+     * Add a down event point.
+     * @param x the x-coordinate of this down event.
+     * @param y the y-coordinate of this down event.
+     * @param downEventTime the time of this down event.
+     * @param lastLetterTypingTime the last typing input time.
+     * @param activePointerCount the number of active pointers when this pointer down event occurs.
+     */
+    public void addDownEventPoint(final int x, final int y, final long downEventTime,
+            final long lastLetterTypingTime, final int activePointerCount) {
+        if (activePointerCount == 1) {
+            sGestureFirstDownTime = downEventTime;
+        }
+        final int elapsedTimeSinceFirstDown = getElapsedTimeSinceFirstDown(downEventTime);
+        final int elapsedTimeSinceLastTyping = (int)(downEventTime - lastLetterTypingTime);
+        mRecognitionPoints.addDownEventPoint(
+                x, y, elapsedTimeSinceFirstDown, elapsedTimeSinceLastTyping);
+    }
+
+    /**
+     * Add a move event point.
+     * @param x the x-coordinate of this move event.
+     * @param y the y-coordinate of this move event.
+     * @param moveEventTime the time of this move event.
+     * @param isMajorEvent false if this is a historical move event.
+     * @param listener {@link BatchInputArbiterListener#onStartUpdateBatchInputTimer()} of this
+     *     <code>listener</code> may be called if enough move points have been added.
+     * @return true if this move event occurs on the valid gesture area.
+     */
+    public boolean addMoveEventPoint(final int x, final int y, final long moveEventTime,
+            final boolean isMajorEvent, final BatchInputArbiterListener listener) {
+        final int beforeLength = mRecognitionPoints.getLength();
+        final boolean onValidArea = mRecognitionPoints.addEventPoint(
+                x, y, getElapsedTimeSinceFirstDown(moveEventTime), isMajorEvent);
+        if (mRecognitionPoints.getLength() > beforeLength) {
+            listener.onStartUpdateBatchInputTimer();
+        }
+        return onValidArea;
+    }
+
+    /**
+     * Determine whether the batch input has started or not.
+     * @param listener {@link BatchInputArbiterListener#onStartBatchInput()} of this
+     *     <code>listener</code> will be called when the batch input has started successfully.
+     * @return true if the batch input has started successfully.
+     */
+    public boolean mayStartBatchInput(final BatchInputArbiterListener listener) {
+        if (!mRecognitionPoints.isStartOfAGesture()) {
+            return false;
+        }
+        synchronized (sAggregatedPointers) {
+            sAggregatedPointers.reset();
+            sLastRecognitionPointSize = 0;
+            sLastRecognitionTime = 0;
+            listener.onStartBatchInput();
+        }
+        return true;
+    }
+
+    /**
+     * Add synthetic move event point. After adding the point,
+     * {@link #updateBatchInput(long,BatchInputArbiterListener)} will be called internally.
+     * @param syntheticMoveEventTime the synthetic move event time.
+     * @param listener the listener to be passed to
+     *     {@link #updateBatchInput(long,BatchInputArbiterListener)}.
+     */
+    public void updateBatchInputByTimer(final long syntheticMoveEventTime,
+            final BatchInputArbiterListener listener) {
+        mRecognitionPoints.duplicateLastPointWith(
+                getElapsedTimeSinceFirstDown(syntheticMoveEventTime));
+        updateBatchInput(syntheticMoveEventTime, listener);
+    }
+
+    /**
+     * Determine whether we have enough gesture points to lookup dictionary.
+     * @param moveEventTime the time of this move event.
+     * @param listener {@link BatchInputArbiterListener#onUpdateBatchInput(InputPointers,long)} of
+     *     this <code>listener</code> will be called when enough event points we have. Also
+     *     {@link BatchInputArbiterListener#onStartUpdateBatchInputTimer()} will be called to have
+     *     possible future synthetic move event.
+     */
+    public void updateBatchInput(final long moveEventTime,
+            final BatchInputArbiterListener listener) {
+        synchronized (sAggregatedPointers) {
+            mRecognitionPoints.appendIncrementalBatchPoints(sAggregatedPointers);
+            final int size = sAggregatedPointers.getPointerSize();
+            if (size > sLastRecognitionPointSize && mRecognitionPoints.hasRecognitionTimePast(
+                    moveEventTime, sLastRecognitionTime)) {
+                listener.onUpdateBatchInput(sAggregatedPointers, moveEventTime);
+                listener.onStartUpdateBatchInputTimer();
+                // The listener may change the size of the pointers (when auto-committing
+                // for example), so we need to get the size from the pointers again.
+                sLastRecognitionPointSize = sAggregatedPointers.getPointerSize();
+                sLastRecognitionTime = moveEventTime;
+            }
+        }
+    }
+
+    /**
+     * Determine whether the batch input has ended successfully or continues.
+     * @param upEventTime the time of this up event.
+     * @param activePointerCount the number of active pointers when this pointer up event occurs.
+     * @param listener {@link BatchInputArbiterListener#onEndBatchInput(InputPointers,long)} of this
+     *     <code>listener</code> will be called when the batch input has started successfully.
+     * @return true if the batch input has ended successfully.
+     */
+    public boolean mayEndBatchInput(final long upEventTime, final int activePointerCount,
+            final BatchInputArbiterListener listener) {
+        synchronized (sAggregatedPointers) {
+            mRecognitionPoints.appendAllBatchPoints(sAggregatedPointers);
+            if (activePointerCount == 1) {
+                listener.onEndBatchInput(sAggregatedPointers, upEventTime);
+                return true;
+            }
+        }
+        return false;
+    }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeRecognitionPoints.java b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeRecognitionPoints.java
index 5d4f5e8..e49e538 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeRecognitionPoints.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeRecognitionPoints.java
@@ -18,14 +18,15 @@
 
 import android.util.Log;
 
+import com.android.inputmethod.latin.Constants;
 import com.android.inputmethod.latin.InputPointers;
 import com.android.inputmethod.latin.utils.ResizableIntArray;
 
 /**
  * This class holds event points to recognize a gesture stroke.
- * TODO: Should be final and package private class.
+ * TODO: Should be package private class.
  */
-public class GestureStrokeRecognitionPoints {
+public final class GestureStrokeRecognitionPoints {
     private static final String TAG = GestureStrokeRecognitionPoints.class.getSimpleName();
     private static final boolean DEBUG = false;
     private static final boolean DEBUG_SPEED = false;
@@ -34,12 +35,13 @@
     // Proportional to the keyboard height.
     public static final float EXTRA_GESTURE_TRAIL_AREA_ABOVE_KEYBOARD_RATIO = 0.25f;
 
-    public static final int DEFAULT_CAPACITY = 128;
-
     private final int mPointerId;
-    private final ResizableIntArray mEventTimes = new ResizableIntArray(DEFAULT_CAPACITY);
-    private final ResizableIntArray mXCoordinates = new ResizableIntArray(DEFAULT_CAPACITY);
-    private final ResizableIntArray mYCoordinates = new ResizableIntArray(DEFAULT_CAPACITY);
+    private final ResizableIntArray mEventTimes = new ResizableIntArray(
+            Constants.DEFAULT_GESTURE_POINTS_CAPACITY);
+    private final ResizableIntArray mXCoordinates = new ResizableIntArray(
+            Constants.DEFAULT_GESTURE_POINTS_CAPACITY);
+    private final ResizableIntArray mYCoordinates = new ResizableIntArray(
+            Constants.DEFAULT_GESTURE_POINTS_CAPACITY);
 
     private final GestureStrokeRecognitionParams mRecognitionParams;
 
@@ -67,12 +69,14 @@
 
     private static final int MSEC_PER_SEC = 1000;
 
+    // TODO: Make this package private
     public GestureStrokeRecognitionPoints(final int pointerId,
             final GestureStrokeRecognitionParams recognitionParams) {
         mPointerId = pointerId;
         mRecognitionParams = recognitionParams;
     }
 
+    // TODO: Make this package private
     public void setKeyboardGeometry(final int keyWidth, final int keyboardHeight) {
         mKeyWidth = keyWidth;
         mMinYCoordinate = -(int)(keyboardHeight * EXTRA_GESTURE_TRAIL_AREA_ABOVE_KEYBOARD_RATIO);
@@ -99,11 +103,13 @@
         }
     }
 
+    // TODO: Make this package private
     public int getLength() {
         return mEventTimes.getLength();
     }
 
-    public void onDownEvent(final int x, final int y, final int elapsedTimeSinceFirstDown,
+    // TODO: Make this package private
+    public void addDownEventPoint(final int x, final int y, final int elapsedTimeSinceFirstDown,
             final int elapsedTimeSinceLastTyping) {
         reset();
         if (elapsedTimeSinceLastTyping < mRecognitionParams.mStaticTimeThresholdAfterFastTyping) {
@@ -113,7 +119,9 @@
             Log.d(TAG, String.format("[%d] onDownEvent: dT=%3d%s", mPointerId,
                     elapsedTimeSinceLastTyping, mAfterFastTyping ? " afterFastTyping" : ""));
         }
-        addPointOnKeyboard(x, y, elapsedTimeSinceFirstDown, true /* isMajorEvent */);
+        // Call {@link #addEventPoint(int,int,int,boolean)} to record this down event point as a
+        // major event point.
+        addEventPoint(x, y, elapsedTimeSinceFirstDown, true /* isMajorEvent */);
     }
 
     private int getGestureDynamicDistanceThreshold(final int deltaTime) {
@@ -137,6 +145,7 @@
         return mRecognitionParams.mDynamicTimeThresholdFrom - decayedThreshold;
     }
 
+    // TODO: Make this package private
     public final boolean isStartOfAGesture() {
         if (!hasDetectedFastMove()) {
             return false;
@@ -167,6 +176,7 @@
         return isStartOfAGesture;
     }
 
+    // TODO: Make this package private
     public void duplicateLastPointWith(final int time) {
         final int lastIndex = getLength() - 1;
         if (lastIndex >= 0) {
@@ -250,19 +260,20 @@
     }
 
     /**
-     * Add a touch event as a gesture point. Returns true if the touch event is on the valid
-     * gesture area.
-     * @param x the x-coordinate of the touch event
-     * @param y the y-coordinate of the touch event
+     * Add an event point to this gesture stroke recognition points. Returns true if the event
+     * point is on the valid gesture area.
+     * @param x the x-coordinate of the event point
+     * @param y the y-coordinate of the event point
      * @param time the elapsed time in millisecond from the first gesture down
      * @param isMajorEvent false if this is a historical move event
-     * @return true if the touch event is on the valid gesture area
+     * @return true if the event point is on the valid gesture area
      */
-    public boolean addPointOnKeyboard(final int x, final int y, final int time,
+    // TODO: Make this package private
+    public boolean addEventPoint(final int x, final int y, final int time,
             final boolean isMajorEvent) {
         final int size = getLength();
         if (size <= 0) {
-            // Down event
+            // The first event of this stroke (a.k.a. down event).
             appendPoint(x, y, time);
             updateMajorEvent(x, y, time);
         } else {
@@ -291,15 +302,18 @@
         }
     }
 
+    // TODO: Make this package private
     public final boolean hasRecognitionTimePast(
             final long currentTime, final long lastRecognitionTime) {
         return currentTime > lastRecognitionTime + mRecognitionParams.mRecognitionMinimumTime;
     }
 
+    // TODO: Make this package private
     public final void appendAllBatchPoints(final InputPointers out) {
         appendBatchPoints(out, getLength());
     }
 
+    // TODO: Make this package private
     public final void appendIncrementalBatchPoints(final InputPointers out) {
         appendBatchPoints(out, mIncrementalRecognitionSize);
     }
diff --git a/java/src/com/android/inputmethod/latin/Constants.java b/java/src/com/android/inputmethod/latin/Constants.java
index fbd6ef4..00b54f5 100644
--- a/java/src/com/android/inputmethod/latin/Constants.java
+++ b/java/src/com/android/inputmethod/latin/Constants.java
@@ -257,6 +257,13 @@
     public static final int SCREEN_METRICS_LARGE_TABLET = 2;
     public static final int SCREEN_METRICS_SMALL_TABLET = 3;
 
+    /**
+     * Default capacity of gesture points container.
+     * This constant is used by {@link BatchInputArbiter} and etc. to preallocate regions that
+     * contain gesture event points.
+     */
+    public static final int DEFAULT_GESTURE_POINTS_CAPACITY = 128;
+
     private Constants() {
         // This utility class is not publicly instantiable.
     }
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 68ed238..d8fb4f2 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -1147,7 +1147,7 @@
 
     @Override
     public boolean onEvaluateFullscreenMode() {
-        // Reread resource value here, because this method is called by framework anytime as needed.
+        // Reread resource value here, because this method is called by the framework as needed.
         final boolean isFullscreenModeAllowed = Settings.readUseFullscreenMode(getResources());
         if (super.onEvaluateFullscreenMode() && isFullscreenModeAllowed) {
             // TODO: Remove this hack. Actually we should not really assume NO_EXTRACT_UI
@@ -1272,7 +1272,7 @@
 
     @Override
     public void onCancelBatchInput() {
-        mInputLogic.onCancelBatchInput(mInputUpdater);
+        mInputLogic.onCancelBatchInput(mHandler, mInputUpdater);
     }
 
     // TODO[IL]: Make this a package-private standalone class in inputlogic/ and remove all
@@ -1310,17 +1310,15 @@
             return true;
         }
 
-        // Run in the UI thread.
+        // Run on the UI thread.
         public void onStartBatchInput() {
             synchronized (mLock) {
                 mHandler.removeMessages(MSG_UPDATE_GESTURE_PREVIEW_AND_SUGGESTION_STRIP);
                 mInBatchInput = true;
-                mLatinIme.mHandler.showGesturePreviewAndSuggestionStrip(
-                        SuggestedWords.EMPTY, false /* dismissGestureFloatingPreviewText */);
             }
         }
 
-        // Run in the Handler thread.
+        // Run on the Handler thread.
         private void updateBatchInput(final InputPointers batchPointers, final int sequenceNumber) {
             synchronized (mLock) {
                 if (!mInBatchInput) {
@@ -1339,7 +1337,7 @@
             }
         }
 
-        // Run in the UI thread.
+        // Run on the UI thread.
         public void onUpdateBatchInput(final InputPointers batchPointers,
                 final int sequenceNumber) {
             if (mHandler.hasMessages(MSG_UPDATE_GESTURE_PREVIEW_AND_SUGGESTION_STRIP)) {
@@ -1352,12 +1350,10 @@
         public void onCancelBatchInput() {
             synchronized (mLock) {
                 mInBatchInput = false;
-                mLatinIme.mHandler.showGesturePreviewAndSuggestionStrip(
-                        SuggestedWords.EMPTY, true /* dismissGestureFloatingPreviewText */);
             }
         }
 
-        // Run in the UI thread.
+        // Run on the UI thread.
         public void onEndBatchInput(final InputPointers batchPointers) {
             synchronized(mLock) {
                 getSuggestedWordsGestureLocked(batchPointers, SuggestedWords.NOT_A_SEQUENCE_NUMBER,
@@ -1407,7 +1403,7 @@
         }
     }
 
-    // This method must run in UI Thread.
+    // This method must run on the UI Thread.
     private void showGesturePreviewAndSuggestionStrip(final SuggestedWords suggestedWords,
             final boolean dismissGestureFloatingPreviewText) {
         showSuggestionStrip(suggestedWords);
@@ -1418,7 +1414,7 @@
         }
     }
 
-    // This method must run in UI Thread.
+    // This method must run on the UI Thread.
     public void onEndBatchInputAsyncInternal(final SuggestedWords suggestedWords) {
         final String batchInputText = suggestedWords.isEmpty() ? null : suggestedWords.getWord(0);
         if (TextUtils.isEmpty(batchInputText)) {
@@ -1615,19 +1611,6 @@
                 false /* isPrediction */);
     }
 
-    private void setAutoCorrection(final SuggestedWords suggestedWords, final String typedWord) {
-        if (suggestedWords.isEmpty()) return;
-        final String autoCorrection;
-        if (suggestedWords.mWillAutoCorrect) {
-            autoCorrection = suggestedWords.getWord(SuggestedWords.INDEX_OF_AUTO_CORRECTION);
-        } else {
-            // We can't use suggestedWords.getWord(SuggestedWords.INDEX_OF_TYPED_WORD)
-            // because it may differ from mWordComposer.mTypedWord.
-            autoCorrection = typedWord;
-        }
-        mInputLogic.mWordComposer.setAutoCorrection(autoCorrection);
-    }
-
     private void showSuggestionStripWithTypedWord(final SuggestedWords suggestedWords,
             final String typedWord) {
       if (suggestedWords.isEmpty()) {
@@ -1636,10 +1619,17 @@
           clearSuggestionStrip();
           return;
       }
-      setAutoCorrection(suggestedWords, typedWord);
-      final boolean isAutoCorrection = suggestedWords.willAutoCorrect();
-      setSuggestedWords(suggestedWords, isAutoCorrection);
-      setAutoCorrectionIndicator(isAutoCorrection);
+      final String autoCorrection;
+      if (suggestedWords.mWillAutoCorrect) {
+          autoCorrection = suggestedWords.getWord(SuggestedWords.INDEX_OF_AUTO_CORRECTION);
+      } else {
+          // We can't use suggestedWords.getWord(SuggestedWords.INDEX_OF_TYPED_WORD)
+          // because it may differ from mWordComposer.mTypedWord.
+          autoCorrection = typedWord;
+      }
+      mInputLogic.mWordComposer.setAutoCorrection(autoCorrection);
+      setSuggestedWords(suggestedWords, suggestedWords.mWillAutoCorrect);
+      setAutoCorrectionIndicator(suggestedWords.mWillAutoCorrect);
       setSuggestionStripShown(isSuggestionsStripVisible());
       // An auto-correction is available, cache it in accessibility code so
       // we can be speak it if the user touches a key that will insert it.
diff --git a/java/src/com/android/inputmethod/latin/SuggestedWords.java b/java/src/com/android/inputmethod/latin/SuggestedWords.java
index 97c89dd4..f9de89c 100644
--- a/java/src/com/android/inputmethod/latin/SuggestedWords.java
+++ b/java/src/com/android/inputmethod/latin/SuggestedWords.java
@@ -104,10 +104,6 @@
         return debugString;
     }
 
-    public boolean willAutoCorrect() {
-        return mWillAutoCorrect;
-    }
-
     @Override
     public String toString() {
         // Pretty-print method to help debug
@@ -150,7 +146,7 @@
         for (int index = 1; index < previousSize; index++) {
             final SuggestedWordInfo prevWordInfo = previousSuggestions.getInfo(index);
             final String prevWord = prevWordInfo.mWord;
-            // Filter out duplicate suggestion.
+            // Filter out duplicate suggestions.
             if (!alreadySeen.contains(prevWord)) {
                 suggestionsList.add(prevWordInfo);
                 alreadySeen.add(prevWord);
diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
index 0686ff6..4a4abd7 100644
--- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
+++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
@@ -68,6 +68,8 @@
     // TODO : Remove this member when we can.
     private final LatinIME mLatinIME;
 
+    private InputLogicHandler mInputLogicHandler;
+
     // TODO : make all these fields private as soon as possible.
     // Current space state of the input method. This can be any of the above constants.
     public int mSpaceState;
@@ -102,6 +104,7 @@
         mWordComposer = new WordComposer();
         mEventInterpreter = new EventInterpreter(latinIME);
         mConnection = new RichInputConnection(latinIME);
+        mInputLogicHandler = null;
     }
 
     /**
@@ -116,12 +119,15 @@
      * @param restarting whether input is starting in the same field as before.
      */
     public void startInput(final boolean restarting) {
+        mInputLogicHandler = new InputLogicHandler();
     }
 
     /**
      * Clean up the input logic after input is finished.
      */
     public void finishInput() {
+        mInputLogicHandler.destroy();
+        mInputLogicHandler = null;
     }
 
     /**
@@ -297,6 +303,8 @@
             final KeyboardSwitcher keyboardSwitcher, final LatinIME.UIHandler handler,
             final LatinIME.InputUpdater inputUpdater) {
         inputUpdater.onStartBatchInput();
+        handler.showGesturePreviewAndSuggestionStrip(
+                SuggestedWords.EMPTY, false /* dismissGestureFloatingPreviewText */);
         handler.cancelUpdateSuggestionStrip();
         mConnection.beginBatchEdit();
         if (mWordComposer.isComposingWord()) {
@@ -397,9 +405,12 @@
         inputUpdater.onEndBatchInput(batchPointers);
     }
 
-    // TODO: remove this argument
-    public void onCancelBatchInput(final LatinIME.InputUpdater inputUpdater) {
+    // TODO: remove these arguments
+    public void onCancelBatchInput(final LatinIME.UIHandler handler,
+            final LatinIME.InputUpdater inputUpdater) {
         inputUpdater.onCancelBatchInput();
+        handler.showGesturePreviewAndSuggestionStrip(
+                SuggestedWords.EMPTY, true /* dismissGestureFloatingPreviewText */);
     }
 
     /**
diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogicHandler.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogicHandler.java
new file mode 100644
index 0000000..d611e4b
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogicHandler.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.inputlogic;
+
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Message;
+
+/**
+ * A helper to manage deferred tasks for the input logic.
+ */
+// TODO: Make this package private
+public class InputLogicHandler implements Handler.Callback {
+    final Handler mNonUIThreadHandler;
+
+    public InputLogicHandler() {
+        final HandlerThread handlerThread = new HandlerThread(
+                InputLogicHandler.class.getSimpleName());
+        handlerThread.start();
+        mNonUIThreadHandler = new Handler(handlerThread.getLooper(), this);
+    }
+
+    public void destroy() {
+        mNonUIThreadHandler.getLooper().quit();
+    }
+
+    /**
+     * Handle a message.
+     * @see android.os.Handler.Callback#handleMessage(android.os.Message)
+     */
+    @Override
+    public boolean handleMessage(final Message msg) {
+        return true;
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java
index 72281e6..f836e61 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java
@@ -208,7 +208,7 @@
         }
         final String word = suggestedWords.getWord(indexInSuggestedWords);
         final boolean isAutoCorrect = indexInSuggestedWords == 1
-                && suggestedWords.willAutoCorrect();
+                && suggestedWords.mWillAutoCorrect;
         final boolean isTypedWordValid = indexInSuggestedWords == 0
                 && suggestedWords.mTypedWordValid;
         if (!isAutoCorrect && !isTypedWordValid) {
@@ -232,7 +232,7 @@
             final SuggestedWords suggestedWords) {
         final int indexToDisplayMostImportantSuggestion;
         final int indexToDisplaySecondMostImportantSuggestion;
-        if (suggestedWords.willAutoCorrect()) {
+        if (suggestedWords.mWillAutoCorrect) {
             indexToDisplayMostImportantSuggestion = SuggestedWords.INDEX_OF_AUTO_CORRECTION;
             indexToDisplaySecondMostImportantSuggestion = SuggestedWords.INDEX_OF_TYPED_WORD;
         } else {
@@ -257,7 +257,7 @@
         final boolean isSuggested = (indexInSuggestedWords != SuggestedWords.INDEX_OF_TYPED_WORD);
 
         final int color;
-        if (positionInStrip == mCenterPositionInStrip && suggestedWords.willAutoCorrect()) {
+        if (positionInStrip == mCenterPositionInStrip && suggestedWords.mWillAutoCorrect) {
             color = mColorAutoCorrect;
         } else if (positionInStrip == mCenterPositionInStrip && suggestedWords.mTypedWordValid) {
             color = mColorValidTypedWord;