From 221df9e59aade0860624aeeb9b7b25311584c93a Mon Sep 17 00:00:00 2001 From: Jean Chalard Date: Fri, 20 Dec 2013 18:05:35 +0900 Subject: [PATCH] [IL26] Move restartSuggestionsOnWordTouchedByCursor Bug: 8636060 Change-Id: I373874585f4fa663b4207b9a02d751805259eb36 --- .../android/inputmethod/latin/LatinIME.java | 119 ++--------------- .../latin/inputlogic/InputLogic.java | 124 ++++++++++++++++++ 2 files changed, 132 insertions(+), 111 deletions(-) diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index c66fca58c..dd2c80664 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -43,7 +43,6 @@ import android.os.SystemClock; import android.preference.PreferenceManager; import android.text.InputType; import android.text.TextUtils; -import android.text.style.SuggestionSpan; import android.util.Log; import android.util.Pair; import android.util.PrintWriterPrinter; @@ -81,13 +80,11 @@ import com.android.inputmethod.latin.suggestions.SuggestionStripView; import com.android.inputmethod.latin.utils.ApplicationUtils; import com.android.inputmethod.latin.utils.AutoCorrectionUtils; import com.android.inputmethod.latin.utils.CapsModeUtils; -import com.android.inputmethod.latin.utils.CollectionUtils; import com.android.inputmethod.latin.utils.CompletionInfoUtils; import com.android.inputmethod.latin.utils.IntentUtils; import com.android.inputmethod.latin.utils.JniUtils; import com.android.inputmethod.latin.utils.LatinImeLoggerUtils; import com.android.inputmethod.latin.utils.LeakGuardHandlerWrapper; -import com.android.inputmethod.latin.utils.TextRange; import com.android.inputmethod.research.ResearchLogger; import java.io.FileDescriptor; @@ -212,7 +209,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } break; case MSG_RESUME_SUGGESTIONS: - latinIme.restartSuggestionsOnWordTouchedByCursor(); + latinIme.mInputLogic.restartSuggestionsOnWordTouchedByCursor( + latinIme.mSettings.getCurrent(), latinIme.mKeyboardSwitcher, + latinIme.mInputUpdater); break; case MSG_REOPEN_DICTIONARIES: latinIme.initSuggest(); @@ -1367,7 +1366,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen 1 /* nthPreviousWord */)); } - static final class InputUpdater implements Handler.Callback { + // 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(); @@ -1617,7 +1618,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen return mSettings.getCurrent().mSuggestPuncList == mInputLogic.mSuggestedWords; } - private boolean isSuggestionsStripVisible() { + // TODO[IL]: Define a clear interface for this + public boolean isSuggestionsStripVisible() { final SettingsValues currentSettings = mSettings.getCurrent(); if (mSuggestionStripView == null) return false; @@ -1903,111 +1905,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen setSuggestionStripShown(isSuggestionsStripVisible()); } - private boolean isResumableWord(final String word, final SettingsValues settings) { - final int firstCodePoint = word.codePointAt(0); - return settings.isWordCodePoint(firstCodePoint) - && Constants.CODE_SINGLE_QUOTE != firstCodePoint - && Constants.CODE_DASH != firstCodePoint; - } - - /** - * Check if the cursor is touching a word. If so, restart suggestions on this word, else - * do nothing. - */ - private void restartSuggestionsOnWordTouchedByCursor() { - // 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. - final SettingsValues settingsValues = mSettings.getCurrent(); - if (settingsValues.isBrokenByRecorrection()) return; - // A simple way to test for support from the TextView. - if (!isSuggestionsStripVisible()) return; - // Recorrection is not supported in languages without spaces because we don't know - // how to segment them yet. - if (!settingsValues.mCurrentLanguageHasSpaces) return; - // If the cursor is not touching a word, or if there is a selection, return right away. - if (mInputLogic.mLastSelectionStart != mInputLogic.mLastSelectionEnd) return; - // If we don't know the cursor location, return. - if (mInputLogic.mLastSelectionStart < 0) return; - final SettingsValues currentSettings = mSettings.getCurrent(); - 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 > mInputLogic.mLastSelectionStart) return; - final ArrayList suggestions = CollectionUtils.newArrayList(); - final String typedWord = range.mWord.toString(); - if (!isResumableWord(typedWord, currentSettings)) return; - int i = 0; - for (final SuggestionSpan span : range.getSuggestionSpansAtWord()) { - for (final String s : span.getSuggestions()) { - ++i; - if (!TextUtils.equals(s, typedWord)) { - suggestions.add(new SuggestedWordInfo(s, - SuggestionStripView.MAX_SUGGESTIONS - i, - SuggestedWordInfo.KIND_RESUMED, Dictionary.DICTIONARY_RESUMED, - SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */, - SuggestedWordInfo.NOT_A_CONFIDENCE - /* autoCommitFirstWordConfidence */)); - } - } - } - 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()); - mInputLogic.mWordComposer.setCursorPositionWithinWord( - typedWord.codePointCount(0, numberOfCharsInWordBeforeCursor)); - 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. - mInputUpdater.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) { - // 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; - } - // We need to pass typedWord because mWordComposer.mTypedWord may - // differ from typedWord. - unsetIsAutoCorrectionIndicatorOnAndCallShowSuggestionStrip( - suggestedWords, typedWord); - }}); - } else { - // We found suggestion spans in the word. We'll create the SuggestedWords out of - // them, and make willAutoCorrect false. - final SuggestedWords suggestedWords = new SuggestedWords(suggestions, - true /* typedWordValid */, false /* willAutoCorrect */, - false /* isPunctuationSuggestions */, false /* isObsoleteSuggestions */, - false /* isPrediction */); - // We need to pass typedWord because mWordComposer.mTypedWord may differ from typedWord. - unsetIsAutoCorrectionIndicatorOnAndCallShowSuggestionStrip(suggestedWords, typedWord); - } - } - public void unsetIsAutoCorrectionIndicatorOnAndCallShowSuggestionStrip( final SuggestedWords suggestedWords, final String typedWord) { // Note that it's very important here that suggestedWords.mWillAutoCorrect is false. diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java index 27b7b98d8..5f3297794 100644 --- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java +++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java @@ -18,6 +18,7 @@ package com.android.inputmethod.latin.inputlogic; import android.os.SystemClock; import android.text.TextUtils; +import android.text.style.SuggestionSpan; import android.util.Log; import android.view.KeyCharacterMap; import android.view.KeyEvent; @@ -30,6 +31,7 @@ import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.keyboard.KeyboardSwitcher; import com.android.inputmethod.keyboard.MainKeyboardView; import com.android.inputmethod.latin.Constants; +import com.android.inputmethod.latin.Dictionary; import com.android.inputmethod.latin.LastComposedWord; import com.android.inputmethod.latin.LatinIME; import com.android.inputmethod.latin.LatinImeLogger; @@ -38,11 +40,13 @@ import com.android.inputmethod.latin.SubtypeSwitcher; import com.android.inputmethod.latin.Suggest; import com.android.inputmethod.latin.Suggest.OnGetSuggestedWordsCallback; import com.android.inputmethod.latin.SuggestedWords; +import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; import com.android.inputmethod.latin.WordComposer; import com.android.inputmethod.latin.define.ProductionFlag; import com.android.inputmethod.latin.personalization.UserHistoryDictionary; import com.android.inputmethod.latin.settings.Settings; import com.android.inputmethod.latin.settings.SettingsValues; +import com.android.inputmethod.latin.suggestions.SuggestionStripView; import com.android.inputmethod.latin.utils.AsyncResultHolder; import com.android.inputmethod.latin.utils.AutoCorrectionUtils; import com.android.inputmethod.latin.utils.CollectionUtils; @@ -50,8 +54,10 @@ import com.android.inputmethod.latin.utils.InputTypeUtils; import com.android.inputmethod.latin.utils.LatinImeLoggerUtils; import com.android.inputmethod.latin.utils.RecapitalizeStatus; import com.android.inputmethod.latin.utils.StringUtils; +import com.android.inputmethod.latin.utils.TextRange; import com.android.inputmethod.research.ResearchLogger; +import java.util.ArrayList; import java.util.TreeSet; import java.util.concurrent.TimeUnit; @@ -953,6 +959,107 @@ public final class InputLogic { } } + /** + * 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. + */ + // TODO: make this private. + public void restartSuggestionsOnWordTouchedByCursor(final SettingsValues settingsValues, + // TODO: Remove these argument. + final KeyboardSwitcher keyboardSwitcher, final LatinIME.InputUpdater inputUpdater) { + // 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. + if (settingsValues.isBrokenByRecorrection()) return; + // A simple way to test for support from the TextView. + if (!mLatinIME.isSuggestionsStripVisible()) return; + // Recorrection is not supported in languages without spaces because we don't know + // how to segment them yet. + if (!settingsValues.mCurrentLanguageHasSpaces) return; + // If the cursor is not touching a word, or if there is a selection, return right away. + if (mLastSelectionStart != mLastSelectionEnd) return; + // If we don't know the cursor location, return. + if (mLastSelectionStart < 0) return; + if (!mConnection.isCursorTouchingWord(settingsValues)) return; + final TextRange range = mConnection.getWordRangeAtCursor( + settingsValues.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; + final ArrayList suggestions = CollectionUtils.newArrayList(); + final String typedWord = range.mWord.toString(); + if (!isResumableWord(settingsValues, typedWord)) return; + int i = 0; + for (final SuggestionSpan span : range.getSuggestionSpansAtWord()) { + for (final String s : span.getSuggestions()) { + ++i; + if (!TextUtils.equals(s, typedWord)) { + suggestions.add(new SuggestedWordInfo(s, + SuggestionStripView.MAX_SUGGESTIONS - i, + SuggestedWordInfo.KIND_RESUMED, Dictionary.DICTIONARY_RESUMED, + SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */, + SuggestedWordInfo.NOT_A_CONFIDENCE + /* autoCommitFirstWordConfidence */)); + } + } + } + mWordComposer.setComposingWord(typedWord, + getNthPreviousWordForSuggestion(settingsValues, + // 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), + keyboardSwitcher.getKeyboard()); + mWordComposer.setCursorPositionWithinWord( + typedWord.codePointCount(0, numberOfCharsInWordBeforeCursor)); + mConnection.setComposingRegion(mLastSelectionStart - numberOfCharsInWordBeforeCursor, + 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. + inputUpdater.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) { + // 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; + } + // We need to pass typedWord because mWordComposer.mTypedWord may + // differ from typedWord. + mLatinIME.unsetIsAutoCorrectionIndicatorOnAndCallShowSuggestionStrip( + suggestedWords, typedWord); + }}); + } else { + // We found suggestion spans in the word. We'll create the SuggestedWords out of + // them, and make willAutoCorrect false. + final SuggestedWords suggestedWords = new SuggestedWords(suggestions, + true /* typedWordValid */, false /* willAutoCorrect */, + false /* isPunctuationSuggestions */, false /* isObsoleteSuggestions */, + false /* isPrediction */); + // We need to pass typedWord because mWordComposer.mTypedWord may differ from typedWord. + mLatinIME.unsetIsAutoCorrectionIndicatorOnAndCallShowSuggestionStrip(suggestedWords, + typedWord); + } + } + /** * Reverts a previous commit with auto-correction. * @@ -1100,6 +1207,23 @@ public final class InputLogic { } } + /** + * Tests the passed word for resumability. + * + * We can resume suggestions on words whose first code point is a word code point (with some + * nuances: check the code for details). + * + * @param settings the current values of the settings. + * @param word the word to evaluate. + * @return whether it's fine to resume suggestions on this word. + */ + private static boolean isResumableWord(final SettingsValues settings, final String word) { + final int firstCodePoint = word.codePointAt(0); + return settings.isWordCodePoint(firstCodePoint) + && Constants.CODE_SINGLE_QUOTE != firstCodePoint + && Constants.CODE_DASH != firstCodePoint; + } + /** * @param actionId the action to perform */