Merge "Add shadow to gesture preview trail"
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
index d15f14f..4e41b77 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
@@ -207,7 +207,8 @@
      * Update keyboard shift state triggered by connected EditText status change.
      */
     public void updateShiftState() {
-        mState.onUpdateShiftState(mLatinIME.getCurrentAutoCapsState());
+        mState.onUpdateShiftState(mLatinIME.getCurrentAutoCapsState(),
+                mLatinIME.getCurrentRecapitalizeState());
     }
 
     // TODO: Remove this method. Come up with a more comprehensive way to reset the keyboard layout
@@ -276,7 +277,8 @@
     // Implements {@link KeyboardState.SwitchActions}.
     @Override
     public void requestUpdatingShiftState() {
-        mState.onUpdateShiftState(mLatinIME.getCurrentAutoCapsState());
+        mState.onUpdateShiftState(mLatinIME.getCurrentAutoCapsState(),
+                mLatinIME.getCurrentRecapitalizeState());
     }
 
     // Implements {@link KeyboardState.SwitchActions}.
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
index 95d9ccb..b1d4997 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
@@ -20,6 +20,7 @@
 import android.util.Log;
 
 import com.android.inputmethod.latin.Constants;
+import com.android.inputmethod.latin.RecapitalizeStatus;
 
 /**
  * Keyboard state machine.
@@ -29,7 +30,7 @@
  * The input events are {@link #onLoadKeyboard()}, {@link #onSaveKeyboardState()},
  * {@link #onPressKey(int, boolean, int)}, {@link #onReleaseKey(int, boolean)},
  * {@link #onCodeInput(int, boolean, int)}, {@link #onCancelInput(boolean)},
- * {@link #onUpdateShiftState(int)}, {@link #onLongPressTimeout(int)}.
+ * {@link #onUpdateShiftState(int, int)}, {@link #onLongPressTimeout(int)}.
  *
  * The actions are {@link SwitchActions}'s methods.
  */
@@ -48,7 +49,7 @@
         public void setSymbolsShiftedKeyboard();
 
         /**
-         * Request to call back {@link KeyboardState#onUpdateShiftState(int)}.
+         * Request to call back {@link KeyboardState#onUpdateShiftState(int, int)}.
          */
         public void requestUpdatingShiftState();
 
@@ -80,6 +81,7 @@
     private boolean mIsSymbolShifted;
     private boolean mPrevMainKeyboardWasShiftLocked;
     private boolean mPrevSymbolsKeyboardWasShifted;
+    private int mRecapitalizeMode;
 
     // For handling long press.
     private boolean mLongPressShiftLockFired;
@@ -110,6 +112,7 @@
 
     public KeyboardState(final SwitchActions switchActions) {
         mSwitchActions = switchActions;
+        mRecapitalizeMode = RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE;
     }
 
     public void onLoadKeyboard() {
@@ -283,6 +286,7 @@
         mSwitchActions.setAlphabetKeyboard();
         mIsAlphabetMode = true;
         mIsSymbolShifted = false;
+        mRecapitalizeMode = RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE;
         mSwitchState = SWITCH_STATE_ALPHA;
         mSwitchActions.requestUpdatingShiftState();
     }
@@ -386,11 +390,13 @@
         }
     }
 
-    public void onUpdateShiftState(final int autoCaps) {
+    public void onUpdateShiftState(final int autoCaps, final int recapitalizeMode) {
         if (DEBUG_EVENT) {
-            Log.d(TAG, "onUpdateShiftState: autoCaps=" + autoCaps + " " + this);
+            Log.d(TAG, "onUpdateShiftState: autoCaps=" + autoCaps + ", recapitalizeMode="
+                    + recapitalizeMode + " " + this);
         }
-        updateAlphabetShiftState(autoCaps);
+        mRecapitalizeMode = recapitalizeMode;
+        updateAlphabetShiftState(autoCaps, recapitalizeMode);
     }
 
     // TODO: Remove this method. Come up with a more comprehensive way to reset the keyboard layout
@@ -402,8 +408,28 @@
         resetKeyboardStateToAlphabet();
     }
 
-    private void updateAlphabetShiftState(final int autoCaps) {
+    private void updateShiftStateForRecapitalize(final int recapitalizeMode) {
+        switch (recapitalizeMode) {
+        case RecapitalizeStatus.CAPS_MODE_ALL_UPPER:
+            setShifted(SHIFT_LOCK_SHIFTED);
+            break;
+        case RecapitalizeStatus.CAPS_MODE_FIRST_WORD_UPPER:
+            setShifted(AUTOMATIC_SHIFT);
+            break;
+        case RecapitalizeStatus.CAPS_MODE_ALL_LOWER:
+        case RecapitalizeStatus.CAPS_MODE_ORIGINAL_MIXED_CASE:
+        default:
+            setShifted(UNSHIFT);
+        }
+    }
+
+    private void updateAlphabetShiftState(final int autoCaps, final int recapitalizeMode) {
         if (!mIsAlphabetMode) return;
+        if (RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE != recapitalizeMode) {
+            // We are recapitalizing. Match the keyboard to the current recapitalize state.
+            updateShiftStateForRecapitalize(recapitalizeMode);
+            return;
+        }
         if (!mShiftKeyState.isReleasing()) {
             // Ignore update shift state event while the shift key is being pressed (including
             // chording).
@@ -421,6 +447,9 @@
 
     private void onPressShift() {
         mLongPressShiftLockFired = false;
+        // If we are recapitalizing, we don't do any of the normal processing, including
+        // importantly the double tap timer.
+        if (RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE != mRecapitalizeMode) return;
         if (mIsAlphabetMode) {
             mIsInDoubleTapShiftKey = mSwitchActions.isInDoubleTapTimeout();
             if (!mIsInDoubleTapShiftKey) {
@@ -467,7 +496,11 @@
     }
 
     private void onReleaseShift(final boolean withSliding) {
-        if (mIsAlphabetMode) {
+        if (RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE != mRecapitalizeMode) {
+            // We are recapitalizing. We should match the keyboard state to the recapitalize
+            // state in priority.
+            updateShiftStateForRecapitalize(mRecapitalizeMode);
+        } else if (mIsAlphabetMode) {
             final boolean isShiftLocked = mAlphabetShiftState.isShiftLocked();
             mIsInAlphabetUnshiftedFromShifted = false;
             if (mIsInDoubleTapShiftKey) {
@@ -597,7 +630,7 @@
 
         // If the code is a letter, update keyboard shift state.
         if (Constants.isLetterCode(code)) {
-            updateAlphabetShiftState(autoCaps);
+            updateAlphabetShiftState(autoCaps, RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE);
         }
     }
 
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index bf4c22d..0a6f2ab 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -161,7 +161,7 @@
             mPositionalInfoForUserDictPendingAddition = null;
     private final WordComposer mWordComposer = new WordComposer();
     private final RichInputConnection mConnection = new RichInputConnection(this);
-    private RecapitalizeStatus mRecapitalizeStatus = null;
+    private final RecapitalizeStatus mRecapitalizeStatus = new RecapitalizeStatus();
 
     // Keep track of the last selection range to decide if we need to show word alternatives
     private static final int NOT_A_CURSOR_POSITION = -1;
@@ -742,6 +742,7 @@
         resetComposingState(true /* alsoResetLastComposedWord */);
         mDeleteCount = 0;
         mSpaceState = SPACE_STATE_NONE;
+        mRecapitalizeStatus.deactivate();
         mCurrentlyPressedHardwareKeys.clear();
 
         if (mSuggestionStripView != null) {
@@ -925,7 +926,7 @@
             // We moved the cursor. If we are touching a word, we need to resume suggestion.
             mHandler.postResumeSuggestions();
             // Reset the last recapitalization.
-            mRecapitalizeStatus = null;
+            mRecapitalizeStatus.deactivate();
             mKeyboardSwitcher.updateShiftState();
         }
         mExpectingUpdateSelection = false;
@@ -995,8 +996,6 @@
             }
         }
         if (!mSettings.getCurrent().isApplicationSpecifiedCompletionsOn()) return;
-        mApplicationSpecifiedCompletions =
-                CompletionInfoUtils.removeNulls(applicationSpecifiedCompletions);
         if (applicationSpecifiedCompletions == null) {
             clearSuggestionStrip();
             if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
@@ -1004,6 +1003,8 @@
             }
             return;
         }
+        mApplicationSpecifiedCompletions =
+                CompletionInfoUtils.removeNulls(applicationSpecifiedCompletions);
 
         final ArrayList<SuggestedWords.SuggestedWordInfo> applicationSuggestedWords =
                 SuggestedWords.getFromApplicationSpecifiedCompletions(
@@ -1179,6 +1180,15 @@
                 SPACE_STATE_PHANTOM == mSpaceState);
     }
 
+    public int getCurrentRecapitalizeState() {
+        if (!mRecapitalizeStatus.isActive()
+                || !mRecapitalizeStatus.isSetAt(mLastSelectionStart, mLastSelectionEnd)) {
+            // Not recapitalizing at the moment
+            return RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE;
+        }
+        return mRecapitalizeStatus.getCurrentMode();
+    }
+
     // Factor in auto-caps and manual caps and compute the current caps mode.
     private int getActualCapsMode() {
         final int keyboardShiftMode = mKeyboardSwitcher.getKeyboardShiftMode();
@@ -1391,7 +1401,12 @@
         case Constants.CODE_SHIFT:
             // Note: calling back to the keyboard on Shift key is handled in onPressKey()
             // and onReleaseKey().
-            handleRecapitalize();
+            final Keyboard currentKeyboard = switcher.getKeyboard();
+            if (null != currentKeyboard && currentKeyboard.mId.isAlphabetKeyboard()) {
+                // TODO: Instead of checking for alphabetic keyboard here, separate keycodes for
+                // alphabetic shift and shift while in symbol layout.
+                handleRecapitalize();
+            }
             break;
         case Constants.CODE_SWITCH_ALPHA_SYMBOL:
             // Note: calling back to the keyboard on symbol key is handled in onPressKey()
@@ -1953,10 +1968,9 @@
     private void handleRecapitalize() {
         if (mLastSelectionStart == mLastSelectionEnd) return; // No selection
         // If we have a recapitalize in progress, use it; otherwise, create a new one.
-        if (null == mRecapitalizeStatus
+        if (!mRecapitalizeStatus.isActive()
                 || !mRecapitalizeStatus.isSetAt(mLastSelectionStart, mLastSelectionEnd)) {
-            mRecapitalizeStatus =
-                    new RecapitalizeStatus(mLastSelectionStart, mLastSelectionEnd,
+            mRecapitalizeStatus.initialize(mLastSelectionStart, mLastSelectionEnd,
                     mConnection.getSelectedText(0 /* flags, 0 for no styles */).toString(),
                     mSettings.getCurrentLocale(), mSettings.getWordSeparators());
             // We trim leading and trailing whitespace.
@@ -1979,6 +1993,8 @@
         mLastSelectionStart = mRecapitalizeStatus.getNewCursorStart();
         mLastSelectionEnd = mRecapitalizeStatus.getNewCursorEnd();
         mConnection.setSelection(mLastSelectionStart, mLastSelectionEnd);
+        // Match the keyboard to the new state.
+        mKeyboardSwitcher.updateShiftState();
     }
 
     // Returns true if we did an autocorrection, false otherwise.
@@ -2413,6 +2429,7 @@
         if (!mConnection.isCursorTouchingWord(mSettings.getCurrent())) return;
         final Range range = mConnection.getWordRangeAtCursor(mSettings.getWordSeparators(),
                 0 /* additionalPrecedingWordsCount */);
+        if (null == range) return; // Happens if we don't have an input connection at all
         final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList();
         final String typedWord = range.mWord.toString();
         if (range.mWord instanceof SpannableString) {
diff --git a/java/src/com/android/inputmethod/latin/RecapitalizeStatus.java b/java/src/com/android/inputmethod/latin/RecapitalizeStatus.java
index 9edd3a1..8a704ab 100644
--- a/java/src/com/android/inputmethod/latin/RecapitalizeStatus.java
+++ b/java/src/com/android/inputmethod/latin/RecapitalizeStatus.java
@@ -24,6 +24,7 @@
  * The status of the current recapitalize process.
  */
 public class RecapitalizeStatus {
+    public static final int NOT_A_RECAPITALIZE_MODE = -1;
     public static final int CAPS_MODE_ORIGINAL_MIXED_CASE = 0;
     public static final int CAPS_MODE_ALL_LOWER = 1;
     public static final int CAPS_MODE_FIRST_WORD_UPPER = 2;
@@ -37,6 +38,7 @@
         CAPS_MODE_FIRST_WORD_UPPER,
         CAPS_MODE_ALL_UPPER
     };
+
     private static final int getStringMode(final String string, final String separators) {
         if (StringUtils.isIdenticalAfterUpcase(string)) {
             return CAPS_MODE_ALL_UPPER;
@@ -50,24 +52,29 @@
     }
 
     /**
-     * We store the location of the cursor and the string that was there before the undoable
+     * We store the location of the cursor and the string that was there before the recapitalize
      * action was done, and the location of the cursor and the string that was there after.
      */
     private int mCursorStartBefore;
-    private int mCursorEndBefore;
     private String mStringBefore;
     private int mCursorStartAfter;
     private int mCursorEndAfter;
     private int mRotationStyleCurrentIndex;
-    private final boolean mSkipOriginalMixedCaseMode;
-    private final Locale mLocale;
-    private final String mSeparators;
+    private boolean mSkipOriginalMixedCaseMode;
+    private Locale mLocale;
+    private String mSeparators;
     private String mStringAfter;
+    private boolean mIsActive;
 
-    public RecapitalizeStatus(final int cursorStart, final int cursorEnd, final String string,
+    public RecapitalizeStatus() {
+        // By default, initialize with dummy values that won't match any real recapitalize.
+        initialize(-1, -1, "", Locale.getDefault(), "");
+        deactivate();
+    }
+
+    public void initialize(final int cursorStart, final int cursorEnd, final String string,
             final Locale locale, final String separators) {
         mCursorStartBefore = cursorStart;
-        mCursorEndBefore = cursorEnd;
         mStringBefore = string;
         mCursorStartAfter = cursorStart;
         mCursorEndAfter = cursorEnd;
@@ -89,6 +96,15 @@
             mRotationStyleCurrentIndex = currentMode;
             mSkipOriginalMixedCaseMode = true;
         }
+        mIsActive = true;
+    }
+
+    public void deactivate() {
+        mIsActive = false;
+    }
+
+    public boolean isActive() {
+        return mIsActive;
     }
 
     public boolean isSetAt(final int cursorStart, final int cursorEnd) {
@@ -110,23 +126,23 @@
             }
             ++count;
             switch (ROTATION_STYLE[mRotationStyleCurrentIndex]) {
-                case CAPS_MODE_ORIGINAL_MIXED_CASE:
-                    mStringAfter = mStringBefore;
-                    break;
-                case CAPS_MODE_ALL_LOWER:
-                    mStringAfter = mStringBefore.toLowerCase(mLocale);
-                    break;
-                case CAPS_MODE_FIRST_WORD_UPPER:
-                    mStringAfter = StringUtils.capitalizeEachWord(mStringBefore, mSeparators,
-                            mLocale);
-                    break;
-                case CAPS_MODE_ALL_UPPER:
-                    mStringAfter = mStringBefore.toUpperCase(mLocale);
-                    break;
-                default:
-                    mStringAfter = mStringBefore;
+            case CAPS_MODE_ORIGINAL_MIXED_CASE:
+                mStringAfter = mStringBefore;
+                break;
+            case CAPS_MODE_ALL_LOWER:
+                mStringAfter = mStringBefore.toLowerCase(mLocale);
+                break;
+            case CAPS_MODE_FIRST_WORD_UPPER:
+                mStringAfter = StringUtils.capitalizeEachWord(mStringBefore, mSeparators,
+                        mLocale);
+                break;
+            case CAPS_MODE_ALL_UPPER:
+                mStringAfter = mStringBefore.toUpperCase(mLocale);
+                break;
+            default:
+                mStringAfter = mStringBefore;
             }
-        } while (mStringAfter.equals(oldResult) && count < 5);
+        } while (mStringAfter.equals(oldResult) && count < ROTATION_STYLE.length + 1);
         mCursorEndAfter = mCursorStartAfter + mStringAfter.length();
     }
 
@@ -148,7 +164,7 @@
             if (!Character.isWhitespace(codePoint)) break;
         }
         if (0 != nonWhitespaceStart || len != nonWhitespaceEnd) {
-            mCursorEndBefore = mCursorEndAfter = mCursorStartBefore + nonWhitespaceEnd;
+            mCursorEndAfter = mCursorStartBefore + nonWhitespaceEnd;
             mCursorStartBefore = mCursorStartAfter = mCursorStartBefore + nonWhitespaceStart;
             mStringAfter = mStringBefore =
                     mStringBefore.substring(nonWhitespaceStart, nonWhitespaceEnd);
@@ -166,4 +182,8 @@
     public int getNewCursorEnd() {
         return mCursorEndAfter;
     }
+
+    public int getCurrentMode() {
+        return ROTATION_STYLE[mRotationStyleCurrentIndex];
+    }
 }
diff --git a/native/jni/src/binary_format.h b/native/jni/src/binary_format.h
index 1c4061f..2d2e195 100644
--- a/native/jni/src/binary_format.h
+++ b/native/jni/src/binary_format.h
@@ -92,6 +92,7 @@
             const int unigramProbability, const int bigramProbability);
     static int getProbability(const int position, const std::map<int, int> *bigramMap,
             const uint8_t *bigramFilter, const int unigramProbability);
+    static float getMultiWordCostMultiplier(const uint8_t *const dict);
 
     // Flags for special processing
     // Those *must* match the flags in makedict (BinaryDictInputOutput#*_PROCESSING_FLAG) or
@@ -241,6 +242,17 @@
     return ((msb & 0x7F) << 8) | dict[(*pos)++];
 }
 
+inline float BinaryFormat::getMultiWordCostMultiplier(const uint8_t *const dict) {
+    const int headerValue = readHeaderValueInt(dict, "MULTIPLE_WORDS_DEMOTION_RATE");
+    if (headerValue == S_INT_MIN) {
+        return 1.0f;
+    }
+    if (headerValue <= 0) {
+        return static_cast<float>(MAX_VALUE_FOR_WEIGHTING);
+    }
+    return 100.0f / static_cast<float>(headerValue);
+}
+
 inline uint8_t BinaryFormat::getFlagsAndForwardPointer(const uint8_t *const dict, int *pos) {
     return dict[(*pos)++];
 }
diff --git a/native/jni/src/defines.h b/native/jni/src/defines.h
index a7b023a..6ef9f41 100644
--- a/native/jni/src/defines.h
+++ b/native/jni/src/defines.h
@@ -424,10 +424,9 @@
     CT_OMISSION,
     CT_INSERTION,
     CT_TRANSPOSITION,
-    CT_SPACE_SUBSTITUTION,
-    CT_SPACE_OMISSION,
     CT_COMPLETION,
     CT_TERMINAL,
-    CT_NEW_WORD,
+    CT_NEW_WORD_SPACE_OMITTION,
+    CT_NEW_WORD_SPACE_SUBSTITUTION,
 } CorrectionType;
 #endif // LATINIME_DEFINES_H
diff --git a/native/jni/src/suggest/core/policy/weighting.cpp b/native/jni/src/suggest/core/policy/weighting.cpp
index e62b704..b9c0b81 100644
--- a/native/jni/src/suggest/core/policy/weighting.cpp
+++ b/native/jni/src/suggest/core/policy/weighting.cpp
@@ -38,7 +38,7 @@
     case CT_SUBSTITUTION:
         PROF_SUBSTITUTION(node->mProfiler);
         return;
-    case CT_NEW_WORD:
+    case CT_NEW_WORD_SPACE_OMITTION:
         PROF_NEW_WORD(node->mProfiler);
         return;
     case CT_MATCH:
@@ -50,7 +50,7 @@
     case CT_TERMINAL:
         PROF_TERMINAL(node->mProfiler);
         return;
-    case CT_SPACE_SUBSTITUTION:
+    case CT_NEW_WORD_SPACE_SUBSTITUTION:
         PROF_SPACE_SUBSTITUTION(node->mProfiler);
         return;
     case CT_INSERTION:
@@ -107,16 +107,16 @@
     case CT_SUBSTITUTION:
         // only used for typing
         return weighting->getSubstitutionCost();
-    case CT_NEW_WORD:
-        return weighting->getNewWordCost(dicNode);
+    case CT_NEW_WORD_SPACE_OMITTION:
+        return weighting->getNewWordCost(traverseSession, dicNode);
     case CT_MATCH:
         return weighting->getMatchedCost(traverseSession, dicNode, inputStateG);
     case CT_COMPLETION:
         return weighting->getCompletionCost(traverseSession, dicNode);
     case CT_TERMINAL:
         return weighting->getTerminalSpatialCost(traverseSession, dicNode);
-    case CT_SPACE_SUBSTITUTION:
-        return weighting->getSpaceSubstitutionCost();
+    case CT_NEW_WORD_SPACE_SUBSTITUTION:
+        return weighting->getSpaceSubstitutionCost(traverseSession, dicNode);
     case CT_INSERTION:
         return weighting->getInsertionCost(traverseSession, parentDicNode, dicNode);
     case CT_TRANSPOSITION:
@@ -135,7 +135,7 @@
         return 0.0f;
     case CT_SUBSTITUTION:
         return 0.0f;
-    case CT_NEW_WORD:
+    case CT_NEW_WORD_SPACE_OMITTION:
         return weighting->getNewWordBigramCost(traverseSession, parentDicNode, bigramCacheMap);
     case CT_MATCH:
         return 0.0f;
@@ -147,8 +147,8 @@
                         traverseSession->getOffsetDict(), dicNode, bigramCacheMap);
         return weighting->getTerminalLanguageCost(traverseSession, dicNode, languageImprobability);
     }
-    case CT_SPACE_SUBSTITUTION:
-        return 0.0f;
+    case CT_NEW_WORD_SPACE_SUBSTITUTION:
+        return weighting->getNewWordBigramCost(traverseSession, parentDicNode, bigramCacheMap);
     case CT_INSERTION:
         return 0.0f;
     case CT_TRANSPOSITION:
@@ -168,7 +168,7 @@
         case CT_SUBSTITUTION:
             // Should return true?
             return false;
-        case CT_NEW_WORD:
+        case CT_NEW_WORD_SPACE_OMITTION:
             return false;
         case CT_MATCH:
             return false;
@@ -176,7 +176,7 @@
             return false;
         case CT_TERMINAL:
             return false;
-        case CT_SPACE_SUBSTITUTION:
+        case CT_NEW_WORD_SPACE_SUBSTITUTION:
             return false;
         case CT_INSERTION:
             return true;
@@ -197,7 +197,7 @@
             return false;
         case CT_SUBSTITUTION:
             return false;
-        case CT_NEW_WORD:
+        case CT_NEW_WORD_SPACE_OMITTION:
             return false;
         case CT_MATCH:
             return weighting->isProximityDicNode(traverseSession, dicNode);
@@ -205,7 +205,7 @@
             return false;
         case CT_TERMINAL:
             return false;
-        case CT_SPACE_SUBSTITUTION:
+        case CT_NEW_WORD_SPACE_SUBSTITUTION:
             return false;
         case CT_INSERTION:
             return false;
@@ -224,7 +224,7 @@
             return 0;
         case CT_SUBSTITUTION:
             return 0;
-        case CT_NEW_WORD:
+        case CT_NEW_WORD_SPACE_OMITTION:
             return 0;
         case CT_MATCH:
             return 1;
@@ -232,7 +232,7 @@
             return 0;
         case CT_TERMINAL:
             return 0;
-        case CT_SPACE_SUBSTITUTION:
+        case CT_NEW_WORD_SPACE_SUBSTITUTION:
             return 1;
         case CT_INSERTION:
             return 2;
diff --git a/native/jni/src/suggest/core/policy/weighting.h b/native/jni/src/suggest/core/policy/weighting.h
index b92dbe2..bce479c 100644
--- a/native/jni/src/suggest/core/policy/weighting.h
+++ b/native/jni/src/suggest/core/policy/weighting.h
@@ -56,7 +56,8 @@
             const DicTraverseSession *const traverseSession,
             const DicNode *const parentDicNode, const DicNode *const dicNode) const = 0;
 
-    virtual float getNewWordCost(const DicNode *const dicNode) const = 0;
+    virtual float getNewWordCost(const DicTraverseSession *const traverseSession,
+            const DicNode *const dicNode) const = 0;
 
     virtual float getNewWordBigramCost(
             const DicTraverseSession *const traverseSession, const DicNode *const dicNode,
@@ -76,7 +77,8 @@
 
     virtual float getSubstitutionCost() const = 0;
 
-    virtual float getSpaceSubstitutionCost() const = 0;
+    virtual float getSpaceSubstitutionCost(const DicTraverseSession *const traverseSession,
+            const DicNode *const dicNode) const = 0;
 
     Weighting() {}
     virtual ~Weighting() {}
diff --git a/native/jni/src/suggest/core/session/dic_traverse_session.cpp b/native/jni/src/suggest/core/session/dic_traverse_session.cpp
index 5b783a2..3c44db2 100644
--- a/native/jni/src/suggest/core/session/dic_traverse_session.cpp
+++ b/native/jni/src/suggest/core/session/dic_traverse_session.cpp
@@ -16,6 +16,7 @@
 
 #include "suggest/core/session/dic_traverse_session.h"
 
+#include "binary_format.h"
 #include "defines.h"
 #include "dictionary.h"
 #include "dic_traverse_wrapper.h"
@@ -63,6 +64,7 @@
 void DicTraverseSession::init(const Dictionary *const dictionary, const int *prevWord,
         int prevWordLength) {
     mDictionary = dictionary;
+    mMultiWordCostMultiplier = BinaryFormat::getMultiWordCostMultiplier(mDictionary->getDict());
     if (!prevWord) {
         mPrevWordPos = NOT_VALID_WORD;
         return;
diff --git a/native/jni/src/suggest/core/session/dic_traverse_session.h b/native/jni/src/suggest/core/session/dic_traverse_session.h
index fe05276..d9c2a51 100644
--- a/native/jni/src/suggest/core/session/dic_traverse_session.h
+++ b/native/jni/src/suggest/core/session/dic_traverse_session.h
@@ -36,7 +36,8 @@
     AK_FORCE_INLINE DicTraverseSession(JNIEnv *env, jstring localeStr)
             : mPrevWordPos(NOT_VALID_WORD), mProximityInfo(0),
               mDictionary(0), mDicNodesCache(), mBigramCacheMap(),
-              mInputSize(0), mPartiallyCommited(false), mMaxPointerCount(1) {
+              mInputSize(0), mPartiallyCommited(false), mMaxPointerCount(1),
+              mMultiWordCostMultiplier(1.0f) {
         // NOTE: mProximityInfoStates is an array of instances.
         // No need to initialize it explicitly here.
     }
@@ -52,6 +53,7 @@
             const int maxPointerCount);
     void resetCache(const int nextActiveCacheSize, const int maxWords);
 
+    // TODO: Remove
     const uint8_t *getOffsetDict() const;
     int getDictFlags() const;
 
@@ -150,6 +152,10 @@
         return mProximityInfoStates[0].touchPositionCorrectionEnabled();
     }
 
+    float getMultiWordCostMultiplier() const {
+        return mMultiWordCostMultiplier;
+    }
+
  private:
     DISALLOW_IMPLICIT_CONSTRUCTORS(DicTraverseSession);
     // threshold to start caching
@@ -170,6 +176,11 @@
     int mInputSize;
     bool mPartiallyCommited;
     int mMaxPointerCount;
+
+    /////////////////////////////////
+    // Configuration per dictionary
+    float mMultiWordCostMultiplier;
+
 };
 } // namespace latinime
 #endif // LATINIME_DIC_TRAVERSE_SESSION_H
diff --git a/native/jni/src/suggest/core/suggest.cpp b/native/jni/src/suggest/core/suggest.cpp
index 67d351f..9de2cd2 100644
--- a/native/jni/src/suggest/core/suggest.cpp
+++ b/native/jni/src/suggest/core/suggest.cpp
@@ -33,16 +33,9 @@
 namespace latinime {
 
 // Initialization of class constants.
-const int Suggest::LOOKAHEAD_DIC_NODES_CACHE_SIZE = 25;
 const int Suggest::MIN_LEN_FOR_MULTI_WORD_AUTOCORRECT = 16;
 const int Suggest::MIN_CONTINUOUS_SUGGESTION_INPUT_SIZE = 2;
 const float Suggest::AUTOCORRECT_CLASSIFICATION_THRESHOLD = 0.33f;
-const float Suggest::AUTOCORRECT_LANGUAGE_FEATURE_THRESHOLD = 0.6f;
-
-const bool Suggest::CORRECT_SPACE_OMISSION = true;
-const bool Suggest::CORRECT_TRANSPOSITION = true;
-const bool Suggest::CORRECT_INSERTION = true;
-const bool Suggest::CORRECT_OMISSION_G = true;
 
 /**
  * Returns a set of suggestions for the given input touch points. The commitPoint argument indicates
@@ -270,12 +263,8 @@
             // latest touch point yet. These are needed to apply look-ahead correction operations
             // that require special handling of the latest touch point. For example, with insertions
             // (e.g., "thiis" -> "this") the latest touch point should not be consumed at all.
-            if (CORRECT_TRANSPOSITION) {
-                processDicNodeAsTransposition(traverseSession, &dicNode);
-            }
-            if (CORRECT_INSERTION) {
-                processDicNodeAsInsertion(traverseSession, &dicNode);
-            }
+            processDicNodeAsTransposition(traverseSession, &dicNode);
+            processDicNodeAsInsertion(traverseSession, &dicNode);
         } else { // !isLookAheadCorrection
             // Only consider typing error corrections if the normalized compound distance is
             // below a spatial distance threshold.
@@ -531,13 +520,10 @@
     DicNode newDicNode;
     DicNodeUtils::initAsRootWithPreviousWord(traverseSession->getDicRootPos(),
             traverseSession->getOffsetDict(), dicNode, &newDicNode);
-    Weighting::addCostAndForwardInputIndex(WEIGHTING, CT_NEW_WORD, traverseSession, dicNode,
+    const CorrectionType correctionType = spaceSubstitution ?
+            CT_NEW_WORD_SPACE_SUBSTITUTION : CT_NEW_WORD_SPACE_OMITTION;
+    Weighting::addCostAndForwardInputIndex(WEIGHTING, correctionType, traverseSession, dicNode,
             &newDicNode, traverseSession->getBigramCacheMap());
-    if (spaceSubstitution) {
-        // Merge this with CT_NEW_WORD
-        Weighting::addCostAndForwardInputIndex(WEIGHTING, CT_SPACE_SUBSTITUTION,
-                traverseSession, 0, &newDicNode, 0 /* bigramCacheMap */);
-    }
     traverseSession->getDicTraverseCache()->copyPushNextActive(&newDicNode);
 }
 } // namespace latinime
diff --git a/native/jni/src/suggest/core/suggest.h b/native/jni/src/suggest/core/suggest.h
index becd6c1..875cbe4 100644
--- a/native/jni/src/suggest/core/suggest.h
+++ b/native/jni/src/suggest/core/suggest.h
@@ -76,31 +76,16 @@
     void processDicNodeAsMatch(DicTraverseSession *traverseSession,
             DicNode *childDicNode) const;
 
-    // Dic nodes cache size for lookahead (autocompletion)
-    static const int LOOKAHEAD_DIC_NODES_CACHE_SIZE;
-    // Max characters to lookahead
-    static const int MAX_LOOKAHEAD;
     // Inputs longer than this will autocorrect if the suggestion is multi-word
     static const int MIN_LEN_FOR_MULTI_WORD_AUTOCORRECT;
     static const int MIN_CONTINUOUS_SUGGESTION_INPUT_SIZE;
-    // Base value for converting costs into scores (low so will not autocorrect without classifier)
-    static const float BASE_OUTPUT_SCORE;
 
     // Threshold for autocorrection classifier
     static const float AUTOCORRECT_CLASSIFICATION_THRESHOLD;
-    // Threshold for computing the language model feature for autocorrect classification
-    static const float AUTOCORRECT_LANGUAGE_FEATURE_THRESHOLD;
-
-    // Typing error correction settings
-    static const bool CORRECT_SPACE_OMISSION;
-    static const bool CORRECT_TRANSPOSITION;
-    static const bool CORRECT_INSERTION;
 
     const Traversal *const TRAVERSAL;
     const Scoring *const SCORING;
     const Weighting *const WEIGHTING;
-
-    static const bool CORRECT_OMISSION_G;
 };
 } // namespace latinime
 #endif // LATINIME_SUGGEST_IMPL_H
diff --git a/native/jni/src/suggest/policyimpl/typing/typing_traversal.cpp b/native/jni/src/suggest/policyimpl/typing/typing_traversal.cpp
index 66f8ba9..e7e40e3 100644
--- a/native/jni/src/suggest/policyimpl/typing/typing_traversal.cpp
+++ b/native/jni/src/suggest/policyimpl/typing/typing_traversal.cpp
@@ -18,7 +18,7 @@
 
 namespace latinime {
 const bool TypingTraversal::CORRECT_OMISSION = true;
-const bool TypingTraversal::CORRECT_SPACE_SUBSTITUTION = true;
-const bool TypingTraversal::CORRECT_SPACE_OMISSION = true;
+const bool TypingTraversal::CORRECT_NEW_WORD_SPACE_SUBSTITUTION = true;
+const bool TypingTraversal::CORRECT_NEW_WORD_SPACE_OMISSION = true;
 const TypingTraversal TypingTraversal::sInstance;
 }  // namespace latinime
diff --git a/native/jni/src/suggest/policyimpl/typing/typing_traversal.h b/native/jni/src/suggest/policyimpl/typing/typing_traversal.h
index f22029a..9f83474 100644
--- a/native/jni/src/suggest/policyimpl/typing/typing_traversal.h
+++ b/native/jni/src/suggest/policyimpl/typing/typing_traversal.h
@@ -66,7 +66,7 @@
 
     AK_FORCE_INLINE bool isSpaceSubstitutionTerminal(
             const DicTraverseSession *const traverseSession, const DicNode *const dicNode) const {
-        if (!CORRECT_SPACE_SUBSTITUTION) {
+        if (!CORRECT_NEW_WORD_SPACE_SUBSTITUTION) {
             return false;
         }
         if (!canDoLookAheadCorrection(traverseSession, dicNode)) {
@@ -80,7 +80,7 @@
 
     AK_FORCE_INLINE bool isSpaceOmissionTerminal(
             const DicTraverseSession *const traverseSession, const DicNode *const dicNode) const {
-        if (!CORRECT_SPACE_OMISSION) {
+        if (!CORRECT_NEW_WORD_SPACE_OMISSION) {
             return false;
         }
         const int inputSize = traverseSession->getInputSize();
@@ -173,8 +173,8 @@
  private:
     DISALLOW_COPY_AND_ASSIGN(TypingTraversal);
     static const bool CORRECT_OMISSION;
-    static const bool CORRECT_SPACE_SUBSTITUTION;
-    static const bool CORRECT_SPACE_OMISSION;
+    static const bool CORRECT_NEW_WORD_SPACE_SUBSTITUTION;
+    static const bool CORRECT_NEW_WORD_SPACE_OMISSION;
     static const TypingTraversal sInstance;
 
     TypingTraversal() {}
diff --git a/native/jni/src/suggest/policyimpl/typing/typing_weighting.h b/native/jni/src/suggest/policyimpl/typing/typing_weighting.h
index 2dcee34..74e4e34 100644
--- a/native/jni/src/suggest/policyimpl/typing/typing_weighting.h
+++ b/native/jni/src/suggest/policyimpl/typing/typing_weighting.h
@@ -128,10 +128,12 @@
         return cost + weightedDistance;
     }
 
-    float getNewWordCost(const DicNode *const dicNode) const {
+    float getNewWordCost(const DicTraverseSession *const traverseSession,
+            const DicNode *const dicNode) const {
         const bool isCapitalized = dicNode->isCapitalized();
-        return isCapitalized ?
+        const float cost = isCapitalized ?
                 ScoringParams::COST_NEW_WORD_CAPITALIZED : ScoringParams::COST_NEW_WORD;
+        return cost * traverseSession->getMultiWordCostMultiplier();
     }
 
     float getNewWordBigramCost(
@@ -183,8 +185,13 @@
         return ScoringParams::SUBSTITUTION_COST;
     }
 
-    AK_FORCE_INLINE float getSpaceSubstitutionCost() const {
-        return ScoringParams::SPACE_SUBSTITUTION_COST;
+    AK_FORCE_INLINE float getSpaceSubstitutionCost(
+            const DicTraverseSession *const traverseSession,
+            const DicNode *const dicNode) const {
+        const bool isCapitalized = dicNode->isCapitalized();
+        const float cost = ScoringParams::SPACE_SUBSTITUTION_COST + (isCapitalized ?
+                ScoringParams::COST_NEW_WORD_CAPITALIZED : ScoringParams::COST_NEW_WORD);
+        return cost * traverseSession->getMultiWordCostMultiplier();
     }
 
  private:
diff --git a/tests/src/com/android/inputmethod/keyboard/internal/MockKeyboardSwitcher.java b/tests/src/com/android/inputmethod/keyboard/internal/MockKeyboardSwitcher.java
index eb48408..74506d2 100644
--- a/tests/src/com/android/inputmethod/keyboard/internal/MockKeyboardSwitcher.java
+++ b/tests/src/com/android/inputmethod/keyboard/internal/MockKeyboardSwitcher.java
@@ -19,6 +19,7 @@
 import android.text.TextUtils;
 
 import com.android.inputmethod.latin.Constants;
+import com.android.inputmethod.latin.RecapitalizeStatus;
 
 public class MockKeyboardSwitcher implements KeyboardState.SwitchActions {
     public interface MockConstants {
@@ -120,7 +121,7 @@
 
     @Override
     public void requestUpdatingShiftState() {
-        mState.onUpdateShiftState(mAutoCapsState);
+        mState.onUpdateShiftState(mAutoCapsState, RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE);
     }
 
     @Override
@@ -162,7 +163,7 @@
     }
 
     public void updateShiftState() {
-        mState.onUpdateShiftState(mAutoCapsState);
+        mState.onUpdateShiftState(mAutoCapsState, RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE);
     }
 
     public void loadKeyboard() {
diff --git a/tests/src/com/android/inputmethod/latin/RecapitalizeStatusTests.java b/tests/src/com/android/inputmethod/latin/RecapitalizeStatusTests.java
index 4dfae4c..9d7203e 100644
--- a/tests/src/com/android/inputmethod/latin/RecapitalizeStatusTests.java
+++ b/tests/src/com/android/inputmethod/latin/RecapitalizeStatusTests.java
@@ -24,29 +24,26 @@
 @SmallTest
 public class RecapitalizeStatusTests extends AndroidTestCase {
     public void testTrim() {
-        RecapitalizeStatus status = new RecapitalizeStatus(30, 40, "abcdefghij",
-                Locale.ENGLISH, " ");
+        final RecapitalizeStatus status = new RecapitalizeStatus();
+        status.initialize(30, 40, "abcdefghij", Locale.ENGLISH, " ");
         status.trim();
         assertEquals("abcdefghij", status.getRecapitalizedString());
         assertEquals(30, status.getNewCursorStart());
         assertEquals(40, status.getNewCursorEnd());
 
-        status = new RecapitalizeStatus(30, 44, "    abcdefghij",
-                Locale.ENGLISH, " ");
+        status.initialize(30, 44, "    abcdefghij", Locale.ENGLISH, " ");
         status.trim();
         assertEquals("abcdefghij", status.getRecapitalizedString());
         assertEquals(34, status.getNewCursorStart());
         assertEquals(44, status.getNewCursorEnd());
 
-        status = new RecapitalizeStatus(30, 40, "abcdefgh  ",
-                Locale.ENGLISH, " ");
+        status.initialize(30, 40, "abcdefgh  ", Locale.ENGLISH, " ");
         status.trim();
         assertEquals("abcdefgh", status.getRecapitalizedString());
         assertEquals(30, status.getNewCursorStart());
         assertEquals(38, status.getNewCursorEnd());
 
-        status = new RecapitalizeStatus(30, 45, "   abcdefghij  ",
-                Locale.ENGLISH, " ");
+        status.initialize(30, 45, "   abcdefghij  ", Locale.ENGLISH, " ");
         status.trim();
         assertEquals("abcdefghij", status.getRecapitalizedString());
         assertEquals(33, status.getNewCursorStart());
@@ -54,8 +51,8 @@
     }
 
     public void testRotate() {
-        RecapitalizeStatus status = new RecapitalizeStatus(29, 40, "abcd efghij",
-                Locale.ENGLISH, " ");
+        final RecapitalizeStatus status = new RecapitalizeStatus();
+        status.initialize(29, 40, "abcd efghij", Locale.ENGLISH, " ");
         status.rotate();
         assertEquals("Abcd Efghij", status.getRecapitalizedString());
         assertEquals(29, status.getNewCursorStart());
@@ -67,8 +64,7 @@
         status.rotate();
         assertEquals("Abcd Efghij", status.getRecapitalizedString());
 
-        status = new RecapitalizeStatus(29, 40, "Abcd Efghij",
-                Locale.ENGLISH, " ");
+        status.initialize(29, 40, "Abcd Efghij", Locale.ENGLISH, " ");
         status.rotate();
         assertEquals("ABCD EFGHIJ", status.getRecapitalizedString());
         assertEquals(29, status.getNewCursorStart());
@@ -80,8 +76,7 @@
         status.rotate();
         assertEquals("ABCD EFGHIJ", status.getRecapitalizedString());
 
-        status = new RecapitalizeStatus(29, 40, "ABCD EFGHIJ",
-                Locale.ENGLISH, " ");
+        status.initialize(29, 40, "ABCD EFGHIJ", Locale.ENGLISH, " ");
         status.rotate();
         assertEquals("abcd efghij", status.getRecapitalizedString());
         assertEquals(29, status.getNewCursorStart());
@@ -93,8 +88,7 @@
         status.rotate();
         assertEquals("abcd efghij", status.getRecapitalizedString());
 
-        status = new RecapitalizeStatus(29, 39, "AbCDefghij",
-                Locale.ENGLISH, " ");
+        status.initialize(29, 39, "AbCDefghij", Locale.ENGLISH, " ");
         status.rotate();
         assertEquals("abcdefghij", status.getRecapitalizedString());
         assertEquals(29, status.getNewCursorStart());
@@ -108,8 +102,7 @@
         status.rotate();
         assertEquals("abcdefghij", status.getRecapitalizedString());
 
-        status = new RecapitalizeStatus(29, 40, "Abcd efghij",
-                Locale.ENGLISH, " ");
+        status.initialize(29, 40, "Abcd efghij", Locale.ENGLISH, " ");
         status.rotate();
         assertEquals("abcd efghij", status.getRecapitalizedString());
         assertEquals(29, status.getNewCursorStart());
@@ -123,8 +116,7 @@
         status.rotate();
         assertEquals("abcd efghij", status.getRecapitalizedString());
 
-        status = new RecapitalizeStatus(30, 34, "grüß", Locale.GERMAN, " ");
-        status.rotate();
+        status.initialize(30, 34, "grüß", Locale.GERMAN, " "); status.rotate();
         assertEquals("Grüß", status.getRecapitalizedString());
         assertEquals(30, status.getNewCursorStart());
         assertEquals(34, status.getNewCursorEnd());
@@ -141,9 +133,7 @@
         assertEquals(30, status.getNewCursorStart());
         assertEquals(34, status.getNewCursorEnd());
 
-
-        status = new RecapitalizeStatus(30, 33, "œuf", Locale.FRENCH, " ");
-        status.rotate();
+        status.initialize(30, 33, "œuf", Locale.FRENCH, " "); status.rotate();
         assertEquals("Œuf", status.getRecapitalizedString());
         assertEquals(30, status.getNewCursorStart());
         assertEquals(33, status.getNewCursorEnd());
@@ -160,8 +150,7 @@
         assertEquals(30, status.getNewCursorStart());
         assertEquals(33, status.getNewCursorEnd());
 
-        status = new RecapitalizeStatus(30, 33, "œUf", Locale.FRENCH, " ");
-        status.rotate();
+        status.initialize(30, 33, "œUf", Locale.FRENCH, " "); status.rotate();
         assertEquals("œuf", status.getRecapitalizedString());
         assertEquals(30, status.getNewCursorStart());
         assertEquals(33, status.getNewCursorEnd());
@@ -182,8 +171,7 @@
         assertEquals(30, status.getNewCursorStart());
         assertEquals(33, status.getNewCursorEnd());
 
-        status = new RecapitalizeStatus(30, 35, "école", Locale.FRENCH, " ");
-        status.rotate();
+        status.initialize(30, 35, "école", Locale.FRENCH, " "); status.rotate();
         assertEquals("École", status.getRecapitalizedString());
         assertEquals(30, status.getNewCursorStart());
         assertEquals(35, status.getNewCursorEnd());