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
main
Yohei Yukawa 2014-08-26 21:55:48 -07:00
parent bea17c49ec
commit a475c85480
2 changed files with 52 additions and 16 deletions

View File

@ -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();

View File

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