diff options
Diffstat (limited to 'java/src/com/android/inputmethod/latin/LatinIME.java')
-rw-r--r-- | java/src/com/android/inputmethod/latin/LatinIME.java | 1306 |
1 files changed, 241 insertions, 1065 deletions
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index 67b570277..ab9b2e598 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -76,6 +76,8 @@ import com.android.inputmethod.keyboard.MainKeyboardView; import com.android.inputmethod.latin.Suggest.OnGetSuggestedWordsCallback; import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; import com.android.inputmethod.latin.define.ProductionFlag; +import com.android.inputmethod.latin.inputlogic.InputLogic; +import com.android.inputmethod.latin.inputlogic.SpaceState; import com.android.inputmethod.latin.personalization.DictionaryDecayBroadcastReciever; import com.android.inputmethod.latin.personalization.PersonalizationDictionarySessionRegister; import com.android.inputmethod.latin.personalization.UserHistoryDictionary; @@ -115,15 +117,11 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen Suggest.SuggestInitializationListener { private static final String TAG = LatinIME.class.getSimpleName(); private static final boolean TRACE = false; - private static boolean DEBUG; + // TODO[IL]: Make this private + public static boolean DEBUG; private static final int EXTENDED_TOUCHABLE_REGION_HEIGHT = 100; - // How many continuous deletes at which to start deleting at a higher speed. - private static final int DELETE_ACCELERATE_AT = 20; - // Key events coming any faster than this are long-presses. - private static final int QUICK_PRESS = 200; - private static final int PENDING_IMS_CALLBACK_DURATION = 800; private static final int PERIOD_FOR_AUDIO_AND_HAPTIC_FEEDBACK_IN_KEY_REPEAT = 2; @@ -137,79 +135,38 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen */ private static final String SCHEME_PACKAGE = "package"; - private static final int SPACE_STATE_NONE = 0; - // Double space: the state where the user pressed space twice quickly, which LatinIME - // resolved as period-space. Undoing this converts the period to a space. - private static final int SPACE_STATE_DOUBLE = 1; - // Swap punctuation: the state where a weak space and a punctuation from the suggestion strip - // have just been swapped. Undoing this swaps them back; the space is still considered weak. - private static final int SPACE_STATE_SWAP_PUNCTUATION = 2; - // Weak space: a space that should be swapped only by suggestion strip punctuation. Weak - // spaces happen when the user presses space, accepting the current suggestion (whether - // it's an auto-correction or not). - private static final int SPACE_STATE_WEAK = 3; - // Phantom space: a not-yet-inserted space that should get inserted on the next input, - // character provided it's not a separator. If it's a separator, the phantom space is dropped. - // Phantom spaces happen when a user chooses a word from the suggestion strip. - private static final int SPACE_STATE_PHANTOM = 4; - - // Current space state of the input method. This can be any of the above constants. - private int mSpaceState; - private final Settings mSettings; + private final InputLogic mInputLogic = new InputLogic(this); private View mExtractArea; private View mKeyPreviewBackingView; private SuggestionStripView mSuggestionStripView; - // Never null - private SuggestedWords mSuggestedWords = SuggestedWords.EMPTY; - private Suggest mSuggest; + private CompletionInfo[] mApplicationSpecifiedCompletions; - private AppWorkaroundsUtils mAppWorkAroundsUtils = new AppWorkaroundsUtils(); + // TODO[IL]: Make this an AsyncResultHolder or a Future in SettingsValues + public AppWorkaroundsUtils mAppWorkAroundsUtils = new AppWorkaroundsUtils(); private RichInputMethodManager mRichImm; @UsedForTesting final KeyboardSwitcher mKeyboardSwitcher; private final SubtypeSwitcher mSubtypeSwitcher; private final SubtypeState mSubtypeState = new SubtypeState(); - // At start, create a default event interpreter that does nothing by passing it no decoder spec. - // The event interpreter should never be null. - private EventInterpreter mEventInterpreter = new EventInterpreter(this); private boolean mIsMainDictionaryAvailable; private UserBinaryDictionary mUserDictionary; private boolean mIsUserDictionaryAvailable; - private LastComposedWord mLastComposedWord = LastComposedWord.NOT_A_COMPOSED_WORD; - private final WordComposer mWordComposer = new WordComposer(); - private final RichInputConnection mConnection = new RichInputConnection(this); - private final RecapitalizeStatus mRecapitalizeStatus = new RecapitalizeStatus(); - - // Keep track of the last selection range to decide if we need to show word alternatives - private static final int NOT_A_CURSOR_POSITION = -1; - private int mLastSelectionStart = NOT_A_CURSOR_POSITION; - private int mLastSelectionEnd = NOT_A_CURSOR_POSITION; - - private int mDeleteCount; - private long mLastKeyTime; - private final TreeSet<Long> mCurrentlyPressedHardwareKeys = CollectionUtils.newTreeSet(); // Personalization debugging params private boolean mUseOnlyPersonalizationDictionaryForDebug = false; private boolean mBoostPersonalizationDictionaryForDebug = false; - // Member variables for remembering the current device orientation. - private int mDisplayOrientation; + // Member variable for remembering the current device orientation. + // TODO[IL]: Move this to SettingsValues. + public int mDisplayOrientation; // Object for reacting to adding/removing a dictionary pack. private BroadcastReceiver mDictionaryPackInstallReceiver = new DictionaryPackInstallBroadcastReceiver(this); - // Keeps track of most recently inserted text (multi-character key) for reverting - private String mEnteredText; - - // TODO: This boolean is persistent state and causes large side effects at unexpected times. - // Find a way to remove it for readability. - private boolean mIsAutoCorrectionIndicatorOn; - private AlertDialog mOptionsDialog; private final boolean mIsHardwareAcceleratedDrawingEnabled; @@ -547,7 +504,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen initSuggest(); if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - ResearchLogger.getInstance().init(this, mKeyboardSwitcher, mSuggest); + ResearchLogger.getInstance().init(this, mKeyboardSwitcher, mInputLogic.mSuggest); } mDisplayOrientation = getResources().getConfiguration().orientation; @@ -588,9 +545,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // the layout; at this time, we need to skip resetting the contacts dictionary. It will // be done later inside {@see #initSuggest()} when the reopenDictionaries message is // processed. - if (!mHandler.hasPendingReopenDictionaries() && mSuggest != null) { + if (!mHandler.hasPendingReopenDictionaries() && mInputLogic.mSuggest != null) { // May need to reset dictionaries depending on the user settings. - mSuggest.setAdditionalDictionaries(mSuggest /* oldSuggest */, mSettings.getCurrent()); + mInputLogic.mSuggest.setAdditionalDictionaries(mInputLogic.mSuggest /* oldSuggest */, + mSettings.getCurrent()); } } @@ -636,24 +594,26 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mUserDictionary = new UserBinaryDictionary(this, subtypeLocale); mIsUserDictionaryAvailable = mUserDictionary.isEnabled(); newSuggest.setUserDictionary(mUserDictionary); - newSuggest.setAdditionalDictionaries(mSuggest /* oldSuggest */, mSettings.getCurrent()); - final Suggest oldSuggest = mSuggest; - mSuggest = newSuggest; + newSuggest.setAdditionalDictionaries(mInputLogic.mSuggest /* oldSuggest */, + mSettings.getCurrent()); + final Suggest oldSuggest = mInputLogic.mSuggest; + mInputLogic.mSuggest = newSuggest; if (oldSuggest != null) oldSuggest.close(); } /* package private */ void resetSuggestMainDict() { final Locale subtypeLocale = mSubtypeSwitcher.getCurrentSubtypeLocale(); - mSuggest.resetMainDict(this, subtypeLocale, this /* SuggestInitializationListener */); + mInputLogic.mSuggest.resetMainDict(this, subtypeLocale, + this /* SuggestInitializationListener */); mIsMainDictionaryAvailable = DictionaryFactory.isDictionaryAvailable(this, subtypeLocale); } @Override public void onDestroy() { - final Suggest suggest = mSuggest; + final Suggest suggest = mInputLogic.mSuggest; if (suggest != null) { suggest.close(); - mSuggest = null; + mInputLogic.mSuggest = null; } mSettings.onDestroy(); unregisterReceiver(mReceiver); @@ -676,10 +636,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen if (mDisplayOrientation != conf.orientation) { mDisplayOrientation = conf.orientation; mHandler.startOrientationChanging(); - mConnection.beginBatchEdit(); - commitTyped(LastComposedWord.NOT_A_SEPARATOR); - mConnection.finishComposingText(); - mConnection.endBatchEdit(); + mInputLogic.mConnection.beginBatchEdit(); + mInputLogic.commitTyped(LastComposedWord.NOT_A_SEPARATOR); + mInputLogic.mConnection.finishComposingText(); + mInputLogic.mConnection.endBatchEdit(); if (isShowingOptionDialog()) { mOptionsDialog.dismiss(); } @@ -823,16 +783,16 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // The app calling setText() has the effect of clearing the composing // span, so we should reset our state unconditionally, even if restarting is true. - mEnteredText = null; - resetComposingState(true /* alsoResetLastComposedWord */); - mDeleteCount = 0; - mSpaceState = SPACE_STATE_NONE; - mRecapitalizeStatus.deactivate(); - mCurrentlyPressedHardwareKeys.clear(); + mInputLogic.mEnteredText = null; + mInputLogic.resetComposingState(true /* alsoResetLastComposedWord */); + mInputLogic.mDeleteCount = 0; + mInputLogic.mSpaceState = SpaceState.NONE; + mInputLogic.mRecapitalizeStatus.deactivate(); + mInputLogic.mCurrentlyPressedHardwareKeys.clear(); // Note: the following does a round-trip IPC on the main thread: be careful final Locale currentLocale = mSubtypeSwitcher.getCurrentSubtypeLocale(); - final Suggest suggest = mSuggest; + final Suggest suggest = mInputLogic.mSuggest; if (null != suggest && null != currentLocale && !currentLocale.equals(suggest.mLocale)) { initSuggest(); } @@ -841,14 +801,15 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // otherwise it will clear the suggestion strip. setPunctuationSuggestions(); } - mSuggestedWords = SuggestedWords.EMPTY; + mInputLogic.mSuggestedWords = SuggestedWords.EMPTY; // Sometimes, while rotating, for some reason the framework tells the app we are not // connected to it and that means we can't refresh the cache. In this case, schedule a // refresh later. final boolean canReachInputConnection; - if (!mConnection.resetCachesUponCursorMoveAndReturnSuccess(editorInfo.initialSelStart, - editorInfo.initialSelEnd, false /* shouldFinishComposition */)) { + if (!mInputLogic.mConnection.resetCachesUponCursorMoveAndReturnSuccess( + editorInfo.initialSelStart, editorInfo.initialSelEnd, + false /* shouldFinishComposition */)) { // We try resetting the caches up to 5 times before giving up. mHandler.postResetCaches(isDifferentTextField, 5 /* remainingTries */); // mLastSelection{Start,End} are reset later in this method, don't need to do it here @@ -888,8 +849,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen setSuggestionStripShownInternal( isSuggestionsStripVisible(), /* needsInputViewShown */ false); - mLastSelectionStart = editorInfo.initialSelStart; - mLastSelectionEnd = editorInfo.initialSelEnd; + mInputLogic.mLastSelectionStart = editorInfo.initialSelStart; + mInputLogic.mLastSelectionEnd = editorInfo.initialSelEnd; // In some cases (namely, after rotation of the device) editorInfo.initialSelStart is lying // so we try using some heuristics to find out about these and fix them. tryFixLyingCursorPosition(); @@ -922,26 +883,29 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen * than it really is. */ private void tryFixLyingCursorPosition() { - final CharSequence textBeforeCursor = - mConnection.getTextBeforeCursor(Constants.EDITOR_CONTENTS_CACHE_SIZE, 0); + final CharSequence textBeforeCursor = mInputLogic.mConnection.getTextBeforeCursor( + Constants.EDITOR_CONTENTS_CACHE_SIZE, 0); if (null == textBeforeCursor) { - mLastSelectionStart = mLastSelectionEnd = NOT_A_CURSOR_POSITION; + mInputLogic.mLastSelectionStart = mInputLogic.mLastSelectionEnd = + Constants.NOT_A_CURSOR_POSITION; } else { final int textLength = textBeforeCursor.length(); - if (textLength > mLastSelectionStart + if (textLength > mInputLogic.mLastSelectionStart || (textLength < Constants.EDITOR_CONTENTS_CACHE_SIZE - && mLastSelectionStart < Constants.EDITOR_CONTENTS_CACHE_SIZE)) { + && mInputLogic.mLastSelectionStart < + Constants.EDITOR_CONTENTS_CACHE_SIZE)) { // It should not be possible to have only one of those variables be // NOT_A_CURSOR_POSITION, so if they are equal, either the selection is zero-sized // (simple cursor, no selection) or there is no cursor/we don't know its pos - final boolean wasEqual = mLastSelectionStart == mLastSelectionEnd; - mLastSelectionStart = textLength; + final boolean wasEqual = + mInputLogic.mLastSelectionStart == mInputLogic.mLastSelectionEnd; + mInputLogic.mLastSelectionStart = textLength; // We can't figure out the value of mLastSelectionEnd :( // But at least if it's smaller than mLastSelectionStart something is wrong, // and if they used to be equal we also don't want to make it look like there is a // selection. - if (wasEqual || mLastSelectionStart > mLastSelectionEnd) { - mLastSelectionEnd = mLastSelectionStart; + if (wasEqual || mInputLogic.mLastSelectionStart > mInputLogic.mLastSelectionEnd) { + mInputLogic.mLastSelectionEnd = mInputLogic.mLastSelectionStart; } } } @@ -998,12 +962,15 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // Remove pending messages related to update suggestions mHandler.cancelUpdateSuggestionStrip(); // Should do the following in onFinishInputInternal but until JB MR2 it's not called :( - if (mWordComposer.isComposingWord()) mConnection.finishComposingText(); - resetComposingState(true /* alsoResetLastComposedWord */); + if (mInputLogic.mWordComposer.isComposingWord()) { + mInputLogic.mConnection.finishComposingText(); + } + mInputLogic.resetComposingState(true /* alsoResetLastComposedWord */); // Notify ResearchLogger if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - ResearchLogger.latinIME_onFinishInputViewInternal(finishingInput, mLastSelectionStart, - mLastSelectionEnd, getCurrentInputConnection()); + ResearchLogger.latinIME_onFinishInputViewInternal(finishingInput, + mInputLogic.mLastSelectionStart, + mInputLogic.mLastSelectionEnd, getCurrentInputConnection()); } } @@ -1016,21 +983,22 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen if (DEBUG) { Log.i(TAG, "onUpdateSelection: oss=" + oldSelStart + ", ose=" + oldSelEnd - + ", lss=" + mLastSelectionStart - + ", lse=" + mLastSelectionEnd + + ", lss=" + mInputLogic.mLastSelectionStart + + ", lse=" + mInputLogic.mLastSelectionEnd + ", nss=" + newSelStart + ", nse=" + newSelEnd + ", cs=" + composingSpanStart + ", ce=" + composingSpanEnd); } if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - ResearchLogger.latinIME_onUpdateSelection(mLastSelectionStart, mLastSelectionEnd, + ResearchLogger.latinIME_onUpdateSelection(mInputLogic.mLastSelectionStart, + mInputLogic.mLastSelectionEnd, oldSelStart, oldSelEnd, newSelStart, newSelEnd, composingSpanStart, - composingSpanEnd, mConnection); + composingSpanEnd, mInputLogic.mConnection); } - final boolean selectionChanged = mLastSelectionStart != newSelStart - || mLastSelectionEnd != newSelEnd; + final boolean selectionChanged = mInputLogic.mLastSelectionStart != newSelStart + || mInputLogic.mLastSelectionEnd != newSelEnd; // if composingSpanStart and composingSpanEnd are -1, it means there is no composing // span in the view - we can use that to narrow down whether the cursor was moved @@ -1045,24 +1013,25 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // TODO: revisit this when LatinIME supports hardware keyboards. // NOTE: the test harness subclasses LatinIME and overrides isInputViewShown(). // TODO: find a better way to simulate actual execution. - if (isInputViewShown() && !mConnection.isBelatedExpectedUpdate(oldSelStart, newSelStart, - oldSelEnd, newSelEnd)) { + if (isInputViewShown() && !mInputLogic.mConnection.isBelatedExpectedUpdate(oldSelStart, + newSelStart, oldSelEnd, newSelEnd)) { // TODO: the following is probably better done in resetEntireInputState(). // it should only happen when the cursor moved, and the very purpose of the // test below is to narrow down whether this happened or not. Likewise with // the call to updateShiftState. // We set this to NONE because after a cursor move, we don't want the space // state-related special processing to kick in. - mSpaceState = SPACE_STATE_NONE; + mInputLogic.mSpaceState = SpaceState.NONE; // TODO: is it still necessary to test for composingSpan related stuff? final boolean selectionChangedOrSafeToReset = selectionChanged - || (!mWordComposer.isComposingWord()) || noComposingSpan; + || (!mInputLogic.mWordComposer.isComposingWord()) || noComposingSpan; final boolean hasOrHadSelection = (oldSelStart != oldSelEnd || newSelStart != newSelEnd); final int moveAmount = newSelStart - oldSelStart; if (selectionChangedOrSafeToReset && (hasOrHadSelection - || !mWordComposer.moveCursorByAndReturnIfInsideComposingWord(moveAmount))) { + || !mInputLogic.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 @@ -1072,14 +1041,14 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // Another option would be to send suggestions each time we set the composing // text, but that is probably too expensive to do, so we decided to leave things // as is. - resetEntireInputState(newSelStart, newSelEnd); + mInputLogic.resetEntireInputState(mSettings.getCurrent(), newSelStart, newSelEnd); } else { // resetEntireInputState calls resetCachesUponCursorMove, but forcing the // composition to end. But in all cases where we don't reset the entire input // state, we still want to tell the rich input connection about the new cursor // position so that it can update its caches. - mConnection.resetCachesUponCursorMoveAndReturnSuccess(newSelStart, newSelEnd, - false /* shouldFinishComposition */); + mInputLogic.mConnection.resetCachesUponCursorMoveAndReturnSuccess( + newSelStart, newSelEnd, false /* shouldFinishComposition */); } // We moved the cursor. If we are touching a word, we need to resume suggestion, @@ -1088,13 +1057,13 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mHandler.postResumeSuggestions(); } // Reset the last recapitalization. - mRecapitalizeStatus.deactivate(); + mInputLogic.mRecapitalizeStatus.deactivate(); mKeyboardSwitcher.updateShiftState(); } // Make a note of the cursor position - mLastSelectionStart = newSelStart; - mLastSelectionEnd = newSelEnd; + mInputLogic.mLastSelectionStart = newSelStart; + mInputLogic.mLastSelectionEnd = newSelEnd; mSubtypeState.currentSubtypeUsed(); } @@ -1299,42 +1268,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mKeyPreviewBackingView.setVisibility(isFullscreenMode() ? View.GONE : View.VISIBLE); } - // This will reset the whole input state to the starting state. It will clear - // the composing word, reset the last composed word, tell the inputconnection about it. - private void resetEntireInputState(final int newSelStart, final int newSelEnd) { - final boolean shouldFinishComposition = mWordComposer.isComposingWord(); - resetComposingState(true /* alsoResetLastComposedWord */); - final SettingsValues settingsValues = mSettings.getCurrent(); - if (settingsValues.mBigramPredictionEnabled) { - clearSuggestionStrip(); - } else { - setSuggestedWords(settingsValues.mSuggestPuncList, false); - } - mConnection.resetCachesUponCursorMoveAndReturnSuccess(newSelStart, newSelEnd, - shouldFinishComposition); - } - - private void resetComposingState(final boolean alsoResetLastComposedWord) { - mWordComposer.reset(); - if (alsoResetLastComposedWord) { - mLastComposedWord = LastComposedWord.NOT_A_COMPOSED_WORD; - } - } - - private void commitTyped(final String separatorString) { - if (!mWordComposer.isComposingWord()) return; - final String typedWord = mWordComposer.getTypedWord(); - if (typedWord.length() > 0) { - if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - ResearchLogger.getInstance().onWordFinished(typedWord, mWordComposer.isBatchMode()); - } - commitChosenWord(typedWord, LastComposedWord.COMMIT_TYPE_USER_TYPED_WORD, - separatorString); - } - } - // Called from the KeyboardSwitcher which needs to know auto caps state to display // the right layout. + // TODO[IL]: Move this to InputLogic. public int getCurrentAutoCapsState() { final SettingsValues currentSettingsValues = mSettings.getCurrent(); if (!currentSettingsValues.mAutoCap) return Constants.TextUtils.CAP_MODE_OFF; @@ -1344,96 +1280,18 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen final int inputType = ei.inputType; // Warning: this depends on mSpaceState, which may not be the most current value. If // mSpaceState gets updated later, whoever called this may need to be told about it. - return mConnection.getCursorCapsMode(inputType, currentSettingsValues, - SPACE_STATE_PHANTOM == mSpaceState); + return mInputLogic.mConnection.getCursorCapsMode(inputType, currentSettingsValues, + SpaceState.PHANTOM == mInputLogic.mSpaceState); } public int getCurrentRecapitalizeState() { - if (!mRecapitalizeStatus.isActive() - || !mRecapitalizeStatus.isSetAt(mLastSelectionStart, mLastSelectionEnd)) { + if (!mInputLogic.mRecapitalizeStatus.isActive() + || !mInputLogic.mRecapitalizeStatus.isSetAt(mInputLogic.mLastSelectionStart, + mInputLogic.mLastSelectionEnd)) { // Not recapitalizing at the moment return RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE; } - return mRecapitalizeStatus.getCurrentMode(); - } - - // Factor in auto-caps and manual caps and compute the current caps mode. - private int getActualCapsMode() { - final int keyboardShiftMode = mKeyboardSwitcher.getKeyboardShiftMode(); - if (keyboardShiftMode != WordComposer.CAPS_MODE_AUTO_SHIFTED) return keyboardShiftMode; - final int auto = getCurrentAutoCapsState(); - if (0 != (auto & TextUtils.CAP_MODE_CHARACTERS)) { - return WordComposer.CAPS_MODE_AUTO_SHIFT_LOCKED; - } - if (0 != auto) { - return WordComposer.CAPS_MODE_AUTO_SHIFTED; - } - return WordComposer.CAPS_MODE_OFF; - } - - private void swapSwapperAndSpace() { - final CharSequence lastTwo = mConnection.getTextBeforeCursor(2, 0); - // It is guaranteed lastTwo.charAt(1) is a swapper - else this method is not called. - if (lastTwo != null && lastTwo.length() == 2 && lastTwo.charAt(0) == Constants.CODE_SPACE) { - mConnection.deleteSurroundingText(2, 0); - final String text = lastTwo.charAt(1) + " "; - mConnection.commitText(text, 1); - if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - ResearchLogger.latinIME_swapSwapperAndSpace(lastTwo, text); - } - mKeyboardSwitcher.updateShiftState(); - } - } - - private boolean maybeDoubleSpacePeriod() { - final SettingsValues currentSettingsValues = mSettings.getCurrent(); - if (!currentSettingsValues.mUseDoubleSpacePeriod) return false; - if (!mHandler.isAcceptingDoubleSpacePeriod()) return false; - // We only do this when we see two spaces and an accepted code point before the cursor. - // The code point may be a surrogate pair but the two spaces may not, so we need 4 chars. - final CharSequence lastThree = mConnection.getTextBeforeCursor(4, 0); - if (null == lastThree) return false; - final int length = lastThree.length(); - if (length < 3) return false; - if (lastThree.charAt(length - 1) != Constants.CODE_SPACE) return false; - if (lastThree.charAt(length - 2) != Constants.CODE_SPACE) return false; - // We know there are spaces in pos -1 and -2, and we have at least three chars. - // If we have only three chars, isSurrogatePairs can't return true as charAt(1) is a space, - // so this is fine. - final int firstCodePoint = - Character.isSurrogatePair(lastThree.charAt(0), lastThree.charAt(1)) ? - Character.codePointAt(lastThree, 0) : lastThree.charAt(length - 3); - if (canBeFollowedByDoubleSpacePeriod(firstCodePoint)) { - mHandler.cancelDoubleSpacePeriodTimer(); - mConnection.deleteSurroundingText(2, 0); - final String textToInsert = new String( - new int[] { currentSettingsValues.mSentenceSeparator, Constants.CODE_SPACE }, - 0, 2); - mConnection.commitText(textToInsert, 1); - if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - ResearchLogger.latinIME_maybeDoubleSpacePeriod(textToInsert, - false /* isBatchMode */); - } - mWordComposer.discardPreviousWordForSuggestion(); - mKeyboardSwitcher.updateShiftState(); - return true; - } - return false; - } - - private static boolean canBeFollowedByDoubleSpacePeriod(final int codePoint) { - // TODO: Check again whether there really ain't a better way to check this. - // TODO: This should probably be language-dependant... - return Character.isLetterOrDigit(codePoint) - || codePoint == Constants.CODE_SINGLE_QUOTE - || codePoint == Constants.CODE_DOUBLE_QUOTE - || codePoint == Constants.CODE_CLOSING_PARENTHESIS - || codePoint == Constants.CODE_CLOSING_SQUARE_BRACKET - || codePoint == Constants.CODE_CLOSING_CURLY_BRACKET - || codePoint == Constants.CODE_CLOSING_ANGLE_BRACKET - || codePoint == Constants.CODE_PLUS - || codePoint == Constants.CODE_PERCENT - || Character.getType(codePoint) == Character.OTHER_SYMBOL; + return mInputLogic.mRecapitalizeStatus.getCurrentMode(); } // Callback for the {@link SuggestionStripView}, to call when the "add to dictionary" hint is @@ -1445,7 +1303,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen return; } final String wordToEdit; - if (CapsModeUtils.isAutoCapsMode(mLastComposedWord.mCapitalizedMode)) { + if (CapsModeUtils.isAutoCapsMode(mInputLogic.mLastComposedWord.mCapitalizedMode)) { wordToEdit = word.toLowerCase(mSubtypeSwitcher.getCurrentSubtypeLocale()); } else { wordToEdit = word; @@ -1453,7 +1311,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mUserDictionary.addWordToUserDictionary(wordToEdit); } - private void onSettingsKeyPressed() { + // TODO[IL]: Rework the route through which this is called. + public void onSettingsKeyPressed() { if (isShowingOptionDialog()) return; showSubtypeSelectorAndSettings(); } @@ -1476,12 +1335,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen return mOptionsDialog != null && mOptionsDialog.isShowing(); } - private void performEditorAction(final int actionId) { - mConnection.performEditorAction(actionId); - } - // TODO: Revise the language switch key behavior to make it much smarter and more reasonable. - private void handleLanguageSwitchKey() { + // TODO[IL]: Move a part of this to InputLogic and straighten out the interface for this. + public void handleLanguageSwitchKey() { final IBinder token = getWindow().getWindow().getAttributes().token; if (mSettings.getCurrent().mIncludesOtherImesInLanguageSwitchList) { mRichImm.switchToNextInputMethod(token, false /* onlyCurrentIme */); @@ -1490,197 +1346,21 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mSubtypeState.switchSubtype(token, mRichImm); } - private void sendDownUpKeyEvent(final int code) { - final long eventTime = SystemClock.uptimeMillis(); - mConnection.sendKeyEvent(new KeyEvent(eventTime, eventTime, - KeyEvent.ACTION_DOWN, code, 0, 0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, - KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE)); - mConnection.sendKeyEvent(new KeyEvent(SystemClock.uptimeMillis(), eventTime, - KeyEvent.ACTION_UP, code, 0, 0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, - KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE)); - } - - private void sendKeyCodePoint(final int code) { - if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - ResearchLogger.latinIME_sendKeyCodePoint(code); - } - // TODO: Remove this special handling of digit letters. - // For backward compatibility. See {@link InputMethodService#sendKeyChar(char)}. - if (code >= '0' && code <= '9') { - sendDownUpKeyEvent(code - '0' + KeyEvent.KEYCODE_0); - return; - } - - if (Constants.CODE_ENTER == code && mAppWorkAroundsUtils.isBeforeJellyBean()) { - // Backward compatibility mode. Before Jelly bean, the keyboard would simulate - // a hardware keyboard event on pressing enter or delete. This is bad for many - // reasons (there are race conditions with commits) but some applications are - // relying on this behavior so we continue to support it for older apps. - sendDownUpKeyEvent(KeyEvent.KEYCODE_ENTER); - } else { - mConnection.commitText(StringUtils.newSingleCodePointString(code), 1); - } - } - // Implementation of {@link KeyboardActionListener}. @Override public void onCodeInput(final int primaryCode, final int x, final int y) { - if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - ResearchLogger.latinIME_onCodeInput(primaryCode, x, y); - } - final long when = SystemClock.uptimeMillis(); - if (primaryCode != Constants.CODE_DELETE || when > mLastKeyTime + QUICK_PRESS) { - mDeleteCount = 0; - } - mLastKeyTime = when; - mConnection.beginBatchEdit(); - final KeyboardSwitcher switcher = mKeyboardSwitcher; - // The space state depends only on the last character pressed and its own previous - // state. Here, we revert the space state to neutral if the key is actually modifying - // the input contents (any non-shift key), which is what we should do for - // all inputs that do not result in a special state. Each character handling is then - // free to override the state as they see fit. - final int spaceState = mSpaceState; - if (!mWordComposer.isComposingWord()) mIsAutoCorrectionIndicatorOn = false; - - // TODO: Consolidate the double-space period timer, mLastKeyTime, and the space state. - if (primaryCode != Constants.CODE_SPACE) { - mHandler.cancelDoubleSpacePeriodTimer(); - } - - boolean didAutoCorrect = false; - switch (primaryCode) { - case Constants.CODE_DELETE: - mSpaceState = SPACE_STATE_NONE; - handleBackspace(spaceState); - LatinImeLogger.logOnDelete(x, y); - break; - case Constants.CODE_SHIFT: - // Note: Calling back to the keyboard on Shift key is handled in - // {@link #onPressKey(int,int,boolean)} and {@link #onReleaseKey(int,boolean)}. - final Keyboard currentKeyboard = switcher.getKeyboard(); - if (null != currentKeyboard && currentKeyboard.mId.isAlphabetKeyboard()) { - // TODO: Instead of checking for alphabetic keyboard here, separate keycodes for - // alphabetic shift and shift while in symbol layout. - handleRecapitalize(); - } - break; - case Constants.CODE_CAPSLOCK: - // Note: Changing keyboard to shift lock state is handled in - // {@link KeyboardSwitcher#onCodeInput(int)}. - 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: - mSubtypeSwitcher.switchToShortcutIME(this); - 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_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(Constants.CODE_ENTER, x, y, spaceState); - } - break; - case Constants.CODE_SHIFT_ENTER: - didAutoCorrect = handleNonSpecialCharacter(Constants.CODE_ENTER, x, y, spaceState); - break; - default: - didAutoCorrect = handleNonSpecialCharacter(primaryCode, x, y, spaceState); - break; - } - switcher.onCodeInput(primaryCode); - // Reset after any single keystroke, except shift, capslock, and symbol-shift - if (!didAutoCorrect && primaryCode != Constants.CODE_SHIFT - && primaryCode != Constants.CODE_CAPSLOCK - && primaryCode != Constants.CODE_SWITCH_ALPHA_SYMBOL) - mLastComposedWord.deactivate(); - if (Constants.CODE_DELETE != primaryCode) { - mEnteredText = null; - } - mConnection.endBatchEdit(); - } - - private boolean handleNonSpecialCharacter(final int primaryCode, final int x, final int y, - final int spaceState) { - mSpaceState = SPACE_STATE_NONE; - final boolean didAutoCorrect; - final SettingsValues settingsValues = mSettings.getCurrent(); - if (settingsValues.isWordSeparator(primaryCode) - || Character.getType(primaryCode) == Character.OTHER_SYMBOL) { - didAutoCorrect = handleSeparator(primaryCode, x, y, spaceState); - } else { - didAutoCorrect = false; - if (SPACE_STATE_PHANTOM == spaceState) { - if (settingsValues.mIsInternal) { - if (mWordComposer.isComposingWord() && mWordComposer.isBatchMode()) { - LatinImeLoggerUtils.onAutoCorrection( - "", mWordComposer.getTypedWord(), " ", mWordComposer); - } - } - 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 character at the current cursor position. - resetEntireInputState(mLastSelectionStart, mLastSelectionEnd); - } else { - commitTyped(LastComposedWord.NOT_A_SEPARATOR); - } - } - final int keyX, keyY; - final Keyboard keyboard = mKeyboardSwitcher.getKeyboard(); - if (keyboard != null && keyboard.hasProximityCharsCorrection(primaryCode)) { - keyX = x; - keyY = y; - } else { - keyX = Constants.NOT_A_COORDINATE; - keyY = Constants.NOT_A_COORDINATE; - } - handleCharacter(primaryCode, keyX, keyY, spaceState); - } - return didAutoCorrect; + mInputLogic.onCodeInput(primaryCode, x, y, mHandler, mKeyboardSwitcher, mSubtypeSwitcher); } // Called from PointerTracker through the KeyboardActionListener interface + // TODO[IL]: Move this to InputLogic @Override public void onTextInput(final String rawText) { - mConnection.beginBatchEdit(); - if (mWordComposer.isComposingWord()) { - commitCurrentAutoCorrection(rawText); + mInputLogic.mConnection.beginBatchEdit(); + if (mInputLogic.mWordComposer.isComposingWord()) { + mInputLogic.commitCurrentAutoCorrection(mSettings.getCurrent(), rawText, mHandler); } else { - resetComposingState(true /* alsoResetLastComposedWord */); + mInputLogic.resetComposingState(true /* alsoResetLastComposedWord */); } mHandler.postUpdateSuggestionStrip(); if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS @@ -1689,40 +1369,42 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen return; } final String text = specificTldProcessingOnTextInput(rawText); - if (SPACE_STATE_PHANTOM == mSpaceState) { - promotePhantomSpace(); + if (SpaceState.PHANTOM == mInputLogic.mSpaceState) { + mInputLogic.promotePhantomSpace(mSettings.getCurrent()); } - mConnection.commitText(text, 1); + mInputLogic.mConnection.commitText(text, 1); if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { ResearchLogger.latinIME_onTextInput(text, false /* isBatchMode */); } - mConnection.endBatchEdit(); + mInputLogic.mConnection.endBatchEdit(); // Space state must be updated before calling updateShiftState - mSpaceState = SPACE_STATE_NONE; + mInputLogic.mSpaceState = SpaceState.NONE; mKeyboardSwitcher.updateShiftState(); mKeyboardSwitcher.onCodeInput(Constants.CODE_OUTPUT_TEXT); - mEnteredText = text; + mInputLogic.mEnteredText = text; } @Override public void onStartBatchInput() { mInputUpdater.onStartBatchInput(); mHandler.cancelUpdateSuggestionStrip(); - mConnection.beginBatchEdit(); + mInputLogic.mConnection.beginBatchEdit(); final SettingsValues currentSettingsValues = mSettings.getCurrent(); - if (mWordComposer.isComposingWord()) { + if (mInputLogic.mWordComposer.isComposingWord()) { if (currentSettingsValues.mIsInternal) { - if (mWordComposer.isBatchMode()) { - LatinImeLoggerUtils.onAutoCorrection( - "", mWordComposer.getTypedWord(), " ", mWordComposer); + if (mInputLogic.mWordComposer.isBatchMode()) { + LatinImeLoggerUtils.onAutoCorrection("", + mInputLogic.mWordComposer.getTypedWord(), " ", + mInputLogic.mWordComposer); } } - final int wordComposerSize = mWordComposer.size(); + final int wordComposerSize = mInputLogic.mWordComposer.size(); // Since isComposingWord() is true, the size is at least 1. - if (mWordComposer.isCursorFrontOrMiddleOfComposingWord()) { + if (mInputLogic.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. - resetEntireInputState(mLastSelectionStart, mLastSelectionEnd); + mInputLogic.resetEntireInputState(currentSettingsValues, + mInputLogic.mLastSelectionStart, mInputLogic.mLastSelectionEnd); } else if (wordComposerSize <= 1) { // We auto-correct the previous (typed, not gestured) string iff it's one character // long. The reason for this is, even in the middle of gesture typing, you'll still @@ -1731,17 +1413,18 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // tapping probably is that the word you intend to type is not in the dictionary, // so we do not attempt to correct, on the assumption that if that was a dictionary // word, the user would probably have gestured instead. - commitCurrentAutoCorrection(LastComposedWord.NOT_A_SEPARATOR); + mInputLogic.commitCurrentAutoCorrection(currentSettingsValues, + LastComposedWord.NOT_A_SEPARATOR, mHandler); } else { - commitTyped(LastComposedWord.NOT_A_SEPARATOR); + mInputLogic.commitTyped(LastComposedWord.NOT_A_SEPARATOR); } } - final int codePointBeforeCursor = mConnection.getCodePointBeforeCursor(); + final int codePointBeforeCursor = mInputLogic.mConnection.getCodePointBeforeCursor(); if (Character.isLetterOrDigit(codePointBeforeCursor) || currentSettingsValues.isUsuallyFollowedBySpace(codePointBeforeCursor)) { final boolean autoShiftHasBeenOverriden = mKeyboardSwitcher.getKeyboardShiftMode() != getCurrentAutoCapsState(); - mSpaceState = SPACE_STATE_PHANTOM; + mInputLogic.mSpaceState = SpaceState.PHANTOM; if (!autoShiftHasBeenOverriden) { // When we change the space state, we need to update the shift state of the // keyboard unless it has been overridden manually. This is happening for example @@ -1751,10 +1434,12 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mKeyboardSwitcher.updateShiftState(); } } - mConnection.endBatchEdit(); - mWordComposer.setCapitalizedModeAndPreviousWordAtStartComposingTime(getActualCapsMode(), + mInputLogic.mConnection.endBatchEdit(); + mInputLogic.mWordComposer.setCapitalizedModeAndPreviousWordAtStartComposingTime( + mInputLogic.getActualCapsMode(mKeyboardSwitcher), // Prev word is 1st word before cursor - getNthPreviousWordForSuggestion(currentSettingsValues, 1 /* nthPreviousWord */)); + mInputLogic.getNthPreviousWordForSuggestion(currentSettingsValues, + 1 /* nthPreviousWord */)); } static final class InputUpdater implements Handler.Callback { @@ -1857,7 +1542,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // be synchronized. private void getSuggestedWordsGestureLocked(final InputPointers batchPointers, final int sequenceNumber, final OnGetSuggestedWordsCallback callback) { - mLatinIme.mWordComposer.setBatchInputPointers(batchPointers); + mLatinIme.mInputLogic.mWordComposer.setBatchInputPointers(batchPointers); mLatinIme.getSuggestedWordsOrOlderSuggestionsAsync(Suggest.SESSION_GESTURE, sequenceNumber, new OnGetSuggestedWordsCallback() { @Override @@ -1916,19 +1601,22 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen @Override public void onUpdateBatchInput(final InputPointers batchPointers) { if (mSettings.getCurrent().mPhraseGestureEnabled) { - final SuggestedWordInfo candidate = mSuggestedWords.getAutoCommitCandidate(); + final SuggestedWordInfo candidate = + mInputLogic.mSuggestedWords.getAutoCommitCandidate(); // If these suggested words have been generated with out of date input pointers, then // we skip auto-commit (see comments above on the mSequenceNumber member). - if (null != candidate && mSuggestedWords.mSequenceNumber >= mAutoCommitSequenceNumber) { + if (null != candidate + && mInputLogic.mSuggestedWords.mSequenceNumber >= mAutoCommitSequenceNumber) { if (candidate.mSourceDict.shouldAutoCommit(candidate)) { final String[] commitParts = candidate.mWord.split(" ", 2); batchPointers.shift(candidate.mIndexOfTouchPointOfSecondWord); - promotePhantomSpace(); - mConnection.commitText(commitParts[0], 0); - mSpaceState = SPACE_STATE_PHANTOM; + mInputLogic.promotePhantomSpace(mSettings.getCurrent()); + mInputLogic.mConnection.commitText(commitParts[0], 0); + mInputLogic.mSpaceState = SpaceState.PHANTOM; mKeyboardSwitcher.updateShiftState(); - mWordComposer.setCapitalizedModeAndPreviousWordAtStartComposingTime( - getActualCapsMode(), commitParts[0]); + mInputLogic.mWordComposer. + setCapitalizedModeAndPreviousWordAtStartComposingTime( + mInputLogic.getActualCapsMode(mKeyboardSwitcher), commitParts[0]); ++mAutoCommitSequenceNumber; } } @@ -1942,30 +1630,31 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen if (TextUtils.isEmpty(batchInputText)) { return; } - mConnection.beginBatchEdit(); - if (SPACE_STATE_PHANTOM == mSpaceState) { - promotePhantomSpace(); + mInputLogic.mConnection.beginBatchEdit(); + if (SpaceState.PHANTOM == mInputLogic.mSpaceState) { + mInputLogic.promotePhantomSpace(mSettings.getCurrent()); } if (mSettings.getCurrent().mPhraseGestureEnabled) { // Find the last space final int indexOfLastSpace = batchInputText.lastIndexOf(Constants.CODE_SPACE) + 1; if (0 != indexOfLastSpace) { - mConnection.commitText(batchInputText.substring(0, indexOfLastSpace), 1); + mInputLogic.mConnection.commitText(batchInputText.substring(0, indexOfLastSpace), + 1); showSuggestionStrip(suggestedWords.getSuggestedWordsForLastWordOfPhraseGesture()); } final String lastWord = batchInputText.substring(indexOfLastSpace); - mWordComposer.setBatchInputWord(lastWord); - mConnection.setComposingText(lastWord, 1); + mInputLogic.mWordComposer.setBatchInputWord(lastWord); + mInputLogic.mConnection.setComposingText(lastWord, 1); } else { - mWordComposer.setBatchInputWord(batchInputText); - mConnection.setComposingText(batchInputText, 1); + mInputLogic.mWordComposer.setBatchInputWord(batchInputText); + mInputLogic.mConnection.setComposingText(batchInputText, 1); } - mConnection.endBatchEdit(); + mInputLogic.mConnection.endBatchEdit(); if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { ResearchLogger.latinIME_onEndBatchInput(batchInputText, 0, suggestedWords); } // Space state must be updated before calling updateShiftState - mSpaceState = SPACE_STATE_PHANTOM; + mInputLogic.mSpaceState = SpaceState.PHANTOM; mKeyboardSwitcher.updateShiftState(); } @@ -1982,9 +1671,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } // We have a TLD (or something that looks like this): make sure we don't add // a space even if currently in phantom mode. - mSpaceState = SPACE_STATE_NONE; + mInputLogic.mSpaceState = SpaceState.NONE; // TODO: use getCodePointBeforeCursor instead to improve performance and simplify the code - final CharSequence lastOne = mConnection.getTextBeforeCursor(1, 0); + final CharSequence lastOne = mInputLogic.mConnection.getTextBeforeCursor(1, 0); if (lastOne != null && lastOne.length() == 1 && lastOne.charAt(0) == Constants.CODE_PERIOD) { return text.substring(1); @@ -2012,378 +1701,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mInputUpdater.onCancelBatchInput(); } - private void handleBackspace(final int spaceState) { - mDeleteCount++; - - // In many cases, we may have to put the keyboard in auto-shift state again. However - // we want to wait a few milliseconds before doing it to avoid the keyboard flashing - // during key repeat. - mHandler.postUpdateShiftState(); - - if (mWordComposer.isCursorFrontOrMiddleOfComposingWord()) { - // If we are in the middle of a recorrection, we need to commit the recorrection - // first so that we can remove the character at the current cursor position. - resetEntireInputState(mLastSelectionStart, mLastSelectionEnd); - // When we exit this if-clause, mWordComposer.isComposingWord() will return false. - } - if (mWordComposer.isComposingWord()) { - if (mWordComposer.isBatchMode()) { - if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - final String word = mWordComposer.getTypedWord(); - ResearchLogger.latinIME_handleBackspace_batch(word, 1); - } - final String rejectedSuggestion = mWordComposer.getTypedWord(); - mWordComposer.reset(); - mWordComposer.setRejectedBatchModeSuggestion(rejectedSuggestion); - } else { - mWordComposer.deleteLast(); - } - mConnection.setComposingText(getTextWithUnderline(mWordComposer.getTypedWord()), 1); - mHandler.postUpdateSuggestionStrip(); - if (!mWordComposer.isComposingWord()) { - // If we just removed the last character, auto-caps mode may have changed so we - // need to re-evaluate. - mKeyboardSwitcher.updateShiftState(); - } - } else { - final SettingsValues currentSettings = mSettings.getCurrent(); - if (mLastComposedWord.canRevertCommit()) { - if (currentSettings.mIsInternal) { - LatinImeLoggerUtils.onAutoCorrectionCancellation(); - } - revertCommit(); - return; - } - if (mEnteredText != null && mConnection.sameAsTextBeforeCursor(mEnteredText)) { - // Cancel multi-character input: remove the text we just entered. - // This is triggered on backspace after a key that inputs multiple characters, - // like the smiley key or the .com key. - mConnection.deleteSurroundingText(mEnteredText.length(), 0); - if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - ResearchLogger.latinIME_handleBackspace_cancelTextInput(mEnteredText); - } - mEnteredText = null; - // If we have mEnteredText, then we know that mHasUncommittedTypedChars == false. - // In addition we know that spaceState is false, and that we should not be - // reverting any autocorrect at this point. So we can safely return. - return; - } - if (SPACE_STATE_DOUBLE == spaceState) { - mHandler.cancelDoubleSpacePeriodTimer(); - if (mConnection.revertDoubleSpacePeriod()) { - // No need to reset mSpaceState, it has already be done (that's why we - // receive it as a parameter) - return; - } - } else if (SPACE_STATE_SWAP_PUNCTUATION == spaceState) { - if (mConnection.revertSwapPunctuation()) { - // Likewise - return; - } - } - - // No cancelling of commit/double space/swap: we have a regular backspace. - // We should backspace one char and restart suggestion if at the end of a word. - if (mLastSelectionStart != mLastSelectionEnd) { - // If there is a selection, remove it. - final int numCharsDeleted = mLastSelectionEnd - mLastSelectionStart; - mConnection.setSelection(mLastSelectionEnd, mLastSelectionEnd); - // Reset mLastSelectionEnd to mLastSelectionStart. This is what is supposed to - // happen, and if it's wrong, the next call to onUpdateSelection will correct it, - // but we want to set it right away to avoid it being used with the wrong values - // later (typically, in a subsequent press on backspace). - mLastSelectionEnd = mLastSelectionStart; - mConnection.deleteSurroundingText(numCharsDeleted, 0); - if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - ResearchLogger.latinIME_handleBackspace(numCharsDeleted, - false /* shouldUncommitLogUnit */); - } - } else { - // There is no selection, just delete one character. - if (NOT_A_CURSOR_POSITION == mLastSelectionEnd) { - // This should never happen. - Log.e(TAG, "Backspace when we don't know the selection position"); - } - if (mAppWorkAroundsUtils.isBeforeJellyBean() || - currentSettings.mInputAttributes.isTypeNull()) { - // There are two possible reasons to send a key event: either the field has - // type TYPE_NULL, in which case the keyboard should send events, or we are - // running in backward compatibility mode. Before Jelly bean, the keyboard - // would simulate a hardware keyboard event on pressing enter or delete. This - // is bad for many reasons (there are race conditions with commits) but some - // applications are relying on this behavior so we continue to support it for - // older apps, so we retain this behavior if the app has target SDK < JellyBean. - sendDownUpKeyEvent(KeyEvent.KEYCODE_DEL); - if (mDeleteCount > DELETE_ACCELERATE_AT) { - sendDownUpKeyEvent(KeyEvent.KEYCODE_DEL); - } - } else { - final int codePointBeforeCursor = mConnection.getCodePointBeforeCursor(); - if (codePointBeforeCursor == Constants.NOT_A_CODE) { - // Nothing to delete before the cursor. - return; - } - final int lengthToDelete = - Character.isSupplementaryCodePoint(codePointBeforeCursor) ? 2 : 1; - mConnection.deleteSurroundingText(lengthToDelete, 0); - if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - ResearchLogger.latinIME_handleBackspace(lengthToDelete, - true /* shouldUncommitLogUnit */); - } - if (mDeleteCount > DELETE_ACCELERATE_AT) { - final int codePointBeforeCursorToDeleteAgain = - mConnection.getCodePointBeforeCursor(); - if (codePointBeforeCursorToDeleteAgain != Constants.NOT_A_CODE) { - final int lengthToDeleteAgain = Character.isSupplementaryCodePoint( - codePointBeforeCursorToDeleteAgain) ? 2 : 1; - mConnection.deleteSurroundingText(lengthToDeleteAgain, 0); - if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - ResearchLogger.latinIME_handleBackspace(lengthToDeleteAgain, - true /* shouldUncommitLogUnit */); - } - } - } - } - } - if (currentSettings.isSuggestionsRequested(mDisplayOrientation) - && currentSettings.mCurrentLanguageHasSpaces) { - restartSuggestionsOnWordBeforeCursorIfAtEndOfWord(); - } - // We just removed a character. We need to update the auto-caps state. - mKeyboardSwitcher.updateShiftState(); - } - } - - /* - * Strip a trailing space if necessary and returns whether it's a swap weak space situation. - */ - private boolean maybeStripSpace(final int code, final int spaceState, - final boolean isFromSuggestionStrip) { - if (Constants.CODE_ENTER == code && SPACE_STATE_SWAP_PUNCTUATION == spaceState) { - mConnection.removeTrailingSpace(); - return false; - } - if ((SPACE_STATE_WEAK == spaceState || SPACE_STATE_SWAP_PUNCTUATION == spaceState) - && isFromSuggestionStrip) { - final SettingsValues currentSettings = mSettings.getCurrent(); - if (currentSettings.isUsuallyPrecededBySpace(code)) return false; - if (currentSettings.isUsuallyFollowedBySpace(code)) return true; - mConnection.removeTrailingSpace(); - } - return false; - } - - private void handleCharacter(final int primaryCode, final int x, final int y, - final int spaceState) { - // 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. - boolean isComposingWord = mWordComposer.isComposingWord(); - - // TODO: remove isWordConnector() and use isUsuallyFollowedBySpace() instead. - // See onStartBatchInput() to see how to do it. - final SettingsValues currentSettings = mSettings.getCurrent(); - if (SPACE_STATE_PHANTOM == spaceState && !currentSettings.isWordConnector(primaryCode)) { - if (isComposingWord) { - // Sanity check - throw new RuntimeException("Should not be composing here"); - } - promotePhantomSpace(); - } - - 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 character at the current cursor position. - resetEntireInputState(mLastSelectionStart, mLastSelectionEnd); - isComposingWord = false; - } - // We want to find out whether to start composing a new word with this character. If so, - // we need to reset the composing state and switch isComposingWord. The order of the - // tests is important for good performance. - // We only start composing if we're not already composing. - if (!isComposingWord - // We only start composing if this is a word code point. Essentially that means it's a - // a letter or a word connector. - && currentSettings.isWordCodePoint(primaryCode) - // We never go into composing state if suggestions are not requested. - && currentSettings.isSuggestionsRequested(mDisplayOrientation) && - // In languages with spaces, we only start composing a word when we are not already - // touching a word. In languages without spaces, the above conditions are sufficient. - (!mConnection.isCursorTouchingWord(currentSettings) - || !currentSettings.mCurrentLanguageHasSpaces)) { - // Reset entirely the composing state anyway, then start composing a new word unless - // the character is a single quote or a dash. The idea here is, single quote and dash - // are not separators and they should be treated as normal characters, except in the - // first position where they should not start composing a word. - isComposingWord = (Constants.CODE_SINGLE_QUOTE != primaryCode - && Constants.CODE_DASH != primaryCode); - // Here we don't need to reset the last composed word. It will be reset - // when we commit this one, if we ever do; if on the other hand we backspace - // it entirely and resume suggestions on the previous word, we'd like to still - // have touch coordinates for it. - resetComposingState(false /* alsoResetLastComposedWord */); - } - if (isComposingWord) { - final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView(); - // TODO: We should reconsider which coordinate system should be used to represent - // keyboard event. - final int keyX = mainKeyboardView.getKeyX(x); - final int keyY = mainKeyboardView.getKeyY(y); - mWordComposer.add(primaryCode, keyX, keyY); - // If it's the first letter, make note of auto-caps state - if (mWordComposer.size() == 1) { - // We pass 1 to getPreviousWordForSuggestion because we were not composing a word - // yet, so the word we want is the 1st word before the cursor. - mWordComposer.setCapitalizedModeAndPreviousWordAtStartComposingTime( - getActualCapsMode(), - getNthPreviousWordForSuggestion(currentSettings, 1 /* nthPreviousWord */)); - } - mConnection.setComposingText(getTextWithUnderline(mWordComposer.getTypedWord()), 1); - } else { - final boolean swapWeakSpace = maybeStripSpace(primaryCode, spaceState, - Constants.SUGGESTION_STRIP_COORDINATE == x); - - sendKeyCodePoint(primaryCode); - - if (swapWeakSpace) { - swapSwapperAndSpace(); - mSpaceState = SPACE_STATE_WEAK; - } - // In case the "add to dictionary" hint was still displayed. - if (null != mSuggestionStripView) mSuggestionStripView.dismissAddToDictionaryHint(); - } - mHandler.postUpdateSuggestionStrip(); - if (currentSettings.mIsInternal) { - LatinImeLoggerUtils.onNonSeparator((char)primaryCode, x, y); - } - } - - private void handleRecapitalize() { - if (mLastSelectionStart == mLastSelectionEnd) return; // No selection - // If we have a recapitalize in progress, use it; otherwise, create a new one. - if (!mRecapitalizeStatus.isActive() - || !mRecapitalizeStatus.isSetAt(mLastSelectionStart, mLastSelectionEnd)) { - final CharSequence selectedText = - mConnection.getSelectedText(0 /* flags, 0 for no styles */); - if (TextUtils.isEmpty(selectedText)) return; // Race condition with the input connection - final SettingsValues currentSettings = mSettings.getCurrent(); - mRecapitalizeStatus.initialize(mLastSelectionStart, mLastSelectionEnd, - selectedText.toString(), currentSettings.mLocale, - currentSettings.mWordSeparators); - // We trim leading and trailing whitespace. - mRecapitalizeStatus.trim(); - // Trimming the object may have changed the length of the string, and we need to - // reposition the selection handles accordingly. As this result in an IPC call, - // only do it if it's actually necessary, in other words if the recapitalize status - // is not set at the same place as before. - if (!mRecapitalizeStatus.isSetAt(mLastSelectionStart, mLastSelectionEnd)) { - mLastSelectionStart = mRecapitalizeStatus.getNewCursorStart(); - mLastSelectionEnd = mRecapitalizeStatus.getNewCursorEnd(); - } - } - mConnection.finishComposingText(); - mRecapitalizeStatus.rotate(); - final int numCharsDeleted = mLastSelectionEnd - mLastSelectionStart; - mConnection.setSelection(mLastSelectionEnd, mLastSelectionEnd); - mConnection.deleteSurroundingText(numCharsDeleted, 0); - mConnection.commitText(mRecapitalizeStatus.getRecapitalizedString(), 0); - mLastSelectionStart = mRecapitalizeStatus.getNewCursorStart(); - mLastSelectionEnd = mRecapitalizeStatus.getNewCursorEnd(); - mConnection.setSelection(mLastSelectionStart, mLastSelectionEnd); - // Match the keyboard to the new state. - mKeyboardSwitcher.updateShiftState(); - } - - // Returns true if we do an autocorrection, false otherwise. - private boolean handleSeparator(final int primaryCode, final int x, final int y, - final int spaceState) { - boolean didAutoCorrect = false; - final SettingsValues currentSettings = mSettings.getCurrent(); - // We avoid sending spaces in languages without spaces if we were composing. - final boolean shouldAvoidSendingCode = Constants.CODE_SPACE == primaryCode - && !currentSettings.mCurrentLanguageHasSpaces && mWordComposer.isComposingWord(); - 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 separator at the current cursor position. - resetEntireInputState(mLastSelectionStart, mLastSelectionEnd); - } - if (mWordComposer.isComposingWord()) { // May have changed since we stored wasComposing - if (currentSettings.mCorrectionEnabled) { - final String separator = shouldAvoidSendingCode ? LastComposedWord.NOT_A_SEPARATOR - : StringUtils.newSingleCodePointString(primaryCode); - commitCurrentAutoCorrection(separator); - didAutoCorrect = true; - } else { - commitTyped(StringUtils.newSingleCodePointString(primaryCode)); - } - } - - final boolean swapWeakSpace = maybeStripSpace(primaryCode, spaceState, - Constants.SUGGESTION_STRIP_COORDINATE == x); - - if (SPACE_STATE_PHANTOM == spaceState && - currentSettings.isUsuallyPrecededBySpace(primaryCode)) { - promotePhantomSpace(); - } - if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - ResearchLogger.latinIME_handleSeparator(primaryCode, mWordComposer.isComposingWord()); - } - - if (!shouldAvoidSendingCode) { - sendKeyCodePoint(primaryCode); - } - - if (Constants.CODE_SPACE == primaryCode) { - if (currentSettings.isSuggestionsRequested(mDisplayOrientation)) { - if (maybeDoubleSpacePeriod()) { - mSpaceState = SPACE_STATE_DOUBLE; - } else if (!isShowingPunctuationList()) { - mSpaceState = SPACE_STATE_WEAK; - } - } - - mHandler.startDoubleSpacePeriodTimer(); - mHandler.postUpdateSuggestionStrip(); - } else { - if (swapWeakSpace) { - swapSwapperAndSpace(); - mSpaceState = SPACE_STATE_SWAP_PUNCTUATION; - } else if (SPACE_STATE_PHANTOM == spaceState - && currentSettings.isUsuallyFollowedBySpace(primaryCode)) { - // If we are in phantom space state, and the user presses a separator, we want to - // stay in phantom space state so that the next keypress has a chance to add the - // space. For example, if I type "Good dat", pick "day" from the suggestion strip - // then insert a comma and go on to typing the next word, I want the space to be - // inserted automatically before the next word, the same way it is when I don't - // input the comma. - // The case is a little different if the separator is a space stripper. Such a - // separator does not normally need a space on the right (that's the difference - // between swappers and strippers), so we should not stay in phantom space state if - // the separator is a stripper. Hence the additional test above. - mSpaceState = SPACE_STATE_PHANTOM; - } - - // Set punctuation right away. onUpdateSelection will fire but tests whether it is - // already displayed or not, so it's okay. - setPunctuationSuggestions(); - } - if (currentSettings.mIsInternal) { - LatinImeLoggerUtils.onSeparator((char)primaryCode, x, y); - } - - mKeyboardSwitcher.updateShiftState(); - return didAutoCorrect; - } - - private CharSequence getTextWithUnderline(final String text) { - return mIsAutoCorrectionIndicatorOn - ? SuggestionSpanUtils.getTextWithAutoCorrectionIndicatorUnderline(this, text) - : text; - } - + // TODO[IL]: Rename this to avoid using handle* private void handleClose() { // TODO: Verify that words are logged properly when IME is closed. - commitTyped(LastComposedWord.NOT_A_SEPARATOR); + mInputLogic.commitTyped(LastComposedWord.NOT_A_SEPARATOR); requestHideSelf(0); final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView(); if (mainKeyboardView != null) { @@ -2391,12 +1712,12 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } } - // TODO: make this private + // TODO[IL]: Move this to InputLogic and make it private // Outside LatinIME, only used by the test suite. @UsedForTesting - boolean isShowingPunctuationList() { - if (mSuggestedWords == null) return false; - return mSettings.getCurrent().mSuggestPuncList == mSuggestedWords; + public boolean isShowingPunctuationList() { + if (mInputLogic.mSuggestedWords == null) return false; + return mSettings.getCurrent().mSuggestPuncList == mInputLogic.mSuggestedWords; } private boolean isSuggestionsStripVisible() { @@ -2414,13 +1735,21 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen return currentSettings.isSuggestionsRequested(mDisplayOrientation); } - private void clearSuggestionStrip() { + public void dismissAddToDictionaryHint() { + if (null != mSuggestionStripView) { + mSuggestionStripView.dismissAddToDictionaryHint(); + } + } + + // TODO[IL]: Define a clear interface for this + public void clearSuggestionStrip() { setSuggestedWords(SuggestedWords.EMPTY, false); setAutoCorrectionIndicator(false); } - private void setSuggestedWords(final SuggestedWords words, final boolean isAutoCorrection) { - mSuggestedWords = words; + // TODO[IL]: Define a clear interface for this + public void setSuggestedWords(final SuggestedWords words, final boolean isAutoCorrection) { + mInputLogic.mSuggestedWords = words; if (mSuggestionStripView != null) { mSuggestionStripView.setSuggestions(words); mKeyboardSwitcher.onAutoCorrectionStateChanged(isAutoCorrection); @@ -2429,34 +1758,36 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen private void setAutoCorrectionIndicator(final boolean newAutoCorrectionIndicator) { // Put a blue underline to a word in TextView which will be auto-corrected. - if (mIsAutoCorrectionIndicatorOn != newAutoCorrectionIndicator - && mWordComposer.isComposingWord()) { - mIsAutoCorrectionIndicatorOn = newAutoCorrectionIndicator; + if (mInputLogic.mIsAutoCorrectionIndicatorOn != newAutoCorrectionIndicator + && mInputLogic.mWordComposer.isComposingWord()) { + mInputLogic.mIsAutoCorrectionIndicatorOn = newAutoCorrectionIndicator; final CharSequence textWithUnderline = - getTextWithUnderline(mWordComposer.getTypedWord()); + mInputLogic.getTextWithUnderline(mInputLogic.mWordComposer.getTypedWord()); // TODO: when called from an updateSuggestionStrip() call that results from a posted // 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); + mInputLogic.mConnection.setComposingText(textWithUnderline, 1); } } - private void updateSuggestionStrip() { + // TODO[IL]: Move this to InputLogic and make private again + public void updateSuggestionStrip() { mHandler.cancelUpdateSuggestionStrip(); final SettingsValues currentSettings = mSettings.getCurrent(); // Check if we have a suggestion engine attached. - if (mSuggest == null + if (mInputLogic.mSuggest == null || !currentSettings.isSuggestionsRequested(mDisplayOrientation)) { - if (mWordComposer.isComposingWord()) { + if (mInputLogic.mWordComposer.isComposingWord()) { Log.w(TAG, "Called updateSuggestionsOrPredictions but suggestions were not " + "requested!"); } return; } - if (!mWordComposer.isComposingWord() && !currentSettings.mBigramPredictionEnabled) { + if (!mInputLogic.mWordComposer.isComposingWord() + && !currentSettings.mBigramPredictionEnabled) { setPunctuationSuggestions(); return; } @@ -2478,28 +1809,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } } - /** - * Get the nth previous word before the cursor as context for the suggestion process. - * @param currentSettings the current settings values. - * @param nthPreviousWord reverse index of the word to get (1-indexed) - * @return the nth previous word before the cursor. - */ - private String getNthPreviousWordForSuggestion(final SettingsValues currentSettings, - final int nthPreviousWord) { - if (currentSettings.mCurrentLanguageHasSpaces) { - // If we are typing in a language with spaces we can just look up the previous - // word from textview. - return mConnection.getNthPreviousWord(currentSettings, nthPreviousWord); - } else { - return LastComposedWord.NOT_A_COMPOSED_WORD == mLastComposedWord ? null - : mLastComposedWord.mCommittedWord; - } - } - private void getSuggestedWords(final int sessionId, final int sequenceNumber, final OnGetSuggestedWordsCallback callback) { final Keyboard keyboard = mKeyboardSwitcher.getKeyboard(); - final Suggest suggest = mSuggest; + final Suggest suggest = mInputLogic.mSuggest; if (keyboard == null || suggest == null) { callback.onGetSuggestedWords(SuggestedWords.EMPTY); return; @@ -2511,22 +1824,25 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen final int[] additionalFeaturesOptions = currentSettings.mAdditionalFeaturesSettingValues; if (DEBUG) { - if (mWordComposer.isComposingWord() || mWordComposer.isBatchMode()) { - final String previousWord = mWordComposer.getPreviousWordForSuggestion(); + if (mInputLogic.mWordComposer.isComposingWord() + || mInputLogic.mWordComposer.isBatchMode()) { + final String previousWord + = mInputLogic.mWordComposer.getPreviousWordForSuggestion(); // TODO: this is for checking consistency with older versions. Remove this when // we are confident this is stable. // We're checking the previous word in the text field against the memorized previous // word. If we are composing a word we should have the second word before the cursor // memorized, otherwise we should have the first. - final String rereadPrevWord = getNthPreviousWordForSuggestion(currentSettings, - mWordComposer.isComposingWord() ? 2 : 1); + final String rereadPrevWord = mInputLogic.getNthPreviousWordForSuggestion( + currentSettings, mInputLogic.mWordComposer.isComposingWord() ? 2 : 1); if (!TextUtils.equals(previousWord, rereadPrevWord)) { throw new RuntimeException("Unexpected previous word: " + previousWord + " <> " + rereadPrevWord); } } } - suggest.getSuggestedWords(mWordComposer, mWordComposer.getPreviousWordForSuggestion(), + suggest.getSuggestedWords(mInputLogic.mWordComposer, + mInputLogic.mWordComposer.getPreviousWordForSuggestion(), keyboard.getProximityInfo(), currentSettings.mBlockPotentiallyOffensive, currentSettings.mCorrectionEnabled, additionalFeaturesOptions, sessionId, sequenceNumber, callback); @@ -2539,7 +1855,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen @Override public void onGetSuggestedWords(SuggestedWords suggestedWords) { callback.onGetSuggestedWords(maybeRetrieveOlderSuggestions( - mWordComposer.getTypedWord(), suggestedWords)); + mInputLogic.mWordComposer.getTypedWord(), suggestedWords)); } }); } @@ -2563,7 +1879,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } private SuggestedWords getOlderSuggestions(final String typedWord) { - SuggestedWords previousSuggestedWords = mSuggestedWords; + SuggestedWords previousSuggestedWords = mInputLogic.mSuggestedWords; if (previousSuggestedWords == mSettings.getCurrent().mSuggestPuncList) { previousSuggestedWords = SuggestedWords.EMPTY; } @@ -2591,7 +1907,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // because it may differ from mWordComposer.mTypedWord. autoCorrection = typedWord; } - mWordComposer.setAutoCorrection(autoCorrection); + mInputLogic.mWordComposer.setAutoCorrection(autoCorrection); } private void showSuggestionStripWithTypedWord(final SuggestedWords suggestedWords, @@ -2621,50 +1937,11 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen suggestedWords.getWord(SuggestedWords.INDEX_OF_TYPED_WORD)); } - private void commitCurrentAutoCorrection(final String separator) { - // Complete any pending suggestions query first - if (mHandler.hasPendingUpdateSuggestions()) { - updateSuggestionStrip(); - } - final String typedAutoCorrection = mWordComposer.getAutoCorrectionOrNull(); - final String typedWord = mWordComposer.getTypedWord(); - final String autoCorrection = (typedAutoCorrection != null) - ? typedAutoCorrection : typedWord; - if (autoCorrection != null) { - if (TextUtils.isEmpty(typedWord)) { - throw new RuntimeException("We have an auto-correction but the typed word " - + "is empty? Impossible! I must commit suicide."); - } - if (mSettings.isInternal()) { - LatinImeLoggerUtils.onAutoCorrection( - typedWord, autoCorrection, separator, mWordComposer); - } - if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - final SuggestedWords suggestedWords = mSuggestedWords; - ResearchLogger.latinIme_commitCurrentAutoCorrection(typedWord, autoCorrection, - separator, mWordComposer.isBatchMode(), suggestedWords); - } - commitChosenWord(autoCorrection, LastComposedWord.COMMIT_TYPE_DECIDED_WORD, - separator); - if (!typedWord.equals(autoCorrection)) { - // This will make the correction flash for a short while as a visual clue - // to the user that auto-correction happened. It has no other effect; in particular - // note that this won't affect the text inside the text field AT ALL: it only makes - // the segment of text starting at the supplied index and running for the length - // of the auto-correction flash. At this moment, the "typedWord" argument is - // ignored by TextView. - mConnection.commitCorrection( - new CorrectionInfo(mLastSelectionEnd - typedWord.length(), - typedWord, autoCorrection)); - } - } - } - // Called from {@link SuggestionStripView} through the {@link SuggestionStripView#Listener} // interface @Override public void pickSuggestionManually(final int index, final SuggestedWordInfo suggestionInfo) { - final SuggestedWords suggestedWords = mSuggestedWords; + final SuggestedWords suggestedWords = mInputLogic.mSuggestedWords; final String suggestion = suggestionInfo.mWord; // If this is a punctuation picked from the suggestion strip, pass it to onCodeInput if (suggestion.length() == 1 && isShowingPunctuationList()) { @@ -2682,57 +1959,57 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen return; } - mConnection.beginBatchEdit(); + mInputLogic.mConnection.beginBatchEdit(); final SettingsValues currentSettings = mSettings.getCurrent(); - if (SPACE_STATE_PHANTOM == mSpaceState && suggestion.length() > 0 + if (SpaceState.PHANTOM == mInputLogic.mSpaceState && suggestion.length() > 0 // In the batch input mode, a manually picked suggested word should just replace // the current batch input text and there is no need for a phantom space. - && !mWordComposer.isBatchMode()) { + && !mInputLogic.mWordComposer.isBatchMode()) { final int firstChar = Character.codePointAt(suggestion, 0); if (!currentSettings.isWordSeparator(firstChar) || currentSettings.isUsuallyPrecededBySpace(firstChar)) { - promotePhantomSpace(); + mInputLogic.promotePhantomSpace(currentSettings); } } if (currentSettings.isApplicationSpecifiedCompletionsOn() && mApplicationSpecifiedCompletions != null && index >= 0 && index < mApplicationSpecifiedCompletions.length) { - mSuggestedWords = SuggestedWords.EMPTY; + mInputLogic.mSuggestedWords = SuggestedWords.EMPTY; if (mSuggestionStripView != null) { mSuggestionStripView.clear(); } mKeyboardSwitcher.updateShiftState(); - resetComposingState(true /* alsoResetLastComposedWord */); + mInputLogic.resetComposingState(true /* alsoResetLastComposedWord */); final CompletionInfo completionInfo = mApplicationSpecifiedCompletions[index]; - mConnection.commitCompletion(completionInfo); - mConnection.endBatchEdit(); + mInputLogic.mConnection.commitCompletion(completionInfo); + mInputLogic.mConnection.endBatchEdit(); return; } // We need to log before we commit, because the word composer will store away the user // typed word. - final String replacedWord = mWordComposer.getTypedWord(); + final String replacedWord = mInputLogic.mWordComposer.getTypedWord(); LatinImeLogger.logOnManualSuggestion(replacedWord, suggestion, index, suggestedWords); commitChosenWord(suggestion, LastComposedWord.COMMIT_TYPE_MANUAL_PICK, LastComposedWord.NOT_A_SEPARATOR); if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { ResearchLogger.latinIME_pickSuggestionManually(replacedWord, index, suggestion, - mWordComposer.isBatchMode(), suggestionInfo.mScore, suggestionInfo.mKind, - suggestionInfo.mSourceDict.mDictType); + mInputLogic.mWordComposer.isBatchMode(), suggestionInfo.mScore, + suggestionInfo.mKind, suggestionInfo.mSourceDict.mDictType); } - mConnection.endBatchEdit(); + mInputLogic.mConnection.endBatchEdit(); // Don't allow cancellation of manual pick - mLastComposedWord.deactivate(); + mInputLogic.mLastComposedWord.deactivate(); // Space state must be updated before calling updateShiftState - mSpaceState = SPACE_STATE_PHANTOM; + mInputLogic.mSpaceState = SpaceState.PHANTOM; mKeyboardSwitcher.updateShiftState(); // 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). // Please note that if mSuggest is null, it means that everything is off: suggestion // and correction, so we shouldn't try to show the hint - final Suggest suggest = mSuggest; + final Suggest suggest = mInputLogic.mSuggest; final boolean showingAddToDictionaryHint = (SuggestedWordInfo.KIND_TYPED == suggestionInfo.mKind || SuggestedWordInfo.KIND_OOV_CORRECTION == suggestionInfo.mKind) @@ -2756,10 +2033,11 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen /** * Commits the chosen word to the text field and saves it for later retrieval. */ - private void commitChosenWord(final String chosenWord, final int commitType, + // TODO[IL]: Move to InputLogic and make public again + public void commitChosenWord(final String chosenWord, final int commitType, final String separatorString) { - final SuggestedWords suggestedWords = mSuggestedWords; - mConnection.commitText(SuggestionSpanUtils.getTextWithSuggestionSpan( + final SuggestedWords suggestedWords = mInputLogic.mSuggestedWords; + mInputLogic.mConnection.commitText(SuggestionSpanUtils.getTextWithSuggestionSpan( this, chosenWord, suggestedWords, mIsMainDictionaryAvailable), 1); // Add the word to the user history dictionary final String prevWord = addToUserHistoryDictionary(chosenWord); @@ -2767,8 +2045,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // what user typed. Note: currently this is done much later in // LastComposedWord#didCommitTypedWord by string equality of the remembered // strings. - mLastComposedWord = mWordComposer.commitWord(commitType, chosenWord, separatorString, - prevWord); + mInputLogic.mLastComposedWord = mInputLogic.mWordComposer.commitWord(commitType, + chosenWord, separatorString, prevWord); final boolean shouldDiscardPreviousWordForSuggestion; if (0 == StringUtils.codePointCount(separatorString)) { // Separator is 0-length. Discard the word only if the current language has spaces. @@ -2780,11 +2058,12 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen !StringUtils.containsOnlyWhitespace(separatorString); } if (shouldDiscardPreviousWordForSuggestion) { - mWordComposer.discardPreviousWordForSuggestion(); + mInputLogic.mWordComposer.discardPreviousWordForSuggestion(); } } - private void setPunctuationSuggestions() { + // TODO[IL]: Define a clean interface for this + public void setPunctuationSuggestions() { final SettingsValues currentSettings = mSettings.getCurrent(); if (currentSettings.mBigramPredictionEnabled) { clearSuggestionStrip(); @@ -2797,7 +2076,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen private String addToUserHistoryDictionary(final String suggestion) { if (TextUtils.isEmpty(suggestion)) return null; - final Suggest suggest = mSuggest; + final Suggest suggest = mInputLogic.mSuggest; if (suggest == null) return null; // If correction is not enabled, we don't add words to the user history dictionary. @@ -2809,9 +2088,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen final UserHistoryDictionary userHistoryDictionary = suggest.getUserHistoryDictionary(); if (userHistoryDictionary == null) return null; - final String prevWord = mConnection.getNthPreviousWord(currentSettings, 2); + final String prevWord = mInputLogic.mConnection.getNthPreviousWord(currentSettings, 2); final String secondWord; - if (mWordComposer.wasAutoCapitalized() && !mWordComposer.isMostlyCaps()) { + if (mInputLogic.mWordComposer.wasAutoCapitalized() + && !mInputLogic.mWordComposer.isMostlyCaps()) { secondWord = suggestion.toLowerCase(mSubtypeSwitcher.getCurrentSubtypeLocale()); } else { secondWord = suggestion; @@ -2848,19 +2128,19 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // how to segment them yet. if (!mSettings.getCurrent().mCurrentLanguageHasSpaces) return; // If the cursor is not touching a word, or if there is a selection, return right away. - if (mLastSelectionStart != mLastSelectionEnd) return; + if (mInputLogic.mLastSelectionStart != mInputLogic.mLastSelectionEnd) return; // If we don't know the cursor location, return. - if (mLastSelectionStart < 0) return; + if (mInputLogic.mLastSelectionStart < 0) return; final SettingsValues currentSettings = mSettings.getCurrent(); - if (!mConnection.isCursorTouchingWord(currentSettings)) return; - final TextRange range = mConnection.getWordRangeAtCursor(currentSettings.mWordSeparators, - 0 /* additionalPrecedingWordsCount */); + if (!mInputLogic.mConnection.isCursorTouchingWord(currentSettings)) return; + final TextRange range = mInputLogic.mConnection.getWordRangeAtCursor( + currentSettings.mWordSeparators, 0 /* additionalPrecedingWordsCount */); if (null == range) return; // Happens if we don't have an input connection at all if (range.length() <= 0) return; // Race condition. No text to resume on, so bail out. // If for some strange reason (editor bug or so) we measure the text before the cursor as // longer than what the entire text is supposed to be, the safe thing to do is bail out. final int numberOfCharsInWordBeforeCursor = range.getNumberOfCharsInWordBeforeCursor(); - if (numberOfCharsInWordBeforeCursor > mLastSelectionStart) return; + if (numberOfCharsInWordBeforeCursor > mInputLogic.mLastSelectionStart) return; final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList(); final String typedWord = range.mWord.toString(); if (!isResumableWord(typedWord, currentSettings)) return; @@ -2878,18 +2158,18 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } } } - mWordComposer.setComposingWord(typedWord, - getNthPreviousWordForSuggestion(currentSettings, + mInputLogic.mWordComposer.setComposingWord(typedWord, + mInputLogic.getNthPreviousWordForSuggestion(currentSettings, // We want the previous word for suggestion. If we have chars in the word // before the cursor, then we want the word before that, hence 2; otherwise, // we want the word immediately before the cursor, hence 1. 0 == numberOfCharsInWordBeforeCursor ? 1 : 2), mKeyboardSwitcher.getKeyboard()); - mWordComposer.setCursorPositionWithinWord( + mInputLogic.mWordComposer.setCursorPositionWithinWord( typedWord.codePointCount(0, numberOfCharsInWordBeforeCursor)); - mConnection.setComposingRegion( - mLastSelectionStart - numberOfCharsInWordBeforeCursor, - mLastSelectionEnd + range.getNumberOfCharsInWordAfterCursor()); + mInputLogic.mConnection.setComposingRegion( + mInputLogic.mLastSelectionStart - numberOfCharsInWordBeforeCursor, + mInputLogic.mLastSelectionEnd + range.getNumberOfCharsInWordAfterCursor()); if (suggestions.isEmpty()) { // We come here if there weren't any suggestion spans on this word. We will try to // compute suggestions for it instead. @@ -2938,42 +2218,11 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // We also need to unset mIsAutoCorrectionIndicatorOn to avoid showSuggestionStrip touching // the text to adapt it. // TODO: remove mIsAutoCorrectionIndicatorOn (see comment on definition) - mIsAutoCorrectionIndicatorOn = false; + mInputLogic.mIsAutoCorrectionIndicatorOn = false; mHandler.showSuggestionStripWithTypedWord(suggestedWords, typedWord); } /** - * Check if the cursor is actually at the end of a word. If so, restart suggestions on this - * word, else do nothing. - */ - private void restartSuggestionsOnWordBeforeCursorIfAtEndOfWord() { - final CharSequence word = - mConnection.getWordBeforeCursorIfAtEndOfWord(mSettings.getCurrent()); - if (null != word) { - final String wordString = word.toString(); - restartSuggestionsOnWordBeforeCursor(wordString); - // TODO: Handle the case where the user manually moves the cursor and then backs up over - // a separator. In that case, the current log unit should not be uncommitted. - if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - ResearchLogger.getInstance().uncommitCurrentLogUnit(wordString, - true /* dumpCurrentLogUnit */); - } - } - } - - private void restartSuggestionsOnWordBeforeCursor(final String word) { - mWordComposer.setComposingWord(word, - // Previous word is the 2nd word before cursor because we are restarting on the - // 1st word before cursor. - getNthPreviousWordForSuggestion(mSettings.getCurrent(), 2 /* nthPreviousWord */), - mKeyboardSwitcher.getKeyboard()); - final int length = word.length(); - mConnection.deleteSurroundingText(length, 0); - mConnection.setComposingText(word, 1); - mHandler.postUpdateSuggestionStrip(); - } - - /** * Retry resetting caches in the rich input connection. * * When the editor can't be accessed we can't reset the caches, so we schedule a retry. @@ -2984,8 +2233,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen * @param remainingTries How many times we may try again before giving up. */ private void retryResetCaches(final boolean tryResumeSuggestions, final int remainingTries) { - if (!mConnection.resetCachesUponCursorMoveAndReturnSuccess(mLastSelectionStart, - mLastSelectionEnd, false)) { + if (!mInputLogic.mConnection.resetCachesUponCursorMoveAndReturnSuccess( + mInputLogic.mLastSelectionStart, mInputLogic.mLastSelectionEnd, false)) { if (0 < remainingTries) { mHandler.postResetCaches(tryResumeSuggestions, remainingTries - 1); return; @@ -2998,74 +2247,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen if (tryResumeSuggestions) mHandler.postResumeSuggestions(); } - private void revertCommit() { - final String previousWord = mLastComposedWord.mPrevWord; - final String originallyTypedWord = mLastComposedWord.mTypedWord; - final String committedWord = mLastComposedWord.mCommittedWord; - final int cancelLength = committedWord.length(); - // We want java chars, not codepoints for the following. - final int separatorLength = mLastComposedWord.mSeparatorString.length(); - // TODO: should we check our saved separator against the actual contents of the text view? - final int deleteLength = cancelLength + separatorLength; - if (DEBUG) { - if (mWordComposer.isComposingWord()) { - throw new RuntimeException("revertCommit, but we are composing a word"); - } - final CharSequence wordBeforeCursor = - mConnection.getTextBeforeCursor(deleteLength, 0).subSequence(0, cancelLength); - if (!TextUtils.equals(committedWord, wordBeforeCursor)) { - throw new RuntimeException("revertCommit check failed: we thought we were " - + "reverting \"" + committedWord - + "\", but before the cursor we found \"" + wordBeforeCursor + "\""); - } - } - mConnection.deleteSurroundingText(deleteLength, 0); - if (!TextUtils.isEmpty(previousWord) && !TextUtils.isEmpty(committedWord)) { - if (mSuggest != null) { - mSuggest.cancelAddingUserHistory(previousWord, committedWord); - } - } - final String stringToCommit = originallyTypedWord + mLastComposedWord.mSeparatorString; - if (mSettings.getCurrent().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(stringToCommit, 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. - mWordComposer.setComposingWord(stringToCommit, previousWord, - mKeyboardSwitcher.getKeyboard()); - mConnection.setComposingText(stringToCommit, 1); - } - if (mSettings.isInternal()) { - LatinImeLoggerUtils.onSeparator(mLastComposedWord.mSeparatorString, - Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE); - } - if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - ResearchLogger.latinIME_revertCommit(committedWord, originallyTypedWord, - mWordComposer.isBatchMode(), mLastComposedWord.mSeparatorString); - } - // 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. - mHandler.postUpdateSuggestionStrip(); - } - - // This essentially inserts a space, and that's it. - public void promotePhantomSpace() { - final SettingsValues currentSettings = mSettings.getCurrent(); - if (currentSettings.shouldInsertSpacesAutomatically() - && currentSettings.mCurrentLanguageHasSpaces - && !mConnection.textBeforeCursorLooksLikeURL()) { - if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - ResearchLogger.latinIME_promotePhantomSpace(); - } - sendKeyCodePoint(Constants.CODE_SPACE); - } - } - // TODO: Make this private // Outside LatinIME, only used by the {@link InputTestsBase} test suite. @UsedForTesting @@ -3090,7 +2271,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen return; } if (repeatCount > 0) { - if (code == Constants.CODE_DELETE && !mConnection.canDeleteCharacters()) { + if (code == Constants.CODE_DELETE && !mInputLogic.mConnection.canDeleteCharacters()) { // No need to feedback when repeat delete key will have no effect. return; } @@ -3144,9 +2325,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // onHardwareKeyEvent, like onKeyDown returns true if it handled the event, false if // it doesn't know what to do with it and leave it to the application. For example, // hardware key events for adjusting the screen's brightness are passed as is. - if (mEventInterpreter.onHardwareKeyEvent(event)) { + if (mInputLogic.mEventInterpreter.onHardwareKeyEvent(event)) { final long keyIdentifier = event.getDeviceId() << 32 + event.getKeyCode(); - mCurrentlyPressedHardwareKeys.add(keyIdentifier); + mInputLogic.mCurrentlyPressedHardwareKeys.add(keyIdentifier); return true; } return super.onKeyDown(keyCode, event); @@ -3155,7 +2336,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen @Override public boolean onKeyUp(final int keyCode, final KeyEvent event) { final long keyIdentifier = event.getDeviceId() << 32 + event.getKeyCode(); - if (mCurrentlyPressedHardwareKeys.remove(keyIdentifier)) { + if (mInputLogic.mCurrentlyPressedHardwareKeys.remove(keyIdentifier)) { return true; } return super.onKeyUp(keyCode, event); @@ -3187,7 +2368,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen public void launchKeyboardedDialogActivity(final Class<? extends Activity> activityClass) { // Put the text in the attached EditText into a safe, saved state before switching to a // new activity that will also use the soft keyboard. - commitTyped(LastComposedWord.NOT_A_SEPARATOR); + mInputLogic.commitTyped(LastComposedWord.NOT_A_SEPARATOR); launchSubActivity(activityClass); } @@ -3253,26 +2434,21 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // TODO: can this be removed somehow without breaking the tests? @UsedForTesting - /* package for test */ String getFirstSuggestedWord() { - return mSuggestedWords.size() > 0 ? mSuggestedWords.getWord(0) : null; + /* package for test */ SuggestedWords getSuggestedWords() { + // You may not use this method for anything else than debug + return DEBUG ? mInputLogic.mSuggestedWords : null; } // DO NOT USE THIS for any other purpose than testing. This is information private to LatinIME. @UsedForTesting /* package for test */ boolean isCurrentlyWaitingForMainDictionary() { - return mSuggest.isCurrentlyWaitingForMainDictionary(); - } - - // DO NOT USE THIS for any other purpose than testing. This is information private to LatinIME. - @UsedForTesting - /* package for test */ boolean hasMainDictionary() { - return mSuggest.hasMainDictionary(); + return mInputLogic.mSuggest.isCurrentlyWaitingForMainDictionary(); } // DO NOT USE THIS for any other purpose than testing. This can break the keyboard badly. @UsedForTesting /* package for test */ void replaceMainDictionaryForTest(final Locale locale) { - mSuggest.resetMainDict(this, locale, null); + mInputLogic.mSuggest.resetMainDict(this, locale, null); } public void debugDumpStateAndCrashWithException(final String context) { @@ -3297,7 +2473,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen p.println(" mIsSuggestionsRequested = " + settingsValues.isSuggestionsRequested(mDisplayOrientation)); p.println(" mCorrectionEnabled=" + settingsValues.mCorrectionEnabled); - p.println(" isComposingWord=" + mWordComposer.isComposingWord()); + p.println(" isComposingWord=" + mInputLogic.mWordComposer.isComposingWord()); p.println(" mSoundOn=" + settingsValues.mSoundOn); p.println(" mVibrateOn=" + settingsValues.mVibrateOn); p.println(" mKeyPreviewPopupOn=" + settingsValues.mKeyPreviewPopupOn); |