Merge "Classify touches into three types."
diff --git a/java/res/values/config.xml b/java/res/values/config.xml
index 9992478..bcce581 100644
--- a/java/res/values/config.xml
+++ b/java/res/values/config.xml
@@ -82,8 +82,10 @@
              will be subject to auto-correction. -->
         <item>0</item>
     </string-array>
-    <!-- Threshold of the normalized score of the best suggestion for the spell checker to declare a word to be a typo -->
-    <string name="spellchecker_typo_threshold_value" translatable="false">0.11</string>
+    <!-- Threshold of the normalized score of the best suggestion for the spell checker to declare a word to be "likely" -->
+    <string name="spellchecker_likely_threshold_value" translatable="false">0.11</string>
+    <!-- Threshold of the normalized score of any dictionary lookup to be offered as a suggestion by the spell checker -->
+    <string name="spellchecker_suggestion_threshold_value" translatable="false">0.03</string>
     <!--  Screen metrics for logging.
             0 = "mdpi phone screen"
             1 = "hdpi phone screen"
diff --git a/java/res/values/strings.xml b/java/res/values/strings.xml
index a1c1a9f..9b3829f 100644
--- a/java/res/values/strings.xml
+++ b/java/res/values/strings.xml
@@ -99,7 +99,7 @@
     <!-- Option to decide the auto correction threshold score -->
     <!-- Option to enable auto correction [CHAR LIMIT=20]-->
     <string name="auto_correction">Auto correction</string>
-    <!-- Description for auto correction [CHAR LIMIT=35] -->
+    <!-- Description for auto correction [CHAR LIMIT=65 (two lines) or 30 (fits on one line, preferable)] -->
     <string name="auto_correction_summary">Spacebar and punctuation automatically correct mistyped words</string>
     <!-- Option to disable auto correction. [CHAR LIMIT=20] -->
     <string name="auto_correction_threshold_mode_off">Off</string>
diff --git a/java/res/xml/kbd_rows_number.xml b/java/res/xml/kbd_rows_number.xml
index 21d0656..90ac568 100644
--- a/java/res/xml/kbd_rows_number.xml
+++ b/java/res/xml/kbd_rows_number.xml
@@ -36,6 +36,7 @@
                     latin:keyStyle="num2KeyStyle" />
                 <Key
                     latin:keyStyle="num3KeyStyle" />
+                <Spacer />
             </Row>
             <Row>
                 <Key
@@ -44,6 +45,7 @@
                     latin:keyStyle="num5KeyStyle" />
                 <Key
                     latin:keyStyle="num6KeyStyle" />
+                <Spacer />
             </Row>
             <Row>
                 <Key
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
index 13e8ba1..837a533 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
@@ -106,16 +106,12 @@
 
     private static final KeyboardSwitcher sInstance = new KeyboardSwitcher();
 
-    public class KeyboardLayoutState {
+    private class KeyboardLayoutState {
         private boolean mIsValid;
         private boolean mIsAlphabetMode;
         private boolean mIsShiftLocked;
         private boolean mIsShifted;
 
-        public boolean isValid() {
-            return mIsValid;
-        }
-
         public void save() {
             if (mCurrentId == null) {
                 return;
@@ -210,14 +206,15 @@
             mSymbolsShiftedKeyboardId = getKeyboardId(editorInfo, true, true, settingsValues);
             mLayoutSwitchBackSymbols = mResources.getString(R.string.layout_switch_back_symbols);
             setKeyboard(getKeyboard(mSavedKeyboardState.getKeyboardId()));
+            mSavedKeyboardState.restore();
         } catch (RuntimeException e) {
             Log.w(TAG, "loading keyboard failed: " + mMainKeyboardId, e);
             LatinImeLogger.logOnException(mMainKeyboardId.toString(), e);
         }
     }
 
-    public KeyboardLayoutState getKeyboardState() {
-        return mSavedKeyboardState;
+    public void saveKeyboardState() {
+        mSavedKeyboardState.save();
     }
 
     public void onFinishInputView() {
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
index 6a6a0a4..18a9e3a 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
@@ -61,14 +61,26 @@
     public static final Flag FLAG_REQUIRES_GERMAN_UMLAUT_PROCESSING =
             new Flag(R.bool.config_require_umlaut_processing, 0x1);
 
+    // FULL_EDIT_DISTANCE is a flag that forces the dictionary to use full words
+    // when computing edit distance, instead of the default behavior of stopping
+    // the evaluation at the size the user typed.
+    public static final Flag FLAG_USE_FULL_EDIT_DISTANCE = new Flag(0x2);
+
     // Can create a new flag from extravalue :
     // public static final Flag FLAG_MYFLAG =
     //         new Flag("my_flag", 0x02);
 
-    private static final Flag[] ALL_FLAGS = {
+    // ALL_CONFIG_FLAGS is a collection of flags that enable reading all flags from configuration.
+    // This is but a mask - it does not mean the flags will be on, only that the configuration
+    // will be read for this particular flag.
+    public static final Flag[] ALL_CONFIG_FLAGS = {
         // Here should reside all flags that trigger some special processing
         // These *must* match the definition in UnigramDictionary enum in
         // unigram_dictionary.h so please update both at the same time.
+        // Please note that flags created with a resource are of type CONFIG while flags
+        // created with a string are of type EXTRAVALUE. These behave like masks, and the
+        // actual value will be read from the configuration/extra value at run time for
+        // the configuration at dictionary creation time.
         FLAG_REQUIRES_GERMAN_UMLAUT_PROCESSING,
     };
 
@@ -91,7 +103,7 @@
         // the Suggest class knows everything about every single dictionary.
         mDicTypeId = Suggest.DIC_MAIN;
         // TODO: Stop relying on the state of SubtypeSwitcher, get it as a parameter
-        mFlags = Flag.initFlags(null == flagArray ? ALL_FLAGS : flagArray, context,
+        mFlags = Flag.initFlags(null == flagArray ? ALL_CONFIG_FLAGS : flagArray, context,
                 SubtypeSwitcher.getInstance());
         loadDictionary(filename, offset, length);
     }
diff --git a/java/src/com/android/inputmethod/latin/DictionaryFactory.java b/java/src/com/android/inputmethod/latin/DictionaryFactory.java
index dfaad26..1607f86 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryFactory.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryFactory.java
@@ -34,7 +34,7 @@
     private static String TAG = DictionaryFactory.class.getSimpleName();
 
     /**
-     * Initializes a dictionary from a dictionary pack.
+     * Initializes a dictionary from a dictionary pack, with explicit flags.
      *
      * This searches for a content provider providing a dictionary pack for the specified
      * locale. If none is found, it falls back to using the resource passed as fallBackResId
@@ -42,10 +42,11 @@
      * @param context application context for reading resources
      * @param locale the locale for which to create the dictionary
      * @param fallbackResId the id of the resource to use as a fallback if no pack is found
+     * @param flagArray an array of flags to use
      * @return an initialized instance of DictionaryCollection
      */
-    public static DictionaryCollection createDictionaryFromManager(Context context, Locale locale,
-            int fallbackResId) {
+    public static DictionaryCollection createDictionaryFromManager(final Context context,
+            final Locale locale, final int fallbackResId, final Flag[] flagArray) {
         if (null == locale) {
             Log.e(TAG, "No locale defined for dictionary");
             return new DictionaryCollection(createBinaryDictionary(context, fallbackResId, locale));
@@ -57,7 +58,7 @@
         if (null != assetFileList) {
             for (final AssetFileAddress f : assetFileList) {
                 final BinaryDictionary binaryDictionary =
-                        new BinaryDictionary(context, f.mFilename, f.mOffset, f.mLength, null);
+                        new BinaryDictionary(context, f.mFilename, f.mOffset, f.mLength, flagArray);
                 if (binaryDictionary.isValidDictionary()) {
                     dictList.add(binaryDictionary);
                 }
@@ -71,6 +72,22 @@
     }
 
     /**
+     * Initializes a dictionary from a dictionary pack, with default flags.
+     *
+     * This searches for a content provider providing a dictionary pack for the specified
+     * locale. If none is found, it falls back to using the resource passed as fallBackResId
+     * as a dictionary.
+     * @param context application context for reading resources
+     * @param locale the locale for which to create the dictionary
+     * @param fallbackResId the id of the resource to use as a fallback if no pack is found
+     * @return an initialized instance of DictionaryCollection
+     */
+    public static DictionaryCollection createDictionaryFromManager(final Context context,
+            final Locale locale, final int fallbackResId) {
+        return createDictionaryFromManager(context, locale, fallbackResId, null);
+    }
+
+    /**
      * Initializes a dictionary from a raw resource file
      * @param context application context for reading resources
      * @param resId the resource containing the raw binary dictionary
diff --git a/java/src/com/android/inputmethod/latin/Flag.java b/java/src/com/android/inputmethod/latin/Flag.java
index 3cb8f7e..4ba6c80 100644
--- a/java/src/com/android/inputmethod/latin/Flag.java
+++ b/java/src/com/android/inputmethod/latin/Flag.java
@@ -25,8 +25,9 @@
     public final int mMask;
     public final int mSource;
 
-    static private final int SOURCE_CONFIG = 1;
-    static private final int SOURCE_EXTRAVALUE = 2;
+    private static final int SOURCE_CONFIG = 1;
+    private static final int SOURCE_EXTRAVALUE = 2;
+    private static final int SOURCE_PARAM = 3;
 
     public Flag(int resourceId, int mask) {
         mName = null;
@@ -42,6 +43,13 @@
         mMask = mask;
     }
 
+    public Flag(int mask) {
+        mName = null;
+        mResource = 0;
+        mSource = SOURCE_PARAM;
+        mMask = mask;
+    }
+
     // If context/switcher are null, set all related flags in flagArray to on.
     public static int initFlags(Flag[] flagArray, Context context, SubtypeSwitcher switcher) {
         int flags = 0;
@@ -57,6 +65,9 @@
                             switcher.currentSubtypeContainsExtraValueKey(entry.mName))
                         flags |= entry.mMask;
                     break;
+                case Flag.SOURCE_PARAM:
+                    flags |= entry.mMask;
+                    break;
             }
         }
         return flags;
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index cf1cb8f..dfb4d06 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -65,7 +65,6 @@
 import com.android.inputmethod.keyboard.Keyboard;
 import com.android.inputmethod.keyboard.KeyboardActionListener;
 import com.android.inputmethod.keyboard.KeyboardSwitcher;
-import com.android.inputmethod.keyboard.KeyboardSwitcher.KeyboardLayoutState;
 import com.android.inputmethod.keyboard.KeyboardView;
 import com.android.inputmethod.keyboard.LatinKeyboard;
 import com.android.inputmethod.keyboard.LatinKeyboardView;
@@ -132,9 +131,7 @@
     // Key events coming any faster than this are long-presses.
     private static final int QUICK_PRESS = 200;
 
-    private static final int START_INPUT_VIEW_DELAY_WHEN_SCREEN_ORIENTATION_STARTED = 10;
-    private static final int ACCUMULATE_START_INPUT_VIEW_DELAY = 20;
-    private static final int RESTORE_KEYBOARD_STATE_DELAY = 500;
+    private static final int PENDING_IMS_CALLBACK_DURATION = 800;
 
     /**
      * The name of the scheme used by the Package Manager to warn of a new package installation,
@@ -239,10 +236,7 @@
         private static final int MSG_DISMISS_LANGUAGE_ON_SPACEBAR = 5;
         private static final int MSG_SPACE_TYPED = 6;
         private static final int MSG_SET_BIGRAM_PREDICTIONS = 7;
-        private static final int MSG_START_ORIENTATION_CHANGE = 8;
-        private static final int MSG_START_INPUT_VIEW = 9;
-        private static final int MSG_DISPLAY_COMPLETIONS = 10;
-        private static final int MSG_RESTORE_KEYBOARD_LAYOUT = 11;
+        private static final int MSG_PENDING_IMS_CALLBACK = 8;
 
         public UIHandler(LatinIME outerInstance) {
             super(outerInstance);
@@ -291,16 +285,6 @@
                             (LatinKeyboard)msg.obj);
                 }
                 break;
-            case MSG_START_INPUT_VIEW:
-                latinIme.onStartInputView((EditorInfo)msg.obj, false);
-                break;
-            case MSG_DISPLAY_COMPLETIONS:
-                latinIme.onDisplayCompletions((CompletionInfo[])msg.obj);
-                break;
-            case MSG_RESTORE_KEYBOARD_LAYOUT:
-                removeMessages(MSG_UPDATE_SHIFT_STATE);
-                ((KeyboardLayoutState)msg.obj).restore();
-                break;
             }
         }
 
@@ -391,47 +375,89 @@
             return hasMessages(MSG_SPACE_TYPED);
         }
 
-        public void postRestoreKeyboardLayout() {
-            final LatinIME latinIme = getOuterInstance();
-            final KeyboardLayoutState state = latinIme.mKeyboardSwitcher.getKeyboardState();
-            if (state.isValid()) {
-                removeMessages(MSG_RESTORE_KEYBOARD_LAYOUT);
-                sendMessageDelayed(
-                        obtainMessage(MSG_RESTORE_KEYBOARD_LAYOUT, state),
-                        RESTORE_KEYBOARD_STATE_DELAY);
-            }
-        }
+        // Working variables for the following methods.
+        private boolean mIsOrientationChanging;
+        private boolean mPendingSuccesiveImsCallback;
+        private boolean mHasPendingStartInput;
+        private boolean mHasPendingFinishInputView;
+        private boolean mHasPendingFinishInput;
 
         public void startOrientationChanging() {
-            sendMessageDelayed(obtainMessage(MSG_START_ORIENTATION_CHANGE),
-                    START_INPUT_VIEW_DELAY_WHEN_SCREEN_ORIENTATION_STARTED);
+            mIsOrientationChanging = true;
             final LatinIME latinIme = getOuterInstance();
-            latinIme.mKeyboardSwitcher.getKeyboardState().save();
-            postRestoreKeyboardLayout();
+            latinIme.mKeyboardSwitcher.saveKeyboardState();
         }
 
-        public boolean postStartInputView(EditorInfo attribute) {
-            if (hasMessages(MSG_START_ORIENTATION_CHANGE) || hasMessages(MSG_START_INPUT_VIEW)) {
-                removeMessages(MSG_START_INPUT_VIEW);
-                // Postpone onStartInputView by ACCUMULATE_START_INPUT_VIEW_DELAY and see if
-                // orientation change has finished.
-                sendMessageDelayed(obtainMessage(MSG_START_INPUT_VIEW, attribute),
-                        ACCUMULATE_START_INPUT_VIEW_DELAY);
-                return true;
-            }
-            return false;
+        private void resetPendingImsCallback() {
+            mHasPendingFinishInputView = false;
+            mHasPendingFinishInput = false;
+            mHasPendingStartInput = false;
         }
 
-        public boolean postDisplayCompletions(CompletionInfo[] applicationSpecifiedCompletions) {
-            if (hasMessages(MSG_START_INPUT_VIEW) || hasMessages(MSG_DISPLAY_COMPLETIONS)) {
-                removeMessages(MSG_DISPLAY_COMPLETIONS);
-                // Postpone onDisplayCompletions by ACCUMULATE_START_INPUT_VIEW_DELAY.
-                sendMessageDelayed(
-                        obtainMessage(MSG_DISPLAY_COMPLETIONS, applicationSpecifiedCompletions),
-                        ACCUMULATE_START_INPUT_VIEW_DELAY);
-                return true;
+        private void executePendingImsCallback(LatinIME latinIme, EditorInfo attribute,
+                boolean restarting) {
+            if (mHasPendingFinishInputView)
+                latinIme.onFinishInputViewInternal(mHasPendingFinishInput);
+            if (mHasPendingFinishInput)
+                latinIme.onFinishInputInternal();
+            if (mHasPendingStartInput)
+                latinIme.onStartInputInternal(attribute, restarting);
+            resetPendingImsCallback();
+        }
+
+        public void onStartInput(EditorInfo attribute, boolean restarting) {
+            if (hasMessages(MSG_PENDING_IMS_CALLBACK)) {
+                // Typically this is the second onStartInput after orientation changed.
+                mHasPendingStartInput = true;
+            } else {
+                if (mIsOrientationChanging && restarting) {
+                    // This is the first onStartInput after orientation changed.
+                    mIsOrientationChanging = false;
+                    mPendingSuccesiveImsCallback = true;
+                }
+                final LatinIME latinIme = getOuterInstance();
+                executePendingImsCallback(latinIme, attribute, restarting);
+                latinIme.onStartInputInternal(attribute, restarting);
             }
-            return false;
+        }
+
+        public void onStartInputView(EditorInfo attribute, boolean restarting) {
+             if (hasMessages(MSG_PENDING_IMS_CALLBACK)) {
+                 // Typically this is the second onStartInputView after orientation changed.
+                 resetPendingImsCallback();
+             } else {
+                 if (mPendingSuccesiveImsCallback) {
+                     // This is the first onStartInputView after orientation changed.
+                     mPendingSuccesiveImsCallback = false;
+                     resetPendingImsCallback();
+                     sendMessageDelayed(obtainMessage(MSG_PENDING_IMS_CALLBACK),
+                             PENDING_IMS_CALLBACK_DURATION);
+                 }
+                 final LatinIME latinIme = getOuterInstance();
+                 executePendingImsCallback(latinIme, attribute, restarting);
+                 latinIme.onStartInputViewInternal(attribute, restarting);
+             }
+        }
+
+        public void onFinishInputView(boolean finishingInput) {
+            if (hasMessages(MSG_PENDING_IMS_CALLBACK)) {
+                // Typically this is the first onFinishInputView after orientation changed.
+                mHasPendingFinishInputView = true;
+            } else {
+                final LatinIME latinIme = getOuterInstance();
+                latinIme.onFinishInputViewInternal(finishingInput);
+            }
+        }
+
+        public void onFinishInput() {
+            if (hasMessages(MSG_PENDING_IMS_CALLBACK)) {
+                // Typically this is the first onFinishInput after orientation changed.
+                mHasPendingFinishInput = true;
+            } else {
+                final LatinIME latinIme = getOuterInstance();
+                executePendingImsCallback(latinIme, null, false);
+                latinIme.onFinishInputInternal();
+            }
         }
     }
 
@@ -646,12 +672,31 @@
     }
 
     @Override
-    public void onStartInputView(EditorInfo attribute, boolean restarting) {
-        mHandler.postRestoreKeyboardLayout();
-        if (mHandler.postStartInputView(attribute)) {
-            return;
-        }
+    public void onStartInput(EditorInfo attribute, boolean restarting) {
+        mHandler.onStartInput(attribute, restarting);
+    }
 
+    @Override
+    public void onStartInputView(EditorInfo attribute, boolean restarting) {
+        mHandler.onStartInputView(attribute, restarting);
+    }
+
+    @Override
+    public void onFinishInputView(boolean finishingInput) {
+        mHandler.onFinishInputView(finishingInput);
+    }
+
+    @Override
+    public void onFinishInput() {
+        mHandler.onFinishInput();
+    }
+
+    private void onStartInputInternal(EditorInfo attribute, boolean restarting) {
+        super.onStartInput(attribute, restarting);
+    }
+
+    private void onStartInputViewInternal(EditorInfo attribute, boolean restarting) {
+        super.onStartInputView(attribute, restarting);
         final KeyboardSwitcher switcher = mKeyboardSwitcher;
         LatinKeyboardView inputView = switcher.getKeyboardView();
 
@@ -785,8 +830,7 @@
         if (inputView != null) inputView.closing();
     }
 
-    @Override
-    public void onFinishInput() {
+    private void onFinishInputInternal() {
         super.onFinishInput();
 
         LatinImeLogger.commit();
@@ -799,8 +843,7 @@
         if (mUserBigramDictionary != null) mUserBigramDictionary.flushPendingWrites();
     }
 
-    @Override
-    public void onFinishInputView(boolean finishingInput) {
+    private void onFinishInputViewInternal(boolean finishingInput) {
         super.onFinishInputView(finishingInput);
         mKeyboardSwitcher.onFinishInputView();
         KeyboardView inputView = mKeyboardSwitcher.getKeyboardView();
@@ -939,9 +982,6 @@
 
     @Override
     public void onDisplayCompletions(CompletionInfo[] applicationSpecifiedCompletions) {
-        if (mHandler.postDisplayCompletions(applicationSpecifiedCompletions)) {
-            return;
-        }
         if (DEBUG) {
             Log.i(TAG, "Received completions:");
             if (applicationSpecifiedCompletions != null) {
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
index 37145b2..9e030eb 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
@@ -28,11 +28,13 @@
 import com.android.inputmethod.compat.ArraysCompatUtils;
 import com.android.inputmethod.keyboard.Key;
 import com.android.inputmethod.keyboard.ProximityInfo;
+import com.android.inputmethod.latin.BinaryDictionary;
 import com.android.inputmethod.latin.Dictionary;
 import com.android.inputmethod.latin.Dictionary.DataType;
 import com.android.inputmethod.latin.Dictionary.WordCallback;
 import com.android.inputmethod.latin.DictionaryCollection;
 import com.android.inputmethod.latin.DictionaryFactory;
+import com.android.inputmethod.latin.Flag;
 import com.android.inputmethod.latin.LocaleUtils;
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.SynchronouslyLoadedUserDictionary;
@@ -65,16 +67,33 @@
     private final static SuggestionsInfo IN_DICT_EMPTY_SUGGESTIONS =
             new SuggestionsInfo(SuggestionsInfo.RESULT_ATTR_IN_THE_DICTIONARY,
                     EMPTY_STRING_ARRAY);
+    private final static Flag[] USE_FULL_EDIT_DISTANCE_FLAG_ARRAY;
+    static {
+        // See BinaryDictionary.java for an explanation of these flags
+        // Specifially, ALL_CONFIG_FLAGS means that we want to consider all flags with the
+        // current dictionary configuration - for example, consider the UMLAUT flag
+        // so that it will be turned on for German dictionaries and off for others.
+        USE_FULL_EDIT_DISTANCE_FLAG_ARRAY = Arrays.copyOf(BinaryDictionary.ALL_CONFIG_FLAGS,
+                BinaryDictionary.ALL_CONFIG_FLAGS.length + 1);
+        USE_FULL_EDIT_DISTANCE_FLAG_ARRAY[BinaryDictionary.ALL_CONFIG_FLAGS.length] =
+                BinaryDictionary.FLAG_USE_FULL_EDIT_DISTANCE;
+    }
     private Map<String, DictionaryPool> mDictionaryPools =
             Collections.synchronizedMap(new TreeMap<String, DictionaryPool>());
     private Map<String, Dictionary> mUserDictionaries =
             Collections.synchronizedMap(new TreeMap<String, Dictionary>());
 
-    private double mTypoThreshold;
+    // The threshold for a candidate to be offered as a suggestion.
+    private double mSuggestionThreshold;
+    // The threshold for a suggestion to be considered "likely".
+    private double mLikelyThreshold;
 
     @Override public void onCreate() {
         super.onCreate();
-        mTypoThreshold = Double.parseDouble(getString(R.string.spellchecker_typo_threshold_value));
+        mSuggestionThreshold =
+                Double.parseDouble(getString(R.string.spellchecker_suggestion_threshold_value));
+        mLikelyThreshold =
+                Double.parseDouble(getString(R.string.spellchecker_likely_threshold_value));
     }
 
     @Override
@@ -96,7 +115,8 @@
         private final ArrayList<CharSequence> mSuggestions;
         private final int[] mScores;
         private final String mOriginalText;
-        private final double mThreshold;
+        private final double mSuggestionThreshold;
+        private final double mLikelyThreshold;
         private final int mMaxLength;
         private int mLength = 0;
 
@@ -105,10 +125,11 @@
         private String mBestSuggestion = null;
         private int mBestScore = Integer.MIN_VALUE; // As small as possible
 
-        SuggestionsGatherer(final String originalText, final double threshold,
-                final int maxLength) {
+        SuggestionsGatherer(final String originalText, final double suggestionThreshold,
+                final double likelyThreshold, final int maxLength) {
             mOriginalText = originalText;
-            mThreshold = threshold;
+            mSuggestionThreshold = suggestionThreshold;
+            mLikelyThreshold = likelyThreshold;
             mMaxLength = maxLength;
             mSuggestions = new ArrayList<CharSequence>(maxLength + 1);
             mScores = new int[mMaxLength];
@@ -122,28 +143,42 @@
             // if it doesn't. See documentation for binarySearch.
             final int insertIndex = positionIndex >= 0 ? positionIndex : -positionIndex - 1;
 
+            if (insertIndex == 0 && mLength >= mMaxLength) {
+                // In the future, we may want to keep track of the best suggestion score even if
+                // we are asked for 0 suggestions. In this case, we can use the following
+                // (tested) code to keep it:
+                // If the maxLength is 0 (should never be less, but if it is, it's treated as 0)
+                // then we need to keep track of the best suggestion in mBestScore and
+                // mBestSuggestion. This is so that we know whether the best suggestion makes
+                // the score cutoff, since we need to know that to return a meaningful
+                // looksLikeTypo.
+                // if (0 >= mMaxLength) {
+                //     if (score > mBestScore) {
+                //         mBestScore = score;
+                //         mBestSuggestion = new String(word, wordOffset, wordLength);
+                //     }
+                // }
+                return true;
+            }
+
+            // Compute the normalized score and skip this word if it's normalized score does not
+            // make the threshold.
+            final String wordString = new String(word, wordOffset, wordLength);
+            final double normalizedScore =
+                    Utils.calcNormalizedScore(mOriginalText, wordString, score);
+            if (normalizedScore < mSuggestionThreshold) {
+                if (DBG) Log.i(TAG, wordString + " does not make the score threshold");
+                return true;
+            }
+
             if (mLength < mMaxLength) {
                 final int copyLen = mLength - insertIndex;
                 ++mLength;
                 System.arraycopy(mScores, insertIndex, mScores, insertIndex + 1, copyLen);
-                mSuggestions.add(insertIndex, new String(word, wordOffset, wordLength));
+                mSuggestions.add(insertIndex, wordString);
             } else {
-                if (insertIndex == 0) {
-                    // If the maxLength is 0 (should never be less, but if it is, it's treated as 0)
-                    // then we need to keep track of the best suggestion in mBestScore and
-                    // mBestSuggestion. This is so that we know whether the best suggestion makes
-                    // the score cutoff, since we need to know that to return a meaningful
-                    // looksLikeTypo.
-                    if (0 >= mMaxLength) {
-                        if (score > mBestScore) {
-                            mBestScore = score;
-                            mBestSuggestion = new String(word, wordOffset, wordLength);
-                        }
-                    }
-                    return true;
-                }
                 System.arraycopy(mScores, 1, mScores, 0, insertIndex);
-                mSuggestions.add(insertIndex, new String(word, wordOffset, wordLength));
+                mSuggestions.add(insertIndex, wordString);
                 mSuggestions.remove(0);
             }
             mScores[insertIndex] = score;
@@ -165,7 +200,7 @@
                     gatheredSuggestions = EMPTY_STRING_ARRAY;
                     final double normalizedScore =
                             Utils.calcNormalizedScore(mOriginalText, mBestSuggestion, mBestScore);
-                    hasLikelySuggestions = (normalizedScore > mThreshold);
+                    hasLikelySuggestions = (normalizedScore > mLikelyThreshold);
                 }
             } else {
                 if (DBG) {
@@ -199,10 +234,11 @@
                 final CharSequence bestSuggestion = mSuggestions.get(0);
                 final double normalizedScore =
                         Utils.calcNormalizedScore(mOriginalText, bestSuggestion, bestScore);
-                hasLikelySuggestions = (normalizedScore > mThreshold);
+                hasLikelySuggestions = (normalizedScore > mLikelyThreshold);
                 if (DBG) {
                     Log.i(TAG, "Best suggestion : " + bestSuggestion + ", score " + bestScore);
-                    Log.i(TAG, "Normalized score = " + normalizedScore + " (threshold " + mThreshold
+                    Log.i(TAG, "Normalized score = " + normalizedScore
+                            + " (threshold " + mLikelyThreshold
                             + ") => hasLikelySuggestions = " + hasLikelySuggestions);
                 }
             }
@@ -240,7 +276,8 @@
         final Resources resources = getResources();
         final int fallbackResourceId = Utils.getMainDictionaryResourceId(resources);
         final DictionaryCollection dictionaryCollection =
-                DictionaryFactory.createDictionaryFromManager(this, locale, fallbackResourceId);
+                DictionaryFactory.createDictionaryFromManager(this, locale, fallbackResourceId,
+                        USE_FULL_EDIT_DISTANCE_FLAG_ARRAY);
         final String localeStr = locale.toString();
         Dictionary userDict = mUserDictionaries.get(localeStr);
         if (null == userDict) {
@@ -354,8 +391,8 @@
                 }
 
                 // TODO: Don't gather suggestions if the limit is <= 0 unless necessary
-                final SuggestionsGatherer suggestionsGatherer =
-                        new SuggestionsGatherer(text, mService.mTypoThreshold, suggestionsLimit);
+                final SuggestionsGatherer suggestionsGatherer = new SuggestionsGatherer(text,
+                        mService.mSuggestionThreshold, mService.mLikelyThreshold, suggestionsLimit);
                 final WordComposer composer = new WordComposer();
                 final int length = text.length();
                 for (int i = 0; i < length; ++i) {
diff --git a/native/src/correction.cpp b/native/src/correction.cpp
index 5d3bd71..308cca2 100644
--- a/native/src/correction.cpp
+++ b/native/src/correction.cpp
@@ -62,7 +62,8 @@
 }
 
 void Correction::setCorrectionParams(const int skipPos, const int excessivePos,
-        const int transposedPos, const int spaceProximityPos, const int missingSpacePos) {
+        const int transposedPos, const int spaceProximityPos, const int missingSpacePos,
+        const bool useFullEditDistance) {
     // TODO: remove
     mTransposedPos = transposedPos;
     mExcessivePos = excessivePos;
@@ -74,6 +75,7 @@
 
     mSpaceProximityPos = spaceProximityPos;
     mMissingSpacePos = missingSpacePos;
+    mUseFullEditDistance = useFullEditDistance;
 }
 
 void Correction::checkState() {
@@ -464,7 +466,7 @@
 }
 
 inline static int powerIntCapped(const int base, const int n) {
-    if (n == 0) return 1;
+    if (n <= 0) return 1;
     if (base == 2) {
         return n < 31 ? 1 << n : S_INT_MAX;
     } else {
@@ -554,6 +556,8 @@
     const int excessiveCount = correction->mExcessiveCount + correction->mTransposedCount % 2;
     const int proximityMatchedCount = correction->mProximityCount;
     const bool lastCharExceeded = correction->mLastCharExceeded;
+    const bool useFullEditDistance = correction->mUseFullEditDistance;
+    const int outputLength = outputIndex + 1;
     if (skippedCount >= inputLength || inputLength == 0) {
         return -1;
     }
@@ -707,6 +711,12 @@
         multiplyIntCapped(fullWordMultiplier, &finalFreq);
     }
 
+    if (useFullEditDistance && outputLength > inputLength + 1) {
+        const int diff = outputLength - inputLength - 1;
+        const int divider = diff < 31 ? 1 << diff : S_INT_MAX;
+        finalFreq = divider > finalFreq ? 1 : finalFreq / divider;
+    }
+
     if (DEBUG_DICT_FULL) {
         LOGI("calc: %d, %d", outputIndex, sameLength);
     }
diff --git a/native/src/correction.h b/native/src/correction.h
index 7d73dfa..84e0752 100644
--- a/native/src/correction.h
+++ b/native/src/correction.h
@@ -44,7 +44,7 @@
 
     // TODO: remove
     void setCorrectionParams(const int skipPos, const int excessivePos, const int transposedPos,
-            const int spaceProximityPos, const int missingSpacePos);
+            const int spaceProximityPos, const int missingSpacePos, const bool useFullEditDistance);
     void checkState();
     bool initProcessState(const int index);
 
@@ -106,6 +106,7 @@
     const int FULL_WORD_MULTIPLIER;
     const ProximityInfo *mProximityInfo;
 
+    bool mUseFullEditDistance;
     int mMaxEditDistance;
     int mMaxDepth;
     int mInputLength;
diff --git a/native/src/unigram_dictionary.cpp b/native/src/unigram_dictionary.cpp
index cdd84aa..f23bd32 100644
--- a/native/src/unigram_dictionary.cpp
+++ b/native/src/unigram_dictionary.cpp
@@ -132,7 +132,8 @@
         memcpy(codesDest, codesSrc, remainingBytes);
 
     getWordSuggestions(proximityInfo, xcoordinates, ycoordinates, codesBuffer,
-            (codesDest - codesBuffer) / MAX_PROXIMITY_CHARS + codesRemain, outWords, frequencies);
+            (codesDest - codesBuffer) / MAX_PROXIMITY_CHARS + codesRemain, outWords, frequencies,
+            flags);
 }
 
 int UnigramDictionary::getSuggestions(ProximityInfo *proximityInfo, const int *xcoordinates,
@@ -146,7 +147,7 @@
                 codesSize, flags, codes, codesSize, 0, codesBuffer, outWords, frequencies);
     } else { // Normal processing
         getWordSuggestions(proximityInfo, xcoordinates, ycoordinates, codes, codesSize,
-                outWords, frequencies);
+                outWords, frequencies, flags);
     }
 
     PROF_START(20);
@@ -175,7 +176,7 @@
 
 void UnigramDictionary::getWordSuggestions(ProximityInfo *proximityInfo,
         const int *xcoordinates, const int *ycoordinates, const int *codes, const int codesSize,
-        unsigned short *outWords, int *frequencies) {
+        unsigned short *outWords, int *frequencies, const int flags) {
 
     PROF_OPEN;
     PROF_START(0);
@@ -187,9 +188,10 @@
     mCorrection->initCorrection(mProximityInfo, mInputLength, maxDepth);
     PROF_END(0);
 
+    const bool useFullEditDistance = USE_FULL_EDIT_DISTANCE & flags;
     // TODO: remove
     PROF_START(1);
-    getSuggestionCandidates();
+    getSuggestionCandidates(useFullEditDistance);
     PROF_END(1);
 
     PROF_START(2);
@@ -212,7 +214,7 @@
             if (DEBUG_DICT) {
                 LOGI("--- Suggest missing space characters %d", i);
             }
-            getMissingSpaceWords(mInputLength, i, mCorrection);
+            getMissingSpaceWords(mInputLength, i, mCorrection, useFullEditDistance);
         }
     }
     PROF_END(5);
@@ -231,7 +233,7 @@
                         i, x, y, proximityInfo->hasSpaceProximity(x, y));
             }
             if (proximityInfo->hasSpaceProximity(x, y)) {
-                getMistypedSpaceWords(mInputLength, i, mCorrection);
+                getMistypedSpaceWords(mInputLength, i, mCorrection, useFullEditDistance);
             }
         }
     }
@@ -315,10 +317,10 @@
 static const char QUOTE = '\'';
 static const char SPACE = ' ';
 
-void UnigramDictionary::getSuggestionCandidates() {
+void UnigramDictionary::getSuggestionCandidates(const bool useFullEditDistance) {
     // TODO: Remove setCorrectionParams
     mCorrection->setCorrectionParams(0, 0, 0,
-            -1 /* spaceProximityPos */, -1 /* missingSpacePos */);
+            -1 /* spaceProximityPos */, -1 /* missingSpacePos */, useFullEditDistance);
     int rootPosition = ROOT_POS;
     // Get the number of children of root, then increment the position
     int childCount = Dictionary::getCount(DICT_ROOT, &rootPosition);
@@ -349,16 +351,20 @@
 }
 
 void UnigramDictionary::getMissingSpaceWords(
-        const int inputLength, const int missingSpacePos, Correction *correction) {
+        const int inputLength, const int missingSpacePos, Correction *correction,
+        const bool useFullEditDistance) {
     correction->setCorrectionParams(-1 /* skipPos */, -1 /* excessivePos */,
-            -1 /* transposedPos */, -1 /* spaceProximityPos */, missingSpacePos);
+            -1 /* transposedPos */, -1 /* spaceProximityPos */, missingSpacePos,
+            useFullEditDistance);
     getSplitTwoWordsSuggestion(inputLength, correction);
 }
 
 void UnigramDictionary::getMistypedSpaceWords(
-        const int inputLength, const int spaceProximityPos, Correction *correction) {
+        const int inputLength, const int spaceProximityPos, Correction *correction,
+        const bool useFullEditDistance) {
     correction->setCorrectionParams(-1 /* skipPos */, -1 /* excessivePos */,
-            -1 /* transposedPos */, spaceProximityPos, -1 /* missingSpacePos */);
+            -1 /* transposedPos */, spaceProximityPos, -1 /* missingSpacePos */,
+            useFullEditDistance);
     getSplitTwoWordsSuggestion(inputLength, correction);
 }
 
diff --git a/native/src/unigram_dictionary.h b/native/src/unigram_dictionary.h
index 65746db..ef9709a 100644
--- a/native/src/unigram_dictionary.h
+++ b/native/src/unigram_dictionary.h
@@ -78,7 +78,7 @@
 
     void getWordSuggestions(ProximityInfo *proximityInfo, const int *xcoordinates,
             const int *ycoordinates, const int *codes, const int codesSize,
-            unsigned short *outWords, int *frequencies);
+            unsigned short *outWords, int *frequencies, const int flags);
     bool isDigraph(const int* codes, const int i, const int codesSize) const;
     void getWordWithDigraphSuggestionsRec(ProximityInfo *proximityInfo,
         const int *xcoordinates, const int* ycoordinates, const int *codesBuffer,
@@ -87,13 +87,13 @@
     void initSuggestions(ProximityInfo *proximityInfo, const int *xcoordinates,
             const int *ycoordinates, const int *codes, const int codesSize,
             unsigned short *outWords, int *frequencies);
-    void getSuggestionCandidates();
+    void getSuggestionCandidates(const bool useFullEditDistance);
     bool addWord(unsigned short *word, int length, int frequency);
     void getSplitTwoWordsSuggestion(const int inputLength, Correction *correction);
-    void getMissingSpaceWords(
-            const int inputLength, const int missingSpacePos, Correction *correction);
-    void getMistypedSpaceWords(
-            const int inputLength, const int spaceProximityPos, Correction *correction);
+    void getMissingSpaceWords(const int inputLength, const int missingSpacePos,
+            Correction *correction, const bool useFullEditDistance);
+    void getMistypedSpaceWords(const int inputLength, const int spaceProximityPos,
+            Correction *correction, const bool useFullEditDistance);
     void onTerminal(const int freq, Correction *correction);
     bool needsToSkipCurrentNode(const unsigned short c,
             const int inputIndex, const int skipPos, const int depth);
@@ -122,7 +122,8 @@
     // or something very bad (like, the apocalypse) will happen.
     // Please update both at the same time.
     enum {
-        REQUIRES_GERMAN_UMLAUT_PROCESSING = 0x1
+        REQUIRES_GERMAN_UMLAUT_PROCESSING = 0x1,
+        USE_FULL_EDIT_DISTANCE = 0x2
     };
     static const struct digraph_t { int first; int second; } GERMAN_UMLAUT_DIGRAPHS[];