diff options
4 files changed, 179 insertions, 25 deletions
diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java index ea63cef02..035557610 100644 --- a/java/src/com/android/inputmethod/latin/RichInputConnection.java +++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java @@ -16,9 +16,13 @@ package com.android.inputmethod.latin; +import android.graphics.Color; import android.inputmethodservice.InputMethodService; import android.os.Build; +import android.text.SpannableStringBuilder; +import android.text.Spanned; import android.text.TextUtils; +import android.text.style.BackgroundColorSpan; import android.util.Log; import android.view.KeyEvent; import android.view.inputmethod.CompletionInfo; @@ -81,6 +85,18 @@ public final class RichInputConnection { */ private final StringBuilder mComposingText = new StringBuilder(); + /** + * This variable is a temporary object used in + * {@link #commitTextWithBackgroundColor(CharSequence, int, int)} to avoid object creation. + */ + private SpannableStringBuilder mTempObjectForCommitText = new SpannableStringBuilder(); + /** + * This variable is used to track whether the last committed text had the background color or + * not. + * TODO: Omit this flag if possible. + */ + private boolean mLastCommittedTextHasBackgroundColor = false; + private final InputMethodService mParent; InputConnection mIC; int mNestLevel; @@ -219,12 +235,37 @@ public final class RichInputConnection { // it works, but it's wrong and should be fixed. mCommittedTextBeforeComposingText.append(mComposingText); mComposingText.setLength(0); + // TODO: Clear this flag in setComposingRegion() and setComposingText() as well if needed. + mLastCommittedTextHasBackgroundColor = false; if (null != mIC) { mIC.finishComposingText(); } } - public void commitText(final CharSequence text, final int i) { + /** + * Synonym of {@code commitTextWithBackgroundColor(text, newCursorPosition, Color.TRANSPARENT}. + * @param text The text to commit. This may include styles. + * See {@link InputConnection#commitText(CharSequence, int)}. + * @param newCursorPosition The new cursor position around the text. + * See {@link InputConnection#commitText(CharSequence, int)}. + */ + public void commitText(final CharSequence text, final int newCursorPosition) { + commitTextWithBackgroundColor(text, newCursorPosition, Color.TRANSPARENT); + } + + /** + * Calls {@link InputConnection#commitText(CharSequence, int)} with the given background color. + * @param text The text to commit. This may include styles. + * See {@link InputConnection#commitText(CharSequence, int)}. + * @param newCursorPosition The new cursor position around the text. + * See {@link InputConnection#commitText(CharSequence, int)}. + * @param color The background color to be attached. Set {@link Color#TRANSPARENT} to disable + * the background color. Note that this method specifies {@link BackgroundColorSpan} with + * {@link Spanned#SPAN_COMPOSING} flag, meaning that the background color persists until + * {@link #finishComposingText()} is called. + */ + public void commitTextWithBackgroundColor(final CharSequence text, final int newCursorPosition, + final int color) { if (DEBUG_BATCH_NESTING) checkBatchEdit(); if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug(); mCommittedTextBeforeComposingText.append(text); @@ -234,11 +275,43 @@ public final class RichInputConnection { mExpectedSelStart += text.length() - mComposingText.length(); mExpectedSelEnd = mExpectedSelStart; mComposingText.setLength(0); + mLastCommittedTextHasBackgroundColor = false; if (null != mIC) { - mIC.commitText(text, i); + if (color == Color.TRANSPARENT) { + mIC.commitText(text, newCursorPosition); + } else { + mTempObjectForCommitText.clear(); + mTempObjectForCommitText.append(text); + final BackgroundColorSpan backgroundColorSpan = new BackgroundColorSpan(color); + mTempObjectForCommitText.setSpan(backgroundColorSpan, 0, text.length(), + Spanned.SPAN_COMPOSING | Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + mIC.commitText(mTempObjectForCommitText, newCursorPosition); + mLastCommittedTextHasBackgroundColor = true; + } } } + /** + * Removes the background color from the highlighted text if necessary. Should be called while + * there is no on-going composing text. + * + * <p>CAVEAT: This method internally calls {@link InputConnection#finishComposingText()}. + * Be careful of any unexpected side effects.</p> + */ + public void removeBackgroundColorFromHighlightedTextIfNecessary() { + // TODO: We haven't yet full tested if we really need to check this flag or not. Omit this + // flag if everything works fine without this condition. + if (!mLastCommittedTextHasBackgroundColor) { + return; + } + if (mComposingText.length() > 0) { + Log.e(TAG, "clearSpansWithComposingFlags should be called when composing text is " + + "empty. mComposingText=" + mComposingText); + return; + } + finishComposingText(); + } + public CharSequence getSelectedText(final int flags) { return (null == mIC) ? null : mIC.getSelectedText(flags); } diff --git a/java/src/com/android/inputmethod/latin/SuggestedWords.java b/java/src/com/android/inputmethod/latin/SuggestedWords.java index d7693af41..38fcb683d 100644 --- a/java/src/com/android/inputmethod/latin/SuggestedWords.java +++ b/java/src/com/android/inputmethod/latin/SuggestedWords.java @@ -19,6 +19,7 @@ package com.android.inputmethod.latin; import android.text.TextUtils; import android.view.inputmethod.CompletionInfo; +import com.android.inputmethod.annotations.UsedForTesting; import com.android.inputmethod.latin.define.DebugFlags; import com.android.inputmethod.latin.utils.StringUtils; @@ -420,4 +421,18 @@ public class SuggestedWords { mWillAutoCorrect, mIsObsoleteSuggestions, mIsPrediction, INPUT_STYLE_TAIL_BATCH); } + + /** + * @return the {@link SuggestedWordInfo} which corresponds to the word that is originally + * typed by the user. Otherwise returns {@code null}. Note that gesture input is not + * considered to be a typed word. + */ + @UsedForTesting + public SuggestedWordInfo getTypedWordInfoOrNull() { + if (this == EMPTY) { + return null; + } + final SuggestedWordInfo info = getInfo(SuggestedWords.INDEX_OF_TYPED_WORD); + return (info.getKind() == SuggestedWordInfo.KIND_TYPED) ? info : null; + } } diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java index 348bae63a..6fce2497e 100644 --- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java +++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java @@ -16,6 +16,7 @@ package com.android.inputmethod.latin.inputlogic; +import android.graphics.Color; import android.os.SystemClock; import android.text.SpannableString; import android.text.TextUtils; @@ -1994,7 +1995,9 @@ public final class InputLogic { } /** - * Commits the chosen word to the text field and saves it for later retrieval. + * Commits the chosen word to the text field and saves it for later retrieval. This is a + * synonym of {@code commitChosenWordWithBackgroundColor(settingsValues, chosenWord, + * commitType, separatorString, Color.TRANSPARENT}. * * @param settingsValues the current values of the settings. * @param chosenWord the word we want to commit. @@ -2003,6 +2006,23 @@ public final class InputLogic { */ private void commitChosenWord(final SettingsValues settingsValues, final String chosenWord, final int commitType, final String separatorString) { + commitChosenWordWithBackgroundColor(settingsValues, chosenWord, commitType, separatorString, + Color.TRANSPARENT); + } + + /** + * Commits the chosen word to the text field and saves it for later retrieval. + * + * @param settingsValues the current values of the settings. + * @param chosenWord the word we want to commit. + * @param commitType the type of the commit, as one of LastComposedWord.COMMIT_TYPE_* + * @param separatorString the separator that's causing the commit, or NOT_A_SEPARATOR if none. + * @param backgroundColor the background color to be specified with the committed text. Pass + * {@link Color#TRANSPARENT} to not specify the background color. + */ + private void commitChosenWordWithBackgroundColor(final SettingsValues settingsValues, + final String chosenWord, final int commitType, final String separatorString, + final int backgroundColor) { final SuggestedWords suggestedWords = mSuggestedWords; final CharSequence chosenWordWithSuggestions = SuggestionSpanUtils.getTextWithSuggestionSpan(mLatinIME, chosenWord, @@ -2012,7 +2032,7 @@ public final class InputLogic { // information from the 1st previous word. final PrevWordsInfo prevWordsInfo = mConnection.getPrevWordsInfoFromNthPreviousWord( settingsValues.mSpacingAndPunctuations, mWordComposer.isComposingWord() ? 2 : 1); - mConnection.commitText(chosenWordWithSuggestions, 1); + mConnection.commitTextWithBackgroundColor(chosenWordWithSuggestions, 1, backgroundColor); // Add the word to the user history dictionary performAdditionToUserHistoryDictionary(settingsValues, chosenWord, prevWordsInfo); // TODO: figure out here if this is an auto-correct or if the best word is actually diff --git a/tests/src/com/android/inputmethod/latin/SuggestedWordsTests.java b/tests/src/com/android/inputmethod/latin/SuggestedWordsTests.java index a5f20b565..2785dec43 100644 --- a/tests/src/com/android/inputmethod/latin/SuggestedWordsTests.java +++ b/tests/src/com/android/inputmethod/latin/SuggestedWordsTests.java @@ -23,24 +23,49 @@ import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; import java.util.ArrayList; import java.util.Locale; -import java.util.Random; @SmallTest public class SuggestedWordsTests extends AndroidTestCase { + + /** + * Helper method to create a dummy {@link SuggestedWordInfo} with specifying + * {@link SuggestedWordInfo#KIND_TYPED}. + * + * @param word the word to be used to create {@link SuggestedWordInfo}. + * @return a new instance of {@link SuggestedWordInfo}. + */ + private static SuggestedWordInfo createTypedWordInfo(final String word) { + // Use 100 as the frequency because the numerical value does not matter as + // long as it's > 1 and < INT_MAX. + return new SuggestedWordInfo(word, 100 /* score */, + SuggestedWordInfo.KIND_TYPED, + null /* sourceDict */, + SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */, + 1 /* autoCommitFirstWordConfidence */); + } + + /** + * Helper method to create a dummy {@link SuggestedWordInfo} with specifying + * {@link SuggestedWordInfo#KIND_CORRECTION}. + * + * @param word the word to be used to create {@link SuggestedWordInfo}. + * @return a new instance of {@link SuggestedWordInfo}. + */ + private static SuggestedWordInfo createCorrectionWordInfo(final String word) { + return new SuggestedWordInfo(word, 1 /* score */, + SuggestedWordInfo.KIND_CORRECTION, + null /* sourceDict */, + SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */, + SuggestedWordInfo.NOT_A_CONFIDENCE /* autoCommitFirstWordConfidence */); + } + public void testGetSuggestedWordsExcludingTypedWord() { final String TYPED_WORD = "typed"; - final int TYPED_WORD_FREQ = 5; final int NUMBER_OF_ADDED_SUGGESTIONS = 5; final ArrayList<SuggestedWordInfo> list = new ArrayList<>(); - list.add(new SuggestedWordInfo(TYPED_WORD, TYPED_WORD_FREQ, - SuggestedWordInfo.KIND_TYPED, null /* sourceDict */, - SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */, - SuggestedWordInfo.NOT_A_CONFIDENCE /* autoCommitFirstWordConfidence */)); + list.add(createTypedWordInfo(TYPED_WORD)); for (int i = 0; i < NUMBER_OF_ADDED_SUGGESTIONS; ++i) { - list.add(new SuggestedWordInfo("" + i, 1, SuggestedWordInfo.KIND_CORRECTION, - null /* sourceDict */, - SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */, - SuggestedWordInfo.NOT_A_CONFIDENCE /* autoCommitFirstWordConfidence */)); + list.add(createCorrectionWordInfo(Integer.toString(i))); } final SuggestedWords words = new SuggestedWords( @@ -66,19 +91,9 @@ public class SuggestedWordsTests extends AndroidTestCase { } // Helper for testGetTransformedWordInfo - private SuggestedWordInfo createWordInfo(final String s) { - // Use 100 as the frequency because the numerical value does not matter as - // long as it's > 1 and < INT_MAX. - return new SuggestedWordInfo(s, 100, - SuggestedWordInfo.KIND_TYPED, null /* sourceDict */, - SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */, - new Random().nextInt(1000000) /* autoCommitFirstWordConfidence */); - } - - // Helper for testGetTransformedWordInfo private SuggestedWordInfo transformWordInfo(final String info, final int trailingSingleQuotesCount) { - final SuggestedWordInfo suggestedWordInfo = createWordInfo(info); + final SuggestedWordInfo suggestedWordInfo = createTypedWordInfo(info); final SuggestedWordInfo returnedWordInfo = Suggest.getTransformedSuggestedWordInfo(suggestedWordInfo, Locale.ENGLISH, false /* isAllUpperCase */, false /* isFirstCharCapitalized */, @@ -102,4 +117,35 @@ public class SuggestedWordsTests extends AndroidTestCase { result = transformWordInfo("didn't", 3); assertEquals(result.mWord, "didn't''"); } + + public void testGetTypedWordInfoOrNull() { + final String TYPED_WORD = "typed"; + final int NUMBER_OF_ADDED_SUGGESTIONS = 5; + final ArrayList<SuggestedWordInfo> list = new ArrayList<>(); + list.add(createTypedWordInfo(TYPED_WORD)); + for (int i = 0; i < NUMBER_OF_ADDED_SUGGESTIONS; ++i) { + list.add(createCorrectionWordInfo(Integer.toString(i))); + } + + // Make sure getTypedWordInfoOrNull() returns non-null object. + final SuggestedWords wordsWithTypedWord = new SuggestedWords( + list, null /* rawSuggestions */, + false /* typedWordValid */, + false /* willAutoCorrect */, + false /* isObsoleteSuggestions */, + false /* isPrediction*/, + SuggestedWords.INPUT_STYLE_NONE); + final SuggestedWordInfo typedWord = wordsWithTypedWord.getTypedWordInfoOrNull(); + assertNotNull(typedWord); + assertEquals(TYPED_WORD, typedWord.mWord); + + // Make sure getTypedWordInfoOrNull() returns null. + final SuggestedWords wordsWithoutTypedWord = + wordsWithTypedWord.getSuggestedWordsExcludingTypedWord( + SuggestedWords.INPUT_STYLE_NONE); + assertNull(wordsWithoutTypedWord.getTypedWordInfoOrNull()); + + // Make sure getTypedWordInfoOrNull() returns null. + assertNull(SuggestedWords.EMPTY.getTypedWordInfoOrNull()); + } } |