Merge "Add a safety net for auto-correction." into honeycomb
diff --git a/java/src/com/android/inputmethod/latin/CandidateView.java b/java/src/com/android/inputmethod/latin/CandidateView.java
index 30f4a59..d2d1f22 100644
--- a/java/src/com/android/inputmethod/latin/CandidateView.java
+++ b/java/src/com/android/inputmethod/latin/CandidateView.java
@@ -45,23 +45,22 @@
 import java.util.ArrayList;
 
 public class CandidateView extends LinearLayout implements OnClickListener, OnLongClickListener {
-    private LatinIME mService;
-    private final ArrayList<View> mWords = new ArrayList<View>();
 
-    private final TextView mPreviewText;
-    private final PopupWindow mPreviewPopup;
-    
+    private static final CharacterStyle BOLD_SPAN = new StyleSpan(Typeface.BOLD);
+    private static final CharacterStyle UNDERLINE_SPAN = new UnderlineSpan();
     private static final int MAX_SUGGESTIONS = 16;
 
+    private final ArrayList<View> mWords = new ArrayList<View>();
     private final boolean mConfigCandidateHighlightFontColorEnabled;
+    private final CharacterStyle mInvertedForegroundColorSpan;
+    private final CharacterStyle mInvertedBackgroundColorSpan;
     private final int mColorNormal;
     private final int mColorRecommended;
     private final int mColorOther;
-    private static final CharacterStyle BOLD_SPAN = new StyleSpan(Typeface.BOLD);
-    private static final CharacterStyle UNDERLINE_SPAN = new UnderlineSpan();
-    private final CharacterStyle mInvertedForegroundColorSpan;
-    private final CharacterStyle mInvertedBackgroundColorSpan;
+    private final PopupWindow mPreviewPopup;
+    private final TextView mPreviewText;
 
+    private LatinIME mService;
     private SuggestedWords mSuggestions = SuggestedWords.EMPTY;
     private boolean mShowingAutoCorrectionInverted;
     private boolean mShowingAddToDictionary;
@@ -186,9 +185,10 @@
             final TextView tv = (TextView)v.findViewById(R.id.candidate_word);
             final TextView dv = (TextView)v.findViewById(R.id.candidate_debug_info);
             tv.setTextColor(mColorNormal);
+            // TODO: Needs safety net?
             if (suggestions.mHasMinimalSuggestion
-                    && ((i == 1 && !suggestions.mTypedWordValid) ||
-                            (i == 0 && suggestions.mTypedWordValid))) {
+                    && ((i == 1 && !suggestions.mTypedWordValid)
+                            || (i == 0 && suggestions.mTypedWordValid))) {
                 final CharacterStyle style;
                 if (mConfigCandidateHighlightFontColorEnabled) {
                     style = BOLD_SPAN;
@@ -329,7 +329,7 @@
             mService.pickSuggestionManually(index, word);
         }
     }
-    
+
     @Override
     public void onDetachedFromWindow() {
         super.onDetachedFromWindow();
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 5d48d6b..fff09fa 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -1537,7 +1537,9 @@
     private void showSuggestions(SuggestedWords suggestedWords, CharSequence typedWord) {
         setSuggestions(suggestedWords);
         if (suggestedWords.size() > 0) {
-            if (suggestedWords.hasAutoCorrectionWord()) {
+            if (Utils.shouldBlockedBySafetyNetForAutoCorrection(suggestedWords)) {
+                mBestWord = typedWord;
+            } else if (suggestedWords.hasAutoCorrectionWord()) {
                 mBestWord = suggestedWords.getWord(1);
             } else {
                 mBestWord = typedWord;
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index a8454b2..24c73e8 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -31,7 +31,7 @@
  */
 public class Suggest implements Dictionary.WordCallback {
 
-    public static final String TAG = "Suggest";
+    public static final String TAG = Suggest.class.getSimpleName();
 
     public static final int APPROX_MAX_WORD_LENGTH = 32;
 
@@ -64,6 +64,8 @@
 
     static final int LARGE_DICTIONARY_THRESHOLD = 200 * 1000;
 
+    private static boolean DBG = LatinImeLogger.sDBG;
+
     private BinaryDictionary mMainDict;
 
     private Dictionary mUserDictionary;
@@ -93,7 +95,7 @@
     private ArrayList<CharSequence> mSuggestions = new ArrayList<CharSequence>();
     ArrayList<CharSequence> mBigramSuggestions  = new ArrayList<CharSequence>();
     private ArrayList<CharSequence> mStringPool = new ArrayList<CharSequence>();
-    private boolean mHaveCorrection;
+    private boolean mHaveAutoCorrection;
     private String mLowerOriginalWord;
 
     // TODO: Remove these member variables by passing more context to addWord() callback method
@@ -198,7 +200,7 @@
     public SuggestedWords.Builder getSuggestedWordBuilder(View view, WordComposer wordComposer,
             CharSequence prevWordForBigram) {
         LatinImeLogger.onStartSuggestion(prevWordForBigram);
-        mHaveCorrection = false;
+        mHaveAutoCorrection = false;
         mIsFirstCharCapitalized = wordComposer.isFirstCharCapitalized();
         mIsAllUpperCase = wordComposer.isAllUpperCase();
         collectGarbage(mSuggestions, mPrefMaxSuggestions);
@@ -273,7 +275,10 @@
                 if (mSuggestions.size() > 0 && isValidWord(typedWord)
                         && (mCorrectionMode == CORRECTION_FULL
                         || mCorrectionMode == CORRECTION_FULL_BIGRAM)) {
-                    mHaveCorrection = true;
+                    if (DBG) {
+                        Log.d(TAG, "Auto corrected by CORRECTION_FULL.");
+                    }
+                    mHaveAutoCorrection = true;
                 }
             }
             if (mMainDict != null) mMainDict.getWords(wordComposer, this, mNextLettersFrequencies);
@@ -289,7 +294,10 @@
                             + "(" + mAutoCorrectionThreshold + ")");
                 }
                 if (normalizedScore >= mAutoCorrectionThreshold) {
-                    mHaveCorrection = true;
+                    if (DBG) {
+                        Log.d(TAG, "Auto corrected by S-threthhold.");
+                    }
+                    mHaveAutoCorrection = true;
                 }
             }
         }
@@ -331,7 +339,10 @@
                     canAdd &= !TextUtils.equals(autoText, mSuggestions.get(i + 1));
                 }
                 if (canAdd) {
-                    mHaveCorrection = true;
+                    if (DBG) {
+                        Log.d(TAG, "Auto corrected by AUTOTEXT.");
+                    }
+                    mHaveAutoCorrection = true;
                     mSuggestions.add(i + 1, autoText);
                     i++;
                 }
@@ -374,7 +385,7 @@
     }
 
     public boolean hasMinimalCorrection() {
-        return mHaveCorrection;
+        return mHaveAutoCorrection;
     }
 
     private boolean compareCaseInsensitive(final String mLowerOriginalWord,
diff --git a/java/src/com/android/inputmethod/latin/Utils.java b/java/src/com/android/inputmethod/latin/Utils.java
index 753e5d6..d2582b1 100644
--- a/java/src/com/android/inputmethod/latin/Utils.java
+++ b/java/src/com/android/inputmethod/latin/Utils.java
@@ -36,6 +36,8 @@
 import java.util.Date;
 
 public class Utils {
+    private static final String TAG = Utils.class.getSimpleName();
+    private static boolean DBG = LatinImeLogger.sDBG;
 
     /**
      * Cancel an {@link AsyncTask}.
@@ -95,6 +97,29 @@
                 || imm.getEnabledInputMethodSubtypeList(null, false).size() > 1;
     }
 
+
+    public static boolean shouldBlockedBySafetyNetForAutoCorrection(SuggestedWords suggestions) {
+        // Safety net for auto correction.
+        // Actually if we hit this safety net, it's actually a bug.
+        if (suggestions.size() <= 1 || suggestions.mTypedWordValid) return false;
+        CharSequence typedWord = suggestions.getWord(0);
+        CharSequence candidateWord = suggestions.getWord(1);
+        final int typedWordLength = typedWord.length();
+        final int maxEditDistanceOfNativeDictionary = typedWordLength < 5 ? 2 : typedWordLength / 2;
+        final int distance = Utils.editDistance(typedWord, candidateWord);
+        if (DBG) {
+            Log.d(TAG, "Autocorrected edit distance = " + distance
+                    + ", " + maxEditDistanceOfNativeDictionary);
+        }
+        if (distance > maxEditDistanceOfNativeDictionary) {
+            Log.w(TAG, "(Error) The edit distance of this correction exceeds limit. "
+                    + "Turning off auto-correction.");
+            return true;
+        } else {
+            return false;
+        }
+    }
+
     /* package */ static class RingCharBuffer {
         private static RingCharBuffer sRingCharBuffer = new RingCharBuffer();
         private static final char PLACEHOLDER_DELIMITER_CHAR = '\uFFFC';