aboutsummaryrefslogtreecommitdiffstats
path: root/java/src/com/android/inputmethod/latin/inputlogic
diff options
context:
space:
mode:
Diffstat (limited to 'java/src/com/android/inputmethod/latin/inputlogic')
-rw-r--r--java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java688
-rw-r--r--java/src/com/android/inputmethod/latin/inputlogic/InputLogicHandler.java9
2 files changed, 487 insertions, 210 deletions
diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
index 74d879919..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);
}
/**
@@ -206,7 +234,7 @@ public final class InputLogic {
final int keyboardShiftMode,
// TODO: remove this argument
final LatinIME.UIHandler handler) {
- final String rawText = event.mText.toString();
+ final String rawText = event.getTextToCommit().toString();
final InputTransaction inputTransaction = new InputTransaction(settingsValues, event,
SystemClock.uptimeMillis(), mSpaceState,
getActualCapsMode(settingsValues, keyboardShiftMode));
@@ -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 */);
@@ -405,128 +446,44 @@ public final class InputLogic {
final int keyboardShiftMode,
// TODO: remove these arguments
final int currentKeyboardScriptId, final LatinIME.UIHandler handler) {
- final InputTransaction inputTransaction = new InputTransaction(settingsValues, event,
- SystemClock.uptimeMillis(), mSpaceState,
+ final Event processedEvent = mWordComposer.processEvent(event);
+ final InputTransaction inputTransaction = new InputTransaction(settingsValues,
+ processedEvent, SystemClock.uptimeMillis(), mSpaceState,
getActualCapsMode(settingsValues, keyboardShiftMode));
- if (event.mKeyCode != Constants.CODE_DELETE
+ if (processedEvent.mKeyCode != Constants.CODE_DELETE
|| inputTransaction.mTimestamp > mLastKeyTime + Constants.LONG_PRESS_MILLISECONDS) {
mDeleteCount = 0;
}
mLastKeyTime = inputTransaction.mTimestamp;
mConnection.beginBatchEdit();
if (!mWordComposer.isComposingWord()) {
+ // TODO: is this useful? It doesn't look like it should be done here, but rather after
+ // a word is committed.
mIsAutoCorrectionIndicatorOn = false;
}
// TODO: Consolidate the double-space period timer, mLastKeyTime, and the space state.
- if (event.mCodePoint != Constants.CODE_SPACE) {
+ if (processedEvent.mCodePoint != Constants.CODE_SPACE) {
cancelDoubleSpacePeriodCountdown();
}
- boolean didAutoCorrect = false;
- if (event.isFunctionalKeyEvent()) {
- // A special key, like delete, shift, emoji, or the settings key.
- switch (event.mKeyCode) {
- case Constants.CODE_DELETE:
- handleBackspace(inputTransaction, currentKeyboardScriptId);
- // Backspace is a functional key, but it affects the contents of the editor.
- inputTransaction.setDidAffectContents();
- break;
- case Constants.CODE_SHIFT:
- performRecapitalization(inputTransaction.mSettingsValues);
- inputTransaction.requireShiftUpdate(InputTransaction.SHIFT_UPDATE_NOW);
- if (mSuggestedWords.mIsPrediction) {
- inputTransaction.setRequiresUpdateSuggestions();
- }
- break;
- case Constants.CODE_CAPSLOCK:
- // Note: Changing keyboard to shift lock state is handled in
- // {@link KeyboardSwitcher#onCodeInput(int)}.
- break;
- case Constants.CODE_SYMBOL_SHIFT:
- // Note: Calling back to the keyboard on the symbol Shift key is handled in
- // {@link #onPressKey(int,int,boolean)} and {@link #onReleaseKey(int,boolean)}.
- break;
- case Constants.CODE_SWITCH_ALPHA_SYMBOL:
- // Note: Calling back to the keyboard on symbol key is handled in
- // {@link #onPressKey(int,int,boolean)} and {@link #onReleaseKey(int,boolean)}.
- break;
- case Constants.CODE_SETTINGS:
- onSettingsKeyPressed();
- break;
- case Constants.CODE_SHORTCUT:
- // We need to switch to the shortcut IME. This is handled by LatinIME since the
- // input logic has no business with IME switching.
- break;
- case Constants.CODE_ACTION_NEXT:
- performEditorAction(EditorInfo.IME_ACTION_NEXT);
- break;
- case Constants.CODE_ACTION_PREVIOUS:
- performEditorAction(EditorInfo.IME_ACTION_PREVIOUS);
- break;
- case Constants.CODE_LANGUAGE_SWITCH:
- handleLanguageSwitchKey();
- break;
- case Constants.CODE_EMOJI:
- // Note: Switching emoji keyboard is being handled in
- // {@link KeyboardState#onCodeInput(int,int)}.
- break;
- case Constants.CODE_ALPHA_FROM_EMOJI:
- // Note: Switching back from Emoji keyboard to the main keyboard is being
- // handled in {@link KeyboardState#onCodeInput(int,int)}.
- break;
- case Constants.CODE_SHIFT_ENTER:
- // TODO: remove this object
- final Event tmpEvent = Event.createSoftwareKeypressEvent(Constants.CODE_ENTER,
- event.mKeyCode, event.mX, event.mY, event.isKeyRepeat());
- final InputTransaction tmpTransaction = new InputTransaction(
- inputTransaction.mSettingsValues, tmpEvent,
- inputTransaction.mTimestamp, inputTransaction.mSpaceState,
- inputTransaction.mShiftState);
- didAutoCorrect = handleNonSpecialCharacter(tmpTransaction, handler);
- // Shift + Enter is treated as a functional key but it results in adding a new
- // line, so that does affect the contents of the editor.
- inputTransaction.setDidAffectContents();
- break;
- default:
- throw new RuntimeException("Unknown key code : " + event.mKeyCode);
- }
- } else {
- inputTransaction.setDidAffectContents();
- switch (event.mCodePoint) {
- case Constants.CODE_ENTER:
- final EditorInfo editorInfo = getCurrentInputEditorInfo();
- final int imeOptionsActionId =
- InputTypeUtils.getImeOptionsActionIdFromEditorInfo(editorInfo);
- if (InputTypeUtils.IME_ACTION_CUSTOM_LABEL == imeOptionsActionId) {
- // Either we have an actionLabel and we should performEditorAction with
- // actionId regardless of its value.
- performEditorAction(editorInfo.actionId);
- } else if (EditorInfo.IME_ACTION_NONE != imeOptionsActionId) {
- // We didn't have an actionLabel, but we had another action to execute.
- // EditorInfo.IME_ACTION_NONE explicitly means no action. In contrast,
- // EditorInfo.IME_ACTION_UNSPECIFIED is the default value for an action, so it
- // means there should be an action and the app didn't bother to set a specific
- // code for it - presumably it only handles one. It does not have to be treated
- // in any specific way: anything that is not IME_ACTION_NONE should be sent to
- // performEditorAction.
- performEditorAction(imeOptionsActionId);
- } else {
- // No action label, and the action from imeOptions is NONE: this is a regular
- // enter key that should input a carriage return.
- didAutoCorrect = handleNonSpecialCharacter(inputTransaction, handler);
- }
- break;
- default:
- didAutoCorrect = handleNonSpecialCharacter(inputTransaction, handler);
- break;
+ Event currentEvent = processedEvent;
+ while (null != currentEvent) {
+ if (currentEvent.isConsumed()) {
+ handleConsumedEvent(currentEvent, inputTransaction);
+ } else if (currentEvent.isFunctionalKeyEvent()) {
+ handleFunctionalEvent(currentEvent, inputTransaction, currentKeyboardScriptId,
+ handler);
+ } else {
+ handleNonFunctionalEvent(currentEvent, inputTransaction, handler);
}
+ currentEvent = currentEvent.mNextEvent;
}
- if (!didAutoCorrect && event.mKeyCode != Constants.CODE_SHIFT
- && event.mKeyCode != Constants.CODE_CAPSLOCK
- && event.mKeyCode != Constants.CODE_SWITCH_ALPHA_SYMBOL)
+ if (!inputTransaction.didAutoCorrect() && processedEvent.mKeyCode != Constants.CODE_SHIFT
+ && processedEvent.mKeyCode != Constants.CODE_CAPSLOCK
+ && processedEvent.mKeyCode != Constants.CODE_SWITCH_ALPHA_SYMBOL)
mLastComposedWord.deactivate();
- if (Constants.CODE_DELETE != event.mKeyCode) {
+ if (Constants.CODE_DELETE != processedEvent.mKeyCode) {
mEnteredText = null;
}
mConnection.endBatchEdit();
@@ -542,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.
@@ -639,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) {
@@ -653,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()) {
@@ -663,7 +624,154 @@ 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);
+ }
+ }
+
+ /**
+ * Handle a consumed event.
+ *
+ * Consumed events represent events that have already been consumed, typically by the
+ * combining chain.
+ *
+ * @param event The event to handle.
+ * @param inputTransaction The transaction in progress.
+ */
+ private void handleConsumedEvent(final Event event, final InputTransaction inputTransaction) {
+ // A consumed event may have text to commit and an update to the composing state, so
+ // we evaluate both. With some combiners, it's possible than an event contains both
+ // and we enter both of the following if clauses.
+ final CharSequence textToCommit = event.getTextToCommit();
+ if (!TextUtils.isEmpty(textToCommit)) {
+ mConnection.commitText(textToCommit, 1);
+ inputTransaction.setDidAffectContents();
+ }
+ if (mWordComposer.isComposingWord()) {
+ setComposingTextInternal(mWordComposer.getTypedWord(), 1);
+ inputTransaction.setDidAffectContents();
+ inputTransaction.setRequiresUpdateSuggestions();
+ }
+ }
+
+ /**
+ * Handle a functional key event.
+ *
+ * A functional event is a special key, like delete, shift, emoji, or the settings key.
+ * Non-special keys are those that generate a single code point.
+ * This includes all letters, digits, punctuation, separators, emoji. It excludes keys that
+ * manage keyboard-related stuff like shift, language switch, settings, layout switch, or
+ * any key that results in multiple code points like the ".com" key.
+ *
+ * @param event The event to handle.
+ * @param inputTransaction The transaction in progress.
+ */
+ private void handleFunctionalEvent(final Event event, final InputTransaction inputTransaction,
+ // TODO: remove these arguments
+ final int currentKeyboardScriptId, final LatinIME.UIHandler handler) {
+ switch (event.mKeyCode) {
+ case Constants.CODE_DELETE:
+ handleBackspaceEvent(event, inputTransaction, currentKeyboardScriptId);
+ // Backspace is a functional key, but it affects the contents of the editor.
+ inputTransaction.setDidAffectContents();
+ break;
+ case Constants.CODE_SHIFT:
+ performRecapitalization(inputTransaction.mSettingsValues);
+ inputTransaction.requireShiftUpdate(InputTransaction.SHIFT_UPDATE_NOW);
+ if (mSuggestedWords.isPrediction()) {
+ inputTransaction.setRequiresUpdateSuggestions();
+ }
+ break;
+ case Constants.CODE_CAPSLOCK:
+ // Note: Changing keyboard to shift lock state is handled in
+ // {@link KeyboardSwitcher#onCodeInput(int)}.
+ break;
+ case Constants.CODE_SYMBOL_SHIFT:
+ // Note: Calling back to the keyboard on the symbol Shift key is handled in
+ // {@link #onPressKey(int,int,boolean)} and {@link #onReleaseKey(int,boolean)}.
+ break;
+ case Constants.CODE_SWITCH_ALPHA_SYMBOL:
+ // Note: Calling back to the keyboard on symbol key is handled in
+ // {@link #onPressKey(int,int,boolean)} and {@link #onReleaseKey(int,boolean)}.
+ break;
+ case Constants.CODE_SETTINGS:
+ onSettingsKeyPressed();
+ break;
+ case Constants.CODE_SHORTCUT:
+ // We need to switch to the shortcut IME. This is handled by LatinIME since the
+ // input logic has no business with IME switching.
+ break;
+ case Constants.CODE_ACTION_NEXT:
+ performEditorAction(EditorInfo.IME_ACTION_NEXT);
+ break;
+ case Constants.CODE_ACTION_PREVIOUS:
+ performEditorAction(EditorInfo.IME_ACTION_PREVIOUS);
+ break;
+ case Constants.CODE_LANGUAGE_SWITCH:
+ handleLanguageSwitchKey();
+ break;
+ case Constants.CODE_EMOJI:
+ // Note: Switching emoji keyboard is being handled in
+ // {@link KeyboardState#onCodeInput(int,int)}.
+ break;
+ case Constants.CODE_ALPHA_FROM_EMOJI:
+ // Note: Switching back from Emoji keyboard to the main keyboard is being
+ // handled in {@link KeyboardState#onCodeInput(int,int)}.
+ break;
+ case Constants.CODE_SHIFT_ENTER:
+ // TODO: remove this object
+ final Event tmpEvent = Event.createSoftwareKeypressEvent(Constants.CODE_ENTER,
+ event.mKeyCode, event.mX, event.mY, event.isKeyRepeat());
+ handleNonSpecialCharacterEvent(tmpEvent, inputTransaction, handler);
+ // Shift + Enter is treated as a functional key but it results in adding a new
+ // line, so that does affect the contents of the editor.
+ inputTransaction.setDidAffectContents();
+ break;
+ default:
+ throw new RuntimeException("Unknown key code : " + event.mKeyCode);
+ }
+ }
+
+ /**
+ * Handle an event that is not a functional event.
+ *
+ * These events are generally events that cause input, but in some cases they may do other
+ * things like trigger an editor action.
+ *
+ * @param event The event to handle.
+ * @param inputTransaction The transaction in progress.
+ */
+ private void handleNonFunctionalEvent(final Event event,
+ final InputTransaction inputTransaction,
+ // TODO: remove this argument
+ final LatinIME.UIHandler handler) {
+ inputTransaction.setDidAffectContents();
+ switch (event.mCodePoint) {
+ case Constants.CODE_ENTER:
+ final EditorInfo editorInfo = getCurrentInputEditorInfo();
+ final int imeOptionsActionId =
+ InputTypeUtils.getImeOptionsActionIdFromEditorInfo(editorInfo);
+ if (InputTypeUtils.IME_ACTION_CUSTOM_LABEL == imeOptionsActionId) {
+ // Either we have an actionLabel and we should performEditorAction with
+ // actionId regardless of its value.
+ performEditorAction(editorInfo.actionId);
+ } else if (EditorInfo.IME_ACTION_NONE != imeOptionsActionId) {
+ // We didn't have an actionLabel, but we had another action to execute.
+ // EditorInfo.IME_ACTION_NONE explicitly means no action. In contrast,
+ // EditorInfo.IME_ACTION_UNSPECIFIED is the default value for an action, so it
+ // means there should be an action and the app didn't bother to set a specific
+ // code for it - presumably it only handles one. It does not have to be treated
+ // in any specific way: anything that is not IME_ACTION_NONE should be sent to
+ // performEditorAction.
+ performEditorAction(imeOptionsActionId);
+ } else {
+ // No action label, and the action from imeOptions is NONE: this is a regular
+ // enter key that should input a carriage return.
+ handleNonSpecialCharacterEvent(event, inputTransaction, handler);
+ }
+ break;
+ default:
+ handleNonSpecialCharacterEvent(event, inputTransaction, handler);
+ break;
}
}
@@ -675,21 +783,29 @@ public final class InputLogic {
* manage keyboard-related stuff like shift, language switch, settings, layout switch, or
* any key that results in multiple code points like the ".com" key.
*
+ * @param event The event to handle.
* @param inputTransaction The transaction in progress.
- * @return whether this caused an auto-correction to happen.
*/
- private boolean handleNonSpecialCharacter(final InputTransaction inputTransaction,
+ private void handleNonSpecialCharacterEvent(final Event event,
+ final InputTransaction inputTransaction,
// TODO: remove this argument
final LatinIME.UIHandler handler) {
- final int codePoint = inputTransaction.mEvent.mCodePoint;
+ 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;
- final boolean didAutoCorrect;
if (inputTransaction.mSettingsValues.isWordSeparator(codePoint)
|| Character.getType(codePoint) == Character.OTHER_SYMBOL) {
- didAutoCorrect = handleSeparator(inputTransaction,
- inputTransaction.mEvent.isSuggestionStripPress(), handler);
+ handleSeparatorEvent(event, inputTransaction, handler);
} else {
- didAutoCorrect = false;
if (SpaceState.PHANTOM == inputTransaction.mSpaceState) {
if (mWordComposer.isCursorFrontOrMiddleOfComposingWord()) {
// If we are in the middle of a recorrection, we need to commit the recorrection
@@ -700,22 +816,23 @@ public final class InputLogic {
commitTyped(inputTransaction.mSettingsValues, LastComposedWord.NOT_A_SEPARATOR);
}
}
- handleNonSeparator(inputTransaction.mSettingsValues, inputTransaction);
+ handleNonSeparatorEvent(event, inputTransaction.mSettingsValues, inputTransaction);
}
- return didAutoCorrect;
}
/**
* Handle a non-separator.
+ * @param event The event to handle.
* @param settingsValues The current settings values.
* @param inputTransaction The transaction in progress.
*/
- private void handleNonSeparator(final SettingsValues settingsValues,
+ private void handleNonSeparatorEvent(final Event event, final SettingsValues settingsValues,
final InputTransaction inputTransaction) {
- final int codePoint = inputTransaction.mEvent.mCodePoint;
+ final int codePoint = event.mCodePoint;
// TODO: refactor this method to stop flipping isComposingWord around all the time, and
- // make it shorter (possibly cut into several pieces). Also factor handleNonSpecialCharacter
- // which has the same name as other handle* methods but is not the same.
+ // make it shorter (possibly cut into several pieces). Also factor
+ // handleNonSpecialCharacterEvent which has the same name as other handle* methods but is
+ // not the same.
boolean isComposingWord = mWordComposer.isComposingWord();
// TODO: remove isWordConnector() and use isUsuallyFollowedBySpace() instead.
@@ -762,41 +879,35 @@ public final class InputLogic {
resetComposingState(false /* alsoResetLastComposedWord */);
}
if (isComposingWord) {
- mWordComposer.processEvent(inputTransaction.mEvent);
+ mWordComposer.applyProcessedEvent(event);
// If it's the first letter, make note of auto-caps state
if (mWordComposer.isSingleLetter()) {
mWordComposer.setCapitalizedModeAtStartComposingTime(inputTransaction.mShiftState);
}
- mConnection.setComposingText(getTextWithUnderline(
- mWordComposer.getTypedWord()), 1);
+ setComposingTextInternal(getTextWithUnderline(mWordComposer.getTypedWord()), 1);
} else {
- final boolean swapWeakSpace = tryStripSpaceAndReturnWhetherShouldSwapInstead(
- inputTransaction, inputTransaction.mEvent.isSuggestionStripPress());
+ final boolean swapWeakSpace = tryStripSpaceAndReturnWhetherShouldSwapInstead(event,
+ inputTransaction);
- if (swapWeakSpace && trySwapSwapperAndSpace(inputTransaction)) {
+ if (swapWeakSpace && trySwapSwapperAndSpace(event, inputTransaction)) {
mSpaceState = SpaceState.WEAK;
} else {
sendKeyCodePoint(settingsValues, codePoint);
}
- // In case the "add to dictionary" hint was still displayed.
- mSuggestionStripViewAccessor.dismissAddToDictionaryHint();
}
inputTransaction.setRequiresUpdateSuggestions();
}
/**
* Handle input of a separator code point.
+ * @param event The event to handle.
* @param inputTransaction The transaction in progress.
- * @param isFromSuggestionStrip whether this code point comes from the suggestion strip.
- * @return whether this caused an auto-correction to happen.
*/
- private boolean handleSeparator(final InputTransaction inputTransaction,
- final boolean isFromSuggestionStrip,
+ private void handleSeparatorEvent(final Event event, final InputTransaction inputTransaction,
// TODO: remove this argument
final LatinIME.UIHandler handler) {
- final int codePoint = inputTransaction.mEvent.mCodePoint;
+ final int codePoint = event.mCodePoint;
final SettingsValues settingsValues = inputTransaction.mSettingsValues;
- boolean didAutoCorrect = false;
final boolean wasComposingWord = mWordComposer.isComposingWord();
// We avoid sending spaces in languages without spaces if we were composing.
final boolean shouldAvoidSendingCode = Constants.CODE_SPACE == codePoint
@@ -814,15 +925,15 @@ public final class InputLogic {
final String separator = shouldAvoidSendingCode ? LastComposedWord.NOT_A_SEPARATOR
: StringUtils.newSingleCodePointString(codePoint);
commitCurrentAutoCorrection(settingsValues, separator, handler);
- didAutoCorrect = true;
+ inputTransaction.setDidAutoCorrect();
} else {
commitTyped(settingsValues,
StringUtils.newSingleCodePointString(codePoint));
}
}
- final boolean swapWeakSpace = tryStripSpaceAndReturnWhetherShouldSwapInstead(
- inputTransaction, isFromSuggestionStrip);
+ final boolean swapWeakSpace = tryStripSpaceAndReturnWhetherShouldSwapInstead(event,
+ inputTransaction);
final boolean isInsideDoubleQuoteOrAfterDigit = Constants.CODE_DOUBLE_QUOTE == codePoint
&& mConnection.isInsideDoubleQuoteOrAfterDigit();
@@ -846,10 +957,10 @@ public final class InputLogic {
promotePhantomSpace(settingsValues);
}
- if (tryPerformDoubleSpacePeriod(inputTransaction)) {
+ if (tryPerformDoubleSpacePeriod(event, inputTransaction)) {
mSpaceState = SpaceState.DOUBLE;
inputTransaction.setRequiresUpdateSuggestions();
- } else if (swapWeakSpace && trySwapSwapperAndSpace(inputTransaction)) {
+ } else if (swapWeakSpace && trySwapSwapperAndSpace(event, inputTransaction)) {
mSpaceState = SpaceState.SWAP_PUNCTUATION;
mSuggestionStripViewAccessor.setNeutralSuggestionStrip();
} else if (Constants.CODE_SPACE == codePoint) {
@@ -892,14 +1003,14 @@ public final class InputLogic {
}
inputTransaction.requireShiftUpdate(InputTransaction.SHIFT_UPDATE_NOW);
- return didAutoCorrect;
}
/**
* Handle a press on the backspace key.
+ * @param event The event to handle.
* @param inputTransaction The transaction in progress.
*/
- private void handleBackspace(final InputTransaction inputTransaction,
+ private void handleBackspaceEvent(final Event event, final InputTransaction inputTransaction,
// TODO: remove this argument, put it into settingsValues
final int currentKeyboardScriptId) {
mSpaceState = SpaceState.NONE;
@@ -913,7 +1024,7 @@ public final class InputLogic {
// Then again, even in the case of a key repeat, if the cursor is at start of text, it
// can't go any further back, so we can update right away even if it's a key repeat.
final int shiftUpdateKind =
- inputTransaction.mEvent.isKeyRepeat() && mConnection.getExpectedSelectionStart() > 0
+ event.isKeyRepeat() && mConnection.getExpectedSelectionStart() > 0
? InputTransaction.SHIFT_UPDATE_LATER : InputTransaction.SHIFT_UPDATE_NOW;
inputTransaction.requireShiftUpdate(shiftUpdateKind);
@@ -933,17 +1044,17 @@ public final class InputLogic {
mDictionaryFacilitator.removeWordFromPersonalizedDicts(rejectedSuggestion);
}
} else {
- mWordComposer.processEvent(inputTransaction.mEvent);
+ 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)) {
@@ -1052,16 +1163,18 @@ public final class InputLogic {
*
* This method will check that there are two characters before the cursor and that the first
* one is a space before it does the actual swapping.
+ * @param event The event to handle.
* @param inputTransaction The transaction in progress.
* @return true if the swap has been performed, false if it was prevented by preliminary checks.
*/
- private boolean trySwapSwapperAndSpace(final InputTransaction inputTransaction) {
+ private boolean trySwapSwapperAndSpace(final Event event,
+ final InputTransaction inputTransaction) {
final int codePointBeforeCursor = mConnection.getCodePointBeforeCursor();
if (Constants.CODE_SPACE != codePointBeforeCursor) {
return false;
}
mConnection.deleteSurroundingText(1, 0);
- final String text = inputTransaction.mEvent.getTextToCommit() + " ";
+ final String text = event.getTextToCommit() + " ";
mConnection.commitText(text, 1);
inputTransaction.requireShiftUpdate(InputTransaction.SHIFT_UPDATE_NOW);
return true;
@@ -1069,13 +1182,14 @@ public final class InputLogic {
/*
* Strip a trailing space if necessary and returns whether it's a swap weak space situation.
+ * @param event The event to handle.
* @param inputTransaction The transaction in progress.
- * @param isFromSuggestionStrip Whether this code point is coming from the suggestion strip.
* @return whether we should swap the space instead of removing it.
*/
- private boolean tryStripSpaceAndReturnWhetherShouldSwapInstead(
- final InputTransaction inputTransaction, final boolean isFromSuggestionStrip) {
- final int codePoint = inputTransaction.mEvent.mCodePoint;
+ private boolean tryStripSpaceAndReturnWhetherShouldSwapInstead(final Event event,
+ final InputTransaction inputTransaction) {
+ final int codePoint = event.mCodePoint;
+ final boolean isFromSuggestionStrip = event.isSuggestionStripPress();
if (Constants.CODE_ENTER == codePoint &&
SpaceState.SWAP_PUNCTUATION == inputTransaction.mSpaceState) {
mConnection.removeTrailingSpace();
@@ -1120,14 +1234,16 @@ public final class InputLogic {
* these conditions are fulfilled, this method applies the transformation and returns true.
* Otherwise, it does nothing and returns false.
*
+ * @param event The event to handle.
* @param inputTransaction The transaction in progress.
* @return true if we applied the double-space-to-period transformation, false otherwise.
*/
- private boolean tryPerformDoubleSpacePeriod(final InputTransaction inputTransaction) {
+ private boolean tryPerformDoubleSpacePeriod(final Event event,
+ final InputTransaction inputTransaction) {
// Check the setting, the typed character and the countdown. If any of the conditions is
// not fulfilled, return false.
if (!inputTransaction.mSettingsValues.mUseDoubleSpacePeriod
- || Constants.CODE_SPACE != inputTransaction.mEvent.mCodePoint
+ || Constants.CODE_SPACE != event.mCodePoint
|| !isDoubleSpacePeriodCountdownActive(inputTransaction)) {
return false;
}
@@ -1235,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()) {
@@ -1253,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();
@@ -1315,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.
@@ -1373,13 +1489,14 @@ public final class InputLogic {
mLatinIME.getCoordinatesForCurrentKeyboard(codePoints));
mWordComposer.setCursorPositionWithinWord(
typedWord.codePointCount(0, numberOfCharsInWordBeforeCursor));
+ mConnection.maybeMoveTheCursorAroundAndRestoreToWorkaroundABug();
mConnection.setComposingRegion(expectedCursorPosition - numberOfCharsInWordBeforeCursor,
expectedCursorPosition + range.getNumberOfCharsInWordAfterCursor());
if (suggestions.size() <= (shouldIncludeResumedWordInSuggestions ? 1 : 0)) {
// 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(
@@ -1389,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
@@ -1409,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);
}
@@ -1424,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) {
@@ -1450,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;
@@ -1487,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();
+ }
}
/**
@@ -1708,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);
}
/**
@@ -1831,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
@@ -1891,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();
@@ -1955,14 +2114,13 @@ public final class InputLogic {
* This method handles the retry, and re-schedules a new retry if we still can't access.
* We only retry up to 5 times before giving up.
*
- * @param settingsValues the current values of the settings.
* @param tryResumeSuggestions Whether we should resume suggestions or not.
* @param remainingTries How many times we may try again before giving up.
* @return whether true if the caches were successfully reset, false otherwise.
*/
// TODO: make this private
- public boolean retryResetCachesAndReturnSuccess(final SettingsValues settingsValues,
- final boolean tryResumeSuggestions, final int remainingTries,
+ public boolean retryResetCachesAndReturnSuccess(final boolean tryResumeSuggestions,
+ final int remainingTries,
// TODO: remove these arguments
final LatinIME.UIHandler handler) {
final boolean shouldFinishComposition = mConnection.hasSelection()
@@ -1988,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));
@@ -2004,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();
}
}