diff --git a/java/src/com/android/inputmethod/latin/InputPointers.java b/java/src/com/android/inputmethod/latin/InputPointers.java index 218243e9f..9d77d4e96 100644 --- a/java/src/com/android/inputmethod/latin/InputPointers.java +++ b/java/src/com/android/inputmethod/latin/InputPointers.java @@ -18,6 +18,7 @@ package com.android.inputmethod.latin; import java.util.Arrays; +// TODO: Add unit test public class InputPointers { private final ScalableIntArray mXCoordinates = new ScalableIntArray(); private final ScalableIntArray mYCoordinates = new ScalableIntArray(); @@ -52,6 +53,25 @@ public class InputPointers { mTimes.copy(ip.mTimes); } + /** + * Append the pointers in the specified {@link InputPointers} to the end of this. + * @param src the source {@link InputPointers} to append the pointers. + * @param startPos the starting index of the pointers in {@code src}. + * @param length the number of pointers to be appended. + */ + public void append(InputPointers src, int startPos, int length) { + final int currentLength = getPointerSize(); + final int newLength = currentLength + length; + mXCoordinates.ensureCapacity(newLength); + mYCoordinates.ensureCapacity(newLength); + mPointerIds.ensureCapacity(newLength); + mTimes.ensureCapacity(newLength); + System.arraycopy(src.getXCoordinates(), startPos, getXCoordinates(), currentLength, length); + System.arraycopy(src.getYCoordinates(), startPos, getYCoordinates(), currentLength, length); + System.arraycopy(src.getPointerIds(), startPos, getPointerIds(), currentLength, length); + System.arraycopy(src.getTimes(), startPos, getTimes(), currentLength, length); + } + public void reset() { mXCoordinates.reset(); mYCoordinates.reset(); @@ -64,19 +84,19 @@ public class InputPointers { } public int[] getXCoordinates() { - return mXCoordinates.mArray; + return mXCoordinates.getPrimitiveArray(); } public int[] getYCoordinates() { - return mYCoordinates.mArray; + return mYCoordinates.getPrimitiveArray(); } public int[] getPointerIds() { - return mPointerIds.mArray; + return mPointerIds.getPrimitiveArray(); } public int[] getTimes() { - return mTimes.mArray; + return mTimes.getPrimitiveArray(); } private static class ScalableIntArray { @@ -98,14 +118,24 @@ public class InputPointers { } public void add(int val) { - if (mLength >= mArray.length) { - final int[] newArray = new int[mLength * 2]; - System.arraycopy(mArray, 0, newArray, 0, mLength); - } + ensureCapacity(mLength); mArray[mLength] = val; ++mLength; } + public void ensureCapacity(int minimumCapacity) { + if (mArray.length < minimumCapacity) { + final int nextCapacity = mArray.length * 2; + grow(minimumCapacity > nextCapacity ? minimumCapacity : nextCapacity); + } + } + + private void grow(int newCapacity) { + final int[] newArray = new int[newCapacity]; + System.arraycopy(mArray, 0, newArray, 0, mLength); + mArray = newArray; + } + public int getLength() { return mLength; } @@ -121,6 +151,7 @@ public class InputPointers { public void copy(ScalableIntArray ip) { mArray = Arrays.copyOf(ip.mArray, ip.mArray.length); + mLength = ip.mLength; } public void set(ScalableIntArray ip) { diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index 243a5f64e..4be2a1799 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -177,9 +177,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen public static class UIHandler extends StaticInnerHandlerWrapper { private static final int MSG_UPDATE_SHIFT_STATE = 1; - private static final int MSG_SET_BIGRAM_PREDICTIONS = 5; private static final int MSG_PENDING_IMS_CALLBACK = 6; - private static final int MSG_UPDATE_SUGGESTIONS = 7; + private static final int MSG_UPDATE_SUGGESTION_STRIP = 7; private int mDelayUpdateSuggestions; private int mDelayUpdateShiftState; @@ -205,30 +204,25 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen final LatinIME latinIme = getOuterInstance(); final KeyboardSwitcher switcher = latinIme.mKeyboardSwitcher; switch (msg.what) { - case MSG_UPDATE_SUGGESTIONS: - latinIme.updateSuggestionsOrPredictions(false /* isPredictions */); + case MSG_UPDATE_SUGGESTION_STRIP: + latinIme.updateSuggestionsOrPredictions(); break; case MSG_UPDATE_SHIFT_STATE: switcher.updateShiftState(); break; - case MSG_SET_BIGRAM_PREDICTIONS: - latinIme.updateSuggestionsOrPredictions(true /* isPredictions */); - break; } } - public void postUpdateSuggestions() { - cancelUpdateSuggestionStrip(); - sendMessageDelayed(obtainMessage(MSG_UPDATE_SUGGESTIONS), mDelayUpdateSuggestions); + public void postUpdateSuggestionStrip() { + sendMessageDelayed(obtainMessage(MSG_UPDATE_SUGGESTION_STRIP), mDelayUpdateSuggestions); } public void cancelUpdateSuggestionStrip() { - removeMessages(MSG_UPDATE_SUGGESTIONS); - removeMessages(MSG_SET_BIGRAM_PREDICTIONS); + removeMessages(MSG_UPDATE_SUGGESTION_STRIP); } public boolean hasPendingUpdateSuggestions() { - return hasMessages(MSG_UPDATE_SUGGESTIONS); + return hasMessages(MSG_UPDATE_SUGGESTION_STRIP); } public void postUpdateShiftState() { @@ -240,11 +234,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen removeMessages(MSG_UPDATE_SHIFT_STATE); } - public void postUpdateBigramPredictions() { - cancelUpdateSuggestionStrip(); - sendMessageDelayed(obtainMessage(MSG_SET_BIGRAM_PREDICTIONS), mDelayUpdateSuggestions); - } - public void startDoubleSpacesTimer() { mDoubleSpaceTimerStart = SystemClock.uptimeMillis(); } @@ -689,8 +678,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mSuggestionsView.clear(); setSuggestionStripShownInternal( isSuggestionsStripVisible(), /* needsInputViewShown */ false); - // Delay updating suggestions because keyboard input view may not be shown at this point. - mHandler.postUpdateSuggestions(); + + mHandler.cancelUpdateSuggestionStrip(); mHandler.cancelDoubleSpacesTimer(); inputView.setKeyPreviewPopupEnabled(mCurrentSettings.mKeyPreviewPopupOn, @@ -1021,7 +1010,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen LastComposedWord.COMMIT_TYPE_USER_TYPED_WORD, typedWord.toString(), separatorCode, prevWord); } - updateSuggestionsOrPredictions(false /* isPredictions */); + updateSuggestionsOrPredictions(); } public int getCurrentAutoCapsState() { @@ -1394,15 +1383,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mWordComposer.deleteLast(); } mConnection.setComposingText(getTextWithUnderline(mWordComposer.getTypedWord()), 1); - // If we have deleted the last remaining character of a word, then we are not - // isComposingWord() any more. - if (!mWordComposer.isComposingWord()) { - // Not composing word any more, so we can show bigrams. - mHandler.postUpdateBigramPredictions(); - } else { - // Still composing a word, so we still have letters to deduce a suggestion from. - mHandler.postUpdateSuggestions(); - } + mHandler.postUpdateSuggestionStrip(); } else { mConnection.deleteSurroundingText(1, 0); if (ProductionFlag.IS_EXPERIMENTAL) { @@ -1545,7 +1526,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen getCurrentAutoCapsState() != Constants.TextUtils.CAP_MODE_OFF); } mConnection.setComposingText(getTextWithUnderline(mWordComposer.getTypedWord()), 1); - mHandler.postUpdateSuggestions(); } else { final boolean swapWeakSpace = maybeStripSpace(primaryCode, spaceState, KeyboardActionListener.SUGGESTION_STRIP_COORDINATE == x); @@ -1556,11 +1536,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen swapSwapperAndSpace(); mSpaceState = SPACE_STATE_WEAK; } - // We may need to update predictions, if the "add to dictionary" hint was displayed - // for example. + // In case the "add to dictionary" hint was still displayed. if (null != mSuggestionsView) mSuggestionsView.dismissAddToDictionaryHint(); - mHandler.postUpdateBigramPredictions(); } + mHandler.postUpdateSuggestionStrip(); Utils.Stats.onNonSeparator((char)primaryCode, x, y); } @@ -1602,7 +1581,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mHandler.startDoubleSpacesTimer(); if (!mConnection.isCursorTouchingWord(mCurrentSettings)) { - mHandler.postUpdateBigramPredictions(); + mHandler.postUpdateSuggestionStrip(); } } else { if (swapWeakSpace) { @@ -1682,7 +1661,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } } - public void updateSuggestionsOrPredictions(final boolean isPredictions) { + public void updateSuggestionsOrPredictions() { mHandler.cancelUpdateSuggestionStrip(); // Check if we have a suggestion engine attached. @@ -1695,17 +1674,16 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen return; } - final CharSequence typedWord; + final String typedWord = mWordComposer.getTypedWord(); final SuggestedWords suggestions; - if (isPredictions || !mWordComposer.isComposingWord()) { - if (!mCurrentSettings.mBigramPredictionEnabled) { - setPunctuationSuggestions(); - return; - } - typedWord = ""; - suggestions = updateBigramPredictions(typedWord); + if (!mWordComposer.isComposingWord() && !mCurrentSettings.mBigramPredictionEnabled) { + setPunctuationSuggestions(); + return; + } + + if (!mWordComposer.isComposingWord()) { + suggestions = updateBigramPredictions(); } else { - typedWord = mWordComposer.getTypedWord(); suggestions = updateSuggestions(typedWord); } @@ -1718,11 +1696,13 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen private SuggestedWords updateSuggestions(final CharSequence typedWord) { // TODO: May need a better way of retrieving previous word - final CharSequence prevWord = mConnection.getPreviousWord(mCurrentSettings.mWordSeparators); + final CharSequence prevWord = + mConnection.getNthPreviousWord(mCurrentSettings.mWordSeparators, 2); // getSuggestedWords handles gracefully a null value of prevWord final SuggestedWords suggestedWords = mSuggest.getSuggestedWords(mWordComposer, prevWord, mKeyboardSwitcher.getKeyboard().getProximityInfo(), - mCurrentSettings.mCorrectionEnabled, false); + // !mWordComposer.isComposingWord() is known to be false + mCurrentSettings.mCorrectionEnabled, !mWordComposer.isComposingWord()); // Basically, we update the suggestion strip only when suggestion count > 1. However, // there is an exception: We update the suggestion strip whenever typed word's length @@ -1774,8 +1754,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen private void commitCurrentAutoCorrection(final int separatorCodePoint) { // Complete any pending suggestions query first if (mHandler.hasPendingUpdateSuggestions()) { - mHandler.cancelUpdateSuggestionStrip(); - updateSuggestionsOrPredictions(false /* isPredictions */); + updateSuggestionsOrPredictions(); } final CharSequence autoCorrection = mWordComposer.getAutoCorrectionOrNull(); if (autoCorrection != null) { @@ -1886,8 +1865,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen if (showingAddToDictionaryHint && mIsUserDictionaryAvailable) { mSuggestionsView.showAddToDictionaryHint(suggestion, mCurrentSettings.mHintToSaveText); } else { - // If we're not showing the "Touch again to save", then show predictions. - mHandler.postUpdateBigramPredictions(); + // If we're not showing the "Touch again to save", then update the suggestion strip. + mHandler.postUpdateSuggestionStrip(); } } @@ -1912,11 +1891,13 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen separatorCode, prevWord); } - private SuggestedWords updateBigramPredictions(final CharSequence typedWord) { - final CharSequence prevWord = mConnection.getThisWord(mCurrentSettings.mWordSeparators); + private SuggestedWords updateBigramPredictions() { + final CharSequence prevWord = + mConnection.getNthPreviousWord(mCurrentSettings.mWordSeparators, 1); return mSuggest.getSuggestedWords(mWordComposer, prevWord, mKeyboardSwitcher.getKeyboard().getProximityInfo(), - mCurrentSettings.mCorrectionEnabled, true); + // !mWordComposer.isComposingWord() is known to be true + mCurrentSettings.mCorrectionEnabled, !mWordComposer.isComposingWord()); } public void setPunctuationSuggestions() { @@ -1940,7 +1921,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen final UserHistoryDictionary userHistoryDictionary = mUserHistoryDictionary; if (userHistoryDictionary != null) { final CharSequence prevWord - = mConnection.getPreviousWord(mCurrentSettings.mWordSeparators); + = mConnection.getNthPreviousWord(mCurrentSettings.mWordSeparators, 2); final String secondWord; if (mWordComposer.isAutoCapitalized() && !mWordComposer.isMostlyCaps()) { secondWord = suggestion.toString().toLowerCase( @@ -1979,7 +1960,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen ResearchLogger.latinIME_deleteSurroundingText(length); } mConnection.setComposingText(word, 1); - mHandler.postUpdateSuggestions(); + mHandler.postUpdateSuggestionStrip(); } private void revertCommit() { @@ -2024,7 +2005,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // separator. mLastComposedWord = LastComposedWord.NOT_A_COMPOSED_WORD; // We have a separator between the word and the cursor: we should show predictions. - mHandler.postUpdateBigramPredictions(); + mHandler.postUpdateSuggestionStrip(); } public boolean isWordSeparator(int code) { @@ -2048,12 +2029,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen loadSettings(); // Since we just changed languages, we should re-evaluate suggestions with whatever word // we are currently composing. If we are not composing anything, we may want to display - // predictions or punctuation signs (which is done by updateBigramPredictions anyway). - if (mWordComposer.isComposingWord()) { - mHandler.postUpdateSuggestions(); - } else { - mHandler.postUpdateBigramPredictions(); - } + // predictions or punctuation signs (which is done by the updateSuggestionStrip anyway). + mHandler.postUpdateSuggestionStrip(); } // TODO: Remove this method from {@link LatinIME} and move {@link FeedbackManager} to diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java index a37f480b7..5786978a8 100644 --- a/java/src/com/android/inputmethod/latin/RichInputConnection.java +++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java @@ -139,12 +139,11 @@ public class RichInputConnection { if (null != mIC) mIC.commitCompletion(completionInfo); } - public CharSequence getPreviousWord(final String sentenceSeperators) { + public CharSequence getNthPreviousWord(final String sentenceSeperators, final int n) { mIC = mParent.getCurrentInputConnection(); - //TODO: Should fix this. This could be slow! if (null == mIC) return null; - CharSequence prev = mIC.getTextBeforeCursor(LOOKBACK_CHARACTER_NUM, 0); - return getPreviousWord(prev, sentenceSeperators); + final CharSequence prev = mIC.getTextBeforeCursor(LOOKBACK_CHARACTER_NUM, 0); + return getNthPreviousWord(prev, sentenceSeperators, n); } /** @@ -177,56 +176,35 @@ public class RichInputConnection { return sep.indexOf(code) != -1; } - // Get the word before the whitespace preceding the non-whitespace preceding the cursor. - // Also, it won't return words that end in a separator. + // Get the nth word before cursor. n = 1 retrieves the word immediately before the cursor, + // n = 2 retrieves the word before that, and so on. This splits on whitespace only. + // Also, it won't return words that end in a separator (if the nth word before the cursor + // ends in a separator, it returns null). // Example : - // "abc def|" -> abc - // "abc def |" -> abc - // "abc def. |" -> abc - // "abc def . |" -> def - // "abc|" -> null - // "abc |" -> null - // "abc. def|" -> null - public static CharSequence getPreviousWord(CharSequence prev, String sentenceSeperators) { + // (n = 1) "abc def|" -> def + // (n = 1) "abc def |" -> def + // (n = 1) "abc def. |" -> null + // (n = 1) "abc def . |" -> null + // (n = 2) "abc def|" -> abc + // (n = 2) "abc def |" -> abc + // (n = 2) "abc def. |" -> abc + // (n = 2) "abc def . |" -> def + // (n = 2) "abc|" -> null + // (n = 2) "abc |" -> null + // (n = 2) "abc. def|" -> null + public static CharSequence getNthPreviousWord(final CharSequence prev, + final String sentenceSeperators, final int n) { if (prev == null) return null; String[] w = spaceRegex.split(prev); - // If we can't find two words, or we found an empty word, return null. - if (w.length < 2 || w[w.length - 2].length() <= 0) return null; + // If we can't find n words, or we found an empty word, return null. + if (w.length < n || w[w.length - n].length() <= 0) return null; // If ends in a separator, return null - char lastChar = w[w.length - 2].charAt(w[w.length - 2].length() - 1); + char lastChar = w[w.length - n].charAt(w[w.length - n].length() - 1); if (sentenceSeperators.contains(String.valueOf(lastChar))) return null; - return w[w.length - 2]; - } - - public CharSequence getThisWord(String sentenceSeperators) { - mIC = mParent.getCurrentInputConnection(); - if (null == mIC) return null; - final CharSequence prev = mIC.getTextBeforeCursor(LOOKBACK_CHARACTER_NUM, 0); - return getThisWord(prev, sentenceSeperators); - } - - // Get the word immediately before the cursor, even if there is whitespace between it and - // the cursor - but not if there is punctuation. - // Example : - // "abc def|" -> def - // "abc def |" -> def - // "abc def. |" -> null - // "abc def . |" -> null - public static CharSequence getThisWord(CharSequence prev, String sentenceSeperators) { - if (prev == null) return null; - String[] w = spaceRegex.split(prev); - - // No word : return null - if (w.length < 1 || w[w.length - 1].length() <= 0) return null; - - // If ends in a separator, return null - char lastChar = w[w.length - 1].charAt(w[w.length - 1].length() - 1); - if (sentenceSeperators.contains(String.valueOf(lastChar))) return null; - - return w[w.length - 1]; + return w[w.length - n]; } /** diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java index bf5e05e9b..bfa41c784 100644 --- a/java/src/com/android/inputmethod/latin/WordComposer.java +++ b/java/src/com/android/inputmethod/latin/WordComposer.java @@ -34,7 +34,7 @@ public class WordComposer { private int[] mPrimaryKeyCodes; private final InputPointers mInputPointers = new InputPointers(); - private StringBuilder mTypedWord; + private final StringBuilder mTypedWord; private CharSequence mAutoCorrection; private boolean mIsResumed; @@ -59,10 +59,6 @@ public class WordComposer { } public WordComposer(WordComposer source) { - init(source); - } - - public void init(WordComposer source) { mPrimaryKeyCodes = Arrays.copyOf(source.mPrimaryKeyCodes, source.mPrimaryKeyCodes.length); mTypedWord = new StringBuilder(source.mTypedWord); mInputPointers.copy(source.mInputPointers); diff --git a/tests/src/com/android/inputmethod/latin/RichInputConnectionTests.java b/tests/src/com/android/inputmethod/latin/RichInputConnectionTests.java index 7bd7b0e5a..ad9937940 100644 --- a/tests/src/com/android/inputmethod/latin/RichInputConnectionTests.java +++ b/tests/src/com/android/inputmethod/latin/RichInputConnectionTests.java @@ -102,31 +102,26 @@ public class RichInputConnectionTests extends AndroidTestCase { */ public void testGetPreviousWord() { // If one of the following cases breaks, the bigram suggestions won't work. - assertEquals(RichInputConnection.getPreviousWord("abc def", sSeparators), "abc"); - assertNull(RichInputConnection.getPreviousWord("abc", sSeparators)); - assertNull(RichInputConnection.getPreviousWord("abc. def", sSeparators)); + assertEquals(RichInputConnection.getNthPreviousWord("abc def", sSeparators, 2), "abc"); + assertNull(RichInputConnection.getNthPreviousWord("abc", sSeparators, 2)); + assertNull(RichInputConnection.getNthPreviousWord("abc. def", sSeparators, 2)); // The following tests reflect the current behavior of the function - // RichInputConnection#getPreviousWord. + // RichInputConnection#getNthPreviousWord. // TODO: However at this time, the code does never go // into such a path, so it should be safe to change the behavior of // this function if needed - especially since it does not seem very // logical. These tests are just there to catch any unintentional // changes in the behavior of the RichInputConnection#getPreviousWord method. - assertEquals(RichInputConnection.getPreviousWord("abc def ", sSeparators), "abc"); - assertEquals(RichInputConnection.getPreviousWord("abc def.", sSeparators), "abc"); - assertEquals(RichInputConnection.getPreviousWord("abc def .", sSeparators), "def"); - assertNull(RichInputConnection.getPreviousWord("abc ", sSeparators)); - } + assertEquals(RichInputConnection.getNthPreviousWord("abc def ", sSeparators, 2), "abc"); + assertEquals(RichInputConnection.getNthPreviousWord("abc def.", sSeparators, 2), "abc"); + assertEquals(RichInputConnection.getNthPreviousWord("abc def .", sSeparators, 2), "def"); + assertNull(RichInputConnection.getNthPreviousWord("abc ", sSeparators, 2)); - /** - * Test for getting the word before the cursor (for bigram) - */ - public void testGetThisWord() { - assertEquals(RichInputConnection.getThisWord("abc def", sSeparators), "def"); - assertEquals(RichInputConnection.getThisWord("abc def ", sSeparators), "def"); - assertNull(RichInputConnection.getThisWord("abc def.", sSeparators)); - assertNull(RichInputConnection.getThisWord("abc def .", sSeparators)); + assertEquals(RichInputConnection.getNthPreviousWord("abc def", sSeparators, 1), "def"); + assertEquals(RichInputConnection.getNthPreviousWord("abc def ", sSeparators, 1), "def"); + assertNull(RichInputConnection.getNthPreviousWord("abc def.", sSeparators, 1)); + assertNull(RichInputConnection.getNthPreviousWord("abc def .", sSeparators, 1)); } /**