From 0e9ee4d3bf75459560670ca5c28ff4c4f7c346fb Mon Sep 17 00:00:00 2001 From: Jean Chalard Date: Wed, 10 Apr 2013 18:30:11 +0900 Subject: [PATCH] If there are no suggestion span, recompute suggestions. Bug: 8084810 Change-Id: I1743c09c43ca6835bb2f607684b037bf17d36335 --- .../android/inputmethod/latin/LatinIME.java | 71 +++++++++++++++---- .../inputmethod/latin/SuggestedWords.java | 17 +++++ .../inputmethod/latin/WordComposer.java | 16 ++++- .../latin/SuggestedWordsTests.java | 60 ++++++++++++++++ 4 files changed, 148 insertions(+), 16 deletions(-) create mode 100644 tests/src/com/android/inputmethod/latin/SuggestedWordsTests.java diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index 47d51c586..da1232f5e 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -1466,7 +1466,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(); @@ -1522,8 +1528,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 @@ -1734,8 +1744,11 @@ public final class LatinIME extends InputMethodService implements KeyboardAction // during key repeat. mHandler.postUpdateShiftState(); - if (mWordComposer.isComposingWord() && !mWordComposer.isCursorAtEndOfComposingWord()) { + 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(); @@ -1870,7 +1883,9 @@ public final class LatinIME extends InputMethodService implements KeyboardAction promotePhantomSpace(); } - if (mWordComposer.isComposingWord() && !mWordComposer.isCursorAtEndOfComposingWord()) { + 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; } @@ -1935,7 +1950,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 sparse array or something @@ -2357,9 +2376,9 @@ public final class LatinIME extends InputMethodService implements KeyboardAction final Range range = mConnection.getWordRangeAtCursor(mSettings.getWordSeparators(), 0 /* additionalPrecedingWordsCount */); final ArrayList suggestions = CollectionUtils.newArrayList(); + final String typedWord = range.mWord.toString(); if (range.mWord instanceof SpannableString) { final SpannableString spannableString = (SpannableString)range.mWord; - final String typedWord = spannableString.toString(); int i = 0; for (Object object : spannableString.getSpans(0, spannableString.length(), SuggestionSpan.class)) { @@ -2374,18 +2393,42 @@ public final class LatinIME extends InputMethodService implements KeyboardAction } } } - mWordComposer.setComposingWord(range.mWord, mKeyboardSwitcher.getKeyboard()); + mWordComposer.setComposingWord(typedWord, mKeyboardSwitcher.getKeyboard()); mWordComposer.setCursorPositionWithinWord(range.mCharsBefore); mConnection.setComposingRegion(mLastSelectionStart - range.mCharsBefore, mLastSelectionEnd + range.mCharsAfter); + final SuggestedWords suggestedWords; if (suggestions.isEmpty()) { - suggestions.add(new SuggestedWordInfo(range.mWord.toString(), 1, - SuggestedWordInfo.KIND_TYPED, Dictionary.TYPE_RESUMED)); + // 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 */); } - showSuggestionStrip(new SuggestedWords(suggestions, - true /* typedWordValid */, false /* willAutoCorrect */, - false /* isPunctuationSuggestions */, false /* isObsoleteSuggestions */, - false /* isPrediction */), range.mWord.toString()); + + // 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); } /** diff --git a/java/src/com/android/inputmethod/latin/SuggestedWords.java b/java/src/com/android/inputmethod/latin/SuggestedWords.java index 158cc1155..616e1911b 100644 --- a/java/src/com/android/inputmethod/latin/SuggestedWords.java +++ b/java/src/com/android/inputmethod/latin/SuggestedWords.java @@ -195,4 +195,21 @@ public final class SuggestedWords { } } } + + // SuggestedWords is an immutable object, as much as possible. We must not just remove + // words from the member ArrayList as some other parties may expect the object to never change. + public SuggestedWords getSuggestedWordsExcludingTypedWord() { + final ArrayList newSuggestions = CollectionUtils.newArrayList(); + for (int i = 0; i < mSuggestedWordInfoList.size(); ++i) { + final SuggestedWordInfo info = mSuggestedWordInfoList.get(i); + if (SuggestedWordInfo.KIND_TYPED != info.mKind) { + newSuggestions.add(info); + } + } + // We should never autocorrect, so we say the typed word is valid. Also, in this case, + // no auto-correction should take place hence willAutoCorrect = false. + return new SuggestedWords(newSuggestions, true /* typedWordValid */, + false /* willAutoCorrect */, mIsPunctuationSuggestions, mIsObsoleteSuggestions, + mIsPrediction); + } } diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java index 098e8ac7b..51bd901fb 100644 --- a/java/src/com/android/inputmethod/latin/WordComposer.java +++ b/java/src/com/android/inputmethod/latin/WordComposer.java @@ -27,6 +27,7 @@ import java.util.Arrays; */ public final class WordComposer { private static final int MAX_WORD_LENGTH = Constants.Dictionary.MAX_WORD_LENGTH; + private static final boolean DBG = LatinImeLogger.sDBG; public static final int CAPS_MODE_OFF = 0; // 1 is shift bit, 2 is caps bit, 4 is auto bit but this is just a convention as these bits @@ -132,6 +133,13 @@ public final class WordComposer { return mPrimaryKeyCodes[index]; } + public int getCodeBeforeCursor() { + if (mCursorPositionWithinWord < 1 || mCursorPositionWithinWord > mPrimaryKeyCodes.length) { + return Constants.NOT_A_CODE; + } + return mPrimaryKeyCodes[mCursorPositionWithinWord - 1]; + } + public InputPointers getInputPointers() { return mInputPointers; } @@ -177,8 +185,12 @@ public final class WordComposer { mCursorPositionWithinWord = posWithinWord; } - public boolean isCursorAtEndOfComposingWord() { - return mCursorPositionWithinWord == mCodePointSize; + public boolean isCursorFrontOrMiddleOfComposingWord() { + if (DBG && mCursorPositionWithinWord > mCodePointSize) { + throw new RuntimeException("Wrong cursor position : " + mCursorPositionWithinWord + + "in a word of size " + mCodePointSize); + } + return mCursorPositionWithinWord != mCodePointSize; } public void setBatchInputPointers(final InputPointers batchPointers) { diff --git a/tests/src/com/android/inputmethod/latin/SuggestedWordsTests.java b/tests/src/com/android/inputmethod/latin/SuggestedWordsTests.java new file mode 100644 index 000000000..916252292 --- /dev/null +++ b/tests/src/com/android/inputmethod/latin/SuggestedWordsTests.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.inputmethod.latin; + +import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; + +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.SmallTest; + +import java.util.ArrayList; +import java.util.Locale; + +@SmallTest +public class SuggestedWordsTests extends AndroidTestCase { + public void testGetSuggestedWordsExcludingTypedWord() { + final String TYPED_WORD = "typed"; + final int TYPED_WORD_FREQ = 5; + final int NUMBER_OF_ADDED_SUGGESTIONS = 5; + final ArrayList list = CollectionUtils.newArrayList(); + list.add(new SuggestedWordInfo(TYPED_WORD, TYPED_WORD_FREQ, + SuggestedWordInfo.KIND_TYPED, "")); + for (int i = 0; i < NUMBER_OF_ADDED_SUGGESTIONS; ++i) { + list.add(new SuggestedWordInfo("" + i, 1, SuggestedWordInfo.KIND_CORRECTION, "")); + } + + final SuggestedWords words = new SuggestedWords( + list, + false /* typedWordValid */, + false /* willAutoCorrect */, + false /* isPunctuationSuggestions */, + false /* isObsoleteSuggestions */, + false /* isPrediction*/); + assertEquals(NUMBER_OF_ADDED_SUGGESTIONS + 1, words.size()); + assertEquals("typed", words.getWord(0)); + assertEquals(SuggestedWordInfo.KIND_TYPED, words.getInfo(0).mKind); + assertEquals("0", words.getWord(1)); + assertEquals(SuggestedWordInfo.KIND_CORRECTION, words.getInfo(1).mKind); + assertEquals("4", words.getWord(5)); + assertEquals(SuggestedWordInfo.KIND_CORRECTION, words.getInfo(5).mKind); + + final SuggestedWords wordsWithoutTyped = words.getSuggestedWordsExcludingTypedWord(); + assertEquals(words.size() - 1, wordsWithoutTyped.size()); + assertEquals("0", wordsWithoutTyped.getWord(0)); + assertEquals(SuggestedWordInfo.KIND_CORRECTION, wordsWithoutTyped.getInfo(0).mKind); + } +}