From a05a0f20776b4c33f41f043f1bff245331937580 Mon Sep 17 00:00:00 2001
From: Jean Chalard <jchalard@google.com>
Date: Mon, 27 Aug 2012 20:09:46 +0900
Subject: [PATCH] Allow Latin IME to cancel smiley-auto-correct consistenly

This change makes Latin IME behave consistently with regards
to other auto-correction cancellations in cases of auto-correction
cancellation after smiley-triggered auto-correction. That is,
pressing the smiley key when the keyboard signals it's about to
auto-correct will get the auto-correction there plus a smiley,
and pressing backspace will cancel the auto-correction, and
pressing backspace again will delete the smiley.

Bug: 7067593
Change-Id: Ia7eef70a5d06b8b9afa1f1fbb0ed1dbc21a3059f
---
 .../inputmethod/latin/LastComposedWord.java   | 12 ++--
 .../android/inputmethod/latin/LatinIME.java   | 60 ++++++++++---------
 .../com/android/inputmethod/latin/Utils.java  | 29 ++++++---
 .../inputmethod/latin/WordComposer.java       |  4 +-
 4 files changed, 61 insertions(+), 44 deletions(-)

diff --git a/java/src/com/android/inputmethod/latin/LastComposedWord.java b/java/src/com/android/inputmethod/latin/LastComposedWord.java
index bb39ce4f71..dd73a978c7 100644
--- a/java/src/com/android/inputmethod/latin/LastComposedWord.java
+++ b/java/src/com/android/inputmethod/latin/LastComposedWord.java
@@ -38,12 +38,12 @@ public class LastComposedWord {
     // an auto-correction.
     public static final int COMMIT_TYPE_CANCEL_AUTO_CORRECT = 3;
 
-    public static final int NOT_A_SEPARATOR = -1;
+    public static final String NOT_A_SEPARATOR = "";
 
     public final int[] mPrimaryKeyCodes;
     public final String mTypedWord;
     public final String mCommittedWord;
-    public final int mSeparatorCode;
+    public final String mSeparatorString;
     public final CharSequence mPrevWord;
     public final InputPointers mInputPointers = new InputPointers(BinaryDictionary.MAX_WORD_LENGTH);
 
@@ -56,14 +56,14 @@ public class LastComposedWord {
     // immutable. Do not fiddle with their contents after you passed them to this constructor.
     public LastComposedWord(final int[] primaryKeyCodes, final InputPointers inputPointers,
             final String typedWord, final String committedWord,
-            final int separatorCode, final CharSequence prevWord) {
+            final String separatorString, final CharSequence prevWord) {
         mPrimaryKeyCodes = primaryKeyCodes;
         if (inputPointers != null) {
             mInputPointers.copy(inputPointers);
         }
         mTypedWord = typedWord;
         mCommittedWord = committedWord;
-        mSeparatorCode = separatorCode;
+        mSeparatorString = separatorString;
         mActive = true;
         mPrevWord = prevWord;
     }
@@ -80,7 +80,7 @@ public class LastComposedWord {
         return TextUtils.equals(mTypedWord, mCommittedWord);
     }
 
-    public static int getSeparatorLength(final int separatorCode) {
-        return NOT_A_SEPARATOR == separatorCode ? 0 : 1;
+    public static int getSeparatorLength(final String separatorString) {
+        return StringUtils.codePointCount(separatorString);
     }
 }
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 83a306818e..76f4957292 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -1055,7 +1055,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
             mLastComposedWord = LastComposedWord.NOT_A_COMPOSED_WORD;
     }
 
-    private void commitTyped(final int separatorCode) {
+    private void commitTyped(final String separatorString) {
         if (!mWordComposer.isComposingWord()) return;
         final CharSequence typedWord = mWordComposer.getTypedWord();
         if (typedWord.length() > 0) {
@@ -1063,7 +1063,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
             final CharSequence prevWord = addToUserHistoryDictionary(typedWord);
             mLastComposedWord = mWordComposer.commitWord(
                     LastComposedWord.COMMIT_TYPE_USER_TYPED_WORD, typedWord.toString(),
-                    separatorCode, prevWord);
+                    separatorString, prevWord);
         }
     }
 
@@ -1340,7 +1340,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
         if (!didAutoCorrect && primaryCode != Keyboard.CODE_SHIFT
                 && primaryCode != Keyboard.CODE_SWITCH_ALPHA_SYMBOL)
             mLastComposedWord.deactivate();
-        mEnteredText = null;
+        if (Keyboard.CODE_DELETE != primaryCode) {
+            mEnteredText = null;
+        }
         mConnection.endBatchEdit();
         if (ProductionFlag.IS_EXPERIMENTAL) {
             ResearchLogger.latinIME_onCodeInput(primaryCode, x, y);
@@ -1352,7 +1354,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
     public void onTextInput(CharSequence rawText) {
         mConnection.beginBatchEdit();
         if (mWordComposer.isComposingWord()) {
-            commitCurrentAutoCorrection(LastComposedWord.NOT_A_SEPARATOR);
+            commitCurrentAutoCorrection(rawText.toString());
+        } else {
+            resetComposingState(true /* alsoResetLastComposedWord */);
         }
         mHandler.postUpdateSuggestionStrip();
         final CharSequence text = specificTldProcessingOnTextInput(rawText);
@@ -1365,7 +1369,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
         mKeyboardSwitcher.onCodeInput(Keyboard.CODE_OUTPUT_TEXT);
         mSpaceState = SPACE_STATE_NONE;
         mEnteredText = text;
-        resetComposingState(true /* alsoResetLastComposedWord */);
     }
 
     @Override
@@ -1451,18 +1454,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
         // In many cases, we may have to put the keyboard in auto-shift state again.
         mHandler.postUpdateShiftState();
 
-        if (mEnteredText != null && mConnection.sameAsTextBeforeCursor(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.
-            final int length = mEnteredText.length();
-            mConnection.deleteSurroundingText(length, 0);
-            // If we have mEnteredText, then we know that mHasUncommittedTypedChars == false.
-            // In addition we know that spaceState is false, and that we should not be
-            // reverting any autocorrect at this point. So we can safely return.
-            return;
-        }
-
         if (mWordComposer.isComposingWord()) {
             final int length = mWordComposer.size();
             if (length > 0) {
@@ -1483,6 +1474,18 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
                 revertCommit();
                 return;
             }
+            if (mEnteredText != null && mConnection.sameAsTextBeforeCursor(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.
+                final int length = mEnteredText.length();
+                mConnection.deleteSurroundingText(length, 0);
+                mEnteredText = null;
+                // If we have mEnteredText, then we know that mHasUncommittedTypedChars == false.
+                // In addition we know that spaceState is false, and that we should not be
+                // reverting any autocorrect at this point. So we can safely return.
+                return;
+            }
             if (SPACE_STATE_DOUBLE == spaceState) {
                 mHandler.cancelDoubleSpacesTimer();
                 if (mConnection.revertDoubleSpace()) {
@@ -1626,10 +1629,11 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
         // Handle separator
         if (mWordComposer.isComposingWord()) {
             if (mCurrentSettings.mCorrectionEnabled) {
-                commitCurrentAutoCorrection(primaryCode);
+                // TODO: maybe cache Strings in an <String> sparse array or something
+                commitCurrentAutoCorrection(new String(new int[]{primaryCode}, 0, 1));
                 didAutoCorrect = true;
             } else {
-                commitTyped(primaryCode);
+                commitTyped(new String(new int[]{primaryCode}, 0, 1));
             }
         }
 
@@ -1834,7 +1838,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
         setSuggestionStripShown(isSuggestionsStripVisible());
     }
 
-    private void commitCurrentAutoCorrection(final int separatorCodePoint) {
+    private void commitCurrentAutoCorrection(final String separatorString) {
         // Complete any pending suggestions query first
         if (mHandler.hasPendingUpdateSuggestions()) {
             updateSuggestionStrip();
@@ -1848,10 +1852,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
                 throw new RuntimeException("We have an auto-correction but the typed word "
                         + "is empty? Impossible! I must commit suicide.");
             }
-            Utils.Stats.onAutoCorrection(typedWord, autoCorrection.toString(), separatorCodePoint);
+            Utils.Stats.onAutoCorrection(typedWord, autoCorrection.toString(), separatorString);
             mExpectingUpdateSelection = true;
             commitChosenWord(autoCorrection, LastComposedWord.COMMIT_TYPE_DECIDED_WORD,
-                    separatorCodePoint);
+                    separatorString);
             if (!typedWord.equals(autoCorrection)) {
                 // This will make the correction flash for a short while as a visual clue
                 // to the user that auto-correction happened.
@@ -1949,7 +1953,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
      * Commits the chosen word to the text field and saves it for later retrieval.
      */
     private void commitChosenWord(final CharSequence chosenWord, final int commitType,
-            final int separatorCode) {
+            final String separatorString) {
         final SuggestedWords suggestedWords = mSuggestionStripView.getSuggestions();
         mConnection.commitText(SuggestionSpanUtils.getTextWithSuggestionSpan(
                 this, chosenWord, suggestedWords, mIsMainDictionaryAvailable), 1);
@@ -1960,7 +1964,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
         // LastComposedWord#didCommitTypedWord by string equality of the remembered
         // strings.
         mLastComposedWord = mWordComposer.commitWord(commitType, chosenWord.toString(),
-                separatorCode, prevWord);
+                separatorString, prevWord);
     }
 
     private void setPunctuationSuggestions() {
@@ -2030,7 +2034,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
         final CharSequence committedWord = mLastComposedWord.mCommittedWord;
         final int cancelLength = committedWord.length();
         final int separatorLength = LastComposedWord.getSeparatorLength(
-                mLastComposedWord.mSeparatorCode);
+                mLastComposedWord.mSeparatorString);
         // TODO: should we check our saved separator against the actual contents of the text view?
         final int deleteLength = cancelLength + separatorLength;
         if (DEBUG) {
@@ -2051,10 +2055,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
             mUserHistoryDictionary.cancelAddingUserHistory(
                     previousWord.toString(), committedWord.toString());
         }
-        mConnection.commitText(originallyTypedWord, 1);
-        // Re-insert the separator
-        sendKeyCodePoint(mLastComposedWord.mSeparatorCode);
-        Utils.Stats.onSeparator(mLastComposedWord.mSeparatorCode,
+        mConnection.commitText(originallyTypedWord + mLastComposedWord.mSeparatorString, 1);
+        Utils.Stats.onSeparator(mLastComposedWord.mSeparatorString,
                 Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
         if (ProductionFlag.IS_EXPERIMENTAL) {
             ResearchLogger.latinIME_revertCommit(originallyTypedWord);
diff --git a/java/src/com/android/inputmethod/latin/Utils.java b/java/src/com/android/inputmethod/latin/Utils.java
index fc7a421001..912f895fd4 100644
--- a/java/src/com/android/inputmethod/latin/Utils.java
+++ b/java/src/com/android/inputmethod/latin/Utils.java
@@ -29,7 +29,6 @@ import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Process;
 import android.text.TextUtils;
-import android.text.format.DateUtils;
 import android.util.Log;
 
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
@@ -404,23 +403,39 @@ public class Utils {
     }
 
     public static class Stats {
+        static final int NOT_A_SEPARATOR_CODE_POINT = -1;
+
         public static void onNonSeparator(final char code, final int x,
                 final int y) {
             RingCharBuffer.getInstance().push(code, x, y);
             LatinImeLogger.logOnInputChar();
         }
 
-        public static void onSeparator(final int code, final int x,
-                final int y) {
-            // TODO: accept code points
-            RingCharBuffer.getInstance().push((char)code, x, y);
+        public static void onSeparator(final int code, final int x, final int y) {
+            // Helper method to log a single code point separator
+            // TODO: cache this mapping of a code point to a string in a sparse array in StringUtils
+            onSeparator(new String(new int[]{code}, 0, 1), x, y);
+        }
+
+        public static void onSeparator(final String separator, final int x, final int y) {
+            final int length = separator.length();
+            for (int i = 0; i < length; i = Character.offsetByCodePoints(separator, i, 1)) {
+                int codePoint = Character.codePointAt(separator, i);
+                // TODO: accept code points
+                RingCharBuffer.getInstance().push((char)codePoint, x, y);
+            }
             LatinImeLogger.logOnInputSeparator();
         }
 
         public static void onAutoCorrection(final String typedWord, final String correctedWord,
-                final int separatorCode) {
+                final String separatorString) {
             if (TextUtils.isEmpty(typedWord)) return;
-            LatinImeLogger.logOnAutoCorrection(typedWord, correctedWord, separatorCode);
+            // TODO: this fails when the separator is more than 1 code point long, but
+            // the backend can't handle it yet. The only case when this happens is with
+            // smileys and other multi-character keys.
+            final int codePoint = TextUtils.isEmpty(separatorString) ? NOT_A_SEPARATOR_CODE_POINT
+                    : separatorString.codePointAt(0);
+            LatinImeLogger.logOnAutoCorrection(typedWord, correctedWord, codePoint);
         }
 
         public static void onAutoCorrectionCancellation() {
diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java
index ecec60f89a..4b7adf26b0 100644
--- a/java/src/com/android/inputmethod/latin/WordComposer.java
+++ b/java/src/com/android/inputmethod/latin/WordComposer.java
@@ -336,14 +336,14 @@ public class WordComposer {
 
     // `type' should be one of the LastComposedWord.COMMIT_TYPE_* constants above.
     public LastComposedWord commitWord(final int type, final String committedWord,
-            final int separatorCode, final CharSequence prevWord) {
+            final String separatorString, final CharSequence prevWord) {
         // Note: currently, we come here whenever we commit a word. If it's a MANUAL_PICK
         // or a DECIDED_WORD we may cancel the commit later; otherwise, we should deactivate
         // the last composed word to ensure this does not happen.
         final int[] primaryKeyCodes = mPrimaryKeyCodes;
         mPrimaryKeyCodes = new int[N];
         final LastComposedWord lastComposedWord = new LastComposedWord(primaryKeyCodes,
-                mInputPointers, mTypedWord.toString(), committedWord, separatorCode,
+                mInputPointers, mTypedWord.toString(), committedWord, separatorString,
                 prevWord);
         mInputPointers.reset();
         if (type != LastComposedWord.COMMIT_TYPE_DECIDED_WORD
-- 
GitLab