Fixes for correction mode - reliably show the corrections when tapping on a word.

Also, continue to show the corrections when user keeps replacing the word repeatedly
with different corrections from the suggestion strip. There were problems with
tapping suggestions quickly or tapping the same suggestion more than once (same length).

Also fixes Bug: 2852891 - Text suggestion appears incorrectly when selecting text that's
not a whole word.

Changed the TextEntryState states to an enum type instead of int. Needed it to show the
states for debugging purposes.
This commit is contained in:
Amith Yamasani 2010-08-13 13:29:49 -07:00
parent c9a181b1b3
commit c20c5a8d4c
3 changed files with 142 additions and 79 deletions

View File

@ -16,12 +16,12 @@
package com.android.inputmethod.latin; package com.android.inputmethod.latin;
import java.util.regex.Pattern;
import android.view.inputmethod.ExtractedText; import android.view.inputmethod.ExtractedText;
import android.view.inputmethod.ExtractedTextRequest; import android.view.inputmethod.ExtractedTextRequest;
import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputConnection;
import java.util.regex.Pattern;
/** /**
* Utility methods to deal with editing text through an InputConnection. * Utility methods to deal with editing text through an InputConnection.
*/ */
@ -203,4 +203,27 @@ public class EditingUtil {
return null; return null;
} }
} }
/**
* Checks if the cursor is touching/inside a word or the selection is for a whole
* word and no more and no less.
* @param range the Range object that contains the bounds of the word around the cursor
* @param start the start of the selection
* @param end the end of the selection, which could be the same as the start, if text is not
* in selection mode
* @return false if the selection is a partial word or straddling multiple words, true if
* the selection is a full word or there is no selection.
*/
public static boolean isFullWordOrInside(Range range, int start, int end) {
// Is the cursor inside or touching a word?
if (start == end) return true;
// Is it a selection? Then is the start of the selection the start of the word and
// the size of the selection the size of the word? Then return true
if (start < end
&& (range.charsBefore == 0 && range.charsAfter == end - start)) {
return true;
}
return false;
}
} }

View File

@ -752,10 +752,10 @@ public class LatinIME extends InputMethodService
mVoiceInputHighlighted = false; mVoiceInputHighlighted = false;
} else if (!mPredicting && !mJustAccepted) { } else if (!mPredicting && !mJustAccepted) {
switch (TextEntryState.getState()) { switch (TextEntryState.getState()) {
case TextEntryState.STATE_ACCEPTED_DEFAULT: case ACCEPTED_DEFAULT:
TextEntryState.reset(); TextEntryState.reset();
// fall through // fall through
case TextEntryState.STATE_SPACE_AFTER_PICKED: case SPACE_AFTER_PICKED:
mJustAddedAutoSpace = false; // The user moved the cursor. mJustAddedAutoSpace = false; // The user moved the cursor.
break; break;
} }
@ -768,10 +768,10 @@ public class LatinIME extends InputMethodService
mLastSelectionEnd = newSelEnd; mLastSelectionEnd = newSelEnd;
// TODO: Uncomment this block when we enable re-editing feature // Check if we should go in or out of correction mode.
// If a word is selected
if (isPredictionOn() && mJustRevertedSeparator == null if (isPredictionOn() && mJustRevertedSeparator == null
&& (candidatesStart == candidatesEnd || newSelStart != oldSelStart) && (candidatesStart == candidatesEnd || newSelStart != oldSelStart
|| TextEntryState.isCorrecting())
&& (newSelStart < newSelEnd - 1 || (!mPredicting)) && (newSelStart < newSelEnd - 1 || (!mPredicting))
&& !mVoiceInputHighlighted) { && !mVoiceInputHighlighted) {
if (isCursorTouchingWord() || mLastSelectionStart < mLastSelectionEnd) { if (isCursorTouchingWord() || mLastSelectionStart < mLastSelectionEnd) {
@ -1207,7 +1207,7 @@ public class LatinIME extends InputMethodService
} }
postUpdateShiftKeyState(); postUpdateShiftKeyState();
TextEntryState.backspace(); TextEntryState.backspace();
if (TextEntryState.getState() == TextEntryState.STATE_UNDO_COMMIT) { if (TextEntryState.getState() == TextEntryState.State.UNDO_COMMIT) {
revertLastWord(deleteChar); revertLastWord(deleteChar);
ic.endBatchEdit(); ic.endBatchEdit();
return; return;
@ -1358,13 +1358,13 @@ public class LatinIME extends InputMethodService
// Handle the case of ". ." -> " .." with auto-space if necessary // Handle the case of ". ." -> " .." with auto-space if necessary
// before changing the TextEntryState. // before changing the TextEntryState.
if (TextEntryState.getState() == TextEntryState.STATE_PUNCTUATION_AFTER_ACCEPTED if (TextEntryState.getState() == TextEntryState.State.PUNCTUATION_AFTER_ACCEPTED
&& primaryCode == KEYCODE_PERIOD) { && primaryCode == KEYCODE_PERIOD) {
reswapPeriodAndSpace(); reswapPeriodAndSpace();
} }
TextEntryState.typedCharacter((char) primaryCode, true); TextEntryState.typedCharacter((char) primaryCode, true);
if (TextEntryState.getState() == TextEntryState.STATE_PUNCTUATION_AFTER_ACCEPTED if (TextEntryState.getState() == TextEntryState.State.PUNCTUATION_AFTER_ACCEPTED
&& primaryCode != KEYCODE_ENTER) { && primaryCode != KEYCODE_ENTER) {
swapPunctuationAndSpace(); swapPunctuationAndSpace();
} else if (isPredictionOn() && primaryCode == KEYCODE_SPACE) { } else if (isPredictionOn() && primaryCode == KEYCODE_SPACE) {
@ -1790,8 +1790,16 @@ public class LatinIME extends InputMethodService
mJustAddedAutoSpace = true; mJustAddedAutoSpace = true;
} }
// Fool the state watcher so that a subsequent backspace will not do a revert // Fool the state watcher so that a subsequent backspace will not do a revert, unless
TextEntryState.typedCharacter((char) KEYCODE_SPACE, true); // we just did a correction, in which case we need to stay in
// TextEntryState.State.PICKED_SUGGESTION state.
if (!correcting) {
TextEntryState.typedCharacter((char) KEYCODE_SPACE, true);
setNextSuggestions();
} else {
// In case the cursor position doesn't change, make sure we show the suggestions again.
postUpdateOldSuggestions();
}
if (index == 0 && mCorrectionMode > 0 && !mSuggest.isValidWord(suggestion) if (index == 0 && mCorrectionMode > 0 && !mSuggest.isValidWord(suggestion)
&& !mSuggest.isValidWord(suggestion.toString().toLowerCase())) { && !mSuggest.isValidWord(suggestion.toString().toLowerCase())) {
mCandidateView.showAddToDictionaryHint(suggestion); mCandidateView.showAddToDictionaryHint(suggestion);
@ -1820,7 +1828,6 @@ public class LatinIME extends InputMethodService
mWordToSuggestions.put(suggestion.toString(), suggestions); mWordToSuggestions.put(suggestion.toString(), suggestions);
} }
} }
// TODO: implement rememberReplacedWord for typed words
} }
/** /**
@ -1860,7 +1867,10 @@ public class LatinIME extends InputMethodService
mPredicting = false; mPredicting = false;
mCommittedLength = suggestion.length(); mCommittedLength = suggestion.length();
((LatinKeyboard) inputView.getKeyboard()).setPreferredLetters(null); ((LatinKeyboard) inputView.getKeyboard()).setPreferredLetters(null);
setNextSuggestions(); // If we just corrected a word, then don't show punctuations
if (!correcting) {
setNextSuggestions();
}
updateShiftKeyState(getCurrentInputEditorInfo()); updateShiftKeyState(getCurrentInputEditorInfo());
} }
@ -1880,13 +1890,16 @@ public class LatinIME extends InputMethodService
EditingUtil.Range range = new EditingUtil.Range(); EditingUtil.Range range = new EditingUtil.Range();
CharSequence touching = EditingUtil.getWordAtCursor(getCurrentInputConnection(), CharSequence touching = EditingUtil.getWordAtCursor(getCurrentInputConnection(),
mWordSeparators, range); mWordSeparators, range);
if (touching != null && touching.length() > 1) { // If it's a selection, check if it's an entire word and no more, no less.
boolean fullword = EditingUtil.isFullWordOrInside(range, mLastSelectionStart,
mLastSelectionEnd);
if (fullword && touching != null && touching.length() > 1) {
// Strip out any trailing word separator
if (mWordSeparators.indexOf(touching.charAt(touching.length() - 1)) > 0) { if (mWordSeparators.indexOf(touching.charAt(touching.length() - 1)) > 0) {
touching = touching.toString().substring(0, touching.length() - 1); touching = touching.toString().substring(0, touching.length() - 1);
} }
// Search for result in spoken word alternatives // Search for result in spoken word alternatives
// TODO: possibly combine the spoken suggestions with the typed suggestions.
String selectedWord = touching.toString().trim(); String selectedWord = touching.toString().trim();
if (!mWordToSuggestions.containsKey(selectedWord)){ if (!mWordToSuggestions.containsKey(selectedWord)){
selectedWord = selectedWord.toLowerCase(); selectedWord = selectedWord.toLowerCase();
@ -1911,7 +1924,8 @@ public class LatinIME extends InputMethodService
ic.endBatchEdit(); ic.endBatchEdit();
return; return;
} }
// If we didn't find a match, search for result in word history
// If we didn't find a match, search for result in typed word history
WordComposer foundWord = null; WordComposer foundWord = null;
WordAlternatives alternatives = null; WordAlternatives alternatives = null;
for (WordAlternatives entry : mWordHistory) { for (WordAlternatives entry : mWordHistory) {

View File

@ -17,17 +17,20 @@
package com.android.inputmethod.latin; package com.android.inputmethod.latin;
import android.content.Context; import android.content.Context;
import android.inputmethodservice.Keyboard.Key;
import android.text.format.DateFormat; import android.text.format.DateFormat;
import android.util.Log; import android.util.Log;
import android.inputmethodservice.Keyboard.Key;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.util.Calendar; import java.util.Calendar;
public class TextEntryState { public class TextEntryState {
private static final boolean DBG = false;
private static final String TAG = "TextEntryState";
private static boolean LOGGING = false; private static boolean LOGGING = false;
private static int sBackspaceCount = 0; private static int sBackspaceCount = 0;
@ -46,20 +49,22 @@ public class TextEntryState {
private static int sActualChars; private static int sActualChars;
public static final int STATE_UNKNOWN = 0; public enum State {
public static final int STATE_START = 1; UNKNOWN,
public static final int STATE_IN_WORD = 2; START,
public static final int STATE_ACCEPTED_DEFAULT = 3; IN_WORD,
public static final int STATE_PICKED_SUGGESTION = 4; ACCEPTED_DEFAULT,
public static final int STATE_PUNCTUATION_AFTER_WORD = 5; PICKED_SUGGESTION,
public static final int STATE_PUNCTUATION_AFTER_ACCEPTED = 6; PUNCTUATION_AFTER_WORD,
public static final int STATE_SPACE_AFTER_ACCEPTED = 7; PUNCTUATION_AFTER_ACCEPTED,
public static final int STATE_SPACE_AFTER_PICKED = 8; SPACE_AFTER_ACCEPTED,
public static final int STATE_UNDO_COMMIT = 9; SPACE_AFTER_PICKED,
public static final int STATE_CORRECTING = 10; UNDO_COMMIT,
public static final int STATE_PICKED_CORRECTION = 11; CORRECTING,
PICKED_CORRECTION;
}
private static int sState = STATE_UNKNOWN; private static State sState = State.UNKNOWN;
private static FileOutputStream sKeyLocationFile; private static FileOutputStream sKeyLocationFile;
private static FileOutputStream sUserActionFile; private static FileOutputStream sUserActionFile;
@ -73,7 +78,7 @@ public class TextEntryState {
sWordNotInDictionaryCount = 0; sWordNotInDictionaryCount = 0;
sTypedChars = 0; sTypedChars = 0;
sActualChars = 0; sActualChars = 0;
sState = STATE_START; sState = State.START;
if (LOGGING) { if (LOGGING) {
try { try {
@ -118,118 +123,133 @@ public class TextEntryState {
} }
sTypedChars += typedWord.length(); sTypedChars += typedWord.length();
sActualChars += actualWord.length(); sActualChars += actualWord.length();
sState = STATE_ACCEPTED_DEFAULT; sState = State.ACCEPTED_DEFAULT;
LatinImeLogger.logOnAutoSuggestion(typedWord.toString(), actualWord.toString()); LatinImeLogger.logOnAutoSuggestion(typedWord.toString(), actualWord.toString());
displayState();
} }
// STATE_ACCEPTED_DEFAULT will be changed to other sub-states // State.ACCEPTED_DEFAULT will be changed to other sub-states
// (see "case STATE_ACCEPTED_DEFAULT" in typedCharacter() below), // (see "case ACCEPTED_DEFAULT" in typedCharacter() below),
// and should be restored back to STATE_ACCEPTED_DEFAULT after processing for each sub-state. // and should be restored back to State.ACCEPTED_DEFAULT after processing for each sub-state.
public static void backToAcceptedDefault(CharSequence typedWord) { public static void backToAcceptedDefault(CharSequence typedWord) {
if (typedWord == null) return; if (typedWord == null) return;
switch (sState) { switch (sState) {
case STATE_SPACE_AFTER_ACCEPTED: case SPACE_AFTER_ACCEPTED:
case STATE_PUNCTUATION_AFTER_ACCEPTED: case PUNCTUATION_AFTER_ACCEPTED:
case STATE_IN_WORD: case IN_WORD:
sState = STATE_ACCEPTED_DEFAULT; sState = State.ACCEPTED_DEFAULT;
break; break;
} }
displayState();
} }
public static void acceptedTyped(CharSequence typedWord) { public static void acceptedTyped(CharSequence typedWord) {
sWordNotInDictionaryCount++; sWordNotInDictionaryCount++;
sState = STATE_PICKED_SUGGESTION; sState = State.PICKED_SUGGESTION;
displayState();
} }
public static void acceptedSuggestion(CharSequence typedWord, CharSequence actualWord) { public static void acceptedSuggestion(CharSequence typedWord, CharSequence actualWord) {
sManualSuggestCount++; sManualSuggestCount++;
int oldState = sState; State oldState = sState;
if (typedWord.equals(actualWord)) { if (typedWord.equals(actualWord)) {
acceptedTyped(typedWord); acceptedTyped(typedWord);
} }
sState = oldState == STATE_CORRECTING ? STATE_PICKED_CORRECTION : STATE_PICKED_SUGGESTION; if (oldState == State.CORRECTING || oldState == State.PICKED_CORRECTION) {
sState = State.PICKED_CORRECTION;
} else {
sState = State.PICKED_SUGGESTION;
}
displayState();
} }
public static void selectedForCorrection() { public static void selectedForCorrection() {
sState = STATE_CORRECTING; sState = State.CORRECTING;
displayState();
} }
public static void typedCharacter(char c, boolean isSeparator) { public static void typedCharacter(char c, boolean isSeparator) {
boolean isSpace = c == ' '; boolean isSpace = c == ' ';
switch (sState) { switch (sState) {
case STATE_IN_WORD: case IN_WORD:
if (isSpace || isSeparator) { if (isSpace || isSeparator) {
sState = STATE_START; sState = State.START;
} else { } else {
// State hasn't changed. // State hasn't changed.
} }
break; break;
case STATE_ACCEPTED_DEFAULT: case ACCEPTED_DEFAULT:
case STATE_SPACE_AFTER_PICKED: case SPACE_AFTER_PICKED:
if (isSpace) { if (isSpace) {
sState = STATE_SPACE_AFTER_ACCEPTED; sState = State.SPACE_AFTER_ACCEPTED;
} else if (isSeparator) { } else if (isSeparator) {
sState = STATE_PUNCTUATION_AFTER_ACCEPTED; sState = State.PUNCTUATION_AFTER_ACCEPTED;
} else { } else {
sState = STATE_IN_WORD; sState = State.IN_WORD;
} }
break; break;
case STATE_PICKED_SUGGESTION: case PICKED_SUGGESTION:
case STATE_PICKED_CORRECTION: case PICKED_CORRECTION:
if (isSpace) { if (isSpace) {
sState = STATE_SPACE_AFTER_PICKED; sState = State.SPACE_AFTER_PICKED;
} else if (isSeparator) { } else if (isSeparator) {
// Swap // Swap
sState = STATE_PUNCTUATION_AFTER_ACCEPTED; sState = State.PUNCTUATION_AFTER_ACCEPTED;
} else { } else {
sState = STATE_IN_WORD; sState = State.IN_WORD;
} }
break; break;
case STATE_START: case START:
case STATE_UNKNOWN: case UNKNOWN:
case STATE_SPACE_AFTER_ACCEPTED: case SPACE_AFTER_ACCEPTED:
case STATE_PUNCTUATION_AFTER_ACCEPTED: case PUNCTUATION_AFTER_ACCEPTED:
case STATE_PUNCTUATION_AFTER_WORD: case PUNCTUATION_AFTER_WORD:
if (!isSpace && !isSeparator) { if (!isSpace && !isSeparator) {
sState = STATE_IN_WORD; sState = State.IN_WORD;
} else { } else {
sState = STATE_START; sState = State.START;
} }
break; break;
case STATE_UNDO_COMMIT: case UNDO_COMMIT:
if (isSpace || isSeparator) { if (isSpace || isSeparator) {
sState = STATE_ACCEPTED_DEFAULT; sState = State.ACCEPTED_DEFAULT;
} else { } else {
sState = STATE_IN_WORD; sState = State.IN_WORD;
} }
break; break;
case STATE_CORRECTING: case CORRECTING:
sState = STATE_START; sState = State.START;
break; break;
} }
displayState();
} }
public static void backspace() { public static void backspace() {
if (sState == STATE_ACCEPTED_DEFAULT) { if (sState == State.ACCEPTED_DEFAULT) {
sState = STATE_UNDO_COMMIT; sState = State.UNDO_COMMIT;
sAutoSuggestUndoneCount++; sAutoSuggestUndoneCount++;
LatinImeLogger.logOnAutoSuggestionCanceled(); LatinImeLogger.logOnAutoSuggestionCanceled();
} else if (sState == STATE_UNDO_COMMIT) { } else if (sState == State.UNDO_COMMIT) {
sState = STATE_IN_WORD; sState = State.IN_WORD;
} }
sBackspaceCount++; sBackspaceCount++;
displayState();
} }
public static void reset() { public static void reset() {
sState = STATE_START; sState = State.START;
displayState();
} }
public static int getState() { public static State getState() {
if (DBG) {
Log.d(TAG, "Returning state = " + sState);
}
return sState; return sState;
} }
public static boolean isCorrecting() { public static boolean isCorrecting() {
return sState == STATE_CORRECTING || sState == STATE_PICKED_CORRECTION; return sState == State.CORRECTING || sState == State.PICKED_CORRECTION;
} }
public static void keyPressedAt(Key key, int x, int y) { public static void keyPressedAt(Key key, int x, int y) {
@ -248,5 +268,11 @@ public class TextEntryState {
} }
} }
} }
private static void displayState() {
if (DBG) {
Log.d(TAG, "State = " + sState);
}
}
} }