merge in jb-mr1-release history after reset to jb-mr1-dev
diff --git a/java/res/values/donottranslate.xml b/java/res/values/donottranslate.xml
index 0970aee..9e07b22 100644
--- a/java/res/values/donottranslate.xml
+++ b/java/res/values/donottranslate.xml
@@ -23,9 +23,9 @@
     <!-- Symbols that should be swapped with a weak space -->
     <string name="weak_space_swapping_symbols">.,;:!?)]}\"</string>
     <!-- Symbols that should strip a weak space -->
-    <string name="weak_space_stripping_symbols">"&#x0009;&#x0020;\n/_\'-"</string>
+    <string name="weak_space_stripping_symbols">"&#x0009;&#x0020;\n/_\'-"@</string>
     <!-- Symbols that should convert weak spaces into real space -->
-    <string name="phantom_space_promoting_symbols">([*&amp;@{&lt;&gt;+=|</string>
+    <string name="phantom_space_promoting_symbols">([*&amp;{&lt;&gt;+=|</string>
     <!-- Symbols that do NOT separate words -->
     <string name="symbols_excluded_from_word_separators">\'-</string>
     <!-- Word separator list is the union of all symbols except those that are not separators:
diff --git a/java/src/com/android/inputmethod/keyboard/Keyboard.java b/java/src/com/android/inputmethod/keyboard/Keyboard.java
index 261d1eb..b7c7f41 100644
--- a/java/src/com/android/inputmethod/keyboard/Keyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/Keyboard.java
@@ -57,6 +57,8 @@
     public static final int CODE_DASH = '-';
     public static final int CODE_SINGLE_QUOTE = '\'';
     public static final int CODE_DOUBLE_QUOTE = '"';
+    public static final int CODE_QUESTION_MARK = '?';
+    public static final int CODE_EXCLAMATION_MARK = '!';
     // TODO: Check how this should work for right-to-left languages. It seems to stand
     // that for rtl languages, a closing parenthesis is a left parenthesis. Is this
     // managed by the font? Or is it a different char?
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
index 2417d6e..a6439c4 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
@@ -545,13 +545,14 @@
     }
 
     private void startBatchInput() {
-        if (!sInGesture && mGestureStrokeWithPreviewTrail.isStartOfAGesture()) {
-            if (DEBUG_LISTENER) {
-                Log.d(TAG, "onStartBatchInput");
-            }
-            sInGesture = true;
-            mListener.onStartBatchInput();
+        if (sInGesture || !mGestureStrokeWithPreviewTrail.isStartOfAGesture()) {
+            return;
         }
+        if (DEBUG_LISTENER) {
+            Log.d(TAG, "onStartBatchInput");
+        }
+        sInGesture = true;
+        mListener.onStartBatchInput();
         final boolean isOldestTracker = sPointerTrackerQueue.getOldestElement() == this;
         mDrawingProxy.showGesturePreviewTrail(this, isOldestTracker);
     }
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 9194a97..db8f269 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -36,6 +36,8 @@
 import android.media.AudioManager;
 import android.net.ConnectivityManager;
 import android.os.Debug;
+import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.Message;
 import android.os.SystemClock;
@@ -184,13 +186,16 @@
         private static final int MSG_UPDATE_SHIFT_STATE = 0;
         private static final int MSG_PENDING_IMS_CALLBACK = 1;
         private static final int MSG_UPDATE_SUGGESTION_STRIP = 2;
+        private static final int MSG_SHOW_GESTURE_PREVIEW_AND_SUGGESTION_STRIP = 3;
+
+        private static final int ARG1_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT = 1;
 
         private int mDelayUpdateSuggestions;
         private int mDelayUpdateShiftState;
         private long mDoubleSpacesTurnIntoPeriodTimeout;
         private long mDoubleSpaceTimerStart;
 
-        public UIHandler(LatinIME outerInstance) {
+        public UIHandler(final LatinIME outerInstance) {
             super(outerInstance);
         }
 
@@ -205,7 +210,7 @@
         }
 
         @Override
-        public void handleMessage(Message msg) {
+        public void handleMessage(final Message msg) {
             final LatinIME latinIme = getOuterInstance();
             final KeyboardSwitcher switcher = latinIme.mKeyboardSwitcher;
             switch (msg.what) {
@@ -215,6 +220,10 @@
             case MSG_UPDATE_SHIFT_STATE:
                 switcher.updateShiftState();
                 break;
+            case MSG_SHOW_GESTURE_PREVIEW_AND_SUGGESTION_STRIP:
+                latinIme.showGesturePreviewAndSuggestionStrip((SuggestedWords)msg.obj,
+                        msg.arg1 == ARG1_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT);
+                break;
             }
         }
 
@@ -239,6 +248,15 @@
             removeMessages(MSG_UPDATE_SHIFT_STATE);
         }
 
+        public void showGesturePreviewAndSuggestionStrip(final SuggestedWords suggestedWords,
+                final boolean dismissGestureFloatingPreviewText) {
+            removeMessages(MSG_SHOW_GESTURE_PREVIEW_AND_SUGGESTION_STRIP);
+            final int arg1 = dismissGestureFloatingPreviewText
+                    ? ARG1_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT : 0;
+            obtainMessage(MSG_SHOW_GESTURE_PREVIEW_AND_SUGGESTION_STRIP, arg1, 0, suggestedWords)
+                    .sendToTarget();
+        }
+
         public void startDoubleSpacesTimer() {
             mDoubleSpaceTimerStart = SystemClock.uptimeMillis();
         }
@@ -276,7 +294,7 @@
             mHasPendingStartInput = false;
         }
 
-        private void executePendingImsCallback(LatinIME latinIme, EditorInfo editorInfo,
+        private void executePendingImsCallback(final LatinIME latinIme, final EditorInfo editorInfo,
                 boolean restarting) {
             if (mHasPendingFinishInputView)
                 latinIme.onFinishInputViewInternal(mHasPendingFinishInput);
@@ -287,7 +305,7 @@
             resetPendingImsCallback();
         }
 
-        public void onStartInput(EditorInfo editorInfo, boolean restarting) {
+        public void onStartInput(final EditorInfo editorInfo, final boolean restarting) {
             if (hasMessages(MSG_PENDING_IMS_CALLBACK)) {
                 // Typically this is the second onStartInput after orientation changed.
                 mHasPendingStartInput = true;
@@ -303,7 +321,7 @@
             }
         }
 
-        public void onStartInputView(EditorInfo editorInfo, boolean restarting) {
+        public void onStartInputView(final EditorInfo editorInfo, final boolean restarting) {
             if (hasMessages(MSG_PENDING_IMS_CALLBACK)
                     && KeyboardId.equivalentEditorInfoForKeyboard(editorInfo, mAppliedEditorInfo)) {
                 // Typically this is the second onStartInputView after orientation changed.
@@ -323,7 +341,7 @@
             }
         }
 
-        public void onFinishInputView(boolean finishingInput) {
+        public void onFinishInputView(final boolean finishingInput) {
             if (hasMessages(MSG_PENDING_IMS_CALLBACK)) {
                 // Typically this is the first onFinishInputView after orientation changed.
                 mHasPendingFinishInputView = true;
@@ -425,7 +443,7 @@
 
     // Note that this method is called from a non-UI thread.
     @Override
-    public void onUpdateMainDictionaryAvailability(boolean isMainDictionaryAvailable) {
+    public void onUpdateMainDictionaryAvailability(final boolean isMainDictionaryAvailable) {
         mIsMainDictionaryAvailable = isMainDictionaryAvailable;
         final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
         if (mainKeyboardView != null) {
@@ -529,7 +547,7 @@
     }
 
     @Override
-    public void onConfigurationChanged(Configuration conf) {
+    public void onConfigurationChanged(final Configuration conf) {
         // System locale has been changed. Needs to reload keyboard.
         if (mSubtypeSwitcher.onConfigurationChanged(conf, this)) {
             loadKeyboard();
@@ -555,7 +573,7 @@
     }
 
     @Override
-    public void setInputView(View view) {
+    public void setInputView(final View view) {
         super.setInputView(view);
         mExtractArea = getWindow().getWindow().getDecorView()
                 .findViewById(android.R.id.extractArea);
@@ -570,23 +588,23 @@
     }
 
     @Override
-    public void setCandidatesView(View view) {
+    public void setCandidatesView(final View view) {
         // To ensure that CandidatesView will never be set.
         return;
     }
 
     @Override
-    public void onStartInput(EditorInfo editorInfo, boolean restarting) {
+    public void onStartInput(final EditorInfo editorInfo, final boolean restarting) {
         mHandler.onStartInput(editorInfo, restarting);
     }
 
     @Override
-    public void onStartInputView(EditorInfo editorInfo, boolean restarting) {
+    public void onStartInputView(final EditorInfo editorInfo, final boolean restarting) {
         mHandler.onStartInputView(editorInfo, restarting);
     }
 
     @Override
-    public void onFinishInputView(boolean finishingInput) {
+    public void onFinishInputView(final boolean finishingInput) {
         mHandler.onFinishInputView(finishingInput);
     }
 
@@ -596,19 +614,19 @@
     }
 
     @Override
-    public void onCurrentInputMethodSubtypeChanged(InputMethodSubtype subtype) {
+    public void onCurrentInputMethodSubtypeChanged(final InputMethodSubtype subtype) {
         // Note that the calling sequence of onCreate() and onCurrentInputMethodSubtypeChanged()
         // is not guaranteed. It may even be called at the same time on a different thread.
         mSubtypeSwitcher.updateSubtype(subtype);
         loadKeyboard();
     }
 
-    private void onStartInputInternal(EditorInfo editorInfo, boolean restarting) {
+    private void onStartInputInternal(final EditorInfo editorInfo, final boolean restarting) {
         super.onStartInput(editorInfo, restarting);
     }
 
     @SuppressWarnings("deprecation")
-    private void onStartInputViewInternal(EditorInfo editorInfo, boolean restarting) {
+    private void onStartInputViewInternal(final EditorInfo editorInfo, final boolean restarting) {
         super.onStartInputView(editorInfo, restarting);
         final KeyboardSwitcher switcher = mKeyboardSwitcher;
         final MainKeyboardView mainKeyboardView = switcher.getMainKeyboardView();
@@ -751,7 +769,7 @@
                     getCurrentInputConnection());
         }
         super.onWindowHidden();
-        final KeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
+        final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
         if (mainKeyboardView != null) {
             mainKeyboardView.closing();
         }
@@ -765,16 +783,16 @@
             ResearchLogger.getInstance().latinIME_onFinishInputInternal();
         }
 
-        final KeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
+        final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
         if (mainKeyboardView != null) {
             mainKeyboardView.closing();
         }
     }
 
-    private void onFinishInputViewInternal(boolean finishingInput) {
+    private void onFinishInputViewInternal(final boolean finishingInput) {
         super.onFinishInputView(finishingInput);
         mKeyboardSwitcher.onFinishInputView();
-        final KeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
+        final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
         if (mainKeyboardView != null) {
             mainKeyboardView.cancelAllMessages();
         }
@@ -783,9 +801,9 @@
     }
 
     @Override
-    public void onUpdateSelection(int oldSelStart, int oldSelEnd,
-            int newSelStart, int newSelEnd,
-            int composingSpanStart, int composingSpanEnd) {
+    public void onUpdateSelection(final int oldSelStart, final int oldSelEnd,
+            final int newSelStart, final int newSelEnd,
+            final int composingSpanStart, final int composingSpanEnd) {
         super.onUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd,
                 composingSpanStart, composingSpanEnd);
         if (DEBUG) {
@@ -883,7 +901,7 @@
      * cause the suggestions strip to disappear and re-appear.
      */
     @Override
-    public void onExtractedCursorMovement(int dx, int dy) {
+    public void onExtractedCursorMovement(final int dx, final int dy) {
         if (mCurrentSettings.isSuggestionsRequested(mDisplayOrientation)) return;
 
         super.onExtractedCursorMovement(dx, dy);
@@ -903,7 +921,7 @@
     }
 
     @Override
-    public void onDisplayCompletions(CompletionInfo[] applicationSpecifiedCompletions) {
+    public void onDisplayCompletions(final CompletionInfo[] applicationSpecifiedCompletions) {
         if (DEBUG) {
             Log.i(TAG, "Received completions:");
             if (applicationSpecifiedCompletions != null) {
@@ -945,7 +963,8 @@
         }
     }
 
-    private void setSuggestionStripShownInternal(boolean shown, boolean needsInputViewShown) {
+    private void setSuggestionStripShownInternal(final boolean shown,
+            final boolean needsInputViewShown) {
         // TODO: Modify this if we support suggestions with hard keyboard
         if (onEvaluateInputViewShown() && mSuggestionsContainer != null) {
             final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
@@ -963,7 +982,7 @@
         }
     }
 
-    private void setSuggestionStripShown(boolean shown) {
+    private void setSuggestionStripShown(final boolean shown) {
         setSuggestionStripShownInternal(shown, /* needsInputViewShown */true);
     }
 
@@ -973,7 +992,7 @@
             return currentHeight;
         }
 
-        final KeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
+        final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
         if (mainKeyboardView == null) {
             return 0;
         }
@@ -993,9 +1012,9 @@
     }
 
     @Override
-    public void onComputeInsets(InputMethodService.Insets outInsets) {
+    public void onComputeInsets(final InputMethodService.Insets outInsets) {
         super.onComputeInsets(outInsets);
-        final KeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
+        final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
         if (mainKeyboardView == null || mSuggestionsContainer == null) {
             return;
         }
@@ -1099,7 +1118,7 @@
         // Note: getCursorCapsMode() returns the current capitalization mode that is any
         // combination of CAP_MODE_CHARACTERS, CAP_MODE_WORDS, and CAP_MODE_SENTENCES. 0 means none
         // of them.
-        return mConnection.getCursorCapsMode(inputType);
+        return mConnection.getCursorCapsMode(inputType, mSubtypeSwitcher.getCurrentSubtypeLocale());
     }
 
     // Factor in auto-caps and manual caps and compute the current caps mode.
@@ -1160,12 +1179,12 @@
     // Callback for the {@link SuggestionStripView}, to call when the "add to dictionary" hint is
     // pressed.
     @Override
-    public boolean addWordToUserDictionary(String word) {
+    public boolean addWordToUserDictionary(final String word) {
         mUserDictionary.addWordToUserDictionary(word, 128);
         return true;
     }
 
-    private static boolean isAlphabet(int code) {
+    private static boolean isAlphabet(final int code) {
         return Character.isLetter(code);
     }
 
@@ -1178,7 +1197,7 @@
     public static final int CODE_SHOW_INPUT_METHOD_PICKER = 1;
 
     @Override
-    public boolean onCustomRequest(int requestCode) {
+    public boolean onCustomRequest(final int requestCode) {
         if (isShowingOptionDialog()) return false;
         switch (requestCode) {
         case CODE_SHOW_INPUT_METHOD_PICKER:
@@ -1196,11 +1215,11 @@
         return mOptionsDialog != null && mOptionsDialog.isShowing();
     }
 
-    private static int getActionId(Keyboard keyboard) {
+    private static int getActionId(final Keyboard keyboard) {
         return keyboard != null ? keyboard.mId.imeActionId() : EditorInfo.IME_ACTION_NONE;
     }
 
-    private void performEditorAction(int actionId) {
+    private void performEditorAction(final int actionId) {
         mConnection.performEditorAction(actionId);
     }
 
@@ -1233,7 +1252,7 @@
                 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE));
     }
 
-    private void sendKeyCodePoint(int code) {
+    private void sendKeyCodePoint(final int code) {
         // TODO: Remove this special handling of digit letters.
         // For backward compatibility. See {@link InputMethodService#sendKeyChar(char)}.
         if (code >= '0' && code <= '9') {
@@ -1261,7 +1280,7 @@
 
     // Implementation of {@link KeyboardActionListener}.
     @Override
-    public void onCodeInput(int primaryCode, int x, int y) {
+    public void onCodeInput(final int primaryCode, final int x, final int y) {
         final long when = SystemClock.uptimeMillis();
         if (primaryCode != Keyboard.CODE_DELETE || when > mLastKeyTime + QUICK_PRESS) {
             mDeleteCount = 0;
@@ -1358,7 +1377,7 @@
 
     // Called from PointerTracker through the KeyboardActionListener interface
     @Override
-    public void onTextInput(CharSequence rawText) {
+    public void onTextInput(final CharSequence rawText) {
         mConnection.beginBatchEdit();
         if (mWordComposer.isComposingWord()) {
             commitCurrentAutoCorrection(rawText.toString());
@@ -1382,7 +1401,7 @@
     public void onStartBatchInput() {
         mConnection.beginBatchEdit();
         if (mWordComposer.isComposingWord()) {
-            commitCurrentAutoCorrection(LastComposedWord.NOT_A_SEPARATOR);
+            commitTyped(LastComposedWord.NOT_A_SEPARATOR);
             mExpectingUpdateSelection = true;
             // TODO: Can we remove this?
             mSpaceState = SPACE_STATE_PHANTOM;
@@ -1392,40 +1411,102 @@
         mWordComposer.setCapitalizedModeAtStartComposingTime(getActualCapsMode());
     }
 
-    @Override
-    public void onUpdateBatchInput(InputPointers batchPointers) {
-        mWordComposer.setBatchInputPointers(batchPointers);
-        final SuggestedWords suggestedWords = getSuggestedWords();
-        showSuggestionStrip(suggestedWords, null);
-        final String gestureFloatingPreviewText = (suggestedWords.size() > 0)
+    private static final class BatchInputUpdater implements Handler.Callback {
+        private final Handler mHandler;
+        private LatinIME mLatinIme;
+
+        private BatchInputUpdater() {
+            final HandlerThread handlerThread = new HandlerThread(
+                    BatchInputUpdater.class.getSimpleName());
+            handlerThread.start();
+            mHandler = new Handler(handlerThread.getLooper(), this);
+        }
+
+        // Initialization-on-demand holder
+        private static final class OnDemandInitializationHolder {
+            public static final BatchInputUpdater sInstance = new BatchInputUpdater();
+        }
+
+        public static BatchInputUpdater getInstance() {
+            return OnDemandInitializationHolder.sInstance;
+        }
+
+        private static final int MSG_UPDATE_GESTURE_PREVIEW_AND_SUGGESTION_STRIP = 1;
+
+        @Override
+        public boolean handleMessage(final Message msg) {
+            switch (msg.what) {
+            case MSG_UPDATE_GESTURE_PREVIEW_AND_SUGGESTION_STRIP:
+                final SuggestedWords suggestedWords = getSuggestedWordsGesture(
+                        (InputPointers)msg.obj, mLatinIme);
+                showGesturePreviewAndSuggestionStrip(
+                        suggestedWords, false /* dismissGestureFloatingPreviewText */, mLatinIme);
+                break;
+            }
+            return true;
+        }
+
+        public void updateGesturePreviewAndSuggestionStrip(final InputPointers batchPointers,
+                final LatinIME latinIme) {
+            mLatinIme = latinIme;
+            if (mHandler.hasMessages(MSG_UPDATE_GESTURE_PREVIEW_AND_SUGGESTION_STRIP)) {
+                return;
+            }
+            mHandler.obtainMessage(
+                    MSG_UPDATE_GESTURE_PREVIEW_AND_SUGGESTION_STRIP, batchPointers)
+                    .sendToTarget();
+        }
+
+        public void showGesturePreviewAndSuggestionStrip(final SuggestedWords suggestedWords,
+                final boolean dismissGestureFloatingPreviewText, final LatinIME latinIme) {
+            latinIme.mHandler.showGesturePreviewAndSuggestionStrip(
+                    suggestedWords, dismissGestureFloatingPreviewText);
+        }
+
+        // {@link LatinIME#getSuggestedWords(int)} method calls with same session id have to
+        // be synchronized.
+        public synchronized SuggestedWords getSuggestedWordsGesture(
+                final InputPointers batchPointers, final LatinIME latinIme) {
+            latinIme.mWordComposer.setBatchInputPointers(batchPointers);
+            return latinIme.getSuggestedWords(Suggest.SESSION_GESTURE);
+        }
+    }
+
+    private void showGesturePreviewAndSuggestionStrip(final SuggestedWords suggestedWords,
+            final boolean dismissGestureFloatingPreviewText) {
+        final String batchInputText = (suggestedWords.size() > 0)
                 ? suggestedWords.getWord(0) : null;
-        mKeyboardSwitcher.getMainKeyboardView()
-                .showGestureFloatingPreviewText(gestureFloatingPreviewText);
+        final KeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
+        mainKeyboardView.showGestureFloatingPreviewText(batchInputText);
+        showSuggestionStrip(suggestedWords, null);
+        if (dismissGestureFloatingPreviewText) {
+            mainKeyboardView.dismissGestureFloatingPreviewText();
+        }
     }
 
     @Override
-    public void onEndBatchInput(InputPointers batchPointers) {
-        mWordComposer.setBatchInputPointers(batchPointers);
-        final SuggestedWords suggestedWords = getSuggestedWords();
-        showSuggestionStrip(suggestedWords, null);
-        final String gestureFloatingPreviewText = (suggestedWords.size() > 0)
+    public void onUpdateBatchInput(final InputPointers batchPointers) {
+        BatchInputUpdater.getInstance().updateGesturePreviewAndSuggestionStrip(batchPointers, this);
+    }
+
+    @Override
+    public void onEndBatchInput(final InputPointers batchPointers) {
+        final BatchInputUpdater batchInputUpdater = BatchInputUpdater.getInstance();
+        final SuggestedWords suggestedWords = batchInputUpdater.getSuggestedWordsGesture(
+                batchPointers, this);
+        batchInputUpdater.showGesturePreviewAndSuggestionStrip(
+                suggestedWords, true /* dismissGestureFloatingPreviewText */, this);
+        final String batchInputText = (suggestedWords.size() > 0)
                 ? suggestedWords.getWord(0) : null;
-        final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
-        mainKeyboardView.showGestureFloatingPreviewText(gestureFloatingPreviewText);
-        mainKeyboardView.dismissGestureFloatingPreviewText();
-        if (suggestedWords == null || suggestedWords.size() == 0) {
+        if (TextUtils.isEmpty(batchInputText)) {
             return;
         }
-        final CharSequence text = suggestedWords.getWord(0);
-        if (TextUtils.isEmpty(text)) {
-            return;
-        }
-        mWordComposer.setBatchInputWord(text);
+        mWordComposer.setBatchInputWord(batchInputText);
         mConnection.beginBatchEdit();
         if (SPACE_STATE_PHANTOM == mSpaceState) {
             sendKeyCodePoint(Keyboard.CODE_SPACE);
         }
-        mConnection.setComposingText(text, 1);
+        mConnection.setComposingText(batchInputText, 1);
         mExpectingUpdateSelection = true;
         mConnection.endBatchEdit();
         mKeyboardSwitcher.updateShiftState();
@@ -1671,7 +1752,8 @@
                 swapSwapperAndSpace();
                 mSpaceState = SPACE_STATE_SWAP_PUNCTUATION;
             } else if (SPACE_STATE_PHANTOM == spaceState
-                    && !mCurrentSettings.isWeakSpaceStripper(primaryCode)) {
+                    && !mCurrentSettings.isWeakSpaceStripper(primaryCode)
+                    && !mCurrentSettings.isPhantomSpacePromotingSymbol(primaryCode)) {
                 // If we are in phantom space state, and the user presses a separator, we want to
                 // stay in phantom space state so that the next keypress has a chance to add the
                 // space. For example, if I type "Good dat", pick "day" from the suggestion strip
@@ -1772,12 +1854,12 @@
             return;
         }
 
-        final SuggestedWords suggestedWords = getSuggestedWords();
+        final SuggestedWords suggestedWords = getSuggestedWords(Suggest.SESSION_TYPING);
         final String typedWord = mWordComposer.getTypedWord();
         showSuggestionStrip(suggestedWords, typedWord);
     }
 
-    private SuggestedWords getSuggestedWords() {
+    private SuggestedWords getSuggestedWords(final int sessionId) {
         final String typedWord = mWordComposer.getTypedWord();
         // Get the word on which we should search the bigrams. If we are composing a word, it's
         // whatever is *before* the half-committed word in the buffer, hence 2; if we aren't, we
@@ -1788,7 +1870,7 @@
                 mWordComposer.isComposingWord() ? 2 : 1);
         final SuggestedWords suggestedWords = mSuggest.getSuggestedWords(mWordComposer,
                 prevWord, mKeyboardSwitcher.getKeyboard().getProximityInfo(),
-                mCurrentSettings.mCorrectionEnabled);
+                mCurrentSettings.mCorrectionEnabled, sessionId);
         return maybeRetrieveOlderSuggestions(typedWord, suggestedWords);
     }
 
@@ -2080,7 +2162,7 @@
     }
 
     // Used by the RingCharBuffer
-    public boolean isWordSeparator(int code) {
+    public boolean isWordSeparator(final int code) {
         return mCurrentSettings.isWordSeparator(code);
     }
 
@@ -2112,14 +2194,14 @@
     // Callback called by PointerTracker through the KeyboardActionListener. This is called when a
     // key is depressed; release matching call is onReleaseKey below.
     @Override
-    public void onPressKey(int primaryCode) {
+    public void onPressKey(final int primaryCode) {
         mKeyboardSwitcher.onPressKey(primaryCode);
     }
 
     // Callback by PointerTracker through the KeyboardActionListener. This is called when a key
     // is released; press matching call is onPressKey above.
     @Override
-    public void onReleaseKey(int primaryCode, boolean withSliding) {
+    public void onReleaseKey(final int primaryCode, final boolean withSliding) {
         mKeyboardSwitcher.onReleaseKey(primaryCode, withSliding);
 
         // If accessibility is on, ensure the user receives keyboard state updates.
@@ -2148,7 +2230,7 @@
     // receive ringer mode change and network state change.
     private BroadcastReceiver mReceiver = new BroadcastReceiver() {
         @Override
-        public void onReceive(Context context, Intent intent) {
+        public void onReceive(final Context context, final Intent intent) {
             final String action = intent.getAction();
             if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
                 mSubtypeSwitcher.onNetworkStateChanged(intent);
@@ -2169,14 +2251,14 @@
         launchSubActivity(DebugSettingsActivity.class);
     }
 
-    public void launchKeyboardedDialogActivity(Class<? extends Activity> activityClass) {
+    public void launchKeyboardedDialogActivity(final Class<? extends Activity> activityClass) {
         // Put the text in the attached EditText into a safe, saved state before switching to a
         // new activity that will also use the soft keyboard.
         commitTyped(LastComposedWord.NOT_A_SEPARATOR);
         launchSubActivity(activityClass);
     }
 
-    private void launchSubActivity(Class<? extends Activity> activityClass) {
+    private void launchSubActivity(final Class<? extends Activity> activityClass) {
         Intent intent = new Intent();
         intent.setClass(LatinIME.this, activityClass);
         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
@@ -2216,7 +2298,7 @@
         showOptionDialog(builder.create());
     }
 
-    public void showOptionDialog(AlertDialog dialog) {
+    public void showOptionDialog(final AlertDialog dialog) {
         final IBinder windowToken = mKeyboardSwitcher.getMainKeyboardView().getWindowToken();
         if (windowToken == null) {
             return;
@@ -2248,7 +2330,7 @@
     }
 
     @Override
-    protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
+    protected void dump(final FileDescriptor fd, final PrintWriter fout, final String[] args) {
         super.dump(fd, fout, args);
 
         final Printer p = new PrintWriterPrinter(fout);
diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java
index 43b9ba7..b85f9dc 100644
--- a/java/src/com/android/inputmethod/latin/RichInputConnection.java
+++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java
@@ -30,6 +30,7 @@
 import com.android.inputmethod.latin.define.ProductionFlag;
 import com.android.inputmethod.research.ResearchLogger;
 
+import java.util.Locale;
 import java.util.regex.Pattern;
 
 /**
@@ -189,7 +190,7 @@
         }
     }
 
-    public int getCursorCapsMode(final int inputType) {
+    public int getCursorCapsMode(final int inputType, final Locale locale) {
         mIC = mParent.getCurrentInputConnection();
         if (null == mIC) return Constants.TextUtils.CAP_MODE_OFF;
         if (!TextUtils.isEmpty(mComposingText)) return Constants.TextUtils.CAP_MODE_OFF;
@@ -204,7 +205,7 @@
         }
         // This never calls InputConnection#getCapsMode - in fact, it's a static method that
         // never blocks or initiates IPC.
-        return StringUtils.getCapsMode(mCommittedTextBeforeComposingText, inputType);
+        return StringUtils.getCapsMode(mCommittedTextBeforeComposingText, inputType, locale);
     }
 
     public CharSequence getTextBeforeCursor(final int i, final int j) {
diff --git a/java/src/com/android/inputmethod/latin/StringUtils.java b/java/src/com/android/inputmethod/latin/StringUtils.java
index 4dec788..6dc1ea8 100644
--- a/java/src/com/android/inputmethod/latin/StringUtils.java
+++ b/java/src/com/android/inputmethod/latin/StringUtils.java
@@ -18,6 +18,8 @@
 
 import android.text.TextUtils;
 
+import com.android.inputmethod.keyboard.Keyboard; // For character constants
+
 import java.util.ArrayList;
 import java.util.Locale;
 
@@ -123,23 +125,6 @@
     }
 
     /**
-     * Returns true if cs contains any upper case characters.
-     *
-     * @param cs the CharSequence to check
-     * @return {@code true} if cs contains any upper case characters, {@code false} otherwise.
-     */
-    public static boolean hasUpperCase(final CharSequence cs) {
-        final int length = cs.length();
-        for (int i = 0, cp = 0; i < length; i += Character.charCount(cp)) {
-            cp = Character.codePointAt(cs, i);
-            if (Character.isUpperCase(cp)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    /**
      * Remove duplicates from an array of strings.
      *
      * This method will always keep the first occurrence of all strings at their position
@@ -209,19 +194,16 @@
      *
      * @param cs The text that should be checked for caps modes.
      * @param reqModes The modes to be checked: may be any combination of
-     * {@link #CAP_MODE_CHARACTERS}, {@link #CAP_MODE_WORDS}, and
-     * {@link #CAP_MODE_SENTENCES}.
+     * {@link TextUtils#CAP_MODE_CHARACTERS}, {@link TextUtils#CAP_MODE_WORDS}, and
+     * {@link TextUtils#CAP_MODE_SENTENCES}.
+     * @param locale The locale to consider for capitalization rules
      *
      * @return Returns the actual capitalization modes that can be in effect
      * at the current position, which is any combination of
-     * {@link #CAP_MODE_CHARACTERS}, {@link #CAP_MODE_WORDS}, and
-     * {@link #CAP_MODE_SENTENCES}.
+     * {@link TextUtils#CAP_MODE_CHARACTERS}, {@link TextUtils#CAP_MODE_WORDS}, and
+     * {@link TextUtils#CAP_MODE_SENTENCES}.
      */
-    public static int getCapsMode(CharSequence cs, int reqModes) {
-        int i;
-        char c;
-        int mode = 0;
-
+    public static int getCapsMode(final CharSequence cs, final int reqModes, final Locale locale) {
         // Quick description of what we want to do:
         // CAP_MODE_CHARACTERS is always on.
         // CAP_MODE_WORDS is on if there is some whitespace before the cursor.
@@ -234,14 +216,11 @@
         // be immediately preceded by punctuation, or by a string of only letters with single
         // periods interleaved.
 
-        // Step 1 : check for cap mode characters. If it's looked for, it's always on.
-        if ((reqModes & TextUtils.CAP_MODE_CHARACTERS) != 0) {
-            mode |= TextUtils.CAP_MODE_CHARACTERS;
-        }
+        // Step 1 : check for cap MODE_CHARACTERS. If it's looked for, it's always on.
         if ((reqModes & (TextUtils.CAP_MODE_WORDS | TextUtils.CAP_MODE_SENTENCES)) == 0) {
-            // Here we are not looking for words or sentences modes, so since we already evaluated
-            // mode characters, we can return.
-            return mode;
+            // Here we are not looking for MODE_WORDS or MODE_SENTENCES, so since we already
+            // evaluated MODE_CHARACTERS, we can return.
+            return TextUtils.CAP_MODE_CHARACTERS & reqModes;
         }
 
         // Step 2 : Skip (ignore at the end of input) any opening punctuation. This includes
@@ -250,9 +229,11 @@
         // it may look like a right parenthesis for example. We also include double quote and
         // single quote since they aren't start punctuation in the unicode sense, but should still
         // be skipped for English. TODO: does this depend on the language?
+        int i;
         for (i = cs.length(); i > 0; i--) {
-            c = cs.charAt(i - 1);
-            if (c != '"' && c != '\'' && Character.getType(c) != Character.START_PUNCTUATION) {
+            final char c = cs.charAt(i - 1);
+            if (c != Keyboard.CODE_DOUBLE_QUOTE && c != Keyboard.CODE_SINGLE_QUOTE
+                    && Character.getType(c) != Character.START_PUNCTUATION) {
                 break;
             }
         }
@@ -266,79 +247,135 @@
         // if the first char that's not a space or tab is a start of line (as in, either \n or
         // start of text).
         int j = i;
-        while (j > 0 && ((c = cs.charAt(j - 1)) == ' ' || c == '\t')) {
+        while (j > 0 && Character.isWhitespace(cs.charAt(j - 1))) {
             j--;
         }
-        if (j == 0 || cs.charAt(j - 1) == '\n') {
-            // Here we know we are at the start of a paragraph, so we turn on word mode.
-            // Note: I think this is entirely buggy. It will return mode words even if the app
-            // didn't request it, and it will fail to return sentence mode even if this is actually
-            // the start of a sentence. As it happens, Latin IME client code considers that mode
-            // word *implies* mode sentence and tests for non-zeroness, so it happens to work.
-            return mode | TextUtils.CAP_MODE_WORDS;
-        }
-        if ((reqModes & TextUtils.CAP_MODE_SENTENCES) == 0) {
-            // If we don't have to check for mode sentence, then we know all we need to know
-            // already. Either we have whitespace immediately before index i and we are at the
-            // start of a word, or we don't and we aren't. But we just went over any whitespace
-            // just before i and in fact j points before any whitespace, so if i != j that means
-            // there is such whitespace. In this case, we have mode words.
-            if (i != j) mode |= TextUtils.CAP_MODE_WORDS;
-            return mode;
+        if (j == 0) {
+            // There is only whitespace between the start of the text and the cursor. Both
+            // MODE_WORDS and MODE_SENTENCES should be active.
+            return (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_WORDS
+                    | TextUtils.CAP_MODE_SENTENCES) & reqModes;
         }
         if (i == j) {
-            // Finally, if we don't have whitespace before index i, it means neither mode words
+            // If we don't have whitespace before index i, it means neither MODE_WORDS
             // nor mode sentences should be on so we can return right away.
-            return mode;
+            return TextUtils.CAP_MODE_CHARACTERS & reqModes;
+        }
+        if ((reqModes & TextUtils.CAP_MODE_SENTENCES) == 0) {
+            // Here we know we have whitespace before the cursor (if not, we returned in the above
+            // if i == j clause), so we need MODE_WORDS to be on. And we don't need to evaluate
+            // MODE_SENTENCES so we can return right away.
+            return (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_WORDS) & reqModes;
         }
         // Please note that because of the reqModes & CAP_MODE_SENTENCES test a few lines above,
-        // we know that mode sentences is being requested.
+        // we know that MODE_SENTENCES is being requested.
 
-        // Step 4 : Search for sentence mode.
-        for (; j > 0; j--) {
-            // Here we look to go over any closing punctuation. This is because in dominant variants
-            // of English, the final period is placed within double quotes and maybe other closing
-            // punctuation signs.
-            // TODO: this is wrong for almost everything except American typography rules for
-            // English. It's wrong for British typography rules for English, it's wrong for French,
-            // it's wrong for German, it's wrong for Spanish, and possibly everything else.
-            // (note that American rules and British rules have nothing to do with en_US and en_GB,
-            // as both rules are used in both countries - it's merely a name for the set of rules)
-            c = cs.charAt(j - 1);
-            if (c != '"' && c != '\'' && Character.getType(c) != Character.END_PUNCTUATION) {
-                break;
-            }
-        }
-
-        if (j > 0) {
-            c = cs.charAt(j - 1);
-            if (c == '.' || c == '?' || c == '!') {
-                // Here we found a marker for sentence end (we consider these to be one of
-                // either . or ? or ! only). So this is probably the end of a sentence, but if we
-                // found a period, we still want to check the case where this is a abbreviation
-                // period rather than a full stop. To do this, we look for a period within a word
-                // before the period we just found; if any, we take that to mean it was an
-                // abbreviation.
-                // A typical example of the above is "In the U.S. ", where the last period is
-                // not a full stop and we should not capitalize.
-                // TODO: the rule below is broken. In particular it fails for runs of periods,
-                // whatever the reason. In the example "in the U.S..", the last period is a full
-                // stop following the abbreviation period, and we should capitalize but we don't.
-                // Likewise, "I don't know... " should capitalize, but fails to do so.
-                if (c == '.') {
-                    for (int k = j - 2; k >= 0; k--) {
-                        c = cs.charAt(k);
-                        if (c == '.') {
-                            return mode;
-                        }
-                        if (!Character.isLetter(c)) {
-                            break;
-                        }
-                    }
+        // Step 4 : Search for MODE_SENTENCES.
+        // English is a special case in that "American typography" rules, which are the most common
+        // in English, state that a sentence terminator immediately following a quotation mark
+        // should be swapped with it and de-duplicated (included in the quotation mark),
+        // e.g. <<Did he say, "let's go home?">>
+        // No other language has such a rule as far as I know, instead putting inside the quotation
+        // mark as the exact thing quoted and handling the surrounding punctuation independently,
+        // e.g. <<Did he say, "let's go home"?>>
+        // Hence, specifically for English, we treat this special case here.
+        if (Locale.ENGLISH.getLanguage().equals(locale.getLanguage())) {
+            for (; j > 0; j--) {
+                // Here we look to go over any closing punctuation. This is because in dominant
+                // variants of English, the final period is placed within double quotes and maybe
+                // other closing punctuation signs. This is generally not true in other languages.
+                final char c = cs.charAt(j - 1);
+                if (c != Keyboard.CODE_DOUBLE_QUOTE && c != Keyboard.CODE_SINGLE_QUOTE
+                        && Character.getType(c) != Character.END_PUNCTUATION) {
+                    break;
                 }
-                return mode | TextUtils.CAP_MODE_SENTENCES;
             }
         }
-        return mode;
+
+        if (j <= 0) return TextUtils.CAP_MODE_CHARACTERS & reqModes;
+        char c = cs.charAt(--j);
+
+        // We found the next interesting chunk of text ; next we need to determine if it's the
+        // end of a sentence. If we have a question mark or an exclamation mark, it's the end of
+        // a sentence. If it's neither, the only remaining case is the period so we get the opposite
+        // case out of the way.
+        if (c == Keyboard.CODE_QUESTION_MARK || c == Keyboard.CODE_EXCLAMATION_MARK) {
+            return (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_SENTENCES) & reqModes;
+        }
+        if (c != Keyboard.CODE_PERIOD || j <= 0) {
+            return (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_WORDS) & reqModes;
+        }
+
+        // We found out that we have a period. We need to determine if this is a full stop or
+        // otherwise sentence-ending period, or an abbreviation like "e.g.". An abbreviation
+        // looks like (\w\.){2,}
+        // To find out, we will have a simple state machine with the following states :
+        // START, WORD, PERIOD, ABBREVIATION
+        // On START : (just before the first period)
+        //           letter => WORD
+        //           whitespace => end with no caps (it was a stand-alone period)
+        //           otherwise => end with caps (several periods/symbols in a row)
+        // On WORD : (within the word just before the first period)
+        //           letter => WORD
+        //           period => PERIOD
+        //           otherwise => end with caps (it was a word with a full stop at the end)
+        // On PERIOD : (period within a potential abbreviation)
+        //           letter => LETTER
+        //           otherwise => end with caps (it was not an abbreviation)
+        // On LETTER : (letter within a potential abbreviation)
+        //           letter => LETTER
+        //           period => PERIOD
+        //           otherwise => end with no caps (it was an abbreviation)
+        // "Not an abbreviation" in the above chart essentially covers cases like "...yes.". This
+        // should capitalize.
+
+        final int START = 0;
+        final int WORD = 1;
+        final int PERIOD = 2;
+        final int LETTER = 3;
+        final int caps = (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_WORDS
+                | TextUtils.CAP_MODE_SENTENCES) & reqModes;
+        final int noCaps = (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_WORDS) & reqModes;
+        int state = START;
+        while (j > 0) {
+            c = cs.charAt(--j);
+            switch (state) {
+            case START:
+                if (Character.isLetter(c)) {
+                    state = WORD;
+                } else if (Character.isWhitespace(c)) {
+                    return noCaps;
+                } else {
+                    return caps;
+                }
+                break;
+            case WORD:
+                if (Character.isLetter(c)) {
+                    state = WORD;
+                } else if (c == Keyboard.CODE_PERIOD) {
+                    state = PERIOD;
+                } else {
+                    return caps;
+                }
+                break;
+            case PERIOD:
+                if (Character.isLetter(c)) {
+                    state = LETTER;
+                } else {
+                    return caps;
+                }
+                break;
+            case LETTER:
+                if (Character.isLetter(c)) {
+                    state = LETTER;
+                } else if (c == Keyboard.CODE_PERIOD) {
+                    state = PERIOD;
+                } else {
+                    return noCaps;
+                }
+            }
+        }
+        // Here we arrived at the start of the line. This should behave exactly like whitespace.
+        return (START == state || LETTER == state) ? noCaps : caps;
     }
 }
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index f922bc9..0418d31 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -37,6 +37,11 @@
 public class Suggest {
     public static final String TAG = Suggest.class.getSimpleName();
 
+    // Session id for
+    // {@link #getSuggestedWords(WordComposer,CharSequence,ProximityInfo,boolean,int)}.
+    public static final int SESSION_TYPING = 0;
+    public static final int SESSION_GESTURE = 1;
+
     // TODO: rename this to CORRECTION_OFF
     public static final int CORRECTION_NONE = 0;
     // TODO: rename this to CORRECTION_ON
@@ -157,13 +162,6 @@
 
     public SuggestedWords getSuggestedWords(
             final WordComposer wordComposer, CharSequence prevWordForBigram,
-            final ProximityInfo proximityInfo, final boolean isCorrectionEnabled) {
-        return getSuggestedWordsWithSessionId(
-                wordComposer, prevWordForBigram, proximityInfo, isCorrectionEnabled, 0);
-    }
-
-    public SuggestedWords getSuggestedWordsWithSessionId(
-            final WordComposer wordComposer, CharSequence prevWordForBigram,
             final ProximityInfo proximityInfo, final boolean isCorrectionEnabled, int sessionId) {
         LatinImeLogger.onStartSuggestion(prevWordForBigram);
         if (wordComposer.isBatchMode()) {
diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java
index ef10f72..6f50869 100644
--- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java
+++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java
@@ -1596,12 +1596,6 @@
         return newDict;
     }
 
-    // TODO: remove this method.
-    public static FusionDictionary readDictionaryBinary(final ByteBuffer buffer,
-            final FusionDictionary dict) throws IOException, UnsupportedFormatException {
-        return readDictionaryBinary(new ByteBufferWrapper(buffer), dict);
-    }
-
     /**
      * Basic test to find out whether the file is a binary dictionary or not.
      *
diff --git a/native/jni/src/correction.cpp b/native/jni/src/correction.cpp
index 15a7131..49e3e3c 100644
--- a/native/jni/src/correction.cpp
+++ b/native/jni/src/correction.cpp
@@ -631,7 +631,7 @@
 inline static int getQuoteCount(const unsigned short *word, const int length) {
     int quoteCount = 0;
     for (int i = 0; i < length; ++i) {
-        if (word[i] == '\'') {
+        if (word[i] == SINGLE_QUOTE) {
             ++quoteCount;
         }
     }
diff --git a/native/jni/src/proximity_info.h b/native/jni/src/proximity_info.h
index 2f258ef..0d8c6a3 100644
--- a/native/jni/src/proximity_info.h
+++ b/native/jni/src/proximity_info.h
@@ -27,6 +27,11 @@
 
 class Correction;
 
+inline bool isSkippableChar(const uint16_t character) {
+    // TODO: Do not hardcode here
+    return character == '\'' || character == '-';
+}
+
 class ProximityInfo {
  public:
     ProximityInfo(JNIEnv *env, const jstring localeJStr, const int maxProximityCharsSize,
diff --git a/native/jni/src/proximity_info_state.cpp b/native/jni/src/proximity_info_state.cpp
index 7e917a9..5cb9235 100644
--- a/native/jni/src/proximity_info_state.cpp
+++ b/native/jni/src/proximity_info_state.cpp
@@ -295,29 +295,30 @@
         const NearKeysDistanceMap *const currentNearKeysDistances,
         const NearKeysDistanceMap *const prevNearKeysDistances,
         const NearKeysDistanceMap *const prevPrevNearKeysDistances) const {
-    static const float BASE_SAMPLE_RATE_SCALE = 0.1f;
-    static const float SAVE_DISTANCE_SCALE = 20.0f;
+    static const int DISTANCE_BASE_SCALE = 100;
+    static const int SAVE_DISTANCE_SCALE = 200;
+    static const int SKIP_DISTANCE_SCALE = 25;
+    static const int CHECK_LOCALMIN_DISTANCE_THRESHOLD_SCALE = 40;
+    static const int STRAIGHT_SKIP_DISTANCE_THRESHOLD_SCALE = 50;
+    static const int CORNER_CHECK_DISTANCE_THRESHOLD_SCALE = 27;
     static const float SAVE_DISTANCE_SCORE = 2.0f;
-    static const float SKIP_DISTANCE_SCALE = 2.5f;
     static const float SKIP_DISTANCE_SCORE = -1.0f;
-    static const float CHECK_LOCALMIN_DISTANCE_THRESHOLD_SCALE = 4.0f;
     static const float CHECK_LOCALMIN_DISTANCE_SCORE = -1.0f;
     static const float STRAIGHT_ANGLE_THRESHOLD = M_PI_F / 36.0f;
-    static const float STRAIGHT_SKIP_DISTANCE_THRESHOLD_SCALE = 5.0f;
     static const float STRAIGHT_SKIP_NEAREST_DISTANCE_THRESHOLD = 0.5f;
     static const float STRAIGHT_SKIP_SCORE = -1.0f;
     static const float CORNER_ANGLE_THRESHOLD = M_PI_F / 2.0f;
-    static const float CORNER_CHECK_DISTANCE_THRESHOLD_SCALE = 2.7f;
     static const float CORNER_SCORE = 1.0f;
 
     const std::size_t size = mInputXs.size();
     if (size <= 1) {
-        return 0;
+        return 0.0f;
     }
-    const float baseSampleRate = mProximityInfo->getMostCommonKeyWidth() * BASE_SAMPLE_RATE_SCALE;
-    const float distNext = getDistanceFloat(x, y, mInputXs.back(), mInputYs.back());
-    const float distPrev = getDistanceFloat(mInputXs.back(), mInputYs.back(),
-            mInputXs[size - 2], mInputYs[size - 2]);
+    const int baseSampleRate = mProximityInfo->getMostCommonKeyWidth();
+    const int distNext = getDistanceInt(x, y, mInputXs.back(), mInputYs.back())
+            * DISTANCE_BASE_SCALE;
+    const int distPrev = getDistanceInt(mInputXs.back(), mInputYs.back(),
+            mInputXs[size - 2], mInputYs[size - 2]) * DISTANCE_BASE_SCALE;
     float score = 0.0f;
 
     // Sum of distances
@@ -469,9 +470,7 @@
         const int index = inputIndex * mProximityInfo->getKeyCount() + keyId;
         return min(mDistanceCache[index] * scale, mMaxPointToKeyLength);
     }
-    // TODO: Do not hardcode here
-    // No penalty to ' and -
-    if (codePoint == '\'' || codePoint == '-') {
+    if (isSkippableChar(codePoint)) {
         return 0;
     }
     // If the char is not a key on the keyboard then return the max length.
diff --git a/native/jni/src/unigram_dictionary.cpp b/native/jni/src/unigram_dictionary.cpp
index 6eaff48..49d044f 100644
--- a/native/jni/src/unigram_dictionary.cpp
+++ b/native/jni/src/unigram_dictionary.cpp
@@ -314,7 +314,6 @@
     correction->initCorrection(proximityInfo, inputSize, maxDepth);
 }
 
-static const char QUOTE = '\'';
 static const char SPACE = ' ';
 
 void UnigramDictionary::getOneWordSuggestions(ProximityInfo *proximityInfo,
diff --git a/tests/src/com/android/inputmethod/latin/StringUtilsTests.java b/tests/src/com/android/inputmethod/latin/StringUtilsTests.java
index 5db06ef..00cca9d 100644
--- a/tests/src/com/android/inputmethod/latin/StringUtilsTests.java
+++ b/tests/src/com/android/inputmethod/latin/StringUtilsTests.java
@@ -17,6 +17,9 @@
 package com.android.inputmethod.latin;
 
 import android.test.AndroidTestCase;
+import android.text.TextUtils;
+
+import java.util.Locale;
 
 public class StringUtilsTests extends AndroidTestCase {
     public void testContainsInArray() {
@@ -89,14 +92,50 @@
                 StringUtils.removeFromCsvIfExists("key", "key1,key,key3,key,key5"));
     }
 
-    public void testHasUpperCase() {
-        assertTrue("single upper-case string", StringUtils.hasUpperCase("String"));
-        assertTrue("multi upper-case string", StringUtils.hasUpperCase("stRInG"));
-        assertTrue("all upper-case string", StringUtils.hasUpperCase("STRING"));
-        assertTrue("upper-case string with non-letters", StringUtils.hasUpperCase("He's"));
+    private void onePathForCaps(final CharSequence cs, final int expectedResult, final int mask,
+            final Locale l) {
+        int oneTimeResult = expectedResult & mask;
+        assertEquals("After >" + cs + "<", oneTimeResult, StringUtils.getCapsMode(cs, mask, l));
+    }
 
-        assertFalse("empty string", StringUtils.hasUpperCase(""));
-        assertFalse("lower-case string", StringUtils.hasUpperCase("string"));
-        assertFalse("lower-case string with non-letters", StringUtils.hasUpperCase("he's"));
+    private void allPathsForCaps(final CharSequence cs, final int expectedResult, final Locale l) {
+        final int c = TextUtils.CAP_MODE_CHARACTERS;
+        final int w = TextUtils.CAP_MODE_WORDS;
+        final int s = TextUtils.CAP_MODE_SENTENCES;
+        onePathForCaps(cs, expectedResult, c | w | s, l);
+        onePathForCaps(cs, expectedResult, w | s, l);
+        onePathForCaps(cs, expectedResult, c | s, l);
+        onePathForCaps(cs, expectedResult, c | w, l);
+        onePathForCaps(cs, expectedResult, c, l);
+        onePathForCaps(cs, expectedResult, w, l);
+        onePathForCaps(cs, expectedResult, s, l);
+    }
+
+    public void testGetCapsMode() {
+        final int c = TextUtils.CAP_MODE_CHARACTERS;
+        final int w = TextUtils.CAP_MODE_WORDS;
+        final int s = TextUtils.CAP_MODE_SENTENCES;
+        Locale l = Locale.ENGLISH;
+        allPathsForCaps("", c | w | s, l);
+        allPathsForCaps("Word", c, l);
+        allPathsForCaps("Word.", c, l);
+        allPathsForCaps("Word ", c | w, l);
+        allPathsForCaps("Word. ", c | w | s, l);
+        allPathsForCaps("Word..", c, l);
+        allPathsForCaps("Word.. ", c | w | s, l);
+        allPathsForCaps("Word... ", c | w | s, l);
+        allPathsForCaps("Word ... ", c | w | s, l);
+        allPathsForCaps("Word . ", c | w, l);
+        allPathsForCaps("In the U.S ", c | w, l);
+        allPathsForCaps("In the U.S. ", c | w, l);
+        allPathsForCaps("Some stuff (e.g. ", c | w, l);
+        allPathsForCaps("In the U.S.. ", c | w | s, l);
+        allPathsForCaps("\"Word.\" ", c | w | s, l);
+        allPathsForCaps("\"Word\". ", c | w | s, l);
+        allPathsForCaps("\"Word\" ", c | w, l);
+        l = Locale.FRENCH;
+        allPathsForCaps("\"Word.\" ", c | w, l);
+        allPathsForCaps("\"Word\". ", c | w | s, l);
+        allPathsForCaps("\"Word\" ", c | w, l);
     }
 }
diff --git a/tools/dicttool/src/android/inputmethod/latin/dicttool/DictionaryMaker.java b/tools/dicttool/src/android/inputmethod/latin/dicttool/DictionaryMaker.java
index a613770..4f88749 100644
--- a/tools/dicttool/src/android/inputmethod/latin/dicttool/DictionaryMaker.java
+++ b/tools/dicttool/src/android/inputmethod/latin/dicttool/DictionaryMaker.java
@@ -244,7 +244,8 @@
             inStream = new FileInputStream(file);
             final ByteBuffer buffer = inStream.getChannel().map(
                     FileChannel.MapMode.READ_ONLY, 0, file.length());
-            return BinaryDictInputOutput.readDictionaryBinary(buffer, null);
+            return BinaryDictInputOutput.readDictionaryBinary(
+                    new BinaryDictInputOutput.ByteBufferWrapper(buffer), null);
         } finally {
             if (inStream != null) {
                 try {