diff options
Diffstat (limited to 'java/src/com/android/inputmethod/latin/LatinIME.java')
-rw-r--r-- | java/src/com/android/inputmethod/latin/LatinIME.java | 264 |
1 files changed, 219 insertions, 45 deletions
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index 56b1c786e..efa51569f 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -44,7 +44,9 @@ import android.os.Message; import android.os.SystemClock; import android.preference.PreferenceManager; import android.text.InputType; +import android.text.SpannableString; import android.text.TextUtils; +import android.text.style.SuggestionSpan; import android.util.Log; import android.util.PrintWriterPrinter; import android.util.Printer; @@ -72,6 +74,7 @@ import com.android.inputmethod.keyboard.KeyboardActionListener; import com.android.inputmethod.keyboard.KeyboardId; import com.android.inputmethod.keyboard.KeyboardSwitcher; import com.android.inputmethod.keyboard.MainKeyboardView; +import com.android.inputmethod.latin.RichInputConnection.Range; import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; import com.android.inputmethod.latin.Utils.Stats; import com.android.inputmethod.latin.define.ProductionFlag; @@ -158,6 +161,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction mPositionalInfoForUserDictPendingAddition = null; 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; @@ -197,6 +201,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction private static final int MSG_PENDING_IMS_CALLBACK = 1; private static final int MSG_UPDATE_SUGGESTION_STRIP = 2; private static final int MSG_SHOW_GESTURE_PREVIEW_AND_SUGGESTION_STRIP = 3; + private static final int MSG_RESUME_SUGGESTIONS = 4; private static final int ARG1_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT = 1; @@ -234,6 +239,9 @@ public final class LatinIME extends InputMethodService implements KeyboardAction latinIme.showGesturePreviewAndSuggestionStrip((SuggestedWords)msg.obj, msg.arg1 == ARG1_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT); break; + case MSG_RESUME_SUGGESTIONS: + latinIme.restartSuggestionsOnWordTouchedByCursor(); + break; } } @@ -241,6 +249,10 @@ public final class LatinIME extends InputMethodService implements KeyboardAction sendMessageDelayed(obtainMessage(MSG_UPDATE_SUGGESTION_STRIP), mDelayUpdateSuggestions); } + public void postResumeSuggestions() { + sendMessageDelayed(obtainMessage(MSG_RESUME_SUGGESTIONS), mDelayUpdateSuggestions); + } + public void cancelUpdateSuggestionStrip() { removeMessages(MSG_UPDATE_SUGGESTION_STRIP); } @@ -427,6 +439,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction mHandler.onCreate(); DEBUG = LatinImeLogger.sDBG; + // TODO: Resolve mutual dependencies of {@link #loadSettings()} and {@link #initSuggest()}. loadSettings(); initSuggest(); @@ -464,6 +477,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction final InputAttributes inputAttributes = new InputAttributes(getCurrentInputEditorInfo(), isFullscreenMode()); mSettings.loadSettings(locale, inputAttributes); + // May need to reset the contacts dictionary depending on the user settings. resetContactsDictionary(null == mSuggest ? null : mSuggest.getContactsDictionary()); } @@ -730,8 +744,14 @@ public final class LatinIME extends InputMethodService implements KeyboardAction resetComposingState(true /* alsoResetLastComposedWord */); mDeleteCount = 0; mSpaceState = SPACE_STATE_NONE; + mRecapitalizeStatus.deactivate(); mCurrentlyPressedHardwareKeys.clear(); + // Note: the following does a round-trip IPC on the main thread: be careful + final Locale currentLocale = mSubtypeSwitcher.getCurrentSubtypeLocale(); + if (null != mSuggest && null != currentLocale && !currentLocale.equals(mSuggest.mLocale)) { + initSuggest(); + } if (mSuggestionStripView != null) { // This will set the punctuation suggestions if next word suggestion is off; // otherwise it will clear the suggestion strip. @@ -784,8 +804,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction // to the user dictionary. if (null != mPositionalInfoForUserDictPendingAddition && mPositionalInfoForUserDictPendingAddition.tryReplaceWithActualWord( - mConnection, editorInfo, mLastSelectionEnd, - mSubtypeSwitcher.getCurrentSubtypeLocale())) { + mConnection, editorInfo, mLastSelectionEnd, currentLocale)) { mPositionalInfoForUserDictPendingAddition = null; } // If tryReplaceWithActualWord returns false, we don't know what word was @@ -910,13 +929,13 @@ public final class LatinIME extends InputMethodService implements KeyboardAction resetEntireInputState(newSelStart); } + // We moved the cursor. If we are touching a word, we need to resume suggestion. + mHandler.postResumeSuggestions(); + // Reset the last recapitalization. + mRecapitalizeStatus.deactivate(); mKeyboardSwitcher.updateShiftState(); } mExpectingUpdateSelection = false; - // TODO: Decide to call restartSuggestionsOnWordBeforeCursorIfAtEndOfWord() or not - // here. It would probably be too expensive to call directly here but we may want to post a - // message to delay it. The point would be to unify behavior between backspace to the - // end of a word and manually put the pointer at the end of the word. // Make a note of the cursor position mLastSelectionStart = newSelStart; @@ -983,7 +1002,6 @@ public final class LatinIME extends InputMethodService implements KeyboardAction } } if (!mSettings.getCurrent().isApplicationSpecifiedCompletionsOn()) return; - mApplicationSpecifiedCompletions = applicationSpecifiedCompletions; if (applicationSpecifiedCompletions == null) { clearSuggestionStrip(); if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { @@ -991,6 +1009,8 @@ public final class LatinIME extends InputMethodService implements KeyboardAction } return; } + mApplicationSpecifiedCompletions = + CompletionInfoUtils.removeNulls(applicationSpecifiedCompletions); final ArrayList<SuggestedWords.SuggestedWordInfo> applicationSuggestedWords = SuggestedWords.getFromApplicationSpecifiedCompletions( @@ -1166,6 +1186,15 @@ public final class LatinIME extends InputMethodService implements KeyboardAction SPACE_STATE_PHANTOM == mSpaceState); } + public int getCurrentRecapitalizeState() { + if (!mRecapitalizeStatus.isActive() + || !mRecapitalizeStatus.isSetAt(mLastSelectionStart, 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(); @@ -1243,10 +1272,6 @@ public final class LatinIME extends InputMethodService implements KeyboardAction } else { wordToEdit = word; } - mPositionalInfoForUserDictPendingAddition = - new PositionalInfoForUserDictPendingAddition( - wordToEdit, mLastSelectionEnd, getCurrentInputEditorInfo(), - mLastComposedWord.mCapitalizedMode); mUserDictionary.addWordToUserDictionary(wordToEdit); } @@ -1380,8 +1405,18 @@ public final class LatinIME extends InputMethodService implements KeyboardAction LatinImeLogger.logOnDelete(x, y); break; case Constants.CODE_SHIFT: + // Note: calling back to the keyboard on Shift key is handled in onPressKey() + // and onReleaseKey(). + 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_SWITCH_ALPHA_SYMBOL: - // Shift and symbol key is handled in onPressKey() and onReleaseKey(). + // Note: calling back to the keyboard on symbol key is handled in onPressKey() + // and onReleaseKey(). break; case Constants.CODE_SETTINGS: onSettingsKeyPressed(); @@ -1459,7 +1494,13 @@ public final class LatinIME extends InputMethodService implements KeyboardAction "", mWordComposer.getTypedWord(), " ", mWordComposer); } } - commitTyped(LastComposedWord.NOT_A_SEPARATOR); + 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); + } else { + commitTyped(LastComposedWord.NOT_A_SEPARATOR); + } } final int keyX, keyY; final Keyboard keyboard = mKeyboardSwitcher.getKeyboard(); @@ -1515,8 +1556,12 @@ public final class LatinIME extends InputMethodService implements KeyboardAction } final int wordComposerSize = mWordComposer.size(); // Since isComposingWord() is true, the size is at least 1. - final int lastChar = mWordComposer.getCodeAt(wordComposerSize - 1); - if (wordComposerSize <= 1) { + final int lastChar = mWordComposer.getCodeBeforeCursor(); + if (mWordComposer.isCursorFrontOrMiddleOfComposingWord()) { + // If we are in the middle of a recorrection, we need to commit the recorrection + // first so that we can insert the batch input at the current cursor position. + resetEntireInputState(mLastSelectionStart); + } 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 // tap one-letter words and you want them auto-corrected (typically, "i" in English @@ -1540,7 +1585,8 @@ public final class LatinIME extends InputMethodService implements KeyboardAction } } else { final int codePointBeforeCursor = mConnection.getCodePointBeforeCursor(); - if (mSettings.getCurrent().isUsuallyFollowedBySpace(codePointBeforeCursor)) { + if (Character.isLetter(codePointBeforeCursor) + || mSettings.getCurrent().isUsuallyFollowedBySpace(codePointBeforeCursor)) { mSpaceState = SPACE_STATE_PHANTOM; } } @@ -1551,7 +1597,8 @@ public final class LatinIME extends InputMethodService implements KeyboardAction private static final class BatchInputUpdater implements Handler.Callback { private final Handler mHandler; private LatinIME mLatinIme; - private boolean mInBatchInput; // synchronized using "this". + private final Object mLock = new Object(); + private boolean mInBatchInput; // synchronized using {@link #mLock}. private BatchInputUpdater() { final HandlerThread handlerThread = new HandlerThread( @@ -1582,21 +1629,25 @@ public final class LatinIME extends InputMethodService implements KeyboardAction } // Run in the UI thread. - public synchronized void onStartBatchInput(final LatinIME latinIme) { - mHandler.removeMessages(MSG_UPDATE_GESTURE_PREVIEW_AND_SUGGESTION_STRIP); - mLatinIme = latinIme; - mInBatchInput = true; + public void onStartBatchInput(final LatinIME latinIme) { + synchronized (mLock) { + mHandler.removeMessages(MSG_UPDATE_GESTURE_PREVIEW_AND_SUGGESTION_STRIP); + mLatinIme = latinIme; + mInBatchInput = true; + } } // Run in the Handler thread. - private synchronized void updateBatchInput(final InputPointers batchPointers) { - if (!mInBatchInput) { - // Batch input has ended or canceled while the message was being delivered. - return; + private void updateBatchInput(final InputPointers batchPointers) { + synchronized (mLock) { + if (!mInBatchInput) { + // Batch input has ended or canceled while the message was being delivered. + return; + } + final SuggestedWords suggestedWords = getSuggestedWordsGestureLocked(batchPointers); + mLatinIme.mHandler.showGesturePreviewAndSuggestionStrip( + suggestedWords, false /* dismissGestureFloatingPreviewText */); } - final SuggestedWords suggestedWords = getSuggestedWordsGestureLocked(batchPointers); - mLatinIme.mHandler.showGesturePreviewAndSuggestionStrip( - suggestedWords, false /* dismissGestureFloatingPreviewText */); } // Run in the UI thread. @@ -1609,19 +1660,23 @@ public final class LatinIME extends InputMethodService implements KeyboardAction .sendToTarget(); } - public synchronized void onCancelBatchInput() { - mInBatchInput = false; - mLatinIme.mHandler.showGesturePreviewAndSuggestionStrip( - SuggestedWords.EMPTY, true /* dismissGestureFloatingPreviewText */); + public void onCancelBatchInput() { + synchronized (mLock) { + mInBatchInput = false; + mLatinIme.mHandler.showGesturePreviewAndSuggestionStrip( + SuggestedWords.EMPTY, true /* dismissGestureFloatingPreviewText */); + } } // Run in the UI thread. - public synchronized SuggestedWords onEndBatchInput(final InputPointers batchPointers) { - mInBatchInput = false; - final SuggestedWords suggestedWords = getSuggestedWordsGestureLocked(batchPointers); - mLatinIme.mHandler.showGesturePreviewAndSuggestionStrip( - suggestedWords, true /* dismissGestureFloatingPreviewText */); - return suggestedWords; + public SuggestedWords onEndBatchInput(final InputPointers batchPointers) { + synchronized (mLock) { + mInBatchInput = false; + final SuggestedWords suggestedWords = getSuggestedWordsGestureLocked(batchPointers); + mLatinIme.mHandler.showGesturePreviewAndSuggestionStrip( + suggestedWords, true /* dismissGestureFloatingPreviewText */); + return suggestedWords; + } } // {@link LatinIME#getSuggestedWords(int)} method calls with same session id have to @@ -1717,6 +1772,12 @@ public final class LatinIME extends InputMethodService implements KeyboardAction // 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); + // When we exit this if-clause, mWordComposer.isComposingWord() will return false. + } if (mWordComposer.isComposingWord()) { final int length = mWordComposer.size(); if (length > 0) { @@ -1727,7 +1788,9 @@ public final class LatinIME extends InputMethodService implements KeyboardAction ResearchLogger.getInstance().uncommitCurrentLogUnit( word, false /* dumpCurrentLogUnit */); } + final String rejectedSuggestion = mWordComposer.getTypedWord(); mWordComposer.reset(); + mWordComposer.setRejectedBatchModeSuggestion(rejectedSuggestion); } else { mWordComposer.deleteLast(); } @@ -1848,6 +1911,12 @@ public final class LatinIME extends InputMethodService implements KeyboardAction 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); + isComposingWord = false; + } // NOTE: isCursorTouchingWord() is a blocking IPC call, so it often takes several // dozen milliseconds. Avoid calling it as much as possible, since we are on the UI // thread here. @@ -1902,6 +1971,38 @@ public final class LatinIME extends InputMethodService implements KeyboardAction } } + 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)) { + mRecapitalizeStatus.initialize(mLastSelectionStart, mLastSelectionEnd, + mConnection.getSelectedText(0 /* flags, 0 for no styles */).toString(), + mSettings.getCurrentLocale(), mSettings.getWordSeparators()); + // 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.setSelection(mLastSelectionStart, mLastSelectionEnd); + } + } + 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 did an autocorrection, false otherwise. private boolean handleSeparator(final int primaryCode, final int x, final int y, final int spaceState) { @@ -1909,7 +2010,11 @@ public final class LatinIME extends InputMethodService implements KeyboardAction ResearchLogger.latinIME_handleSeparator(primaryCode, mWordComposer.isComposingWord()); } boolean didAutoCorrect = false; - // Handle separator + 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); + } if (mWordComposer.isComposingWord()) { if (mSettings.getCurrent().mCorrectionEnabled) { // TODO: maybe cache Strings in an <String> sparse array or something @@ -2321,6 +2426,73 @@ public final class LatinIME extends InputMethodService implements KeyboardAction } /** + * Check if the cursor is touching a word. If so, restart suggestions on this word, else + * do nothing. + */ + private void restartSuggestionsOnWordTouchedByCursor() { + // If the cursor is not touching a word, or if there is a selection, return right away. + if (mLastSelectionStart != mLastSelectionEnd) return; + if (!mConnection.isCursorTouchingWord(mSettings.getCurrent())) return; + final Range range = mConnection.getWordRangeAtCursor(mSettings.getWordSeparators(), + 0 /* additionalPrecedingWordsCount */); + if (null == range) return; // Happens if we don't have an input connection at all + final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList(); + final String typedWord = range.mWord.toString(); + if (range.mWord instanceof SpannableString) { + final SpannableString spannableString = (SpannableString)range.mWord; + int i = 0; + for (Object object : spannableString.getSpans(0, spannableString.length(), + SuggestionSpan.class)) { + SuggestionSpan span = (SuggestionSpan)object; + for (String s : span.getSuggestions()) { + ++i; + if (!TextUtils.equals(s, typedWord)) { + suggestions.add(new SuggestedWordInfo(s, + SuggestionStripView.MAX_SUGGESTIONS - i, + SuggestedWordInfo.KIND_RESUMED, Dictionary.TYPE_RESUMED)); + } + } + } + } + mWordComposer.setComposingWord(typedWord, mKeyboardSwitcher.getKeyboard()); + mWordComposer.setCursorPositionWithinWord(range.mCharsBefore); + mConnection.setComposingRegion(mLastSelectionStart - range.mCharsBefore, + mLastSelectionEnd + range.mCharsAfter); + final SuggestedWords suggestedWords; + 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. + final SuggestedWords suggestedWordsIncludingTypedWord = + getSuggestedWords(Suggest.SESSION_TYPING); + if (suggestedWordsIncludingTypedWord.size() > 1) { + // We were able to compute new suggestions for this word. + // Remove the typed word, since we don't want to display it in this case. + // The #getSuggestedWordsExcludingTypedWord() method sets willAutoCorrect to false. + suggestedWords = + suggestedWordsIncludingTypedWord.getSuggestedWordsExcludingTypedWord(); + } else { + // No saved suggestions, and we were unable to compute any good one either. + // Rather than displaying an empty suggestion strip, we'll display the original + // word alone in the middle. + // Since there is only one word, willAutoCorrect is false. + suggestedWords = suggestedWordsIncludingTypedWord; + } + } else { + // We found suggestion spans in the word. We'll create the SuggestedWords out of + // them, and make willAutoCorrect false. + suggestedWords = new SuggestedWords(suggestions, + true /* typedWordValid */, false /* willAutoCorrect */, + false /* isPunctuationSuggestions */, false /* isObsoleteSuggestions */, + false /* isPrediction */); + } + + // Note that it's very important here that suggestedWords.mWillAutoCorrect is false. + // We never want to auto-correct on a resumed suggestion. Please refer to the three + // places above where suggestedWords is affected. + showSuggestionStrip(suggestedWords, typedWord); + } + + /** * Check if the cursor is actually at the end of a word. If so, restart suggestions on this * word, else do nothing. */ @@ -2328,17 +2500,18 @@ public final class LatinIME extends InputMethodService implements KeyboardAction final CharSequence word = mConnection.getWordBeforeCursorIfAtEndOfWord(mSettings.getCurrent()); if (null != word) { - restartSuggestionsOnWordBeforeCursor(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(word.toString(), + ResearchLogger.getInstance().uncommitCurrentLogUnit(wordString, true /* dumpCurrentLogUnit */); } } } - private void restartSuggestionsOnWordBeforeCursor(final CharSequence word) { + private void restartSuggestionsOnWordBeforeCursor(final String word) { mWordComposer.setComposingWord(word, mKeyboardSwitcher.getKeyboard()); final int length = word.length(); mConnection.deleteSurroundingText(length, 0); @@ -2392,7 +2565,8 @@ public final class LatinIME extends InputMethodService implements KeyboardAction // This essentially inserts a space, and that's it. public void promotePhantomSpace() { - if (mSettings.getCurrent().shouldInsertSpacesAutomatically()) { + if (mSettings.getCurrent().shouldInsertSpacesAutomatically() + && !mConnection.textBeforeCursorLooksLikeURL()) { sendKeyCodePoint(Constants.CODE_SPACE); if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { ResearchLogger.latinIME_promotePhantomSpace(); @@ -2409,8 +2583,8 @@ public final class LatinIME extends InputMethodService implements KeyboardAction // Outside LatinIME, only used by the {@link InputTestsBase} test suite. @UsedForTesting void loadKeyboard() { - // When the device locale is changed in SetupWizard etc., this method may get called via - // onConfigurationChanged before SoftInputWindow is shown. + // TODO: Why are we calling {@link #loadSettings()} and {@link #initSuggest()} in a + // different order than in {@link #onStartInputView}? initSuggest(); loadSettings(); if (mKeyboardSwitcher.getMainKeyboardView() != null) { |