Merge "Reorganize LatinIME native build directory structure.  Now it got NDK friendly."
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index a67e4c9..80f6c15 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -203,7 +203,6 @@
     private boolean mApplicationSpecifiedCompletionOn;
 
     private WordComposer mWordComposer = new WordComposer();
-    private CharSequence mBestWord;
     private boolean mHasUncommittedTypedChars;
 
     private int mCorrectionMode;
@@ -1019,7 +1018,7 @@
                     .setHasMinimalSuggestion(false);
             // When in fullscreen mode, show completions generated by the application
             setSuggestions(builder.build());
-            mBestWord = null;
+            mWordComposer.deleteAutoCorrection();
             setSuggestionStripShown(true);
         }
     }
@@ -1415,12 +1414,7 @@
             final int length = mWordComposer.size();
             if (length > 0) {
                 mWordComposer.deleteLast();
-                final CharSequence textWithUnderline =
-                        mComposingStateManager.isAutoCorrectionIndicatorOn()
-                                ? SuggestionSpanUtils.getTextWithAutoCorrectionIndicatorUnderline(
-                                            this, mWordComposer.getTypedWord())
-                                : mWordComposer.getTypedWord();
-                ic.setComposingText(textWithUnderline, 1);
+                ic.setComposingText(getTextWithUnderline(mWordComposer.getTypedWord()), 1);
                 if (mWordComposer.size() == 0) {
                     mHasUncommittedTypedChars = false;
                     // Remaining size equals zero means we just erased the last character of the
@@ -1552,12 +1546,7 @@
                     mWordComposer.setAutoCapitalized(getCurrentAutoCapsState());
                     mComposingStateManager.onStartComposingText();
                 }
-                final CharSequence textWithUnderline =
-                        mComposingStateManager.isAutoCorrectionIndicatorOn()
-                                ? SuggestionSpanUtils.getTextWithAutoCorrectionIndicatorUnderline(
-                                        this, mWordComposer.getTypedWord())
-                                : mWordComposer.getTypedWord();
-                ic.setComposingText(textWithUnderline, 1);
+                ic.setComposingText(getTextWithUnderline(mWordComposer.getTypedWord()), 1);
             }
             mHandler.postUpdateSuggestions();
         } else {
@@ -1659,10 +1648,18 @@
         Utils.Stats.onSeparator((char)primaryCode, x, y);
 
         if (pickedDefault) {
-            CharSequence typedWord = mWordComposer.getTypedWord();
-            if (!TextUtils.isEmpty(typedWord) && !typedWord.equals(mBestWord)) {
+            final CharSequence autoCorrection = mWordComposer.getAutoCorrectionOrNull();
+            final String typedWord = mWordComposer.getTypedWord();
+            if (TextUtils.isEmpty(typedWord)) {
+                throw new RuntimeException("We have non-committed chars but the typed word "
+                        + "is empty? Impossible! I must commit suicide.");
+            }
+            if (!typedWord.equals(autoCorrection)) {
+                // TODO: if the commitCorrection method is not supported by the platform
+                // this will do nothing and the correction will not be committed at all. What
+                // happens on Froyo/Gingerbread, where this API is not present?
                 InputConnectionCompatUtils.commitCorrection(
-                        ic, mLastSelectionEnd - typedWord.length(), typedWord, mBestWord);
+                        ic, mLastSelectionEnd - typedWord.length(), typedWord, autoCorrection);
             }
         }
         mKeyboardSwitcher.updateShiftState();
@@ -1671,6 +1668,12 @@
         }
     }
 
+    private CharSequence getTextWithUnderline(final CharSequence text) {
+        return mComposingStateManager.isAutoCorrectionIndicatorOn()
+                ? SuggestionSpanUtils.getTextWithAutoCorrectionIndicatorUnderline(this, text)
+                : mWordComposer.getTypedWord();
+    }
+
     private void handleClose() {
         commitTyped(getCurrentInputConnection());
         mVoiceProxy.handleClose();
@@ -1744,18 +1747,21 @@
                     mComposingStateManager.isAutoCorrectionIndicatorOn();
             final boolean newAutoCorrectionIndicator = Utils.willAutoCorrect(words);
             if (oldAutoCorrectionIndicator != newAutoCorrectionIndicator) {
-                if (LatinImeLogger.sDBG) {
+                mComposingStateManager.setAutoCorrectionIndicatorOn(newAutoCorrectionIndicator);
+                if (DEBUG) {
                     Log.d(TAG, "Flip the indicator. " + oldAutoCorrectionIndicator
                             + " -> " + newAutoCorrectionIndicator);
+                    if (newAutoCorrectionIndicator
+                            != mComposingStateManager.isAutoCorrectionIndicatorOn()) {
+                        throw new RuntimeException("Couldn't flip the indicator! We are not "
+                                + "composing a word right now.");
+                    }
                 }
-                final CharSequence textWithUnderline = newAutoCorrectionIndicator
-                        ? SuggestionSpanUtils.getTextWithAutoCorrectionIndicatorUnderline(
-                                this, mWordComposer.getTypedWord())
-                        : mWordComposer.getTypedWord();
+                final CharSequence textWithUnderline =
+                        getTextWithUnderline(mWordComposer.getTypedWord());
                 if (!TextUtils.isEmpty(textWithUnderline)) {
                     ic.setComposingText(textWithUnderline, 1);
                 }
-                mComposingStateManager.setAutoCorrectionIndicatorOn(newAutoCorrectionIndicator);
             }
         }
     }
@@ -1848,14 +1854,15 @@
         setSuggestions(suggestedWords);
         if (suggestedWords.size() > 0) {
             if (shouldBlockAutoCorrectionBySafetyNet) {
-                mBestWord = typedWord;
+                mWordComposer.setAutoCorrection(typedWord);
             } else if (suggestedWords.hasAutoCorrectionWord()) {
-                mBestWord = suggestedWords.getWord(1);
+                mWordComposer.setAutoCorrection(suggestedWords.getWord(1));
             } else {
-                mBestWord = typedWord;
+                mWordComposer.setAutoCorrection(typedWord);
             }
         } else {
-            mBestWord = null;
+            // TODO: replace with mWordComposer.deleteAutoCorrection()?
+            mWordComposer.setAutoCorrection(null);
         }
         setSuggestionStripShown(isSuggestionsStripVisible());
     }
@@ -1866,16 +1873,17 @@
             mHandler.cancelUpdateSuggestions();
             updateSuggestions();
         }
-        if (mBestWord != null && mBestWord.length() > 0) {
-            Utils.Stats.onAutoCorrection(mWordComposer.getTypedWord(), mBestWord.toString(),
-                    separatorCode);
+        final CharSequence autoCorrection = mWordComposer.getAutoCorrectionOrNull();
+        if (autoCorrection != null) {
+            final String typedWord = mWordComposer.getTypedWord();
+            Utils.Stats.onAutoCorrection(typedWord, autoCorrection.toString(), separatorCode);
             mExpectingUpdateSelection = true;
-            commitBestWord(mBestWord);
-            if (!mBestWord.equals(mWordComposer.getTypedWord())) {
-                mWordSavedForAutoCorrectCancellation = mBestWord.toString();
+            commitBestWord(autoCorrection);
+            if (!autoCorrection.equals(typedWord)) {
+                mWordSavedForAutoCorrectCancellation = autoCorrection.toString();
             }
             // Add the word to the user unigram dictionary if it's not a known word
-            addToUserUnigramAndBigramDictionaries(mBestWord,
+            addToUserUnigramAndBigramDictionaries(autoCorrection,
                     UserUnigramDictionary.FREQUENCY_FOR_TYPED);
             return true;
         }
@@ -2154,8 +2162,6 @@
     private void restartSuggestionsOnWordBeforeCursor(final InputConnection ic,
             final CharSequence word) {
         mWordComposer.setComposingWord(word, mKeyboardSwitcher.getLatinKeyboard());
-        // mBestWord will be set appropriately by updateSuggestions() called by the handler
-        mBestWord = null;
         mHasUncommittedTypedChars = true;
         mComposingStateManager.onStartComposingText();
         ic.deleteSurroundingText(word.length(), 0);
@@ -2226,8 +2232,11 @@
         // Here we test whether we indeed have a period and a space before us. This should not
         // be needed, but it's there just in case something went wrong.
         final CharSequence textBeforeCursor = ic.getTextBeforeCursor(2, 0);
-        if (!". ".equals(textBeforeCursor))
-            return false;
+        if (!". ".equals(textBeforeCursor)) {
+            // We should not have come here if we aren't just after a ". ".
+            throw new RuntimeException("Tried to revert double-space combo but we didn't find "
+                    + "\". \" just before the cursor.");
+        }
         ic.beginBatchEdit();
         ic.deleteSurroundingText(2, 0);
         ic.commitText("  ", 1);
@@ -2241,8 +2250,11 @@
         final CharSequence textBeforeCursor = ic.getTextBeforeCursor(2, 0);
         // NOTE: This does not work with surrogate pairs. Hopefully when the keyboard is able to
         // enter surrogate pairs this code will have been removed.
-        if (Keyboard.CODE_SPACE != textBeforeCursor.charAt(1))
-            return false;
+        if (Keyboard.CODE_SPACE != textBeforeCursor.charAt(1)) {
+            // We should not have come here if the text before the cursor is not a space.
+            throw new RuntimeException("Tried to revert a swap of punctiation but we didn't "
+                    + "find a space just before the cursor.");
+        }
         ic.beginBatchEdit();
         ic.deleteSurroundingText(2, 0);
         ic.commitText(" " + textBeforeCursor.subSequence(0, 1), 1);
diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java
index dfb00c8..fcaf81c 100644
--- a/java/src/com/android/inputmethod/latin/WordComposer.java
+++ b/java/src/com/android/inputmethod/latin/WordComposer.java
@@ -41,6 +41,8 @@
     private int[] mYCoordinates;
 
     private StringBuilder mTypedWord;
+    // An auto-correction for this word out of the dictionary.
+    private CharSequence mAutoCorrection;
 
     private int mCapsCount;
 
@@ -60,6 +62,7 @@
         mXCoordinates = new int[N];
         mYCoordinates = new int[N];
         mTrailingSingleQuotesCount = 0;
+        mAutoCorrection = null;
     }
 
     public WordComposer(WordComposer source) {
@@ -75,6 +78,7 @@
         mIsFirstCharCapitalized = source.mIsFirstCharCapitalized;
         mAutoCapitalized = source.mAutoCapitalized;
         mTrailingSingleQuotesCount = source.mTrailingSingleQuotesCount;
+        mAutoCorrection = null;
     }
 
     /**
@@ -86,6 +90,7 @@
         mCapsCount = 0;
         mIsFirstCharCapitalized = false;
         mTrailingSingleQuotesCount = 0;
+        mAutoCorrection = null;
     }
 
     /**
@@ -140,6 +145,7 @@
         } else {
             mTrailingSingleQuotesCount = 0;
         }
+        mAutoCorrection = null;
     }
 
     /**
@@ -173,6 +179,7 @@
             int codePoint = word.charAt(i);
             addKeyInfo(codePoint, keyboard, keyDetector);
         }
+        mAutoCorrection = null;
     }
 
     /**
@@ -224,11 +231,12 @@
                 ++mTrailingSingleQuotesCount;
             }
         }
+        mAutoCorrection = null;
     }
 
     /**
      * Returns the word as it was typed, without any correction applied.
-     * @return the word that was typed so far
+     * @return the word that was typed so far. Never returns null.
      */
     public String getTypedWord() {
         return mTypedWord.toString();
@@ -277,4 +285,25 @@
     public boolean isAutoCapitalized() {
         return mAutoCapitalized;
     }
+
+    /**
+     * Sets the auto-correction for this word.
+     */
+    public void setAutoCorrection(final CharSequence correction) {
+        mAutoCorrection = correction;
+    }
+
+    /**
+     * Remove any auto-correction that may have been set.
+     */
+    public void deleteAutoCorrection() {
+        mAutoCorrection = null;
+    }
+
+    /**
+     * @return the auto-correction for this world, or null if none.
+     */
+    public CharSequence getAutoCorrectionOrNull() {
+        return mAutoCorrection;
+    }
 }