diff options
Diffstat (limited to 'java/src')
5 files changed, 262 insertions, 328 deletions
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index 33f09e45b..5a5674f8f 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -35,8 +35,6 @@ import android.inputmethodservice.InputMethodService; import android.media.AudioManager; import android.net.ConnectivityManager; import android.os.Debug; -import android.os.Handler; -import android.os.HandlerThread; import android.os.IBinder; import android.os.Message; import android.os.SystemClock; @@ -137,7 +135,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen private final boolean mIsHardwareAcceleratedDrawingEnabled; public final UIHandler mHandler = new UIHandler(this); - private InputUpdater mInputUpdater; public static final class UIHandler extends LeakGuardHandlerWrapper<LatinIME> { private static final int MSG_UPDATE_SHIFT_STATE = 0; @@ -183,8 +180,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen switch (msg.what) { case MSG_UPDATE_SUGGESTION_STRIP: latinIme.mInputLogic.performUpdateSuggestionStripSync( - latinIme.mSettings.getCurrent(), this /* handler */, - latinIme.mInputUpdater); + latinIme.mSettings.getCurrent(), this /* handler */); break; case MSG_UPDATE_SHIFT_STATE: switcher.updateShiftState(); @@ -205,8 +201,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen break; case MSG_RESUME_SUGGESTIONS: latinIme.mInputLogic.restartSuggestionsOnWordTouchedByCursor( - latinIme.mSettings.getCurrent(), latinIme.mKeyboardSwitcher, - latinIme.mInputUpdater); + latinIme.mSettings.getCurrent(), 0 /* offset */, + false /* includeResumedWordInSuggestions */, latinIme.mKeyboardSwitcher); break; case MSG_REOPEN_DICTIONARIES: latinIme.initSuggest(); @@ -216,7 +212,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen postUpdateSuggestionStrip(); break; case MSG_ON_END_BATCH_INPUT: - latinIme.onEndBatchInputAsyncInternal((SuggestedWords) msg.obj); + latinIme.mInputLogic.endBatchInputAsyncInternal(latinIme.mSettings.getCurrent(), + (SuggestedWords) msg.obj, latinIme.mKeyboardSwitcher); break; case MSG_RESET_CACHES: latinIme.mInputLogic.retryResetCaches(latinIme.mSettings.getCurrent(), @@ -495,8 +492,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen registerReceiver(mDictionaryPackInstallReceiver, newDictFilter); DictionaryDecayBroadcastReciever.setUpIntervalAlarmForDictionaryDecaying(this); - - mInputUpdater = new InputUpdater(this); } // Has to be package-visible for unit tests @@ -594,9 +589,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen suggest.close(); mInputLogic.mSuggest = null; } - if (mInputUpdater != null) { - mInputUpdater.quitLooper(); - } mSettings.onDestroy(); unregisterReceiver(mReceiver); if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { @@ -754,12 +746,7 @@ 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. - mInputLogic.mEnteredText = null; - mInputLogic.resetComposingState(true /* alsoResetLastComposedWord */); - mInputLogic.mDeleteCount = 0; - mInputLogic.mSpaceState = SpaceState.NONE; - mInputLogic.mRecapitalizeStatus.deactivate(); - mInputLogic.mCurrentlyPressedHardwareKeys.clear(); + mInputLogic.startInput(restarting, editorInfo); // Note: the following does a round-trip IPC on the main thread: be careful final Locale currentLocale = mSubtypeSwitcher.getCurrentSubtypeLocale(); @@ -772,11 +759,11 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // otherwise it will clear the suggestion strip. setPunctuationSuggestions(); } - 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. + // TODO[IL]: Can the following be moved to InputLogic#startInput? final boolean canReachInputConnection; if (!mInputLogic.mConnection.resetCachesUponCursorMoveAndReturnSuccess( editorInfo.initialSelStart, editorInfo.initialSelEnd, @@ -823,12 +810,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen setSuggestionStripShownInternal( isSuggestionsStripVisible(), /* needsInputViewShown */ false); - 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. - mInputLogic.tryFixLyingCursorPosition(); - mHandler.cancelUpdateSuggestionStrip(); mHandler.cancelDoubleSpacePeriodTimer(); @@ -872,10 +853,7 @@ 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 (mInputLogic.mWordComposer.isComposingWord()) { - mInputLogic.mConnection.finishComposingText(); - } - mInputLogic.resetComposingState(true /* alsoResetLastComposedWord */); + mInputLogic.finishInput(); // Notify ResearchLogger if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { ResearchLogger.latinIME_onFinishInputViewInternal(finishingInput, @@ -1252,166 +1230,35 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // Implementation of {@link KeyboardActionListener}. @Override public void onCodeInput(final int primaryCode, final int x, final int y) { - mInputLogic.onCodeInput(primaryCode, x, y, mHandler, mInputUpdater, - mKeyboardSwitcher, mSubtypeSwitcher); + mInputLogic.onCodeInput(primaryCode, x, y, mHandler, mKeyboardSwitcher, mSubtypeSwitcher); } // Called from PointerTracker through the KeyboardActionListener interface @Override public void onTextInput(final String rawText) { - mInputLogic.onTextInput(mSettings.getCurrent(), rawText, mHandler, mInputUpdater); + mInputLogic.onTextInput(mSettings.getCurrent(), rawText, mHandler); mKeyboardSwitcher.updateShiftState(); mKeyboardSwitcher.onCodeInput(Constants.CODE_OUTPUT_TEXT); } @Override public void onStartBatchInput() { - mInputLogic.onStartBatchInput(mSettings.getCurrent(), mKeyboardSwitcher, mHandler, - mInputUpdater); + mInputLogic.onStartBatchInput(mSettings.getCurrent(), mKeyboardSwitcher, mHandler); } @Override public void onUpdateBatchInput(final InputPointers batchPointers) { - mInputLogic.onUpdateBatchInput(mSettings.getCurrent(), batchPointers, mKeyboardSwitcher, - mInputUpdater); + mInputLogic.onUpdateBatchInput(mSettings.getCurrent(), batchPointers, mKeyboardSwitcher); } @Override public void onEndBatchInput(final InputPointers batchPointers) { - mInputLogic.onEndBatchInput(mSettings.getCurrent(), batchPointers, mInputUpdater); + mInputLogic.onEndBatchInput(mSettings.getCurrent(), batchPointers); } @Override public void onCancelBatchInput() { - mInputLogic.onCancelBatchInput(mHandler, mInputUpdater); - } - - // TODO[IL]: Make this a package-private standalone class in inputlogic/ and remove all - // references to it in LatinIME - public static final class InputUpdater implements Handler.Callback { - private final Handler mHandler; - private final LatinIME mLatinIme; - private final Object mLock = new Object(); - private boolean mInBatchInput; // synchronized using {@link #mLock}. - - InputUpdater(final LatinIME latinIme) { - final HandlerThread handlerThread = new HandlerThread( - InputUpdater.class.getSimpleName()); - handlerThread.start(); - mHandler = new Handler(handlerThread.getLooper(), this); - mLatinIme = latinIme; - } - - private static final int MSG_UPDATE_GESTURE_PREVIEW_AND_SUGGESTION_STRIP = 1; - private static final int MSG_GET_SUGGESTED_WORDS = 2; - - @Override - public boolean handleMessage(final Message msg) { - // TODO: straighten message passing - we don't need two kinds of messages calling - // each other. - switch (msg.what) { - case MSG_UPDATE_GESTURE_PREVIEW_AND_SUGGESTION_STRIP: - updateBatchInput((InputPointers)msg.obj, msg.arg2 /* sequenceNumber */); - break; - case MSG_GET_SUGGESTED_WORDS: - mLatinIme.getSuggestedWords(msg.arg1 /* sessionId */, - msg.arg2 /* sequenceNumber */, (OnGetSuggestedWordsCallback) msg.obj); - break; - } - return true; - } - - // Run on the UI thread. - public void onStartBatchInput() { - synchronized (mLock) { - mHandler.removeMessages(MSG_UPDATE_GESTURE_PREVIEW_AND_SUGGESTION_STRIP); - mInBatchInput = true; - } - } - - // Run on the Handler thread. - private void updateBatchInput(final InputPointers batchPointers, final int sequenceNumber) { - synchronized (mLock) { - if (!mInBatchInput) { - // Batch input has ended or canceled while the message was being delivered. - return; - } - - getSuggestedWordsGestureLocked(batchPointers, sequenceNumber, - new OnGetSuggestedWordsCallback() { - @Override - public void onGetSuggestedWords(final SuggestedWords suggestedWords) { - mLatinIme.mHandler.showGesturePreviewAndSuggestionStrip( - suggestedWords, false /* dismissGestureFloatingPreviewText */); - } - }); - } - } - - // Run on the UI thread. - public void onUpdateBatchInput(final InputPointers batchPointers, - final int sequenceNumber) { - if (mHandler.hasMessages(MSG_UPDATE_GESTURE_PREVIEW_AND_SUGGESTION_STRIP)) { - return; - } - mHandler.obtainMessage(MSG_UPDATE_GESTURE_PREVIEW_AND_SUGGESTION_STRIP, 0 /* arg1 */, - sequenceNumber /* arg2 */, batchPointers /* obj */).sendToTarget(); - } - - public void onCancelBatchInput() { - synchronized (mLock) { - mInBatchInput = false; - } - } - - // Run on the UI thread. - public void onEndBatchInput(final InputPointers batchPointers) { - synchronized(mLock) { - getSuggestedWordsGestureLocked(batchPointers, SuggestedWords.NOT_A_SEQUENCE_NUMBER, - new OnGetSuggestedWordsCallback() { - @Override - public void onGetSuggestedWords(final SuggestedWords suggestedWords) { - mInBatchInput = false; - mLatinIme.mHandler.showGesturePreviewAndSuggestionStrip(suggestedWords, - true /* dismissGestureFloatingPreviewText */); - mLatinIme.mHandler.onEndBatchInput(suggestedWords); - } - }); - } - } - - // {@link LatinIME#getSuggestedWords(int)} method calls with same session id have to - // be synchronized. - private void getSuggestedWordsGestureLocked(final InputPointers batchPointers, - final int sequenceNumber, final OnGetSuggestedWordsCallback callback) { - mLatinIme.mInputLogic.mWordComposer.setBatchInputPointers(batchPointers); - getSuggestedWords(Suggest.SESSION_GESTURE, sequenceNumber, - new OnGetSuggestedWordsCallback() { - @Override - public void onGetSuggestedWords(SuggestedWords suggestedWords) { - if (suggestedWords.isEmpty()) { - // Previous suggestions are found in InputLogic#mSuggestedWords. - // Since these are the most recent ones and we just recomputed new - // ones to update them, it means the previous ones are there. - callback.onGetSuggestedWords(mLatinIme.mInputLogic.mSuggestedWords); - } else { - callback.onGetSuggestedWords(suggestedWords); - } - } - }); - } - - public void getSuggestedWords(final int sessionId, final int sequenceNumber, - final OnGetSuggestedWordsCallback callback) { - mHandler.obtainMessage(MSG_GET_SUGGESTED_WORDS, sessionId, sequenceNumber, callback) - .sendToTarget(); - } - - void quitLooper() { - mHandler.removeMessages(MSG_GET_SUGGESTED_WORDS); - mHandler.removeMessages(MSG_UPDATE_GESTURE_PREVIEW_AND_SUGGESTION_STRIP); - mHandler.getLooper().quit(); - } + mInputLogic.onCancelBatchInput(mHandler); } // This method must run on the UI Thread. @@ -1425,40 +1272,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } } - // This method must run on the UI Thread. - public void onEndBatchInputAsyncInternal(final SuggestedWords suggestedWords) { - final String batchInputText = suggestedWords.isEmpty() ? null : suggestedWords.getWord(0); - if (TextUtils.isEmpty(batchInputText)) { - return; - } - 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) { - mInputLogic.mConnection.commitText(batchInputText.substring(0, indexOfLastSpace), - 1); - showSuggestionStrip(suggestedWords.getSuggestedWordsForLastWordOfPhraseGesture()); - } - final String lastWord = batchInputText.substring(indexOfLastSpace); - mInputLogic.mWordComposer.setBatchInputWord(lastWord); - mInputLogic.mConnection.setComposingText(lastWord, 1); - } else { - mInputLogic.mWordComposer.setBatchInputWord(batchInputText); - mInputLogic.mConnection.setComposingText(batchInputText, 1); - } - mInputLogic.mConnection.endBatchEdit(); - if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - ResearchLogger.latinIME_onEndBatchInput(batchInputText, 0, suggestedWords); - } - // Space state must be updated before calling updateShiftState - mInputLogic.mSpaceState = SpaceState.PHANTOM; - mKeyboardSwitcher.updateShiftState(); - } - // Called from PointerTracker through the KeyboardActionListener interface @Override public void onFinishSlidingInput() { @@ -1533,7 +1346,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } } - private void getSuggestedWords(final int sessionId, final int sequenceNumber, + // TODO[IL]: Move this out of LatinIME. + public void getSuggestedWords(final int sessionId, final int sequenceNumber, final OnGetSuggestedWordsCallback callback) { final Keyboard keyboard = mKeyboardSwitcher.getKeyboard(); final Suggest suggest = mInputLogic.mSuggest; diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java index 37311acf2..4d174ddb8 100644 --- a/java/src/com/android/inputmethod/latin/RichInputConnection.java +++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java @@ -706,45 +706,6 @@ public final class RichInputConnection { return TextUtils.equals(text, beforeText); } - /* (non-javadoc) - * Returns the word before the cursor if the cursor is at the end of a word, null otherwise - */ - public CharSequence getWordBeforeCursorIfAtEndOfWord(final SettingsValues settings) { - // Bail out if the cursor is in the middle of a word (cursor must be followed by whitespace, - // separator or end of line/text) - // Example: "test|"<EOL> "te|st" get rejected here - final CharSequence textAfterCursor = getTextAfterCursor(1, 0); - if (!TextUtils.isEmpty(textAfterCursor) - && !settings.isWordSeparator(textAfterCursor.charAt(0))) return null; - - // Bail out if word before cursor is 0-length or a single non letter (like an apostrophe) - // Example: " -|" gets rejected here but "e-|" and "e|" are okay - CharSequence word = getWordAtCursor(settings.mWordSeparators); - // We don't suggest on leading single quotes, so we have to remove them from the word if - // it starts with single quotes. - while (!TextUtils.isEmpty(word) && Constants.CODE_SINGLE_QUOTE == word.charAt(0)) { - word = word.subSequence(1, word.length()); - } - if (TextUtils.isEmpty(word)) return null; - // Find the last code point of the string - final int lastCodePoint = Character.codePointBefore(word, word.length()); - // If for some reason the text field contains non-unicode binary data, or if the - // charsequence is exactly one char long and the contents is a low surrogate, return null. - if (!Character.isDefined(lastCodePoint)) return null; - // Bail out if the cursor is not at the end of a word (cursor must be preceded by - // non-whitespace, non-separator, non-start-of-text) - // Example ("|" is the cursor here) : <SOL>"|a" " |a" " | " all get rejected here. - if (settings.isWordSeparator(lastCodePoint)) return null; - final char firstChar = word.charAt(0); // we just tested that word is not empty - if (word.length() == 1 && !Character.isLetter(firstChar)) return null; - - // We don't restart suggestion if the first character is not a letter, because we don't - // start composing when the first character is not a letter. - if (!Character.isLetter(firstChar)) return null; - - return word; - } - public boolean revertDoubleSpacePeriod() { if (DEBUG_BATCH_NESTING) checkBatchEdit(); // Here we test whether we indeed have a period and a space before us. This should not diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java index 5dc8f3053..fc85c1388 100644 --- a/java/src/com/android/inputmethod/latin/Suggest.java +++ b/java/src/com/android/inputmethod/latin/Suggest.java @@ -126,8 +126,9 @@ public final class Suggest { } else { wordComposerForLookup = wordComposer; } - mDictionaryFacilitator.getSuggestions(wordComposer, prevWordForBigram, proximityInfo, - blockOffensiveWords, additionalFeaturesOptions, SESSION_TYPING, suggestionsSet); + mDictionaryFacilitator.getSuggestions(wordComposerForLookup, prevWordForBigram, + proximityInfo, blockOffensiveWords, additionalFeaturesOptions, SESSION_TYPING, + suggestionsSet); final String whitelistedWord; if (suggestionsSet.isEmpty()) { whitelistedWord = null; diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java index 59b722134..b365003a5 100644 --- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java +++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java @@ -88,12 +88,12 @@ public final class InputLogic { public int mLastSelectionStart = Constants.NOT_A_CURSOR_POSITION; public int mLastSelectionEnd = Constants.NOT_A_CURSOR_POSITION; - public int mDeleteCount; + private int mDeleteCount; private long mLastKeyTime; public final TreeSet<Long> mCurrentlyPressedHardwareKeys = CollectionUtils.newTreeSet(); // Keeps track of most recently inserted text (multi-character key) for reverting - public String mEnteredText; + 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. @@ -116,16 +116,33 @@ public final class InputLogic { * some things must not be done (for example, the keyboard should not be reset to the * alphabetic layout), so do not send false to this just in case. * - * @param restarting whether input is starting in the same field as before. + * @param restarting whether input is starting in the same field as before. Unused for now. + * @param editorInfo the editorInfo associated with the editor. */ - public void startInput(final boolean restarting) { - mInputLogicHandler = new InputLogicHandler(); + public void startInput(final boolean restarting, final EditorInfo editorInfo) { + mEnteredText = null; + resetComposingState(true /* alsoResetLastComposedWord */); + mDeleteCount = 0; + mSpaceState = SpaceState.NONE; + mRecapitalizeStatus.deactivate(); + mCurrentlyPressedHardwareKeys.clear(); + mSuggestedWords = SuggestedWords.EMPTY; + mLastSelectionStart = editorInfo.initialSelStart; + 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(); + mInputLogicHandler = new InputLogicHandler(mLatinIME, this); } /** * Clean up the input logic after input is finished. */ public void finishInput() { + if (mWordComposer.isComposingWord()) { + mConnection.finishComposingText(); + } + resetComposingState(true /* alsoResetLastComposedWord */); mInputLogicHandler.destroy(); mInputLogicHandler = null; } @@ -140,11 +157,11 @@ public final class InputLogic { * @param rawText the text to input. */ public void onTextInput(final SettingsValues settingsValues, final String rawText, - // TODO: remove these arguments - final LatinIME.UIHandler handler, final LatinIME.InputUpdater inputUpdater) { + // TODO: remove this argument + final LatinIME.UIHandler handler) { mConnection.beginBatchEdit(); if (mWordComposer.isComposingWord()) { - commitCurrentAutoCorrection(settingsValues, rawText, handler, inputUpdater); + commitCurrentAutoCorrection(settingsValues, rawText, handler); } else { resetComposingState(true /* alsoResetLastComposedWord */); } @@ -180,8 +197,8 @@ public final class InputLogic { * @param y the y-coordinate where the user pressed the key, or NOT_A_COORDINATE. */ public void onCodeInput(final int code, final int x, final int y, - // TODO: remove these four arguments - final LatinIME.UIHandler handler, final LatinIME.InputUpdater inputUpdater, + // TODO: remove these three arguments + final LatinIME.UIHandler handler, final KeyboardSwitcher keyboardSwitcher, final SubtypeSwitcher subtypeSwitcher) { if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { ResearchLogger.latinIME_onCodeInput(code, x, y); @@ -274,16 +291,16 @@ public final class InputLogic { // No action label, and the action from imeOptions is NONE: this is a regular // enter key that should input a carriage return. didAutoCorrect = handleNonSpecialCharacter(settingsValues, Constants.CODE_ENTER, - x, y, spaceState, keyboardSwitcher, handler, inputUpdater); + x, y, spaceState, keyboardSwitcher, handler); } break; case Constants.CODE_SHIFT_ENTER: didAutoCorrect = handleNonSpecialCharacter(settingsValues, Constants.CODE_ENTER, - x, y, spaceState, keyboardSwitcher, handler, inputUpdater); + x, y, spaceState, keyboardSwitcher, handler); break; default: didAutoCorrect = handleNonSpecialCharacter(settingsValues, - code, x, y, spaceState, keyboardSwitcher, handler, inputUpdater); + code, x, y, spaceState, keyboardSwitcher, handler); break; } keyboardSwitcher.onCodeInput(code); @@ -300,9 +317,8 @@ public final class InputLogic { public void onStartBatchInput(final SettingsValues settingsValues, // TODO: remove these arguments - final KeyboardSwitcher keyboardSwitcher, final LatinIME.UIHandler handler, - final LatinIME.InputUpdater inputUpdater) { - inputUpdater.onStartBatchInput(); + final KeyboardSwitcher keyboardSwitcher, final LatinIME.UIHandler handler) { + mInputLogicHandler.onStartBatchInput(); handler.showGesturePreviewAndSuggestionStrip( SuggestedWords.EMPTY, false /* dismissGestureFloatingPreviewText */); handler.cancelUpdateSuggestionStrip(); @@ -329,7 +345,7 @@ public final class InputLogic { // 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(settingsValues, LastComposedWord.NOT_A_SEPARATOR, - handler, inputUpdater); + handler); } else { commitTyped(settingsValues, LastComposedWord.NOT_A_SEPARATOR); } @@ -374,7 +390,7 @@ public final class InputLogic { public void onUpdateBatchInput(final SettingsValues settingsValues, final InputPointers batchPointers, // TODO: remove these arguments - final KeyboardSwitcher keyboardSwitcher, final LatinIME.InputUpdater inputUpdater) { + final KeyboardSwitcher keyboardSwitcher) { if (settingsValues.mPhraseGestureEnabled) { final SuggestedWordInfo candidate = mSuggestedWords.getAutoCommitCandidate(); // If these suggested words have been generated with out of date input pointers, then @@ -395,20 +411,17 @@ public final class InputLogic { } } } - inputUpdater.onUpdateBatchInput(batchPointers, mAutoCommitSequenceNumber); + mInputLogicHandler.onUpdateBatchInput(batchPointers, mAutoCommitSequenceNumber); } public void onEndBatchInput(final SettingsValues settingValues, - final InputPointers batchPointers, - // TODO: remove these arguments - final LatinIME.InputUpdater inputUpdater) { - inputUpdater.onEndBatchInput(batchPointers); + final InputPointers batchPointers) { + mInputLogicHandler.onEndBatchInput(batchPointers, mAutoCommitSequenceNumber); } - // TODO: remove these arguments - public void onCancelBatchInput(final LatinIME.UIHandler handler, - final LatinIME.InputUpdater inputUpdater) { - inputUpdater.onCancelBatchInput(); + // TODO: remove this argument + public void onCancelBatchInput(final LatinIME.UIHandler handler) { + mInputLogicHandler.onCancelBatchInput(); handler.showGesturePreviewAndSuggestionStrip( SuggestedWords.EMPTY, true /* dismissGestureFloatingPreviewText */); } @@ -431,14 +444,13 @@ public final class InputLogic { private boolean handleNonSpecialCharacter(final SettingsValues settingsValues, final int codePoint, final int x, final int y, final int spaceState, // TODO: remove these arguments - final KeyboardSwitcher keyboardSwitcher, final LatinIME.UIHandler handler, - final LatinIME.InputUpdater inputUpdater) { + final KeyboardSwitcher keyboardSwitcher, final LatinIME.UIHandler handler) { mSpaceState = SpaceState.NONE; final boolean didAutoCorrect; if (settingsValues.isWordSeparator(codePoint) || Character.getType(codePoint) == Character.OTHER_SYMBOL) { didAutoCorrect = handleSeparator(settingsValues, codePoint, x, y, spaceState, - keyboardSwitcher, handler, inputUpdater); + keyboardSwitcher, handler); } else { didAutoCorrect = false; if (SpaceState.PHANTOM == spaceState) { @@ -578,8 +590,7 @@ public final class InputLogic { private boolean handleSeparator(final SettingsValues settingsValues, final int codePoint, final int x, final int y, final int spaceState, // TODO: remove these arguments - final KeyboardSwitcher keyboardSwitcher, final LatinIME.UIHandler handler, - final LatinIME.InputUpdater inputUpdater) { + final KeyboardSwitcher keyboardSwitcher, final LatinIME.UIHandler handler) { boolean didAutoCorrect = false; // We avoid sending spaces in languages without spaces if we were composing. final boolean shouldAvoidSendingCode = Constants.CODE_SPACE == codePoint @@ -595,7 +606,7 @@ public final class InputLogic { if (settingsValues.mCorrectionEnabled) { final String separator = shouldAvoidSendingCode ? LastComposedWord.NOT_A_SEPARATOR : StringUtils.newSingleCodePointString(codePoint); - commitCurrentAutoCorrection(settingsValues, separator, handler, inputUpdater); + commitCurrentAutoCorrection(settingsValues, separator, handler); didAutoCorrect = true; } else { commitTyped(settingsValues, StringUtils.newSingleCodePointString(codePoint)); @@ -669,6 +680,7 @@ public final class InputLogic { // TODO: remove these arguments final LatinIME.UIHandler handler, final KeyboardSwitcher keyboardSwitcher) { mSpaceState = SpaceState.NONE; + final int deleteCountAtStart = mDeleteCount; mDeleteCount++; // In many cases, we may have to put the keyboard in auto-shift state again. However @@ -800,12 +812,13 @@ public final class InputLogic { } } } - if (settingsValues.isSuggestionsRequested() + if (settingsValues.isSuggestionStripVisible() && settingsValues.mCurrentLanguageHasSpaces) { - restartSuggestionsOnWordBeforeCursorIfAtEndOfWord(settingsValues, keyboardSwitcher, - handler); + restartSuggestionsOnWordTouchedByCursor(settingsValues, + deleteCountAtStart - mDeleteCount /* offset */, + true /* includeResumedWordInSuggestions */, keyboardSwitcher); } - // We just removed a character. We need to update the auto-caps state. + // We just removed at least one character. We need to update the auto-caps state. keyboardSwitcher.updateShiftState(); } } @@ -991,8 +1004,8 @@ public final class InputLogic { } public void performUpdateSuggestionStripSync(final SettingsValues settingsValues, - // TODO: Remove this variable - final LatinIME.UIHandler handler, final LatinIME.InputUpdater inputUpdater) { + // TODO: Remove this argument + final LatinIME.UIHandler handler) { handler.cancelUpdateSuggestionStrip(); // Check if we have a suggestion engine attached. @@ -1010,7 +1023,7 @@ public final class InputLogic { } final AsyncResultHolder<SuggestedWords> holder = new AsyncResultHolder<SuggestedWords>(); - inputUpdater.getSuggestedWords(Suggest.SESSION_TYPING, + mInputLogicHandler.getSuggestedWords(Suggest.SESSION_TYPING, SuggestedWords.NOT_A_SEQUENCE_NUMBER, new OnGetSuggestedWordsCallback() { @Override public void onGetSuggestedWords(final SuggestedWords suggestedWords) { @@ -1031,45 +1044,19 @@ public final class InputLogic { } /** - * Check if the cursor is actually at the end of a word. If so, restart suggestions on this - * word, otherwise do nothing. - * @param settingsValues the current values of the settings. - */ - private void restartSuggestionsOnWordBeforeCursorIfAtEndOfWord( - final SettingsValues settingsValues, - // TODO: remove these two arguments - final KeyboardSwitcher keyboardSwitcher, final LatinIME.UIHandler handler) { - final CharSequence word = mConnection.getWordBeforeCursorIfAtEndOfWord(settingsValues); - if (null != word) { - final String wordString = word.toString(); - mWordComposer.setComposingWord(word, - // Previous word is the 2nd word before cursor because we are restarting on the - // 1st word before cursor. - getNthPreviousWordForSuggestion(settingsValues, 2 /* nthPreviousWord */), - keyboardSwitcher.getKeyboard()); - final int length = word.length(); - mConnection.deleteSurroundingText(length, 0); - mConnection.setComposingText(word, 1); - handler.postUpdateSuggestionStrip(); - // 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 */); - } - } - } - - /** * Check if the cursor is touching a word. If so, restart suggestions on this word, else * do nothing. * * @param settingsValues the current values of the settings. + * @param offset how much the cursor is expected to have moved since the last updateSelection. + * @param includeResumedWordInSuggestions whether to include the word on which we resume + * suggestions in the suggestion list. */ // TODO: make this private. public void restartSuggestionsOnWordTouchedByCursor(final SettingsValues settingsValues, - // TODO: Remove these argument. - final KeyboardSwitcher keyboardSwitcher, final LatinIME.InputUpdater inputUpdater) { + final int offset, final boolean includeResumedWordInSuggestions, + // TODO: Remove this argument. + final KeyboardSwitcher keyboardSwitcher) { // HACK: We may want to special-case some apps that exhibit bad behavior in case of // recorrection. This is a temporary, stopgap measure that will be removed later. // TODO: remove this. @@ -1083,6 +1070,7 @@ public final class InputLogic { if (mLastSelectionStart != mLastSelectionEnd) return; // If we don't know the cursor location, return. if (mLastSelectionStart < 0) return; + final int expectedCursorPosition = mLastSelectionStart + offset; // We know Start == End if (!mConnection.isCursorTouchingWord(settingsValues)) return; final TextRange range = mConnection.getWordRangeAtCursor( settingsValues.mWordSeparators, 0 /* additionalPrecedingWordsCount */); @@ -1091,9 +1079,16 @@ public final class InputLogic { // 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 > expectedCursorPosition) return; final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList(); final String typedWord = range.mWord.toString(); + if (includeResumedWordInSuggestions) { + suggestions.add(new SuggestedWordInfo(typedWord, + SuggestionStripView.MAX_SUGGESTIONS + 1, + SuggestedWordInfo.KIND_TYPED, Dictionary.DICTIONARY_USER_TYPED, + SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */, + SuggestedWordInfo.NOT_A_CONFIDENCE /* autoCommitFirstWordConfidence */)); + } if (!isResumableWord(settingsValues, typedWord)) return; int i = 0; for (final SuggestionSpan span : range.getSuggestionSpansAtWord()) { @@ -1118,18 +1113,19 @@ public final class InputLogic { keyboardSwitcher.getKeyboard()); mWordComposer.setCursorPositionWithinWord( typedWord.codePointCount(0, numberOfCharsInWordBeforeCursor)); - mConnection.setComposingRegion(mLastSelectionStart - numberOfCharsInWordBeforeCursor, - mLastSelectionEnd + range.getNumberOfCharsInWordAfterCursor()); + mConnection.setComposingRegion(expectedCursorPosition - numberOfCharsInWordBeforeCursor, + expectedCursorPosition + 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. - inputUpdater.getSuggestedWords(Suggest.SESSION_TYPING, + mInputLogicHandler.getSuggestedWords(Suggest.SESSION_TYPING, SuggestedWords.NOT_A_SEQUENCE_NUMBER, new OnGetSuggestedWordsCallback() { @Override public void onGetSuggestedWords( final SuggestedWords suggestedWordsIncludingTypedWord) { final SuggestedWords suggestedWords; - if (suggestedWordsIncludingTypedWord.size() > 1) { + if (suggestedWordsIncludingTypedWord.size() > 1 + && !includeResumedWordInSuggestions) { // We were able to compute new suggestions for this word. // Remove the typed word, since we don't want to display it in this // case. The #getSuggestedWordsExcludingTypedWord() method sets @@ -1509,6 +1505,47 @@ public final class InputLogic { } /** + * Do the final processing after a batch input has ended. This commits the word to the editor. + * @param settingsValues the current values of the settings. + * @param suggestedWords suggestedWords to use. + */ + public void endBatchInputAsyncInternal(final SettingsValues settingsValues, + final SuggestedWords suggestedWords, + // TODO: remove this argument + final KeyboardSwitcher keyboardSwitcher) { + final String batchInputText = suggestedWords.isEmpty() ? null : suggestedWords.getWord(0); + if (TextUtils.isEmpty(batchInputText)) { + return; + } + mConnection.beginBatchEdit(); + if (SpaceState.PHANTOM == mSpaceState) { + promotePhantomSpace(settingsValues); + } + if (settingsValues.mPhraseGestureEnabled) { + // Find the last space + final int indexOfLastSpace = batchInputText.lastIndexOf(Constants.CODE_SPACE) + 1; + if (0 != indexOfLastSpace) { + mConnection.commitText(batchInputText.substring(0, indexOfLastSpace), 1); + mLatinIME.showSuggestionStrip( + suggestedWords.getSuggestedWordsForLastWordOfPhraseGesture()); + } + final String lastWord = batchInputText.substring(indexOfLastSpace); + mWordComposer.setBatchInputWord(lastWord); + mConnection.setComposingText(lastWord, 1); + } else { + mWordComposer.setBatchInputWord(batchInputText); + mConnection.setComposingText(batchInputText, 1); + } + mConnection.endBatchEdit(); + if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { + ResearchLogger.latinIME_onEndBatchInput(batchInputText, 0, suggestedWords); + } + // Space state must be updated before calling updateShiftState + mSpaceState = SpaceState.PHANTOM; + keyboardSwitcher.updateShiftState(); + } + + /** * Commit the typed string to the editor. * * This is typically called when we should commit the currently composing word without applying @@ -1552,14 +1589,13 @@ public final class InputLogic { * @param settingsValues the current value of the settings. * @param separator the separator that's causing the commit to happen. */ - // TODO: Make this private - public void commitCurrentAutoCorrection(final SettingsValues settingsValues, + private void commitCurrentAutoCorrection(final SettingsValues settingsValues, final String separator, - // TODO: Remove these arguments. - final LatinIME.UIHandler handler, final LatinIME.InputUpdater inputUpdater) { + // TODO: Remove this argument. + final LatinIME.UIHandler handler) { // Complete any pending suggestions query first if (handler.hasPendingUpdateSuggestions()) { - performUpdateSuggestionStripSync(settingsValues, handler, inputUpdater); + performUpdateSuggestionStripSync(settingsValues, handler); } final String typedAutoCorrection = mWordComposer.getAutoCorrectionOrNull(); final String typedWord = mWordComposer.getTypedWord(); @@ -1645,8 +1681,7 @@ public final class InputLogic { * detect the most damaging cases: when the cursor position is declared to be much smaller * than it really is. */ - // TODO: make this private - public void tryFixLyingCursorPosition() { + private void tryFixLyingCursorPosition() { final CharSequence textBeforeCursor = mConnection.getTextBeforeCursor( Constants.EDITOR_CONTENTS_CACHE_SIZE, 0); if (null == textBeforeCursor) { diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogicHandler.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogicHandler.java index d611e4bf8..3258dcdfb 100644 --- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogicHandler.java +++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogicHandler.java @@ -20,18 +20,33 @@ import android.os.Handler; import android.os.HandlerThread; import android.os.Message; +import com.android.inputmethod.latin.InputPointers; +import com.android.inputmethod.latin.LatinIME; +import com.android.inputmethod.latin.Suggest; +import com.android.inputmethod.latin.SuggestedWords; +import com.android.inputmethod.latin.Suggest.OnGetSuggestedWordsCallback; + /** * A helper to manage deferred tasks for the input logic. */ // TODO: Make this package private public class InputLogicHandler implements Handler.Callback { final Handler mNonUIThreadHandler; + // TODO: remove this reference. + final LatinIME mLatinIME; + final InputLogic mInputLogic; + private final Object mLock = new Object(); + private boolean mInBatchInput; // synchronized using {@link #mLock}. + + private static final int MSG_GET_SUGGESTED_WORDS = 1; - public InputLogicHandler() { + public InputLogicHandler(final LatinIME latinIME, final InputLogic inputLogic) { final HandlerThread handlerThread = new HandlerThread( InputLogicHandler.class.getSimpleName()); handlerThread.start(); mNonUIThreadHandler = new Handler(handlerThread.getLooper(), this); + mLatinIME = latinIME; + mInputLogic = inputLogic; } public void destroy() { @@ -42,8 +57,116 @@ public class InputLogicHandler implements Handler.Callback { * Handle a message. * @see android.os.Handler.Callback#handleMessage(android.os.Message) */ + // Called on the Non-UI handler thread by the Handler code. @Override public boolean handleMessage(final Message msg) { + switch (msg.what) { + case MSG_GET_SUGGESTED_WORDS: + mLatinIME.getSuggestedWords(msg.arg1 /* sessionId */, + msg.arg2 /* sequenceNumber */, (OnGetSuggestedWordsCallback) msg.obj); + break; + } return true; } + + // Called on the UI thread by InputLogic. + public void onStartBatchInput() { + synchronized (mLock) { + mInBatchInput = true; + } + } + + /** + * Fetch suggestions corresponding to an update of a batch input. + * @param batchPointers the updated pointers, including the part that was passed last time. + * @param sequenceNumber the sequence number associated with this batch input. + * @param forEnd true if this is the end of a batch input, false if it's an update. + */ + // This method can be called from any thread and will see to it that the correct threads + // are used for parts that require it. This method will send a message to the Non-UI handler + // thread to pull suggestions, and get the inlined callback to get called on the Non-UI + // handler thread. If this is the end of a batch input, the callback will then proceed to + // send a message to the UI handler in LatinIME so that showing suggestions can be done on + // the UI thread. + private void updateBatchInput(final InputPointers batchPointers, + final int sequenceNumber, final boolean forEnd) { + synchronized (mLock) { + if (!mInBatchInput) { + // Batch input has ended or canceled while the message was being delivered. + return; + } + mInputLogic.mWordComposer.setBatchInputPointers(batchPointers); + getSuggestedWords(Suggest.SESSION_GESTURE, sequenceNumber, + new OnGetSuggestedWordsCallback() { + @Override + public void onGetSuggestedWords(SuggestedWords suggestedWords) { + // We're now inside the callback. This always runs on the Non-UI thread, + // no matter what thread updateBatchInput was originally called on. + if (suggestedWords.isEmpty()) { + // Use old suggestions if we don't have any new ones. + // Previous suggestions are found in InputLogic#mSuggestedWords. + // Since these are the most recent ones and we just recomputed + // new ones to update them, then the previous ones are there. + suggestedWords = mInputLogic.mSuggestedWords; + } + mLatinIME.mHandler.showGesturePreviewAndSuggestionStrip(suggestedWords, + forEnd /* dismissGestureFloatingPreviewText */); + if (forEnd) { + mInBatchInput = false; + // The following call schedules onEndBatchInputAsyncInternal + // to be called on the UI thread. + mLatinIME.mHandler.onEndBatchInput(suggestedWords); + } + } + }); + } + } + + /** + * Update a batch input. + * + * This fetches suggestions and updates the suggestion strip and the floating text preview. + * + * @param batchPointers the updated batch pointers. + * @param sequenceNumber the sequence number associated with this batch input. + */ + // Called on the UI thread by InputLogic. + public void onUpdateBatchInput(final InputPointers batchPointers, + final int sequenceNumber) { + updateBatchInput(batchPointers, sequenceNumber, false /* forEnd */); + } + + /** + * Cancel a batch input. + * + * Note that as opposed to onEndBatchInput, we do the UI side of this immediately on the + * same thread, rather than get this to call a method in LatinIME. This is because + * canceling a batch input does not necessitate the long operation of pulling suggestions. + */ + // Called on the UI thread by InputLogic. + public void onCancelBatchInput() { + synchronized (mLock) { + mInBatchInput = false; + } + } + + /** + * Finish a batch input. + * + * This fetches suggestions, updates the suggestion strip and commits the first suggestion. + * It also dismisses the floating text preview. + * + * @param batchPointers the updated batch pointers. + * @param sequenceNumber the sequence number associated with this batch input. + */ + // Called on the UI thread by InputLogic. + public void onEndBatchInput(final InputPointers batchPointers, final int sequenceNumber) { + updateBatchInput(batchPointers, sequenceNumber, true /* forEnd */); + } + + public void getSuggestedWords(final int sessionId, final int sequenceNumber, + final OnGetSuggestedWordsCallback callback) { + mNonUIThreadHandler.obtainMessage( + MSG_GET_SUGGESTED_WORDS, sessionId, sequenceNumber, callback).sendToTarget(); + } } |