diff --git a/java/src/com/android/inputmethod/keyboard/TextDecorator.java b/java/src/com/android/inputmethod/keyboard/TextDecorator.java
index 0eb8b443a3e9204ab4c5aaa35603f3e10c3ee1c6..17851613460960057287eff5726be5c55bc08ca9 100644
--- a/java/src/com/android/inputmethod/keyboard/TextDecorator.java
+++ b/java/src/com/android/inputmethod/keyboard/TextDecorator.java
@@ -158,7 +158,7 @@ public class TextDecorator {
         if (!currentFullScreenMode && fullScreenMode) {
             // Currently full screen mode is not supported.
             // TODO: Support full screen mode.
-            hideIndicator();
+            mUiOperator.hideUi();
         }
         mIsFullScreenMode = fullScreenMode;
     }
@@ -193,17 +193,36 @@ public class TextDecorator {
         layoutImmediately();
     }
 
-    private void hideIndicator() {
-        mUiOperator.hideUi();
+    /**
+     * Hides indicator if the new composing text doesn't match the expected one.
+     *
+     * <p>Calling this method is optional but recommended whenever the new composition is passed to
+     * the application. The motivation of this method is to reduce the UI latency. With this method,
+     * we can hide the indicator without waiting the arrival of the
+     * {@link InputMethodService#onUpdateCursorAnchorInfo(CursorAnchorInfo)} callback, assuming that
+     * the application accepts the new composing text without any modification. Even if this
+     * assumption is false, the indicator will be shown again when
+     * {@link InputMethodService#onUpdateCursorAnchorInfo(CursorAnchorInfo)} is actually received.
+     * </p>
+     *
+     * @param newComposingText the new composing text that is being passed to the application.
+     */
+    public void hideIndicatorIfNecessary(final CharSequence newComposingText) {
+        if (mMode != MODE_COMMIT && mMode != MODE_ADD_TO_DICTIONARY) {
+            return;
+        }
+        if (!TextUtils.equals(newComposingText, mWaitingWord.mWord)) {
+            mUiOperator.hideUi();
+        }
     }
 
     private void cancelLayoutInternalUnexpectedly(final String message) {
-        hideIndicator();
+        mUiOperator.hideUi();
         Log.d(TAG, message);
     }
 
     private void cancelLayoutInternalExpectedly(final String message) {
-        hideIndicator();
+        mUiOperator.hideUi();
         if (DEBUG) {
             Log.d(TAG, message);
         }
@@ -261,7 +280,7 @@ public class TextDecorator {
                     lastCharRectFlag & CursorAnchorInfoCompatWrapper.CHARACTER_RECT_TYPE_MASK;
             if (lastCharRect == null || matrix == null || lastCharRectType !=
                     CursorAnchorInfoCompatWrapper.CHARACTER_RECT_TYPE_FULLY_VISIBLE) {
-                hideIndicator();
+                mUiOperator.hideUi();
                 return;
             }
             final RectF segmentStartCharRect = new RectF(lastCharRect);
@@ -312,13 +331,13 @@ public class TextDecorator {
             if (!TextUtils.isEmpty(composingText)) {
                 // This is an unexpected case.
                 // TODO: Document this.
-                hideIndicator();
+                mUiOperator.hideUi();
                 return;
             }
             // In MODE_ADD_TO_DICTIONARY, we cannot retrieve the character position at all because
             // of the lack of composing text. We will use the insertion marker position instead.
             if (info.isInsertionMarkerClipped()) {
-                hideIndicator();
+                mUiOperator.hideUi();
                 return;
             }
             final float insertionMarkerHolizontal = info.getInsertionMarkerHorizontal();
diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
index 0f2ba53d59caf3dde49ecb1e371f1336287996c2..e83f494b30bcd9abbacb834f3b68f07bad599926 100644
--- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
+++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
@@ -649,7 +649,7 @@ public final class InputLogic {
             // message, this is called outside any batch edit. Potentially, this may result in some
             // janky flickering of the screen, although the display speed makes it unlikely in
             // the practice.
-            mConnection.setComposingText(textWithUnderline, 1);
+            setComposingTextInternal(textWithUnderline, 1);
         }
     }
 
@@ -672,7 +672,7 @@ public final class InputLogic {
             inputTransaction.setDidAffectContents();
         }
         if (mWordComposer.isComposingWord()) {
-            mConnection.setComposingText(mWordComposer.getTypedWord(), 1);
+            setComposingTextInternal(mWordComposer.getTypedWord(), 1);
             inputTransaction.setDidAffectContents();
             inputTransaction.setRequiresUpdateSuggestions();
         }
@@ -908,8 +908,7 @@ public final class InputLogic {
             if (mWordComposer.isSingleLetter()) {
                 mWordComposer.setCapitalizedModeAtStartComposingTime(inputTransaction.mShiftState);
             }
-            mConnection.setComposingText(getTextWithUnderline(
-                    mWordComposer.getTypedWord()), 1);
+            setComposingTextInternal(getTextWithUnderline(mWordComposer.getTypedWord()), 1);
         } else {
             final boolean swapWeakSpace = tryStripSpaceAndReturnWhetherShouldSwapInstead(event,
                     inputTransaction);
@@ -1072,7 +1071,7 @@ public final class InputLogic {
                 mWordComposer.applyProcessedEvent(event);
             }
             if (mWordComposer.isComposingWord()) {
-                mConnection.setComposingText(getTextWithUnderline(mWordComposer.getTypedWord()), 1);
+                setComposingTextInternal(getTextWithUnderline(mWordComposer.getTypedWord()), 1);
             } else {
                 mConnection.commitText("", 1);
             }
@@ -1640,7 +1639,7 @@ public final class InputLogic {
             final int[] codePoints = StringUtils.toCodePointArray(stringToCommit);
             mWordComposer.setComposingWord(codePoints,
                     mLatinIME.getCoordinatesForCurrentKeyboard(codePoints));
-            mConnection.setComposingText(textToCommit, 1);
+            setComposingTextInternal(textToCommit, 1);
         }
         // Don't restart suggestion yet. We'll restart if the user deletes the separator.
         mLastComposedWord = LastComposedWord.NOT_A_COMPOSED_WORD;
@@ -1973,10 +1972,10 @@ public final class InputLogic {
             }
             final String lastWord = batchInputText.substring(indexOfLastSpace);
             mWordComposer.setBatchInputWord(lastWord);
-            mConnection.setComposingText(lastWord, 1);
+            setComposingTextInternal(lastWord, 1);
         } else {
             mWordComposer.setBatchInputWord(batchInputText);
-            mConnection.setComposingText(batchInputText, 1);
+            setComposingTextInternal(batchInputText, 1);
         }
         mConnection.endBatchEdit();
         // Space state must be updated before calling updateShiftState
@@ -2175,6 +2174,24 @@ public final class InputLogic {
                 inputStyle, sequenceNumber, callback);
     }
 
+    /**
+     * Used as an injection point for each call of
+     * {@link RichInputConnection#setComposingText(CharSequence, int)}.
+     *
+     * <p>Currently using this method is optional and you can still directly call
+     * {@link RichInputConnection#setComposingText(CharSequence, int)}, but it is recommended to
+     * use this method whenever possible to optimize the behavior of {@link TextDecorator}.<p>
+     * <p>TODO: Should we move this mechanism to {@link RichInputConnection}?</p>
+     *
+     * @param newComposingText the composing text to be set
+     * @param newCursorPosition the new cursor position
+     */
+    private void setComposingTextInternal(final CharSequence newComposingText,
+            final int newCursorPosition) {
+        mConnection.setComposingText(newComposingText, newCursorPosition);
+        mTextDecorator.hideIndicatorIfNecessary(newComposingText);
+    }
+
     //////////////////////////////////////////////////////////////////////////////////////////////
     // Following methods are tentatively placed in this class for the integration with
     // TextDecorator.