Change the timing of reading the previous word.

Bug: 11328842
Change-Id: I08229e895fc34403932648b9b931583d965f0e01
This commit is contained in:
Jean Chalard 2013-10-22 19:20:45 +09:00
parent da459787e2
commit 4866a3e918
3 changed files with 121 additions and 32 deletions

View file

@ -1779,9 +1779,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mInputUpdater.onStartBatchInput(); mInputUpdater.onStartBatchInput();
mHandler.cancelUpdateSuggestionStrip(); mHandler.cancelUpdateSuggestionStrip();
mConnection.beginBatchEdit(); mConnection.beginBatchEdit();
final SettingsValues settingsValues = mSettings.getCurrent(); final SettingsValues currentSettingsValues = mSettings.getCurrent();
if (mWordComposer.isComposingWord()) { if (mWordComposer.isComposingWord()) {
if (settingsValues.mIsInternal) { if (currentSettingsValues.mIsInternal) {
if (mWordComposer.isBatchMode()) { if (mWordComposer.isBatchMode()) {
LatinImeLoggerUtils.onAutoCorrection( LatinImeLoggerUtils.onAutoCorrection(
"", mWordComposer.getTypedWord(), " ", mWordComposer); "", mWordComposer.getTypedWord(), " ", mWordComposer);
@ -1808,12 +1808,14 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
} }
final int codePointBeforeCursor = mConnection.getCodePointBeforeCursor(); final int codePointBeforeCursor = mConnection.getCodePointBeforeCursor();
if (Character.isLetterOrDigit(codePointBeforeCursor) if (Character.isLetterOrDigit(codePointBeforeCursor)
|| settingsValues.isUsuallyFollowedBySpace(codePointBeforeCursor)) { || currentSettingsValues.isUsuallyFollowedBySpace(codePointBeforeCursor)) {
mSpaceState = SPACE_STATE_PHANTOM; mSpaceState = SPACE_STATE_PHANTOM;
} }
mConnection.endBatchEdit(); mConnection.endBatchEdit();
mKeyboardSwitcher.updateShiftState(); mKeyboardSwitcher.updateShiftState();
mWordComposer.setCapitalizedModeAtStartComposingTime(getActualCapsMode()); mWordComposer.setCapitalizedModeAndPreviousWordAtStartComposingTime(getActualCapsMode(),
// Prev word is 1st word before cursor
getNthPreviousWordForSuggestion(currentSettingsValues, 1 /* nthPreviousWord */));
} }
static final class InputUpdater implements Handler.Callback { static final class InputUpdater implements Handler.Callback {
@ -1986,7 +1988,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mConnection.commitText(commitParts[0], 0); mConnection.commitText(commitParts[0], 0);
mSpaceState = SPACE_STATE_PHANTOM; mSpaceState = SPACE_STATE_PHANTOM;
mKeyboardSwitcher.updateShiftState(); mKeyboardSwitcher.updateShiftState();
mWordComposer.setCapitalizedModeAtStartComposingTime(getActualCapsMode()); mWordComposer.setCapitalizedModeAndPreviousWordAtStartComposingTime(
getActualCapsMode(), commitParts[0]);
++mAutoCommitSequenceNumber; ++mAutoCommitSequenceNumber;
} }
} }
@ -2295,7 +2298,11 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mWordComposer.add(primaryCode, keyX, keyY); mWordComposer.add(primaryCode, keyX, keyY);
// If it's the first letter, make note of auto-caps state // If it's the first letter, make note of auto-caps state
if (mWordComposer.size() == 1) { if (mWordComposer.size() == 1) {
mWordComposer.setCapitalizedModeAtStartComposingTime(getActualCapsMode()); // We pass 1 to getPreviousWordForSuggestion because we were not composing a word
// yet, so the word we want is the 1st word before the cursor.
mWordComposer.setCapitalizedModeAndPreviousWordAtStartComposingTime(
getActualCapsMode(),
getNthPreviousWordForSuggestion(currentSettings, 1 /* nthPreviousWord */));
} }
mConnection.setComposingText(getTextWithUnderline(mWordComposer.getTypedWord()), 1); mConnection.setComposingText(getTextWithUnderline(mWordComposer.getTypedWord()), 1);
} else { } else {
@ -2537,12 +2544,18 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
} }
} }
private String getPreviousWordForSuggestion(final SettingsValues currentSettings) { /**
* Get the nth previous word before the cursor as context for the suggestion process.
* @param currentSettings the current settings values.
* @param nthPreviousWord reverse index of the word to get (1-indexed)
* @return the nth previous word before the cursor.
*/
private String getNthPreviousWordForSuggestion(final SettingsValues currentSettings,
final int nthPreviousWord) {
if (currentSettings.mCurrentLanguageHasSpaces) { if (currentSettings.mCurrentLanguageHasSpaces) {
// If we are typing in a language with spaces we can just look up the previous // If we are typing in a language with spaces we can just look up the previous
// word from textview. // word from textview.
return mConnection.getNthPreviousWord(currentSettings, return mConnection.getNthPreviousWord(currentSettings, nthPreviousWord);
mWordComposer.isComposingWord() ? 2 : 1);
} else { } else {
return LastComposedWord.NOT_A_COMPOSED_WORD == mLastComposedWord ? null return LastComposedWord.NOT_A_COMPOSED_WORD == mLastComposedWord ? null
: mLastComposedWord.mCommittedWord; : mLastComposedWord.mCommittedWord;
@ -2562,8 +2575,31 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// should just skip whitespace if any, so 1. // should just skip whitespace if any, so 1.
final SettingsValues currentSettings = mSettings.getCurrent(); final SettingsValues currentSettings = mSettings.getCurrent();
final int[] additionalFeaturesOptions = currentSettings.mAdditionalFeaturesSettingValues; final int[] additionalFeaturesOptions = currentSettings.mAdditionalFeaturesSettingValues;
final String prevWord = getPreviousWordForSuggestion(currentSettings);
suggest.getSuggestedWords(mWordComposer, prevWord, keyboard.getProximityInfo(), final String previousWord;
if (mWordComposer.isComposingWord() || mWordComposer.isBatchMode()) {
previousWord = mWordComposer.getPreviousWord();
} else {
// Not composing: this is for prediction.
// TODO: read the previous word earlier for prediction, like we are doing for
// normal suggestions.
previousWord = getNthPreviousWordForSuggestion(currentSettings, 1 /* nthPreviousWord*/);
}
if (DEBUG) {
// TODO: this is for checking consistency with older versions. Remove this when
// we are confident this is stable.
// We're checking the previous word in the text field against the memorized previous
// word. If we are composing a word we should have the second word before the cursor
// memorized, otherwise we should have the first.
final String rereadPrevWord = getNthPreviousWordForSuggestion(currentSettings,
mWordComposer.isComposingWord() ? 2 : 1);
if (!TextUtils.equals(previousWord, rereadPrevWord)) {
throw new RuntimeException("Unexpected previous word: "
+ previousWord + " <> " + rereadPrevWord);
}
}
suggest.getSuggestedWords(mWordComposer, mWordComposer.getPreviousWord(),
keyboard.getProximityInfo(),
currentSettings.mBlockPotentiallyOffensive, currentSettings.mCorrectionEnabled, currentSettings.mBlockPotentiallyOffensive, currentSettings.mCorrectionEnabled,
additionalFeaturesOptions, sessionId, sequenceNumber, callback); additionalFeaturesOptions, sessionId, sequenceNumber, callback);
} }
@ -2900,7 +2936,13 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
} }
} }
} }
mWordComposer.setComposingWord(typedWord, mKeyboardSwitcher.getKeyboard()); mWordComposer.setComposingWord(typedWord,
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());
mWordComposer.setCursorPositionWithinWord( mWordComposer.setCursorPositionWithinWord(
typedWord.codePointCount(0, numberOfCharsInWordBeforeCursor)); typedWord.codePointCount(0, numberOfCharsInWordBeforeCursor));
mConnection.setComposingRegion( mConnection.setComposingRegion(
@ -2978,7 +3020,11 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
} }
private void restartSuggestionsOnWordBeforeCursor(final String word) { private void restartSuggestionsOnWordBeforeCursor(final String word) {
mWordComposer.setComposingWord(word, mKeyboardSwitcher.getKeyboard()); mWordComposer.setComposingWord(word,
// Previous word is the 2nd word before cursor because we are restarting on the
// 1st word before cursor.
getNthPreviousWordForSuggestion(mSettings.getCurrent(), 2 /* nthPreviousWord */),
mKeyboardSwitcher.getKeyboard());
final int length = word.length(); final int length = word.length();
mConnection.deleteSurroundingText(length, 0); mConnection.deleteSurroundingText(length, 0);
mConnection.setComposingText(word, 1); mConnection.setComposingText(word, 1);
@ -3044,7 +3090,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
} else { } else {
// For languages without spaces, we revert the typed string but the cursor is flush // For languages without spaces, we revert the typed string but the cursor is flush
// with the typed word, so we need to resume suggestions right away. // with the typed word, so we need to resume suggestions right away.
mWordComposer.setComposingWord(stringToCommit, mKeyboardSwitcher.getKeyboard()); mWordComposer.setComposingWord(stringToCommit, previousWord,
mKeyboardSwitcher.getKeyboard());
mConnection.setComposingText(stringToCommit, 1); mConnection.setComposingText(stringToCommit, 1);
} }
if (mSettings.isInternal()) { if (mSettings.isInternal()) {

View file

@ -48,6 +48,10 @@ public final class WordComposer {
// at any given time. However this is not limited in size, while mPrimaryKeyCodes is limited // at any given time. However this is not limited in size, while mPrimaryKeyCodes is limited
// to MAX_WORD_LENGTH code points. // to MAX_WORD_LENGTH code points.
private final StringBuilder mTypedWord; private final StringBuilder mTypedWord;
// The previous word (before the composing word). Used as context for suggestions. May be null
// after resetting and before starting a new composing word, or when there is no context like
// at the start of text for example.
private String mPreviousWord;
private String mAutoCorrection; private String mAutoCorrection;
private boolean mIsResumed; private boolean mIsResumed;
private boolean mIsBatchMode; private boolean mIsBatchMode;
@ -85,6 +89,7 @@ public final class WordComposer {
mIsBatchMode = false; mIsBatchMode = false;
mCursorPositionWithinWord = 0; mCursorPositionWithinWord = 0;
mRejectedBatchModeSuggestion = null; mRejectedBatchModeSuggestion = null;
mPreviousWord = null;
refreshSize(); refreshSize();
} }
@ -101,6 +106,7 @@ public final class WordComposer {
mIsBatchMode = source.mIsBatchMode; mIsBatchMode = source.mIsBatchMode;
mCursorPositionWithinWord = source.mCursorPositionWithinWord; mCursorPositionWithinWord = source.mCursorPositionWithinWord;
mRejectedBatchModeSuggestion = source.mRejectedBatchModeSuggestion; mRejectedBatchModeSuggestion = source.mRejectedBatchModeSuggestion;
mPreviousWord = source.mPreviousWord;
refreshSize(); refreshSize();
} }
@ -118,6 +124,7 @@ public final class WordComposer {
mIsBatchMode = false; mIsBatchMode = false;
mCursorPositionWithinWord = 0; mCursorPositionWithinWord = 0;
mRejectedBatchModeSuggestion = null; mRejectedBatchModeSuggestion = null;
mPreviousWord = null;
refreshSize(); refreshSize();
} }
@ -284,8 +291,13 @@ public final class WordComposer {
/** /**
* Set the currently composing word to the one passed as an argument. * Set the currently composing word to the one passed as an argument.
* This will register NOT_A_COORDINATE for X and Ys, and use the passed keyboard for proximity. * This will register NOT_A_COORDINATE for X and Ys, and use the passed keyboard for proximity.
* @param word the char sequence to set as the composing word.
* @param previousWord the previous word, to use as context for suggestions. Can be null if
* the context is nil (typically, at start of text).
* @param keyboard the keyboard this is typed on, for coordinate info/proximity.
*/ */
public void setComposingWord(final CharSequence word, final Keyboard keyboard) { public void setComposingWord(final CharSequence word, final String previousWord,
final Keyboard keyboard) {
reset(); reset();
final int length = word.length(); final int length = word.length();
for (int i = 0; i < length; i = Character.offsetByCodePoints(word, i, 1)) { for (int i = 0; i < length; i = Character.offsetByCodePoints(word, i, 1)) {
@ -293,6 +305,7 @@ public final class WordComposer {
addKeyInfo(codePoint, keyboard); addKeyInfo(codePoint, keyboard);
} }
mIsResumed = true; mIsResumed = true;
mPreviousWord = previousWord;
} }
/** /**
@ -343,6 +356,10 @@ public final class WordComposer {
return mTypedWord.toString(); return mTypedWord.toString();
} }
public String getPreviousWord() {
return mPreviousWord;
}
/** /**
* Whether or not the user typed a capital letter as the first letter in the word * Whether or not the user typed a capital letter as the first letter in the word
* @return capitalization preference * @return capitalization preference
@ -388,18 +405,21 @@ public final class WordComposer {
} }
/** /**
* Saves the caps mode at the start of composing. * Saves the caps mode and the previous word at the start of composing.
* *
* WordComposer needs to know about this for several reasons. The first is, we need to know * WordComposer needs to know about the caps mode for several reasons. The first is, we need
* after the fact what the reason was, to register the correct form into the user history * to know after the fact what the reason was, to register the correct form into the user
* dictionary: if the word was automatically capitalized, we should insert it in all-lower * history dictionary: if the word was automatically capitalized, we should insert it in
* case but if it's a manual pressing of shift, then it should be inserted as is. * all-lower case but if it's a manual pressing of shift, then it should be inserted as is.
* Also, batch input needs to know about the current caps mode to display correctly * Also, batch input needs to know about the current caps mode to display correctly
* capitalized suggestions. * capitalized suggestions.
* @param mode the mode at the time of start * @param mode the mode at the time of start
* @param previousWord the previous word as context for suggestions. May be null if none.
*/ */
public void setCapitalizedModeAtStartComposingTime(final int mode) { public void setCapitalizedModeAndPreviousWordAtStartComposingTime(final int mode,
final String previousWord) {
mCapitalizedMode = mode; mCapitalizedMode = mode;
mPreviousWord = previousWord;
} }
/** /**
@ -451,6 +471,7 @@ public final class WordComposer {
mCapsCount = 0; mCapsCount = 0;
mDigitsCount = 0; mDigitsCount = 0;
mIsBatchMode = false; mIsBatchMode = false;
mPreviousWord = mTypedWord.toString();
mTypedWord.setLength(0); mTypedWord.setLength(0);
mCodePointSize = 0; mCodePointSize = 0;
mTrailingSingleQuotesCount = 0; mTrailingSingleQuotesCount = 0;
@ -464,7 +485,8 @@ public final class WordComposer {
return lastComposedWord; return lastComposedWord;
} }
public void resumeSuggestionOnLastComposedWord(final LastComposedWord lastComposedWord) { public void resumeSuggestionOnLastComposedWord(final LastComposedWord lastComposedWord,
final String previousWord) {
mPrimaryKeyCodes = lastComposedWord.mPrimaryKeyCodes; mPrimaryKeyCodes = lastComposedWord.mPrimaryKeyCodes;
mInputPointers.set(lastComposedWord.mInputPointers); mInputPointers.set(lastComposedWord.mInputPointers);
mTypedWord.setLength(0); mTypedWord.setLength(0);
@ -475,6 +497,7 @@ public final class WordComposer {
mCursorPositionWithinWord = mCodePointSize; mCursorPositionWithinWord = mCodePointSize;
mRejectedBatchModeSuggestion = null; mRejectedBatchModeSuggestion = null;
mIsResumed = true; mIsResumed = true;
mPreviousWord = previousWord;
} }
public boolean isBatchMode() { public boolean isBatchMode() {

View file

@ -26,8 +26,15 @@ import android.test.suitebuilder.annotation.SmallTest;
public class WordComposerTests extends AndroidTestCase { public class WordComposerTests extends AndroidTestCase {
public void testMoveCursor() { public void testMoveCursor() {
final WordComposer wc = new WordComposer(); final WordComposer wc = new WordComposer();
// BMP is the Basic Multilingual Plane, as defined by Unicode. This includes
// most characters for most scripts, including all Roman alphabet languages,
// CJK, Arabic, Hebrew. Notable exceptions include some emoji and some
// very rare Chinese ideograms. BMP characters can be encoded on 2 bytes
// in UTF-16, whereas those outside the BMP need 4 bytes.
// http://en.wikipedia.org/wiki/Plane_(Unicode)#Basic_Multilingual_Plane
final String STR_WITHIN_BMP = "abcdef"; final String STR_WITHIN_BMP = "abcdef";
wc.setComposingWord(STR_WITHIN_BMP, null); final String PREVWORD = "prevword";
wc.setComposingWord(STR_WITHIN_BMP, PREVWORD, null);
assertEquals(wc.size(), assertEquals(wc.size(),
STR_WITHIN_BMP.codePointCount(0, STR_WITHIN_BMP.length())); STR_WITHIN_BMP.codePointCount(0, STR_WITHIN_BMP.length()));
assertFalse(wc.isCursorFrontOrMiddleOfComposingWord()); assertFalse(wc.isCursorFrontOrMiddleOfComposingWord());
@ -43,13 +50,19 @@ public class WordComposerTests extends AndroidTestCase {
// Move the cursor to after the 'f' // Move the cursor to after the 'f'
assertTrue(wc.moveCursorByAndReturnIfInsideComposingWord(1)); assertTrue(wc.moveCursorByAndReturnIfInsideComposingWord(1));
assertFalse(wc.isCursorFrontOrMiddleOfComposingWord()); assertFalse(wc.isCursorFrontOrMiddleOfComposingWord());
// Check the previous word is still there
assertEquals(PREVWORD, wc.getPreviousWord());
// Move the cursor past the end of the word // Move the cursor past the end of the word
assertFalse(wc.moveCursorByAndReturnIfInsideComposingWord(1)); assertFalse(wc.moveCursorByAndReturnIfInsideComposingWord(1));
assertFalse(wc.moveCursorByAndReturnIfInsideComposingWord(15)); assertFalse(wc.moveCursorByAndReturnIfInsideComposingWord(15));
// Do what LatinIME does when the cursor is moved outside of the word,
// and check the behavior is correct.
wc.reset();
assertNull(wc.getPreviousWord());
// \uD861\uDED7 is 𨛗, a character outside the BMP // \uD861\uDED7 is 𨛗, a character outside the BMP
final String STR_WITH_SUPPLEMENTARY_CHAR = "abcde\uD861\uDED7fgh"; final String STR_WITH_SUPPLEMENTARY_CHAR = "abcde\uD861\uDED7fgh";
wc.setComposingWord(STR_WITH_SUPPLEMENTARY_CHAR, null); wc.setComposingWord(STR_WITH_SUPPLEMENTARY_CHAR, null, null);
assertEquals(wc.size(), STR_WITH_SUPPLEMENTARY_CHAR.codePointCount(0, assertEquals(wc.size(), STR_WITH_SUPPLEMENTARY_CHAR.codePointCount(0,
STR_WITH_SUPPLEMENTARY_CHAR.length())); STR_WITH_SUPPLEMENTARY_CHAR.length()));
assertFalse(wc.isCursorFrontOrMiddleOfComposingWord()); assertFalse(wc.isCursorFrontOrMiddleOfComposingWord());
@ -59,34 +72,40 @@ public class WordComposerTests extends AndroidTestCase {
assertTrue(wc.isCursorFrontOrMiddleOfComposingWord()); assertTrue(wc.isCursorFrontOrMiddleOfComposingWord());
assertTrue(wc.moveCursorByAndReturnIfInsideComposingWord(1)); assertTrue(wc.moveCursorByAndReturnIfInsideComposingWord(1));
assertFalse(wc.isCursorFrontOrMiddleOfComposingWord()); assertFalse(wc.isCursorFrontOrMiddleOfComposingWord());
assertNull(wc.getPreviousWord());
wc.setComposingWord(STR_WITH_SUPPLEMENTARY_CHAR, null); wc.setComposingWord(STR_WITH_SUPPLEMENTARY_CHAR, STR_WITHIN_BMP, null);
wc.setCursorPositionWithinWord(3); wc.setCursorPositionWithinWord(3);
assertTrue(wc.moveCursorByAndReturnIfInsideComposingWord(7)); assertTrue(wc.moveCursorByAndReturnIfInsideComposingWord(7));
assertEquals(STR_WITHIN_BMP, wc.getPreviousWord());
wc.setComposingWord(STR_WITH_SUPPLEMENTARY_CHAR, null); wc.setComposingWord(STR_WITH_SUPPLEMENTARY_CHAR, STR_WITH_SUPPLEMENTARY_CHAR, null);
wc.setCursorPositionWithinWord(3); wc.setCursorPositionWithinWord(3);
assertTrue(wc.moveCursorByAndReturnIfInsideComposingWord(7)); assertTrue(wc.moveCursorByAndReturnIfInsideComposingWord(7));
assertEquals(STR_WITH_SUPPLEMENTARY_CHAR, wc.getPreviousWord());
wc.setComposingWord(STR_WITH_SUPPLEMENTARY_CHAR, null); wc.setComposingWord(STR_WITH_SUPPLEMENTARY_CHAR, STR_WITHIN_BMP, null);
wc.setCursorPositionWithinWord(3); wc.setCursorPositionWithinWord(3);
assertTrue(wc.moveCursorByAndReturnIfInsideComposingWord(-3)); assertTrue(wc.moveCursorByAndReturnIfInsideComposingWord(-3));
assertFalse(wc.moveCursorByAndReturnIfInsideComposingWord(-1)); assertFalse(wc.moveCursorByAndReturnIfInsideComposingWord(-1));
assertEquals(STR_WITHIN_BMP, wc.getPreviousWord());
wc.setComposingWord(STR_WITH_SUPPLEMENTARY_CHAR, null); wc.setComposingWord(STR_WITH_SUPPLEMENTARY_CHAR, null, null);
wc.setCursorPositionWithinWord(3); wc.setCursorPositionWithinWord(3);
assertFalse(wc.moveCursorByAndReturnIfInsideComposingWord(-9)); assertFalse(wc.moveCursorByAndReturnIfInsideComposingWord(-9));
assertNull(wc.getPreviousWord());
wc.setComposingWord(STR_WITH_SUPPLEMENTARY_CHAR, null); wc.setComposingWord(STR_WITH_SUPPLEMENTARY_CHAR, STR_WITH_SUPPLEMENTARY_CHAR, null);
assertTrue(wc.moveCursorByAndReturnIfInsideComposingWord(-10)); assertTrue(wc.moveCursorByAndReturnIfInsideComposingWord(-10));
assertEquals(STR_WITH_SUPPLEMENTARY_CHAR, wc.getPreviousWord());
wc.setComposingWord(STR_WITH_SUPPLEMENTARY_CHAR, null); wc.setComposingWord(STR_WITH_SUPPLEMENTARY_CHAR, null, null);
assertFalse(wc.moveCursorByAndReturnIfInsideComposingWord(-11)); assertFalse(wc.moveCursorByAndReturnIfInsideComposingWord(-11));
wc.setComposingWord(STR_WITH_SUPPLEMENTARY_CHAR, null); wc.setComposingWord(STR_WITH_SUPPLEMENTARY_CHAR, null, null);
assertTrue(wc.moveCursorByAndReturnIfInsideComposingWord(0)); assertTrue(wc.moveCursorByAndReturnIfInsideComposingWord(0));
wc.setComposingWord(STR_WITH_SUPPLEMENTARY_CHAR, null); wc.setComposingWord(STR_WITH_SUPPLEMENTARY_CHAR, null, null);
wc.setCursorPositionWithinWord(2); wc.setCursorPositionWithinWord(2);
assertTrue(wc.moveCursorByAndReturnIfInsideComposingWord(0)); assertTrue(wc.moveCursorByAndReturnIfInsideComposingWord(0));
} }