From a475c85480b2bc2a8d036b4b1ea29f6a8e749ac5 Mon Sep 17 00:00:00 2001 From: Yohei Yukawa Date: Tue, 26 Aug 2014 21:55:48 -0700 Subject: [PATCH] Introduce a mechanism to hide the indicator speculatively This is an optional optimization to reduce the UI latency. Imagine that the commit indicator is now displayed and the composing text is being updated, it is highly likely that the commit indicator will disappear unless the application rejects the setComposingText request. If we assume that the application will accept the new composing text without any modifications, we can hide the indicator without waiting for the arrival of new CursorAnchorInfo event. This optimization isn't dangerous because we can show the indicator again when we receive new CursorAnchorInfo event and the assumption is turned out to be invalid. Change-Id: Id59c6607a1029782410611e768791af9984f14ac --- .../inputmethod/keyboard/TextDecorator.java | 35 ++++++++++++++----- .../latin/inputlogic/InputLogic.java | 33 ++++++++++++----- 2 files changed, 52 insertions(+), 16 deletions(-) diff --git a/java/src/com/android/inputmethod/keyboard/TextDecorator.java b/java/src/com/android/inputmethod/keyboard/TextDecorator.java index 0eb8b443a..178516134 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. + * + *

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. + *

+ * + * @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 0f2ba53d5..e83f494b3 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)}. + * + *

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}.

+ *

TODO: Should we move this mechanism to {@link RichInputConnection}?

+ * + * @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.