[IL50] Move the InputUpdater out of LatinIME.

...at last

Bug: 8636060
Change-Id: If5e27a9d28ff64d318dd76792f55edfc8a78d2d7
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index e3c9fce..a62c22c 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -35,8 +35,6 @@
 import android.media.AudioManager;
 import android.net.ConnectivityManager;
 import android.os.Debug;
-import android.os.Handler;
-import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.Message;
 import android.os.SystemClock;
@@ -137,7 +135,6 @@
     private final boolean mIsHardwareAcceleratedDrawingEnabled;
 
     public final UIHandler mHandler = new UIHandler(this);
-    private InputUpdater mInputUpdater;
 
     public static final class UIHandler extends LeakGuardHandlerWrapper<LatinIME> {
         private static final int MSG_UPDATE_SHIFT_STATE = 0;
@@ -183,8 +180,7 @@
             switch (msg.what) {
             case MSG_UPDATE_SUGGESTION_STRIP:
                 latinIme.mInputLogic.performUpdateSuggestionStripSync(
-                        latinIme.mSettings.getCurrent(), this /* handler */,
-                        latinIme.mInputUpdater);
+                        latinIme.mSettings.getCurrent(), this /* handler */);
                 break;
             case MSG_UPDATE_SHIFT_STATE:
                 switcher.updateShiftState();
@@ -205,8 +201,7 @@
                 break;
             case MSG_RESUME_SUGGESTIONS:
                 latinIme.mInputLogic.restartSuggestionsOnWordTouchedByCursor(
-                        latinIme.mSettings.getCurrent(), latinIme.mKeyboardSwitcher,
-                        latinIme.mInputUpdater);
+                        latinIme.mSettings.getCurrent(), latinIme.mKeyboardSwitcher);
                 break;
             case MSG_REOPEN_DICTIONARIES:
                 latinIme.initSuggest();
@@ -216,7 +211,7 @@
                 postUpdateSuggestionStrip();
                 break;
             case MSG_ON_END_BATCH_INPUT:
-                latinIme.mInputLogic.onEndBatchInputAsyncInternal(latinIme.mSettings.getCurrent(),
+                latinIme.mInputLogic.endBatchInputAsyncInternal(latinIme.mSettings.getCurrent(),
                         (SuggestedWords) msg.obj, latinIme.mKeyboardSwitcher);
                 break;
             case MSG_RESET_CACHES:
@@ -496,8 +491,6 @@
         registerReceiver(mDictionaryPackInstallReceiver, newDictFilter);
 
         DictionaryDecayBroadcastReciever.setUpIntervalAlarmForDictionaryDecaying(this);
-
-        mInputUpdater = new InputUpdater(this);
     }
 
     // Has to be package-visible for unit tests
@@ -595,9 +588,6 @@
             suggest.close();
             mInputLogic.mSuggest = null;
         }
-        if (mInputUpdater != null) {
-            mInputUpdater.quitLooper();
-        }
         mSettings.onDestroy();
         unregisterReceiver(mReceiver);
         if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
@@ -1239,176 +1229,35 @@
     // Implementation of {@link KeyboardActionListener}.
     @Override
     public void onCodeInput(final int primaryCode, final int x, final int y) {
-        mInputLogic.onCodeInput(primaryCode, x, y, mHandler, mInputUpdater,
-                mKeyboardSwitcher, mSubtypeSwitcher);
+        mInputLogic.onCodeInput(primaryCode, x, y, mHandler, mKeyboardSwitcher, mSubtypeSwitcher);
     }
 
     // Called from PointerTracker through the KeyboardActionListener interface
     @Override
     public void onTextInput(final String rawText) {
-        mInputLogic.onTextInput(mSettings.getCurrent(), rawText, mHandler, mInputUpdater);
+        mInputLogic.onTextInput(mSettings.getCurrent(), rawText, mHandler);
         mKeyboardSwitcher.updateShiftState();
         mKeyboardSwitcher.onCodeInput(Constants.CODE_OUTPUT_TEXT);
     }
 
     @Override
     public void onStartBatchInput() {
-        mInputLogic.onStartBatchInput(mSettings.getCurrent(),  mKeyboardSwitcher, mHandler,
-                mInputUpdater);
+        mInputLogic.onStartBatchInput(mSettings.getCurrent(),  mKeyboardSwitcher, mHandler);
     }
 
     @Override
     public void onUpdateBatchInput(final InputPointers batchPointers) {
-        mInputLogic.onUpdateBatchInput(mSettings.getCurrent(), batchPointers, mKeyboardSwitcher,
-                mInputUpdater);
+        mInputLogic.onUpdateBatchInput(mSettings.getCurrent(), batchPointers, mKeyboardSwitcher);
     }
 
     @Override
     public void onEndBatchInput(final InputPointers batchPointers) {
-        mInputLogic.onEndBatchInput(mSettings.getCurrent(), batchPointers, mInputUpdater);
+        mInputLogic.onEndBatchInput(mSettings.getCurrent(), batchPointers);
     }
 
     @Override
     public void onCancelBatchInput() {
-        mInputLogic.onCancelBatchInput(mHandler, mInputUpdater);
-    }
-
-    // TODO[IL]: Make this a package-private standalone class in inputlogic/ and remove all
-    // references to it in LatinIME
-    public static final class InputUpdater implements Handler.Callback {
-        private final Handler mHandler;
-        private final LatinIME mLatinIme;
-        private final Object mLock = new Object();
-        private boolean mInBatchInput; // synchronized using {@link #mLock}.
-
-        InputUpdater(final LatinIME latinIme) {
-            final HandlerThread handlerThread = new HandlerThread(
-                    InputUpdater.class.getSimpleName());
-            handlerThread.start();
-            mHandler = new Handler(handlerThread.getLooper(), this);
-            mLatinIme = latinIme;
-        }
-
-        private static final int MSG_GET_SUGGESTED_WORDS = 1;
-
-        // Called on the InputUpdater thread by the Handler code.
-        @Override
-        public boolean handleMessage(final Message msg) {
-            switch (msg.what) {
-                case MSG_GET_SUGGESTED_WORDS:
-                    mLatinIme.getSuggestedWords(msg.arg1 /* sessionId */,
-                            msg.arg2 /* sequenceNumber */, (OnGetSuggestedWordsCallback) msg.obj);
-                    break;
-            }
-            return true;
-        }
-
-        // Called on the UI thread by LatinIME.
-        public void onStartBatchInput() {
-            synchronized (mLock) {
-                mInBatchInput = true;
-            }
-        }
-
-        /**
-         * Fetch suggestions corresponding to an update of a batch input.
-         * @param batchPointers the updated pointers, including the part that was passed last time.
-         * @param sequenceNumber the sequence number associated with this batch input.
-         * @param forEnd true if this is the end of a batch input, false if it's an update.
-         */
-        // This method can be called from any thread and will see to it that the correct threads
-        // are used for parts that require it. This method will send a message to the
-        // InputUpdater thread to pull suggestions, and get the inlined callback to get called
-        // on the InputUpdater thread. The callback will then proceed to send a message to the
-        // UI handler in LatinIME so that showing suggestions can be done on the UI thread.
-        private void updateBatchInput(final InputPointers batchPointers,
-                final int sequenceNumber, final boolean forEnd) {
-            synchronized (mLock) {
-                if (!mInBatchInput) {
-                    // Batch input has ended or canceled while the message was being delivered.
-                    return;
-                }
-                mLatinIme.mInputLogic.mWordComposer.setBatchInputPointers(batchPointers);
-                getSuggestedWords(Suggest.SESSION_GESTURE, sequenceNumber,
-                        new OnGetSuggestedWordsCallback() {
-                            @Override
-                            public void onGetSuggestedWords(SuggestedWords suggestedWords) {
-                                // We're now inside the callback. This always runs on the
-                                // InputUpdater thread, no matter what thread updateBatchInput
-                                // was called on.
-                                if (suggestedWords.isEmpty()) {
-                                    // Use old suggestions if we don't have any new ones.
-                                    // Previous suggestions are found in InputLogic#mSuggestedWords.
-                                    // Since these are the most recent ones and we just recomputed
-                                    // new ones to update them, then the previous ones are there.
-                                    suggestedWords = mLatinIme.mInputLogic.mSuggestedWords;
-                                }
-                                mLatinIme.mHandler.showGesturePreviewAndSuggestionStrip(
-                                        suggestedWords,
-                                        forEnd /* dismissGestureFloatingPreviewText */);
-                                if (forEnd) {
-                                    mInBatchInput = false;
-                                    // The following call schedules onEndBatchInputAsyncInternal
-                                    // to be called on the UI thread.
-                                    mLatinIme.mHandler.onEndBatchInput(suggestedWords);
-                                }
-                            }
-                        });
-            }
-        }
-
-        /**
-         * Update a batch input.
-         *
-         * This fetches suggestions and updates the suggestion strip and the floating text preview.
-         *
-         * @param batchPointers the updated batch pointers.
-         * @param sequenceNumber the sequence number associated with this batch input.
-         */
-        // Called on the UI thread by LatinIME.
-        public void onUpdateBatchInput(final InputPointers batchPointers,
-                final int sequenceNumber) {
-            updateBatchInput(batchPointers, sequenceNumber, false /* forEnd */);
-        }
-
-        /**
-         * Cancel a batch input.
-         *
-         * Note that as opposed to onEndBatchInput, we do the UI side of this immediately on the
-         * same thread, rather than get this to call a method in LatinIME. This is because
-         * cancelling a batch input does not necessitate the long operation of pulling suggestions.
-         */
-        // Called on the UI thread by LatinIME.
-        public void onCancelBatchInput() {
-            synchronized (mLock) {
-                mInBatchInput = false;
-            }
-        }
-
-        /**
-         * Finish a batch input.
-         *
-         * This fetches suggestions, updates the suggestion strip and commits the first suggestion.
-         * It also dismisses the floating text preview.
-         *
-         * @param batchPointers the updated batch pointers.
-         * @param sequenceNumber the sequence number associated with this batch input.
-         */
-        // Called on the UI thread by LatinIME.
-        public void onEndBatchInput(final InputPointers batchPointers, final int sequenceNumber) {
-            updateBatchInput(batchPointers, sequenceNumber, true /* forEnd */);
-        }
-
-        public void getSuggestedWords(final int sessionId, final int sequenceNumber,
-                final OnGetSuggestedWordsCallback callback) {
-            mHandler.obtainMessage(MSG_GET_SUGGESTED_WORDS, sessionId, sequenceNumber, callback)
-                    .sendToTarget();
-        }
-
-        void quitLooper() {
-            mHandler.removeMessages(MSG_GET_SUGGESTED_WORDS);
-            mHandler.getLooper().quit();
-        }
+        mInputLogic.onCancelBatchInput(mHandler);
     }
 
     // This method must run on the UI Thread.
@@ -1496,7 +1345,8 @@
         }
     }
 
-    private void getSuggestedWords(final int sessionId, final int sequenceNumber,
+    // TODO[IL]: Move this out of LatinIME.
+    public void getSuggestedWords(final int sessionId, final int sequenceNumber,
             final OnGetSuggestedWordsCallback callback) {
         final Keyboard keyboard = mKeyboardSwitcher.getKeyboard();
         final Suggest suggest = mInputLogic.mSuggest;
diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
index 5fe482b..899341f 100644
--- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
+++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
@@ -132,7 +132,7 @@
         // In some cases (namely, after rotation of the device) editorInfo.initialSelStart is lying
         // so we try using some heuristics to find out about these and fix them.
         tryFixLyingCursorPosition();
-        mInputLogicHandler = new InputLogicHandler();
+        mInputLogicHandler = new InputLogicHandler(mLatinIME, this);
     }
 
     /**
@@ -157,11 +157,11 @@
      * @param rawText the text to input.
      */
     public void onTextInput(final SettingsValues settingsValues, final String rawText,
-            // TODO: remove these arguments
-            final LatinIME.UIHandler handler, final LatinIME.InputUpdater inputUpdater) {
+            // TODO: remove this argument
+            final LatinIME.UIHandler handler) {
         mConnection.beginBatchEdit();
         if (mWordComposer.isComposingWord()) {
-            commitCurrentAutoCorrection(settingsValues, rawText, handler, inputUpdater);
+            commitCurrentAutoCorrection(settingsValues, rawText, handler);
         } else {
             resetComposingState(true /* alsoResetLastComposedWord */);
         }
@@ -197,8 +197,8 @@
      * @param y the y-coordinate where the user pressed the key, or NOT_A_COORDINATE.
      */
     public void onCodeInput(final int code, final int x, final int y,
-            // TODO: remove these four arguments
-            final LatinIME.UIHandler handler, final LatinIME.InputUpdater inputUpdater,
+            // TODO: remove these three arguments
+            final LatinIME.UIHandler handler,
             final KeyboardSwitcher keyboardSwitcher, final SubtypeSwitcher subtypeSwitcher) {
         if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
             ResearchLogger.latinIME_onCodeInput(code, x, y);
@@ -291,16 +291,16 @@
                 // No action label, and the action from imeOptions is NONE: this is a regular
                 // enter key that should input a carriage return.
                 didAutoCorrect = handleNonSpecialCharacter(settingsValues, Constants.CODE_ENTER,
-                        x, y, spaceState, keyboardSwitcher, handler, inputUpdater);
+                        x, y, spaceState, keyboardSwitcher, handler);
             }
             break;
         case Constants.CODE_SHIFT_ENTER:
             didAutoCorrect = handleNonSpecialCharacter(settingsValues, Constants.CODE_ENTER,
-                    x, y, spaceState, keyboardSwitcher, handler, inputUpdater);
+                    x, y, spaceState, keyboardSwitcher, handler);
             break;
         default:
             didAutoCorrect = handleNonSpecialCharacter(settingsValues,
-                    code, x, y, spaceState, keyboardSwitcher, handler, inputUpdater);
+                    code, x, y, spaceState, keyboardSwitcher, handler);
             break;
         }
         keyboardSwitcher.onCodeInput(code);
@@ -317,9 +317,8 @@
 
     public void onStartBatchInput(final SettingsValues settingsValues,
             // TODO: remove these arguments
-            final KeyboardSwitcher keyboardSwitcher, final LatinIME.UIHandler handler,
-            final LatinIME.InputUpdater inputUpdater) {
-        inputUpdater.onStartBatchInput();
+            final KeyboardSwitcher keyboardSwitcher, final LatinIME.UIHandler handler) {
+        mInputLogicHandler.onStartBatchInput();
         handler.showGesturePreviewAndSuggestionStrip(
                 SuggestedWords.EMPTY, false /* dismissGestureFloatingPreviewText */);
         handler.cancelUpdateSuggestionStrip();
@@ -346,7 +345,7 @@
                 // so we do not attempt to correct, on the assumption that if that was a dictionary
                 // word, the user would probably have gestured instead.
                 commitCurrentAutoCorrection(settingsValues, LastComposedWord.NOT_A_SEPARATOR,
-                        handler, inputUpdater);
+                        handler);
             } else {
                 commitTyped(settingsValues, LastComposedWord.NOT_A_SEPARATOR);
             }
@@ -391,7 +390,7 @@
     public void onUpdateBatchInput(final SettingsValues settingsValues,
             final InputPointers batchPointers,
             // TODO: remove these arguments
-            final KeyboardSwitcher keyboardSwitcher, final LatinIME.InputUpdater inputUpdater) {
+            final KeyboardSwitcher keyboardSwitcher) {
         if (settingsValues.mPhraseGestureEnabled) {
             final SuggestedWordInfo candidate = mSuggestedWords.getAutoCommitCandidate();
             // If these suggested words have been generated with out of date input pointers, then
@@ -412,20 +411,17 @@
                 }
             }
         }
-        inputUpdater.onUpdateBatchInput(batchPointers, mAutoCommitSequenceNumber);
+        mInputLogicHandler.onUpdateBatchInput(batchPointers, mAutoCommitSequenceNumber);
     }
 
     public void onEndBatchInput(final SettingsValues settingValues,
-            final InputPointers batchPointers,
-            // TODO: remove these arguments
-            final LatinIME.InputUpdater inputUpdater) {
-        inputUpdater.onEndBatchInput(batchPointers, mAutoCommitSequenceNumber);
+            final InputPointers batchPointers) {
+        mInputLogicHandler.onEndBatchInput(batchPointers, mAutoCommitSequenceNumber);
     }
 
-    // TODO: remove these arguments
-    public void onCancelBatchInput(final LatinIME.UIHandler handler,
-            final LatinIME.InputUpdater inputUpdater) {
-        inputUpdater.onCancelBatchInput();
+    // TODO: remove this argument
+    public void onCancelBatchInput(final LatinIME.UIHandler handler) {
+        mInputLogicHandler.onCancelBatchInput();
         handler.showGesturePreviewAndSuggestionStrip(
                 SuggestedWords.EMPTY, true /* dismissGestureFloatingPreviewText */);
     }
@@ -448,14 +444,13 @@
     private boolean handleNonSpecialCharacter(final SettingsValues settingsValues,
             final int codePoint, final int x, final int y, final int spaceState,
             // TODO: remove these arguments
-            final KeyboardSwitcher keyboardSwitcher, final LatinIME.UIHandler handler,
-            final LatinIME.InputUpdater inputUpdater) {
+            final KeyboardSwitcher keyboardSwitcher, final LatinIME.UIHandler handler) {
         mSpaceState = SpaceState.NONE;
         final boolean didAutoCorrect;
         if (settingsValues.isWordSeparator(codePoint)
                 || Character.getType(codePoint) == Character.OTHER_SYMBOL) {
             didAutoCorrect = handleSeparator(settingsValues, codePoint, x, y, spaceState,
-                    keyboardSwitcher, handler, inputUpdater);
+                    keyboardSwitcher, handler);
         } else {
             didAutoCorrect = false;
             if (SpaceState.PHANTOM == spaceState) {
@@ -595,8 +590,7 @@
     private boolean handleSeparator(final SettingsValues settingsValues,
             final int codePoint, final int x, final int y, final int spaceState,
             // TODO: remove these arguments
-            final KeyboardSwitcher keyboardSwitcher, final LatinIME.UIHandler handler,
-            final LatinIME.InputUpdater inputUpdater) {
+            final KeyboardSwitcher keyboardSwitcher, final LatinIME.UIHandler handler) {
         boolean didAutoCorrect = false;
         // We avoid sending spaces in languages without spaces if we were composing.
         final boolean shouldAvoidSendingCode = Constants.CODE_SPACE == codePoint
@@ -612,7 +606,7 @@
             if (settingsValues.mCorrectionEnabled) {
                 final String separator = shouldAvoidSendingCode ? LastComposedWord.NOT_A_SEPARATOR
                         : StringUtils.newSingleCodePointString(codePoint);
-                commitCurrentAutoCorrection(settingsValues, separator, handler, inputUpdater);
+                commitCurrentAutoCorrection(settingsValues, separator, handler);
                 didAutoCorrect = true;
             } else {
                 commitTyped(settingsValues, StringUtils.newSingleCodePointString(codePoint));
@@ -1008,8 +1002,8 @@
     }
 
     public void performUpdateSuggestionStripSync(final SettingsValues settingsValues,
-            // TODO: Remove this variable
-            final LatinIME.UIHandler handler, final LatinIME.InputUpdater inputUpdater) {
+            // TODO: Remove this argument
+            final LatinIME.UIHandler handler) {
         handler.cancelUpdateSuggestionStrip();
 
         // Check if we have a suggestion engine attached.
@@ -1027,7 +1021,7 @@
         }
 
         final AsyncResultHolder<SuggestedWords> holder = new AsyncResultHolder<SuggestedWords>();
-        inputUpdater.getSuggestedWords(Suggest.SESSION_TYPING,
+        mInputLogicHandler.getSuggestedWords(Suggest.SESSION_TYPING,
                 SuggestedWords.NOT_A_SEQUENCE_NUMBER, new OnGetSuggestedWordsCallback() {
                     @Override
                     public void onGetSuggestedWords(final SuggestedWords suggestedWords) {
@@ -1085,8 +1079,8 @@
      */
     // TODO: make this private.
     public void restartSuggestionsOnWordTouchedByCursor(final SettingsValues settingsValues,
-            // TODO: Remove these argument.
-            final KeyboardSwitcher keyboardSwitcher, final LatinIME.InputUpdater inputUpdater) {
+            // TODO: Remove this argument.
+            final KeyboardSwitcher keyboardSwitcher) {
         // HACK: We may want to special-case some apps that exhibit bad behavior in case of
         // recorrection. This is a temporary, stopgap measure that will be removed later.
         // TODO: remove this.
@@ -1140,7 +1134,7 @@
         if (suggestions.isEmpty()) {
             // We come here if there weren't any suggestion spans on this word. We will try to
             // compute suggestions for it instead.
-            inputUpdater.getSuggestedWords(Suggest.SESSION_TYPING,
+            mInputLogicHandler.getSuggestedWords(Suggest.SESSION_TYPING,
                     SuggestedWords.NOT_A_SEQUENCE_NUMBER, new OnGetSuggestedWordsCallback() {
                         @Override
                         public void onGetSuggestedWords(
@@ -1530,7 +1524,7 @@
      * @param settingsValues the current values of the settings.
      * @param suggestedWords suggestedWords to use.
      */
-    public void onEndBatchInputAsyncInternal(final SettingsValues settingsValues,
+    public void endBatchInputAsyncInternal(final SettingsValues settingsValues,
             final SuggestedWords suggestedWords,
             // TODO: remove this argument
             final KeyboardSwitcher keyboardSwitcher) {
@@ -1613,11 +1607,11 @@
     // TODO: Make this private
     public void commitCurrentAutoCorrection(final SettingsValues settingsValues,
             final String separator,
-            // TODO: Remove these arguments.
-            final LatinIME.UIHandler handler, final LatinIME.InputUpdater inputUpdater) {
+            // TODO: Remove this argument.
+            final LatinIME.UIHandler handler) {
         // Complete any pending suggestions query first
         if (handler.hasPendingUpdateSuggestions()) {
-            performUpdateSuggestionStripSync(settingsValues, handler, inputUpdater);
+            performUpdateSuggestionStripSync(settingsValues, handler);
         }
         final String typedAutoCorrection = mWordComposer.getAutoCorrectionOrNull();
         final String typedWord = mWordComposer.getTypedWord();
diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogicHandler.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogicHandler.java
index d611e4b..3258dcd 100644
--- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogicHandler.java
+++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogicHandler.java
@@ -20,18 +20,33 @@
 import android.os.HandlerThread;
 import android.os.Message;
 
+import com.android.inputmethod.latin.InputPointers;
+import com.android.inputmethod.latin.LatinIME;
+import com.android.inputmethod.latin.Suggest;
+import com.android.inputmethod.latin.SuggestedWords;
+import com.android.inputmethod.latin.Suggest.OnGetSuggestedWordsCallback;
+
 /**
  * A helper to manage deferred tasks for the input logic.
  */
 // TODO: Make this package private
 public class InputLogicHandler implements Handler.Callback {
     final Handler mNonUIThreadHandler;
+    // TODO: remove this reference.
+    final LatinIME mLatinIME;
+    final InputLogic mInputLogic;
+    private final Object mLock = new Object();
+    private boolean mInBatchInput; // synchronized using {@link #mLock}.
 
-    public InputLogicHandler() {
+    private static final int MSG_GET_SUGGESTED_WORDS = 1;
+
+    public InputLogicHandler(final LatinIME latinIME, final InputLogic inputLogic) {
         final HandlerThread handlerThread = new HandlerThread(
                 InputLogicHandler.class.getSimpleName());
         handlerThread.start();
         mNonUIThreadHandler = new Handler(handlerThread.getLooper(), this);
+        mLatinIME = latinIME;
+        mInputLogic = inputLogic;
     }
 
     public void destroy() {
@@ -42,8 +57,116 @@
      * Handle a message.
      * @see android.os.Handler.Callback#handleMessage(android.os.Message)
      */
+    // Called on the Non-UI handler thread by the Handler code.
     @Override
     public boolean handleMessage(final Message msg) {
+        switch (msg.what) {
+            case MSG_GET_SUGGESTED_WORDS:
+                mLatinIME.getSuggestedWords(msg.arg1 /* sessionId */,
+                        msg.arg2 /* sequenceNumber */, (OnGetSuggestedWordsCallback) msg.obj);
+                break;
+        }
         return true;
     }
+
+    // Called on the UI thread by InputLogic.
+    public void onStartBatchInput() {
+        synchronized (mLock) {
+            mInBatchInput = true;
+        }
+    }
+
+    /**
+     * Fetch suggestions corresponding to an update of a batch input.
+     * @param batchPointers the updated pointers, including the part that was passed last time.
+     * @param sequenceNumber the sequence number associated with this batch input.
+     * @param forEnd true if this is the end of a batch input, false if it's an update.
+     */
+    // This method can be called from any thread and will see to it that the correct threads
+    // are used for parts that require it. This method will send a message to the Non-UI handler
+    // thread to pull suggestions, and get the inlined callback to get called on the Non-UI
+    // handler thread. If this is the end of a batch input, the callback will then proceed to
+    // send a message to the UI handler in LatinIME so that showing suggestions can be done on
+    // the UI thread.
+    private void updateBatchInput(final InputPointers batchPointers,
+            final int sequenceNumber, final boolean forEnd) {
+        synchronized (mLock) {
+            if (!mInBatchInput) {
+                // Batch input has ended or canceled while the message was being delivered.
+                return;
+            }
+            mInputLogic.mWordComposer.setBatchInputPointers(batchPointers);
+            getSuggestedWords(Suggest.SESSION_GESTURE, sequenceNumber,
+                    new OnGetSuggestedWordsCallback() {
+                        @Override
+                        public void onGetSuggestedWords(SuggestedWords suggestedWords) {
+                            // We're now inside the callback. This always runs on the Non-UI thread,
+                            // no matter what thread updateBatchInput was originally called on.
+                            if (suggestedWords.isEmpty()) {
+                                // Use old suggestions if we don't have any new ones.
+                                // Previous suggestions are found in InputLogic#mSuggestedWords.
+                                // Since these are the most recent ones and we just recomputed
+                                // new ones to update them, then the previous ones are there.
+                                suggestedWords = mInputLogic.mSuggestedWords;
+                            }
+                            mLatinIME.mHandler.showGesturePreviewAndSuggestionStrip(suggestedWords,
+                                    forEnd /* dismissGestureFloatingPreviewText */);
+                            if (forEnd) {
+                                mInBatchInput = false;
+                                // The following call schedules onEndBatchInputAsyncInternal
+                                // to be called on the UI thread.
+                                mLatinIME.mHandler.onEndBatchInput(suggestedWords);
+                            }
+                        }
+                    });
+        }
+    }
+
+    /**
+     * Update a batch input.
+     *
+     * This fetches suggestions and updates the suggestion strip and the floating text preview.
+     *
+     * @param batchPointers the updated batch pointers.
+     * @param sequenceNumber the sequence number associated with this batch input.
+     */
+    // Called on the UI thread by InputLogic.
+    public void onUpdateBatchInput(final InputPointers batchPointers,
+            final int sequenceNumber) {
+        updateBatchInput(batchPointers, sequenceNumber, false /* forEnd */);
+    }
+
+    /**
+     * Cancel a batch input.
+     *
+     * Note that as opposed to onEndBatchInput, we do the UI side of this immediately on the
+     * same thread, rather than get this to call a method in LatinIME. This is because
+     * canceling a batch input does not necessitate the long operation of pulling suggestions.
+     */
+    // Called on the UI thread by InputLogic.
+    public void onCancelBatchInput() {
+        synchronized (mLock) {
+            mInBatchInput = false;
+        }
+    }
+
+    /**
+     * Finish a batch input.
+     *
+     * This fetches suggestions, updates the suggestion strip and commits the first suggestion.
+     * It also dismisses the floating text preview.
+     *
+     * @param batchPointers the updated batch pointers.
+     * @param sequenceNumber the sequence number associated with this batch input.
+     */
+    // Called on the UI thread by InputLogic.
+    public void onEndBatchInput(final InputPointers batchPointers, final int sequenceNumber) {
+        updateBatchInput(batchPointers, sequenceNumber, true /* forEnd */);
+    }
+
+    public void getSuggestedWords(final int sessionId, final int sequenceNumber,
+            final OnGetSuggestedWordsCallback callback) {
+        mNonUIThreadHandler.obtainMessage(
+                MSG_GET_SUGGESTED_WORDS, sessionId, sequenceNumber, callback).sendToTarget();
+    }
 }