Merge "Restart suggestions when the cursor moves."

This commit is contained in:
Jean Chalard 2013-04-12 12:27:50 +00:00 committed by Android (Google) Code Review
commit 91bcf5eb5d
7 changed files with 103 additions and 15 deletions

View file

@ -37,6 +37,8 @@ public abstract class Dictionary {
public static final String TYPE_USER = "user"; public static final String TYPE_USER = "user";
// User history dictionary internal to LatinIME. // User history dictionary internal to LatinIME.
public static final String TYPE_USER_HISTORY = "history"; public static final String TYPE_USER_HISTORY = "history";
// Spawned by resuming suggestions. Comes from a span that was in the TextView.
public static final String TYPE_RESUMED = "resumed";
protected final String mDictType; protected final String mDictType;
public Dictionary(final String dictType) { public Dictionary(final String dictType) {

View file

@ -44,7 +44,9 @@ import android.os.Message;
import android.os.SystemClock; import android.os.SystemClock;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.text.InputType; import android.text.InputType;
import android.text.SpannableString;
import android.text.TextUtils; import android.text.TextUtils;
import android.text.style.SuggestionSpan;
import android.util.Log; import android.util.Log;
import android.util.PrintWriterPrinter; import android.util.PrintWriterPrinter;
import android.util.Printer; import android.util.Printer;
@ -72,6 +74,7 @@ import com.android.inputmethod.keyboard.KeyboardActionListener;
import com.android.inputmethod.keyboard.KeyboardId; import com.android.inputmethod.keyboard.KeyboardId;
import com.android.inputmethod.keyboard.KeyboardSwitcher; import com.android.inputmethod.keyboard.KeyboardSwitcher;
import com.android.inputmethod.keyboard.MainKeyboardView; import com.android.inputmethod.keyboard.MainKeyboardView;
import com.android.inputmethod.latin.RichInputConnection.Range;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import com.android.inputmethod.latin.Utils.Stats; import com.android.inputmethod.latin.Utils.Stats;
import com.android.inputmethod.latin.define.ProductionFlag; import com.android.inputmethod.latin.define.ProductionFlag;
@ -197,6 +200,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
private static final int MSG_PENDING_IMS_CALLBACK = 1; private static final int MSG_PENDING_IMS_CALLBACK = 1;
private static final int MSG_UPDATE_SUGGESTION_STRIP = 2; private static final int MSG_UPDATE_SUGGESTION_STRIP = 2;
private static final int MSG_SHOW_GESTURE_PREVIEW_AND_SUGGESTION_STRIP = 3; private static final int MSG_SHOW_GESTURE_PREVIEW_AND_SUGGESTION_STRIP = 3;
private static final int MSG_RESUME_SUGGESTIONS = 4;
private static final int ARG1_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT = 1; private static final int ARG1_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT = 1;
@ -234,6 +238,9 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
latinIme.showGesturePreviewAndSuggestionStrip((SuggestedWords)msg.obj, latinIme.showGesturePreviewAndSuggestionStrip((SuggestedWords)msg.obj,
msg.arg1 == ARG1_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT); msg.arg1 == ARG1_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT);
break; break;
case MSG_RESUME_SUGGESTIONS:
latinIme.restartSuggestionsOnWordTouchedByCursor();
break;
} }
} }
@ -241,6 +248,10 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
sendMessageDelayed(obtainMessage(MSG_UPDATE_SUGGESTION_STRIP), mDelayUpdateSuggestions); sendMessageDelayed(obtainMessage(MSG_UPDATE_SUGGESTION_STRIP), mDelayUpdateSuggestions);
} }
public void postResumeSuggestions() {
sendMessageDelayed(obtainMessage(MSG_RESUME_SUGGESTIONS), mDelayUpdateSuggestions);
}
public void cancelUpdateSuggestionStrip() { public void cancelUpdateSuggestionStrip() {
removeMessages(MSG_UPDATE_SUGGESTION_STRIP); removeMessages(MSG_UPDATE_SUGGESTION_STRIP);
} }
@ -910,13 +921,12 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
resetEntireInputState(newSelStart); resetEntireInputState(newSelStart);
} }
// We moved the cursor. If we are touching a word, we need to resume suggestion.
mHandler.postResumeSuggestions();
mKeyboardSwitcher.updateShiftState(); mKeyboardSwitcher.updateShiftState();
} }
mExpectingUpdateSelection = false; mExpectingUpdateSelection = false;
// TODO: Decide to call restartSuggestionsOnWordBeforeCursorIfAtEndOfWord() or not
// here. It would probably be too expensive to call directly here but we may want to post a
// message to delay it. The point would be to unify behavior between backspace to the
// end of a word and manually put the pointer at the end of the word.
// Make a note of the cursor position // Make a note of the cursor position
mLastSelectionStart = newSelStart; mLastSelectionStart = newSelStart;
@ -1728,6 +1738,9 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
// during key repeat. // during key repeat.
mHandler.postUpdateShiftState(); mHandler.postUpdateShiftState();
if (mWordComposer.isComposingWord() && !mWordComposer.isCursorAtEndOfComposingWord()) {
resetEntireInputState(mLastSelectionStart);
}
if (mWordComposer.isComposingWord()) { if (mWordComposer.isComposingWord()) {
final int length = mWordComposer.size(); final int length = mWordComposer.size();
if (length > 0) { if (length > 0) {
@ -1859,6 +1872,10 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
promotePhantomSpace(); promotePhantomSpace();
} }
if (mWordComposer.isComposingWord() && !mWordComposer.isCursorAtEndOfComposingWord()) {
resetEntireInputState(mLastSelectionStart);
isComposingWord = false;
}
// NOTE: isCursorTouchingWord() is a blocking IPC call, so it often takes several // NOTE: isCursorTouchingWord() is a blocking IPC call, so it often takes several
// dozen milliseconds. Avoid calling it as much as possible, since we are on the UI // dozen milliseconds. Avoid calling it as much as possible, since we are on the UI
// thread here. // thread here.
@ -2331,6 +2348,48 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
return prevWord; return prevWord;
} }
/**
* Check if the cursor is touching a word. If so, restart suggestions on this word, else
* do nothing.
*/
private void restartSuggestionsOnWordTouchedByCursor() {
// If the cursor is not touching a word, or if there is a selection, return right away.
if (mLastSelectionStart != mLastSelectionEnd) return;
if (!mConnection.isCursorTouchingWord(mSettings.getCurrent())) return;
final Range range = mConnection.getWordRangeAtCursor(mSettings.getWordSeparators(),
0 /* additionalPrecedingWordsCount */);
final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList();
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)) {
SuggestionSpan span = (SuggestionSpan)object;
for (String s : span.getSuggestions()) {
++i;
if (!TextUtils.equals(s, typedWord)) {
suggestions.add(new SuggestedWordInfo(s,
SuggestionStripView.MAX_SUGGESTIONS - i,
SuggestedWordInfo.KIND_RESUMED, Dictionary.TYPE_RESUMED));
}
}
}
}
mWordComposer.setComposingWord(range.mWord, mKeyboardSwitcher.getKeyboard());
mWordComposer.setCursorPositionWithinWord(range.mCharsBefore);
mConnection.setComposingRegion(mLastSelectionStart - range.mCharsBefore,
mLastSelectionEnd + range.mCharsAfter);
if (suggestions.isEmpty()) {
suggestions.add(new SuggestedWordInfo(range.mWord.toString(), 1,
SuggestedWordInfo.KIND_TYPED, Dictionary.TYPE_RESUMED));
}
showSuggestionStrip(new SuggestedWords(suggestions,
true /* typedWordValid */, false /* willAutoCorrect */,
false /* isPunctuationSuggestions */, false /* isObsoleteSuggestions */,
false /* isPrediction */), range.mWord.toString());
}
/** /**
* Check if the cursor is actually at the end of a word. If so, restart suggestions on this * Check if the cursor is actually at the end of a word. If so, restart suggestions on this
* word, else do nothing. * word, else do nothing.
@ -2339,17 +2398,18 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
final CharSequence word = final CharSequence word =
mConnection.getWordBeforeCursorIfAtEndOfWord(mSettings.getCurrent()); mConnection.getWordBeforeCursorIfAtEndOfWord(mSettings.getCurrent());
if (null != word) { if (null != word) {
restartSuggestionsOnWordBeforeCursor(word); final String wordString = word.toString();
restartSuggestionsOnWordBeforeCursor(wordString);
// TODO: Handle the case where the user manually moves the cursor and then backs up over // TODO: Handle the case where the user manually moves the cursor and then backs up over
// a separator. In that case, the current log unit should not be uncommitted. // a separator. In that case, the current log unit should not be uncommitted.
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
ResearchLogger.getInstance().uncommitCurrentLogUnit(word.toString(), ResearchLogger.getInstance().uncommitCurrentLogUnit(wordString,
true /* dumpCurrentLogUnit */); true /* dumpCurrentLogUnit */);
} }
} }
} }
private void restartSuggestionsOnWordBeforeCursor(final CharSequence word) { private void restartSuggestionsOnWordBeforeCursor(final String word) {
mWordComposer.setComposingWord(word, mKeyboardSwitcher.getKeyboard()); mWordComposer.setComposingWord(word, mKeyboardSwitcher.getKeyboard());
final int length = word.length(); final int length = word.length();
mConnection.deleteSurroundingText(length, 0); mConnection.deleteSurroundingText(length, 0);

View file

@ -17,7 +17,9 @@
package com.android.inputmethod.latin; package com.android.inputmethod.latin;
import android.inputmethodservice.InputMethodService; import android.inputmethodservice.InputMethodService;
import android.text.SpannableString;
import android.text.TextUtils; import android.text.TextUtils;
import android.text.style.SuggestionSpan;
import android.util.Log; import android.util.Log;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.inputmethod.CompletionInfo; import android.view.inputmethod.CompletionInfo;
@ -444,9 +446,9 @@ public final class RichInputConnection {
public final int mCharsAfter; public final int mCharsAfter;
/** The actual characters that make up a word */ /** The actual characters that make up a word */
public final String mWord; public final CharSequence mWord;
public Range(int charsBefore, int charsAfter, String word) { public Range(int charsBefore, int charsAfter, CharSequence word) {
if (charsBefore < 0 || charsAfter < 0) { if (charsBefore < 0 || charsAfter < 0) {
throw new IndexOutOfBoundsException(); throw new IndexOutOfBoundsException();
} }
@ -500,7 +502,7 @@ public final class RichInputConnection {
* separator. For example, if the field contains "he|llo world", where | * separator. For example, if the field contains "he|llo world", where |
* represents the cursor, then "hello " will be returned. * represents the cursor, then "hello " will be returned.
*/ */
public String getWordAtCursor(String separators) { public CharSequence getWordAtCursor(String separators) {
// getWordRangeAtCursor returns null if the connection is null // getWordRangeAtCursor returns null if the connection is null
Range r = getWordRangeAtCursor(separators, 0); Range r = getWordRangeAtCursor(separators, 0);
return (r == null) ? null : r.mWord; return (r == null) ? null : r.mWord;
@ -519,8 +521,10 @@ public final class RichInputConnection {
if (mIC == null || sep == null) { if (mIC == null || sep == null) {
return null; return null;
} }
final CharSequence before = mIC.getTextBeforeCursor(1000, 0); final CharSequence before = mIC.getTextBeforeCursor(1000,
final CharSequence after = mIC.getTextAfterCursor(1000, 0); InputConnection.GET_TEXT_WITH_STYLES);
final CharSequence after = mIC.getTextAfterCursor(1000,
InputConnection.GET_TEXT_WITH_STYLES);
if (before == null || after == null) { if (before == null || after == null) {
return null; return null;
} }
@ -562,8 +566,9 @@ public final class RichInputConnection {
} }
} }
final String word = before.toString().substring(startIndexInBefore, before.length()) final SpannableString word = new SpannableString(TextUtils.concat(
+ after.toString().substring(0, endIndexInAfter); before.subSequence(startIndexInBefore, before.length()),
after.subSequence(0, endIndexInAfter)));
return new Range(before.length() - startIndexInBefore, endIndexInAfter, word); return new Range(before.length() - startIndexInBefore, endIndexInAfter, word);
} }

View file

@ -134,6 +134,10 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
return mSettingsValues.mIsInternal; return mSettingsValues.mIsInternal;
} }
public String getWordSeparators() {
return mSettingsValues.mWordSeparators;
}
// Accessed from the settings interface, hence public // Accessed from the settings interface, hence public
public static boolean readKeypressSoundEnabled(final SharedPreferences prefs, public static boolean readKeypressSoundEnabled(final SharedPreferences prefs,
final Resources res) { final Resources res) {

View file

@ -131,6 +131,7 @@ public final class SuggestedWords {
public static final int KIND_APP_DEFINED = 6; // Suggested by the application public static final int KIND_APP_DEFINED = 6; // Suggested by the application
public static final int KIND_SHORTCUT = 7; // A shortcut public static final int KIND_SHORTCUT = 7; // A shortcut
public static final int KIND_PREDICTION = 8; // A prediction (== a suggestion with no input) public static final int KIND_PREDICTION = 8; // A prediction (== a suggestion with no input)
public static final int KIND_RESUMED = 9; // A resumed suggestion (comes from a span)
public final String mWord; public final String mWord;
public final int mScore; public final int mScore;
public final int mKind; // one of the KIND_* constants above public final int mKind; // one of the KIND_* constants above

View file

@ -49,6 +49,7 @@ public final class WordComposer {
private int mCapitalizedMode; private int mCapitalizedMode;
private int mTrailingSingleQuotesCount; private int mTrailingSingleQuotesCount;
private int mCodePointSize; private int mCodePointSize;
private int mCursorPositionWithinWord;
/** /**
* Whether the user chose to capitalize the first char of the word. * Whether the user chose to capitalize the first char of the word.
@ -62,6 +63,7 @@ public final class WordComposer {
mTrailingSingleQuotesCount = 0; mTrailingSingleQuotesCount = 0;
mIsResumed = false; mIsResumed = false;
mIsBatchMode = false; mIsBatchMode = false;
mCursorPositionWithinWord = 0;
refreshSize(); refreshSize();
} }
@ -76,6 +78,7 @@ public final class WordComposer {
mTrailingSingleQuotesCount = source.mTrailingSingleQuotesCount; mTrailingSingleQuotesCount = source.mTrailingSingleQuotesCount;
mIsResumed = source.mIsResumed; mIsResumed = source.mIsResumed;
mIsBatchMode = source.mIsBatchMode; mIsBatchMode = source.mIsBatchMode;
mCursorPositionWithinWord = source.mCursorPositionWithinWord;
refreshSize(); refreshSize();
} }
@ -91,6 +94,7 @@ public final class WordComposer {
mTrailingSingleQuotesCount = 0; mTrailingSingleQuotesCount = 0;
mIsResumed = false; mIsResumed = false;
mIsBatchMode = false; mIsBatchMode = false;
mCursorPositionWithinWord = 0;
refreshSize(); refreshSize();
} }
@ -135,6 +139,7 @@ public final class WordComposer {
final int newIndex = size(); final int newIndex = size();
mTypedWord.appendCodePoint(primaryCode); mTypedWord.appendCodePoint(primaryCode);
refreshSize(); refreshSize();
mCursorPositionWithinWord = mCodePointSize;
if (newIndex < MAX_WORD_LENGTH) { if (newIndex < MAX_WORD_LENGTH) {
mPrimaryKeyCodes[newIndex] = primaryCode >= Constants.CODE_SPACE mPrimaryKeyCodes[newIndex] = primaryCode >= Constants.CODE_SPACE
? Character.toLowerCase(primaryCode) : primaryCode; ? Character.toLowerCase(primaryCode) : primaryCode;
@ -158,6 +163,14 @@ public final class WordComposer {
mAutoCorrection = null; mAutoCorrection = null;
} }
public void setCursorPositionWithinWord(final int posWithinWord) {
mCursorPositionWithinWord = posWithinWord;
}
public boolean isCursorAtEndOfComposingWord() {
return mCursorPositionWithinWord == mCodePointSize;
}
public void setBatchInputPointers(final InputPointers batchPointers) { public void setBatchInputPointers(final InputPointers batchPointers) {
mInputPointers.set(batchPointers); mInputPointers.set(batchPointers);
mIsBatchMode = true; mIsBatchMode = true;
@ -242,6 +255,7 @@ public final class WordComposer {
++mTrailingSingleQuotesCount; ++mTrailingSingleQuotesCount;
} }
} }
mCursorPositionWithinWord = mCodePointSize;
mAutoCorrection = null; mAutoCorrection = null;
} }
@ -368,6 +382,7 @@ public final class WordComposer {
mCapitalizedMode = CAPS_MODE_OFF; mCapitalizedMode = CAPS_MODE_OFF;
refreshSize(); refreshSize();
mAutoCorrection = null; mAutoCorrection = null;
mCursorPositionWithinWord = 0;
mIsResumed = false; mIsResumed = false;
return lastComposedWord; return lastComposedWord;
} }
@ -380,6 +395,7 @@ public final class WordComposer {
refreshSize(); refreshSize();
mCapitalizedMode = lastComposedWord.mCapitalizedMode; mCapitalizedMode = lastComposedWord.mCapitalizedMode;
mAutoCorrection = null; // This will be filled by the next call to updateSuggestion. mAutoCorrection = null; // This will be filled by the next call to updateSuggestion.
mCursorPositionWithinWord = mCodePointSize;
mIsResumed = true; mIsResumed = true;
} }

View file

@ -1283,7 +1283,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
if (connection != null) { if (connection != null) {
Range range = connection.getWordRangeAtCursor(WHITESPACE_SEPARATORS, 1); Range range = connection.getWordRangeAtCursor(WHITESPACE_SEPARATORS, 1);
if (range != null) { if (range != null) {
word = range.mWord; word = range.mWord.toString();
} }
} }
final ResearchLogger researchLogger = getInstance(); final ResearchLogger researchLogger = getInstance();