diff options
Diffstat (limited to 'java/src/com/android/inputmethod/latin/inputlogic')
-rw-r--r-- | java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java | 330 | ||||
-rw-r--r-- | java/src/com/android/inputmethod/latin/inputlogic/InputLogicHandler.java | 9 |
2 files changed, 276 insertions, 63 deletions
diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java index 418866ae1..fdab7f25f 100644 --- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java +++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java @@ -16,21 +16,29 @@ package com.android.inputmethod.latin.inputlogic; +import android.graphics.Color; +import android.inputmethodservice.InputMethodService; import android.os.SystemClock; import android.text.SpannableString; +import android.text.Spanned; import android.text.TextUtils; +import android.text.style.BackgroundColorSpan; import android.text.style.SuggestionSpan; import android.util.Log; import android.view.KeyCharacterMap; import android.view.KeyEvent; import android.view.inputmethod.CorrectionInfo; +import android.view.inputmethod.CursorAnchorInfo; import android.view.inputmethod.EditorInfo; +import com.android.inputmethod.compat.CursorAnchorInfoCompatWrapper; import com.android.inputmethod.compat.SuggestionSpanUtils; import com.android.inputmethod.event.Event; import com.android.inputmethod.event.InputTransaction; import com.android.inputmethod.keyboard.KeyboardSwitcher; import com.android.inputmethod.keyboard.ProximityInfo; +import com.android.inputmethod.keyboard.TextDecorator; +import com.android.inputmethod.keyboard.TextDecoratorUiOperator; import com.android.inputmethod.latin.Constants; import com.android.inputmethod.latin.Dictionary; import com.android.inputmethod.latin.DictionaryFacilitator; @@ -45,6 +53,7 @@ import com.android.inputmethod.latin.SuggestedWords; import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; import com.android.inputmethod.latin.WordComposer; import com.android.inputmethod.latin.define.DebugFlags; +import com.android.inputmethod.latin.define.ProductionFlags; import com.android.inputmethod.latin.settings.SettingsValues; import com.android.inputmethod.latin.settings.SettingsValuesForSuggestion; import com.android.inputmethod.latin.settings.SpacingAndPunctuations; @@ -80,6 +89,14 @@ public final class InputLogic { public final Suggest mSuggest; private final DictionaryFacilitator mDictionaryFacilitator; + private final TextDecorator mTextDecorator = new TextDecorator(new TextDecorator.Listener() { + @Override + public void onClickComposingTextToAddToDictionary(final String word) { + mLatinIME.addWordToUserDictionary(word); + mLatinIME.dismissAddToDictionaryHint(); + } + }); + public LastComposedWord mLastComposedWord = LastComposedWord.NOT_A_COMPOSED_WORD; // This has package visibility so it can be accessed from InputLogicHandler. /* package */ final WordComposer mWordComposer; @@ -123,8 +140,9 @@ public final class InputLogic { * Call this when input starts or restarts in some editor (typically, in onStartInputView). * * @param combiningSpec the combining spec string for this subtype + * @param settingsValues the current settings values */ - public void startInput(final String combiningSpec) { + public void startInput(final String combiningSpec, final SettingsValues settingsValues) { mEnteredText = null; mWordComposer.restartCombining(combiningSpec); resetComposingState(true /* alsoResetLastComposedWord */); @@ -142,15 +160,25 @@ public final class InputLogic { } else { mInputLogicHandler.reset(); } + + if (ProductionFlags.ENABLE_CURSOR_ANCHOR_INFO_CALLBACK) { + // AcceptTypedWord feature relies on CursorAnchorInfo. + if (settingsValues.mShouldShowUiToAcceptTypedWord) { + mConnection.requestCursorUpdates(true /* enableMonitor */, + true /* requestImmediateCallback */); + } + mTextDecorator.reset(); + } } /** * Call this when the subtype changes. * @param combiningSpec the spec string for the combining rules + * @param settingsValues the current settings values */ - public void onSubtypeChanged(final String combiningSpec) { + public void onSubtypeChanged(final String combiningSpec, final SettingsValues settingsValues) { finishInput(); - startInput(combiningSpec); + startInput(combiningSpec, settingsValues); } /** @@ -216,7 +244,7 @@ public final class InputLogic { } else { resetComposingState(true /* alsoResetLastComposedWord */); } - handler.postUpdateSuggestionStrip(); + handler.postUpdateSuggestionStrip(SuggestedWords.INPUT_STYLE_TYPING); final String text = performSpecificTldProcessingOnTextInput(rawText); if (SpaceState.PHANTOM == mSpaceState) { promotePhantomSpace(settingsValues); @@ -232,6 +260,20 @@ public final class InputLogic { } /** + * Determines whether "Touch again to save" should be shown or not. + * @param suggestionInfo the suggested word chosen by the user. + * @return {@code true} if we should show the "Touch again to save" hint. + */ + private boolean shouldShowAddToDictionaryHint(final SuggestedWordInfo suggestionInfo) { + // We should show the "Touch again to save" hint if the user pressed the first entry + // AND it's in none of our current dictionaries (main, user or otherwise). + return (suggestionInfo.isKindOf(SuggestedWordInfo.KIND_TYPED) + || suggestionInfo.isKindOf(SuggestedWordInfo.KIND_OOV_CORRECTION)) + && !mDictionaryFacilitator.isValidWord(suggestionInfo.mWord, true /* ignoreCase */) + && mDictionaryFacilitator.isUserDictionaryEnabled(); + } + + /** * A suggestion was picked from the suggestion strip. * @param settingsValues the current values of the settings. * @param suggestionInfo the suggestion info. @@ -288,11 +330,9 @@ public final class InputLogic { return inputTransaction; } - // We need to log before we commit, because the word composer will store away the user - // typed word. - final String replacedWord = mWordComposer.getTypedWord(); - commitChosenWord(settingsValues, suggestion, - LastComposedWord.COMMIT_TYPE_MANUAL_PICK, LastComposedWord.NOT_A_SEPARATOR); + final boolean shouldShowAddToDictionaryHint = shouldShowAddToDictionaryHint(suggestionInfo); + commitChosenWord(settingsValues, suggestion, LastComposedWord.COMMIT_TYPE_MANUAL_PICK, + LastComposedWord.NOT_A_SEPARATOR); mConnection.endBatchEdit(); // Don't allow cancellation of manual pick mLastComposedWord.deactivate(); @@ -300,18 +340,12 @@ public final class InputLogic { mSpaceState = SpaceState.PHANTOM; inputTransaction.requireShiftUpdate(InputTransaction.SHIFT_UPDATE_NOW); - // We should show the "Touch again to save" hint if the user pressed the first entry - // AND it's in none of our current dictionaries (main, user or otherwise). - final boolean showingAddToDictionaryHint = - (suggestionInfo.isKindOf(SuggestedWordInfo.KIND_TYPED) - || suggestionInfo.isKindOf(SuggestedWordInfo.KIND_OOV_CORRECTION)) - && !mDictionaryFacilitator.isValidWord(suggestion, true /* ignoreCase */); - - if (showingAddToDictionaryHint && mDictionaryFacilitator.isUserDictionaryEnabled()) { + if (shouldShowAddToDictionaryHint) { mSuggestionStripViewAccessor.showAddToDictionaryHint(suggestion); } else { // If we're not showing the "Touch again to save", then update the suggestion strip. - handler.postUpdateSuggestionStrip(); + // That's going to be predictions (or punctuation suggestions), so INPUT_STYLE_NONE. + handler.postUpdateSuggestionStrip(SuggestedWords.INPUT_STYLE_NONE); } return inputTransaction; } @@ -324,10 +358,11 @@ public final class InputLogic { * @param oldSelEnd old selection end * @param newSelStart new selection start * @param newSelEnd new selection end + * @param settingsValues the current values of the settings. * @return whether the cursor has moved as a result of user interaction. */ public boolean onUpdateSelection(final int oldSelStart, final int oldSelEnd, - final int newSelStart, final int newSelEnd) { + final int newSelStart, final int newSelEnd, final SettingsValues settingsValues) { if (mConnection.isBelatedExpectedUpdate(oldSelStart, newSelStart, oldSelEnd, newSelEnd)) { return false; } @@ -352,8 +387,9 @@ public final class InputLogic { // should be true, but that is if the framework had taken that wrong cursor position // into account, which means we have to reset the entire composing state whenever there // is or was a selection regardless of whether it changed or not. - if (hasOrHadSelection || (selectionChangedOrSafeToReset - && !mWordComposer.moveCursorByAndReturnIfInsideComposingWord(moveAmount))) { + if (hasOrHadSelection || !settingsValues.needsToLookupSuggestions() + || (selectionChangedOrSafeToReset + && !mWordComposer.moveCursorByAndReturnIfInsideComposingWord(moveAmount))) { // If we are composing a word and moving the cursor, we would want to set a // suggestion span for recorrection to work correctly. Unfortunately, that // would involve the keyboard committing some new text, which would move the @@ -380,6 +416,11 @@ public final class InputLogic { // The cursor has been moved : we now accept to perform recapitalization mRecapitalizeStatus.enable(); + // We moved the cursor and need to invalidate the indicator right now. + mTextDecorator.reset(); + // Remaining background color that was used for the add-to-dictionary indicator should be + // removed. + mConnection.removeBackgroundColorFromHighlightedTextIfNecessary(); // We moved the cursor. If we are touching a word, we need to resume suggestion. mLatinIME.mHandler.postResumeSuggestions(false /* shouldIncludeResumedWordInSuggestions */, true /* shouldDelay */); @@ -458,7 +499,9 @@ public final class InputLogic { handler.cancelUpdateSuggestionStrip(); ++mAutoCommitSequenceNumber; mConnection.beginBatchEdit(); - if (mWordComposer.isComposingWord()) { + if (!mWordComposer.isComposingWord()) { + mConnection.removeBackgroundColorFromHighlightedTextIfNecessary(); + } else { if (mWordComposer.isCursorFrontOrMiddleOfComposingWord()) { // If we are in the middle of a recorrection, we need to commit the recorrection // first so that we can insert the batch input at the current cursor position. @@ -555,7 +598,8 @@ public final class InputLogic { // TODO: on the long term, this method should become private, but it will be difficult. // Especially, how do we deal with InputMethodService.onDisplayCompletions? - public void setSuggestedWords(final SuggestedWords suggestedWords) { + public void setSuggestedWords(final SuggestedWords suggestedWords, + final SettingsValues settingsValues, final LatinIME.UIHandler handler) { if (SuggestedWords.EMPTY != suggestedWords) { final String autoCorrection; if (suggestedWords.mWillAutoCorrect) { @@ -569,6 +613,7 @@ public final class InputLogic { } mSuggestedWords = suggestedWords; final boolean newAutoCorrectionIndicator = suggestedWords.mWillAutoCorrect; + // Put a blue underline to a word in TextView which will be auto-corrected. if (mIsAutoCorrectionIndicatorOn != newAutoCorrectionIndicator && mWordComposer.isComposingWord()) { @@ -579,7 +624,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); } } @@ -602,7 +647,7 @@ public final class InputLogic { inputTransaction.setDidAffectContents(); } if (mWordComposer.isComposingWord()) { - mConnection.setComposingText(mWordComposer.getTypedWord(), 1); + setComposingTextInternal(mWordComposer.getTypedWord(), 1); inputTransaction.setDidAffectContents(); inputTransaction.setRequiresUpdateSuggestions(); } @@ -632,7 +677,7 @@ public final class InputLogic { case Constants.CODE_SHIFT: performRecapitalization(inputTransaction.mSettingsValues); inputTransaction.requireShiftUpdate(InputTransaction.SHIFT_UPDATE_NOW); - if (mSuggestedWords.mIsPrediction) { + if (mSuggestedWords.isPrediction()) { inputTransaction.setRequiresUpdateSuggestions(); } break; @@ -745,6 +790,16 @@ public final class InputLogic { final InputTransaction inputTransaction, // TODO: remove this argument final LatinIME.UIHandler handler) { + if (!mWordComposer.isComposingWord()) { + mConnection.removeBackgroundColorFromHighlightedTextIfNecessary(); + // In case the "add to dictionary" hint was still displayed. + // TODO: Do we really need to check if we have composing text here? + if (mSuggestionStripViewAccessor.isShowingAddToDictionaryHint()) { + mSuggestionStripViewAccessor.dismissAddToDictionaryHint(); + mTextDecorator.reset(); + } + } + final int codePoint = event.mCodePoint; mSpaceState = SpaceState.NONE; if (inputTransaction.mSettingsValues.isWordSeparator(codePoint) @@ -829,8 +884,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); @@ -840,8 +894,6 @@ public final class InputLogic { } else { sendKeyCodePoint(settingsValues, codePoint); } - // In case the "add to dictionary" hint was still displayed. - mSuggestionStripViewAccessor.dismissAddToDictionaryHint(); } inputTransaction.setRequiresUpdateSuggestions(); } @@ -995,14 +1047,14 @@ 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); } inputTransaction.setRequiresUpdateSuggestions(); } else { if (mLastComposedWord.canRevertCommit()) { - revertCommit(inputTransaction); + revertCommit(inputTransaction, inputTransaction.mSettingsValues); return; } if (mEnteredText != null && mConnection.sameAsTextBeforeCursor(mEnteredText)) { @@ -1299,7 +1351,8 @@ public final class InputLogic { prevWordsInfo, timeStampInSeconds, settingsValues.mBlockPotentiallyOffensive); } - public void performUpdateSuggestionStripSync(final SettingsValues settingsValues) { + public void performUpdateSuggestionStripSync(final SettingsValues settingsValues, + final int inputStyle) { // Check if we have a suggestion engine attached. if (!settingsValues.needsToLookupSuggestions()) { if (mWordComposer.isComposingWord()) { @@ -1317,8 +1370,8 @@ public final class InputLogic { } final AsyncResultHolder<SuggestedWords> holder = new AsyncResultHolder<>(); - mInputLogicHandler.getSuggestedWords(Suggest.SESSION_TYPING, - SuggestedWords.NOT_A_SEQUENCE_NUMBER, new OnGetSuggestedWordsCallback() { + mInputLogicHandler.getSuggestedWords(inputStyle, SuggestedWords.NOT_A_SEQUENCE_NUMBER, + new OnGetSuggestedWordsCallback() { @Override public void onGetSuggestedWords(final SuggestedWords suggestedWords) { final String typedWord = mWordComposer.getTypedWord(); @@ -1379,12 +1432,11 @@ public final class InputLogic { if (!mConnection.isCursorTouchingWord(settingsValues.mSpacingAndPunctuations)) { // Show predictions. mWordComposer.setCapitalizedModeAtStartComposingTime(WordComposer.CAPS_MODE_OFF); - mLatinIME.mHandler.postUpdateSuggestionStrip(); + mLatinIME.mHandler.postUpdateSuggestionStrip(SuggestedWords.INPUT_STYLE_RECORRECTION); return; } final TextRange range = mConnection.getWordRangeAtCursor( - settingsValues.mSpacingAndPunctuations.mSortedWordSeparators, - currentKeyboardScriptId); + settingsValues.mSpacingAndPunctuations, currentKeyboardScriptId); if (null == range) return; // Happens if we don't have an input connection at all if (range.length() <= 0) { // Race condition, or touching a word in a non-supported script. @@ -1444,7 +1496,7 @@ public final class InputLogic { // If there weren't any suggestion spans on this word, suggestions#size() will be 1 // if shouldIncludeResumedWordInSuggestions is true, 0 otherwise. In this case, we // have no useful suggestions, so we will try to compute some for it instead. - mInputLogicHandler.getSuggestedWords(Suggest.SESSION_TYPING, + mInputLogicHandler.getSuggestedWords(Suggest.SESSION_ID_TYPING, SuggestedWords.NOT_A_SEQUENCE_NUMBER, new OnGetSuggestedWordsCallback() { @Override public void onGetSuggestedWords( @@ -1454,10 +1506,10 @@ public final class InputLogic { && !shouldIncludeResumedWordInSuggestions) { // We were able to compute new suggestions for this word. // Remove the typed word, since we don't want to display it in this - // case. The #getSuggestedWordsExcludingTypedWord() method sets - // willAutoCorrect to false. + // case. The #getSuggestedWordsExcludingTypedWordForRecorrection() + // method sets willAutoCorrect to false. suggestedWords = suggestedWordsIncludingTypedWord - .getSuggestedWordsExcludingTypedWord(); + .getSuggestedWordsExcludingTypedWordForRecorrection(); } else { // No saved suggestions, and we were unable to compute any good one // either. Rather than displaying an empty suggestion strip, we'll @@ -1474,10 +1526,9 @@ public final class InputLogic { // color of the word in the suggestion strip changes according to this parameter, // and false gives the correct color. final SuggestedWords suggestedWords = new SuggestedWords(suggestions, - null /* rawSuggestions */, typedWord, - false /* typedWordValid */, false /* willAutoCorrect */, - false /* isObsoleteSuggestions */, false /* isPrediction */, - SuggestedWords.NOT_A_SEQUENCE_NUMBER); + null /* rawSuggestions */, typedWord, false /* typedWordValid */, + false /* willAutoCorrect */, false /* isObsoleteSuggestions */, + SuggestedWords.INPUT_STYLE_RECORRECTION, SuggestedWords.NOT_A_SEQUENCE_NUMBER); mIsAutoCorrectionIndicatorOn = false; mLatinIME.mHandler.showSuggestionStrip(suggestedWords); } @@ -1489,14 +1540,19 @@ public final class InputLogic { * This is triggered upon pressing backspace just after a commit with auto-correction. * * @param inputTransaction The transaction in progress. + * @param settingsValues the current values of the settings. */ - private void revertCommit(final InputTransaction inputTransaction) { + private void revertCommit(final InputTransaction inputTransaction, + final SettingsValues settingsValues) { final CharSequence originallyTypedWord = mLastComposedWord.mTypedWord; + final String originallyTypedWordString = + originallyTypedWord != null ? originallyTypedWord.toString() : ""; final CharSequence committedWord = mLastComposedWord.mCommittedWord; final String committedWordString = committedWord.toString(); final int cancelLength = committedWord.length(); + final String separatorString = mLastComposedWord.mSeparatorString; // We want java chars, not codepoints for the following. - final int separatorLength = mLastComposedWord.mSeparatorString.length(); + final int separatorLength = separatorString.length(); // TODO: should we check our saved separator against the actual contents of the text view? final int deleteLength = cancelLength + separatorLength; if (DebugFlags.DEBUG_ENABLED) { @@ -1515,7 +1571,7 @@ public final class InputLogic { if (!TextUtils.isEmpty(committedWord)) { mDictionaryFacilitator.removeWordFromPersonalizedDicts(committedWordString); } - final String stringToCommit = originallyTypedWord + mLastComposedWord.mSeparatorString; + final String stringToCommit = originallyTypedWord + separatorString; final SpannableString textToCommit = new SpannableString(stringToCommit); if (committedWord instanceof SpannableString) { final SpannableString committedWordWithSuggestionSpans = (SpannableString)committedWord; @@ -1552,23 +1608,53 @@ public final class InputLogic { suggestions.toArray(new String[suggestions.size()]), 0 /* flags */), 0 /* start */, lastCharIndex /* end */, 0 /* flags */); } + + final boolean shouldShowAddToDictionaryForTypedWord = + shouldShowAddToDictionaryForTypedWord(mLastComposedWord, settingsValues); + if (inputTransaction.mSettingsValues.mSpacingAndPunctuations.mCurrentLanguageHasSpaces) { // For languages with spaces, we revert to the typed string, but the cursor is still // after the separator so we don't resume suggestions. If the user wants to correct // the word, they have to press backspace again. - mConnection.commitText(textToCommit, 1); + if (shouldShowAddToDictionaryForTypedWord) { + mConnection.commitTextWithBackgroundColor(textToCommit, 1, + settingsValues.mTextHighlightColorForAddToDictionaryIndicator, + originallyTypedWordString.length()); + } else { + mConnection.commitText(textToCommit, 1); + } } else { // For languages without spaces, we revert the typed string but the cursor is flush // with the typed word, so we need to resume suggestions right away. final int[] codePoints = StringUtils.toCodePointArray(stringToCommit); mWordComposer.setComposingWord(codePoints, mLatinIME.getCoordinatesForCurrentKeyboard(codePoints)); - mConnection.setComposingText(textToCommit, 1); + if (shouldShowAddToDictionaryForTypedWord) { + setComposingTextInternalWithBackgroundColor(textToCommit, 1, + settingsValues.mTextHighlightColorForAddToDictionaryIndicator, + originallyTypedWordString.length()); + } else { + setComposingTextInternal(textToCommit, 1); + } } // Don't restart suggestion yet. We'll restart if the user deletes the separator. mLastComposedWord = LastComposedWord.NOT_A_COMPOSED_WORD; - // We have a separator between the word and the cursor: we should show predictions. - inputTransaction.setRequiresUpdateSuggestions(); + + if (shouldShowAddToDictionaryForTypedWord) { + // Due to the API limitation as of L, we cannot reliably retrieve the reverted text + // when the separator causes line breaking. Until this API limitation is addressed in + // the framework, show the indicator only when the separator doesn't contain + // line-breaking characters. + if (!StringUtils.hasLineBreakCharacter(separatorString)) { + mTextDecorator.showAddToDictionaryIndicator(originallyTypedWordString, + mConnection.getExpectedSelectionStart(), + mConnection.getExpectedSelectionEnd()); + } + mSuggestionStripViewAccessor.showAddToDictionaryHint(originallyTypedWordString); + } else { + // We have a separator between the word and the cursor: we should show predictions. + inputTransaction.setRequiresUpdateSuggestions(); + } } /** @@ -1773,7 +1859,7 @@ public final class InputLogic { SuggestedWords.getTypedWordAndPreviousSuggestions(typedWord, oldSuggestedWords); return new SuggestedWords(typedWordAndPreviousSuggestions, null /* rawSuggestions */, false /* typedWordValid */, false /* hasAutoCorrectionCandidate */, - true /* isObsoleteSuggestions */, false /* isPrediction */); + true /* isObsoleteSuggestions */, oldSuggestedWords.mInputStyle); } /** @@ -1896,10 +1982,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 @@ -1956,7 +2042,15 @@ public final class InputLogic { // Complete any pending suggestions query first if (handler.hasPendingUpdateSuggestions()) { handler.cancelUpdateSuggestionStrip(); - performUpdateSuggestionStripSync(settingsValues); + // To know the input style here, we should retrieve the in-flight "update suggestions" + // message and read its arg1 member here. However, the Handler class does not let + // us retrieve this message, so we can't do that. But in fact, we notice that + // we only ever come here when the input style was typing. In the case of batch + // input, we update the suggestions synchronously when the tail batch comes. Likewise + // for application-specified completions. As for recorrections, we never auto-correct, + // so we don't come here either. Hence, the input style is necessarily + // INPUT_STYLE_TYPING. + performUpdateSuggestionStripSync(settingsValues, SuggestedWords.INPUT_STYLE_TYPING); } final String typedAutoCorrection = mWordComposer.getAutoCorrectionOrNull(); final String typedWord = mWordComposer.getTypedWord(); @@ -2052,7 +2146,7 @@ public final class InputLogic { } public void getSuggestedWords(final SettingsValues settingsValues, - final ProximityInfo proximityInfo, final int keyboardShiftMode, final int sessionId, + final ProximityInfo proximityInfo, final int keyboardShiftMode, final int inputStyle, final int sequenceNumber, final OnGetSuggestedWordsCallback callback) { mWordComposer.adviseCapitalizedModeBeforeFetchingSuggestions( getActualCapsMode(settingsValues, keyboardShiftMode)); @@ -2068,6 +2162,124 @@ public final class InputLogic { settingsValues.mPhraseGestureEnabled, settingsValues.mAdditionalFeaturesSettingValues), settingsValues.mAutoCorrectionEnabledPerUserSettings, - sessionId, sequenceNumber, callback); + 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) { + setComposingTextInternalWithBackgroundColor(newComposingText, newCursorPosition, + Color.TRANSPARENT, newComposingText.length()); + } + + /** + * Equivalent to {@link #setComposingTextInternal(CharSequence, int)} except that this method + * allows to set {@link BackgroundColorSpan} to the composing text with the given color. + * + * <p>TODO: Currently the background color is exclusive with the black underline, which is + * automatically added by the framework. We need to change the framework if we need to have both + * of them at the same time.</p> + * <p>TODO: Should we move this method to {@link RichInputConnection}?</p> + * + * @param newComposingText the composing text to be set + * @param newCursorPosition the new cursor position + * @param backgroundColor the background color to be set to the composing text. Set + * {@link Color#TRANSPARENT} to disable the background color. + * @param coloredTextLength the length of text, in Java chars, which should be rendered with + * the given background color. + */ + private void setComposingTextInternalWithBackgroundColor(final CharSequence newComposingText, + final int newCursorPosition, final int backgroundColor, final int coloredTextLength) { + final CharSequence composingTextToBeSet; + if (backgroundColor == Color.TRANSPARENT) { + composingTextToBeSet = newComposingText; + } else { + final SpannableString spannable = new SpannableString(newComposingText); + final BackgroundColorSpan backgroundColorSpan = + new BackgroundColorSpan(backgroundColor); + final int spanLength = Math.min(coloredTextLength, spannable.length()); + spannable.setSpan(backgroundColorSpan, 0, spanLength, + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING); + composingTextToBeSet = spannable; + } + mConnection.setComposingText(composingTextToBeSet, newCursorPosition); + } + + ////////////////////////////////////////////////////////////////////////////////////////////// + // Following methods are tentatively placed in this class for the integration with + // TextDecorator. + // TODO: Decouple things that are not related to the input logic. + ////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Sets the UI operator for {@link TextDecorator}. + * @param uiOperator the UI operator which should be associated with {@link TextDecorator}. + */ + public void setTextDecoratorUi(final TextDecoratorUiOperator uiOperator) { + mTextDecorator.setUiOperator(uiOperator); + } + + /** + * Must be called from {@link InputMethodService#onUpdateCursorAnchorInfo(CursorAnchorInfo)} is + * called. + * @param info The wrapper object with which we can access cursor/anchor info. + */ + public void onUpdateCursorAnchorInfo(final CursorAnchorInfoCompatWrapper info) { + mTextDecorator.onUpdateCursorAnchorInfo(info); + } + + /** + * Must be called when {@link InputMethodService#updateFullscreenMode} is called. + * @param isFullscreen {@code true} if the input method is in full-screen mode. + */ + public void onUpdateFullscreenMode(final boolean isFullscreen) { + mTextDecorator.notifyFullScreenMode(isFullscreen); + } + + /** + * Must be called from {@link LatinIME#addWordToUserDictionary(String)}. + */ + public void onAddWordToUserDictionary() { + mConnection.removeBackgroundColorFromHighlightedTextIfNecessary(); + mTextDecorator.reset(); + } + + /** + * Returns whether the add to dictionary indicator should be shown or not. + * @param lastComposedWord the last composed word information. + * @param settingsValues the current settings value. + * @return {@code true} if the commit indicator should be shown. + */ + private boolean shouldShowAddToDictionaryForTypedWord(final LastComposedWord lastComposedWord, + final SettingsValues settingsValues) { + if (!mConnection.isCursorAnchorInfoMonitorEnabled()) { + // We cannot help in this case because we are heavily relying on this new API. + return false; + } + if (!settingsValues.mShouldShowUiToAcceptTypedWord) { + return false; + } + if (TextUtils.isEmpty(lastComposedWord.mTypedWord)) { + return false; + } + if (TextUtils.equals(lastComposedWord.mTypedWord, lastComposedWord.mCommittedWord)) { + return false; + } + if (!mDictionaryFacilitator.isUserDictionaryEnabled()) { + return false; + } + return !mDictionaryFacilitator.isValidWord(lastComposedWord.mTypedWord, + true /* ignoreCase */); } } diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogicHandler.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogicHandler.java index 9dbe2c38b..c6f83d0b9 100644 --- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogicHandler.java +++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogicHandler.java @@ -96,7 +96,7 @@ class InputLogicHandler implements Handler.Callback { public boolean handleMessage(final Message msg) { switch (msg.what) { case MSG_GET_SUGGESTED_WORDS: - mLatinIME.getSuggestedWords(msg.arg1 /* sessionId */, + mLatinIME.getSuggestedWords(msg.arg1 /* inputStyle */, msg.arg2 /* sequenceNumber */, (OnGetSuggestedWordsCallback) msg.obj); break; } @@ -134,7 +134,8 @@ class InputLogicHandler implements Handler.Callback { return; } mInputLogic.mWordComposer.setBatchInputPointers(batchPointers); - getSuggestedWords(Suggest.SESSION_GESTURE, sequenceNumber, + getSuggestedWords(isTailBatchInput ? SuggestedWords.INPUT_STYLE_TAIL_BATCH + : SuggestedWords.INPUT_STYLE_UPDATE_BATCH, sequenceNumber, new OnGetSuggestedWordsCallback() { @Override public void onGetSuggestedWords(SuggestedWords suggestedWords) { @@ -205,9 +206,9 @@ class InputLogicHandler implements Handler.Callback { updateBatchInput(batchPointers, sequenceNumber, true /* isTailBatchInput */); } - public void getSuggestedWords(final int sessionId, final int sequenceNumber, + public void getSuggestedWords(final int inputStyle, final int sequenceNumber, final OnGetSuggestedWordsCallback callback) { mNonUIThreadHandler.obtainMessage( - MSG_GET_SUGGESTED_WORDS, sessionId, sequenceNumber, callback).sendToTarget(); + MSG_GET_SUGGESTED_WORDS, inputStyle, sequenceNumber, callback).sendToTarget(); } } |