Restart suggestions when the cursor moves.

This uses the old suggestions. It does not try to recompute
new suggestions if there are no old suggestions yet: this is
coming in a later change.
If there are no suggestions, this shows the word itself
as a suggestion.

Bug: 8084810
Change-Id: I4c2e25df0ff3673be1825f57a0c19a9d23d47a48
main
Jean Chalard 2013-04-10 16:38:37 +09:00
parent d24f939712
commit 6a114fa700
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";
// User history dictionary internal to LatinIME.
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;
public Dictionary(final String dictType) {

View File

@ -44,7 +44,9 @@ import android.os.Message;
import android.os.SystemClock;
import android.preference.PreferenceManager;
import android.text.InputType;
import android.text.SpannableString;
import android.text.TextUtils;
import android.text.style.SuggestionSpan;
import android.util.Log;
import android.util.PrintWriterPrinter;
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.KeyboardSwitcher;
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.Utils.Stats;
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_UPDATE_SUGGESTION_STRIP = 2;
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;
@ -234,6 +238,9 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
latinIme.showGesturePreviewAndSuggestionStrip((SuggestedWords)msg.obj,
msg.arg1 == ARG1_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT);
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);
}
public void postResumeSuggestions() {
sendMessageDelayed(obtainMessage(MSG_RESUME_SUGGESTIONS), mDelayUpdateSuggestions);
}
public void cancelUpdateSuggestionStrip() {
removeMessages(MSG_UPDATE_SUGGESTION_STRIP);
}
@ -910,13 +921,12 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
resetEntireInputState(newSelStart);
}
// We moved the cursor. If we are touching a word, we need to resume suggestion.
mHandler.postResumeSuggestions();
mKeyboardSwitcher.updateShiftState();
}
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
mLastSelectionStart = newSelStart;
@ -1727,6 +1737,9 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
// during key repeat.
mHandler.postUpdateShiftState();
if (mWordComposer.isComposingWord() && !mWordComposer.isCursorAtEndOfComposingWord()) {
resetEntireInputState(mLastSelectionStart);
}
if (mWordComposer.isComposingWord()) {
final int length = mWordComposer.size();
if (length > 0) {
@ -1858,6 +1871,10 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
promotePhantomSpace();
}
if (mWordComposer.isComposingWord() && !mWordComposer.isCursorAtEndOfComposingWord()) {
resetEntireInputState(mLastSelectionStart);
isComposingWord = false;
}
// 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
// thread here.
@ -2330,6 +2347,48 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
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
* word, else do nothing.
@ -2338,17 +2397,18 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
final CharSequence word =
mConnection.getWordBeforeCursorIfAtEndOfWord(mSettings.getCurrent());
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
// a separator. In that case, the current log unit should not be uncommitted.
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
ResearchLogger.getInstance().uncommitCurrentLogUnit(word.toString(),
ResearchLogger.getInstance().uncommitCurrentLogUnit(wordString,
true /* dumpCurrentLogUnit */);
}
}
}
private void restartSuggestionsOnWordBeforeCursor(final CharSequence word) {
private void restartSuggestionsOnWordBeforeCursor(final String word) {
mWordComposer.setComposingWord(word, mKeyboardSwitcher.getKeyboard());
final int length = word.length();
mConnection.deleteSurroundingText(length, 0);

View File

@ -17,7 +17,9 @@
package com.android.inputmethod.latin;
import android.inputmethodservice.InputMethodService;
import android.text.SpannableString;
import android.text.TextUtils;
import android.text.style.SuggestionSpan;
import android.util.Log;
import android.view.KeyEvent;
import android.view.inputmethod.CompletionInfo;
@ -442,9 +444,9 @@ public final class RichInputConnection {
public final int mCharsAfter;
/** 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) {
throw new IndexOutOfBoundsException();
}
@ -498,7 +500,7 @@ public final class RichInputConnection {
* separator. For example, if the field contains "he|llo world", where |
* 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
Range r = getWordRangeAtCursor(separators, 0);
return (r == null) ? null : r.mWord;
@ -517,8 +519,10 @@ public final class RichInputConnection {
if (mIC == null || sep == null) {
return null;
}
final CharSequence before = mIC.getTextBeforeCursor(1000, 0);
final CharSequence after = mIC.getTextAfterCursor(1000, 0);
final CharSequence before = mIC.getTextBeforeCursor(1000,
InputConnection.GET_TEXT_WITH_STYLES);
final CharSequence after = mIC.getTextAfterCursor(1000,
InputConnection.GET_TEXT_WITH_STYLES);
if (before == null || after == null) {
return null;
}
@ -560,8 +564,9 @@ public final class RichInputConnection {
}
}
final String word = before.toString().substring(startIndexInBefore, before.length())
+ after.toString().substring(0, endIndexInAfter);
final SpannableString word = new SpannableString(TextUtils.concat(
before.subSequence(startIndexInBefore, before.length()),
after.subSequence(0, endIndexInAfter)));
return new Range(before.length() - startIndexInBefore, endIndexInAfter, word);
}

View File

@ -134,6 +134,10 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
return mSettingsValues.mIsInternal;
}
public String getWordSeparators() {
return mSettingsValues.mWordSeparators;
}
// Accessed from the settings interface, hence public
public static boolean readKeypressSoundEnabled(final SharedPreferences prefs,
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_SHORTCUT = 7; // A shortcut
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 int mScore;
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 mTrailingSingleQuotesCount;
private int mCodePointSize;
private int mCursorPositionWithinWord;
/**
* Whether the user chose to capitalize the first char of the word.
@ -62,6 +63,7 @@ public final class WordComposer {
mTrailingSingleQuotesCount = 0;
mIsResumed = false;
mIsBatchMode = false;
mCursorPositionWithinWord = 0;
refreshSize();
}
@ -76,6 +78,7 @@ public final class WordComposer {
mTrailingSingleQuotesCount = source.mTrailingSingleQuotesCount;
mIsResumed = source.mIsResumed;
mIsBatchMode = source.mIsBatchMode;
mCursorPositionWithinWord = source.mCursorPositionWithinWord;
refreshSize();
}
@ -91,6 +94,7 @@ public final class WordComposer {
mTrailingSingleQuotesCount = 0;
mIsResumed = false;
mIsBatchMode = false;
mCursorPositionWithinWord = 0;
refreshSize();
}
@ -135,6 +139,7 @@ public final class WordComposer {
final int newIndex = size();
mTypedWord.appendCodePoint(primaryCode);
refreshSize();
mCursorPositionWithinWord = mCodePointSize;
if (newIndex < MAX_WORD_LENGTH) {
mPrimaryKeyCodes[newIndex] = primaryCode >= Constants.CODE_SPACE
? Character.toLowerCase(primaryCode) : primaryCode;
@ -158,6 +163,14 @@ public final class WordComposer {
mAutoCorrection = null;
}
public void setCursorPositionWithinWord(final int posWithinWord) {
mCursorPositionWithinWord = posWithinWord;
}
public boolean isCursorAtEndOfComposingWord() {
return mCursorPositionWithinWord == mCodePointSize;
}
public void setBatchInputPointers(final InputPointers batchPointers) {
mInputPointers.set(batchPointers);
mIsBatchMode = true;
@ -242,6 +255,7 @@ public final class WordComposer {
++mTrailingSingleQuotesCount;
}
}
mCursorPositionWithinWord = mCodePointSize;
mAutoCorrection = null;
}
@ -368,6 +382,7 @@ public final class WordComposer {
mCapitalizedMode = CAPS_MODE_OFF;
refreshSize();
mAutoCorrection = null;
mCursorPositionWithinWord = 0;
mIsResumed = false;
return lastComposedWord;
}
@ -380,6 +395,7 @@ public final class WordComposer {
refreshSize();
mCapitalizedMode = lastComposedWord.mCapitalizedMode;
mAutoCorrection = null; // This will be filled by the next call to updateSuggestion.
mCursorPositionWithinWord = mCodePointSize;
mIsResumed = true;
}

View File

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