Merge "Straighten out logic for revert word paths"
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 610a383..4d60976 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -209,6 +209,7 @@
 
     private int mCorrectionMode;
     private int mCommittedLength;
+    private String mWordSavedForAutoCorrectCancellation;
     // Keep track of the last selection range to decide if we need to show word alternatives
     private int mLastSelectionStart;
     private int mLastSelectionEnd;
@@ -1431,11 +1432,15 @@
 
         // TODO: Merge space state with TextEntryState
         TextEntryState.backspace();
-        if (TextEntryState.isUndoCommit()) {
-            revertLastWord(ic);
+        if (null != mWordSavedForAutoCorrectCancellation) {
+            cancelAutoCorrect(ic);
+            mWordSavedForAutoCorrectCancellation = null;
             ic.endBatchEdit();
             return;
+        } else {
+            mWordSavedForAutoCorrectCancellation = null;
         }
+
         if (SPACE_STATE_DOUBLE == spaceState) {
             if (revertDoubleSpace(ic)) {
                 ic.endBatchEdit();
@@ -1452,6 +1457,9 @@
         }
 
         if (mEnteredText != null && sameAsTextBeforeCursor(ic, mEnteredText)) {
+            // Cancel multi-character input: remove the text we just entered.
+            // This is triggered on backspace after a key that inputs multiple characters,
+            // like the smiley key or the .com key.
             ic.deleteSurroundingText(mEnteredText.length(), 0);
         } else if (deleteChar) {
             if (mSuggestionsView != null && mSuggestionsView.dismissAddToDictionaryHint()) {
@@ -1462,7 +1470,7 @@
                 // different behavior only in the case of picking the first
                 // suggestion (typed word).  It's intentional to have made this
                 // inconsistent with backspacing after selecting other suggestions.
-                revertLastWord(ic);
+                restartSuggestionsOnManuallyPickedTypedWord(ic);
             } else {
                 ic.deleteSurroundingText(1, 0);
                 if (mDeleteCount > DELETE_ACCELERATE_AT) {
@@ -1601,6 +1609,8 @@
             } else {
                 commitTyped(ic);
             }
+        } else {
+            mWordSavedForAutoCorrectCancellation = null;
         }
 
         final boolean swapMagicSpace;
@@ -1863,6 +1873,9 @@
             TextEntryState.acceptedDefault(mWordComposer.getTypedWord(), mBestWord, separatorCode);
             mExpectingUpdateSelection = true;
             commitBestWord(mBestWord);
+            if (!mBestWord.equals(mWordComposer.getTypedWord())) {
+                mWordSavedForAutoCorrectCancellation = mBestWord.toString();
+            }
             // Add the word to the user unigram dictionary if it's not a known word
             addToUserUnigramAndBigramDictionaries(mBestWord,
                     UserUnigramDictionary.FREQUENCY_FOR_TYPED);
@@ -2160,37 +2173,61 @@
     }
 
     // "ic" must not be null
-    private void revertLastWord(final InputConnection ic) {
-        if (mHasUncommittedTypedChars || mWordComposer.size() <= 0) {
-            sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL);
-            return;
-        }
-
+    private void cancelAutoCorrect(final InputConnection ic) {
+        final int cancelLength = mWordSavedForAutoCorrectCancellation.length();
         final CharSequence separator = ic.getTextBeforeCursor(1, 0);
-        ic.deleteSurroundingText(1, 0);
-        final CharSequence textToTheLeft = ic.getTextBeforeCursor(mCommittedLength, 0);
-        ic.deleteSurroundingText(mCommittedLength, 0);
-
-        // Re-insert "separator" only when the deleted character was word separator and the
-        // composing text wasn't equal to the auto-corrected text which can be found before
-        // the cursor.
-        if (!TextUtils.isEmpty(separator)
-                && mSettingsValues.isWordSeparator(separator.charAt(0))
-                && !TextUtils.equals(mWordComposer.getTypedWord(), textToTheLeft)) {
-            ic.commitText(mWordComposer.getTypedWord(), 1);
-            TextEntryState.acceptedTyped(mWordComposer.getTypedWord());
-            ic.commitText(separator, 1);
-            TextEntryState.typedCharacter(separator.charAt(0), true,
-                    WordComposer.NOT_A_COORDINATE, WordComposer.NOT_A_COORDINATE);
-        } else {
-            // Note: this relies on the last word still being held in the WordComposer
-            // Note: in the interest of code simplicity, we may want to just call
-            // restartSuggestionsOnWordBeforeCursorIfAtEndOfWord instead, but retrieving
-            // the old WordComposer allows to reuse the actual typed coordinates.
-            mHasUncommittedTypedChars = true;
-            ic.setComposingText(mWordComposer.getTypedWord(), 1);
-            TextEntryState.backspace();
+        if (DEBUG) {
+            final String wordBeforeCursor =
+                    ic.getTextBeforeCursor(cancelLength + 1, 0).subSequence(0, cancelLength)
+                    .toString();
+            if (!mWordSavedForAutoCorrectCancellation.equals(wordBeforeCursor)) {
+                throw new RuntimeException("cancelAutoCorrect check failed: we thought we were "
+                        + "reverting \"" + mWordSavedForAutoCorrectCancellation
+                        + "\", but before the cursor we found \"" + wordBeforeCursor + "\"");
+            }
+            if (mWordComposer.getTypedWord().equals(wordBeforeCursor)) {
+                throw new RuntimeException("cancelAutoCorrect check failed: we wanted to cancel "
+                        + "auto correction and revert to \"" + mWordComposer.getTypedWord()
+                        + "\" but we found this very string before the cursor");
+            }
         }
+        ic.deleteSurroundingText(cancelLength + 1, 0);
+
+        // Re-insert the separator
+        ic.commitText(mWordComposer.getTypedWord(), 1);
+        TextEntryState.acceptedTyped(mWordComposer.getTypedWord());
+        ic.commitText(separator, 1);
+        TextEntryState.typedCharacter(separator.charAt(0), true,
+                WordComposer.NOT_A_COORDINATE, WordComposer.NOT_A_COORDINATE);
+        mHandler.cancelUpdateBigramPredictions();
+        mHandler.postUpdateSuggestions();
+    }
+
+    // "ic" must not be null
+    private void restartSuggestionsOnManuallyPickedTypedWord(final InputConnection ic) {
+        final CharSequence separator = ic.getTextBeforeCursor(1, 0);
+        final int restartLength = mCommittedLength;
+        if (DEBUG) {
+            final String wordBeforeCursor =
+                ic.getTextBeforeCursor(restartLength + 1, 0).subSequence(0, restartLength)
+                .toString();
+            if (!mWordComposer.getTypedWord().equals(wordBeforeCursor)) {
+                throw new RuntimeException("restartSuggestionsOnManuallyPickedTypedWord "
+                        + "check failed: we thought we were reverting \""
+                        + mWordComposer.getTypedWord()
+                        + "\", but before the cursor we found \""
+                        + wordBeforeCursor + "\"");
+            }
+        }
+        ic.deleteSurroundingText(restartLength + 1, 0);
+
+        // Note: this relies on the last word still being held in the WordComposer
+        // Note: in the interest of code simplicity, we may want to just call
+        // restartSuggestionsOnWordBeforeCursorIfAtEndOfWord instead, but retrieving
+        // the old WordComposer allows to reuse the actual typed coordinates.
+        mHasUncommittedTypedChars = true;
+        ic.setComposingText(mWordComposer.getTypedWord(), 1);
+        TextEntryState.backspace();
         mHandler.cancelUpdateBigramPredictions();
         mHandler.postUpdateSuggestions();
     }