Merge "Correct GesturePreviewTrail.getWidth() implementation"
diff --git a/java/res/values/attrs.xml b/java/res/values/attrs.xml
index 2f1a209..5c6d0e8 100644
--- a/java/res/values/attrs.xml
+++ b/java/res/values/attrs.xml
@@ -434,4 +434,9 @@
         <!-- Enable proximity characters correction. Disabled by default. -->
         <attr name="enableProximityCharsCorrection" format="boolean" />
     </declare-styleable>
+
+    <declare-styleable name="SeekBarDialogPreference">
+        <attr name="valueFormatText" format="reference" />
+        <attr name="maxValue" format="integer" />
+    </declare-styleable>
 </resources>
diff --git a/java/res/values/config.xml b/java/res/values/config.xml
index dca370a..503e923 100644
--- a/java/res/values/config.xml
+++ b/java/res/values/config.xml
@@ -33,6 +33,7 @@
     <bool name="config_default_next_word_prediction">true</bool>
     <bool name="config_default_sound_enabled">false</bool>
     <bool name="config_default_vibration_enabled">true</bool>
+    <integer name="config_max_vibration_duration">250</integer> <!-- milliseconds -->
     <integer name="config_delay_update_suggestions">100</integer>
     <integer name="config_delay_update_old_suggestions">300</integer>
     <integer name="config_delay_update_shift_state">100</integer>
diff --git a/java/res/values/donottranslate.xml b/java/res/values/donottranslate.xml
index 70ace77..36412b4 100644
--- a/java/res/values/donottranslate.xml
+++ b/java/res/values/donottranslate.xml
@@ -29,19 +29,6 @@
     <string name="symbols_word_separators">"&#x0009;&#x0020;\n"()[]{}*&amp;&lt;&gt;+=|.,;:!?/_\"</string>
     <!-- Word connectors -->
     <string name="symbols_word_connectors">\'-</string>
-    <!-- Symbol characters list that should switch back to the main layout -->
-    <!-- U+2018: "‘" LEFT SINGLE QUOTATION MARK
-         U+2019: "’" RIGHT SINGLE QUOTATION MARK
-         U+201A: "‚" SINGLE LOW-9 QUOTATION MARK
-         U+201B: "‛" SINGLE HIGH-REVERSED-9 QUOTATION MARK
-         U+201C: "“" LEFT DOUBLE QUOTATION MARK
-         U+201D: "”" RIGHT DOUBLE QUOTATION MARK
-         U+201E: "„" DOUBLE LOW-9 QUOTATION MARK
-         U+201F: "‟" DOUBLE HIGH-REVERSED-9 QUOTATION MARK
-         U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
-         U+00BB: "»" RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK -->
-    <!-- <string name="layout_switch_back_symbols">\"\'&#x2018;&#x2019;&#x201A;&#x201B;&#x201C;&#x201D;&#x201E;&#x201F;&#x00AB;&#x00BB;</string> -->
-    <string name="layout_switch_back_symbols"></string>
 
     <!--  Always show the suggestion strip -->
     <string name="prefs_suggestion_visibility_show_value">0</string>
diff --git a/java/res/values/strings.xml b/java/res/values/strings.xml
index f9d51ff..bf0ba40 100644
--- a/java/res/values/strings.xml
+++ b/java/res/values/strings.xml
@@ -76,8 +76,8 @@
     <!-- Description for delay for dismissing a popup on screen: default value of the delay [CHAR LIMIT=15] -->
     <string name="key_preview_popup_dismiss_default_delay">Default</string>
 
-    <!-- Units abbreviation for the keypress vibration duration (milliseconds) [CHAR LIMIT=10] -->
-    <string name="settings_keypress_vibration_duration"><xliff:g id="milliseconds">%s</xliff:g>ms</string>
+    <!-- Units abbreviation for the duration (milliseconds) [CHAR LIMIT=10] -->
+    <string name="abbreviation_unit_milliseconds"><xliff:g id="milliseconds">%s</xliff:g>ms</string>
 
     <!-- Option name for enabling or disabling the use of names of people in Contacts for suggestion and correction [CHAR LIMIT=25] -->
     <string name="use_contacts_dict">Suggest Contact names</string>
diff --git a/java/res/xml/prefs.xml b/java/res/xml/prefs.xml
index 4ffbf14..cc1b52b 100644
--- a/java/res/xml/prefs.xml
+++ b/java/res/xml/prefs.xml
@@ -16,6 +16,7 @@
 
 <PreferenceScreen
     xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
     android:title="@string/english_ime_settings"
     android:key="english_ime_settings">
     <PreferenceCategory
@@ -144,12 +145,15 @@
             <ListPreference
                 android:key="pref_key_preview_popup_dismiss_delay"
                 android:title="@string/key_preview_popup_dismiss_delay" />
-            <PreferenceScreen
+            <com.android.inputmethod.latin.SeekBarDialogPreference
                 android:key="pref_vibration_duration_settings"
-                android:title="@string/prefs_keypress_vibration_duration_settings"/>
-            <PreferenceScreen
+                android:title="@string/prefs_keypress_vibration_duration_settings"
+                latin:valueFormatText="@string/abbreviation_unit_milliseconds"
+                latin:maxValue="@integer/config_max_vibration_duration" />
+            <com.android.inputmethod.latin.SeekBarDialogPreference
                 android:key="pref_keypress_sound_volume"
-                android:title="@string/prefs_keypress_sound_volume_settings" />
+                android:title="@string/prefs_keypress_sound_volume_settings"
+                latin:maxValue="100" /> <!-- percent -->
         </PreferenceScreen>
         <PreferenceScreen
             android:key="debug_settings"
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
index bc9dbc0..bad9a8a 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
@@ -149,7 +149,7 @@
                 settingsValues.isLanguageSwitchKeyEnabled());
         mKeyboardLayoutSet = builder.build();
         try {
-            mState.onLoadKeyboard(mResources.getString(R.string.layout_switch_back_symbols));
+            mState.onLoadKeyboard();
             mFeedbackManager.onSettingsChanged(settingsValues);
         } catch (KeyboardLayoutSetException e) {
             Log.w(TAG, "loading keyboard failed: " + e.mKeyboardId, e.getCause());
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
index 25a1c6a..5a77044 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
@@ -26,7 +26,7 @@
  *
  * This class contains all keyboard state transition logic.
  *
- * The input events are {@link #onLoadKeyboard(String)}, {@link #onSaveKeyboardState()},
+ * 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)}.
@@ -74,7 +74,6 @@
     private static final int SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL = 3;
     private static final int SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE = 4;
     private int mSwitchState = SWITCH_STATE_ALPHA;
-    private String mLayoutSwitchBackSymbols;
 
     private boolean mIsAlphabetMode;
     private AlphabetShiftState mAlphabetShiftState = new AlphabetShiftState();
@@ -109,15 +108,14 @@
         }
     }
 
-    public KeyboardState(SwitchActions switchActions) {
+    public KeyboardState(final SwitchActions switchActions) {
         mSwitchActions = switchActions;
     }
 
-    public void onLoadKeyboard(String layoutSwitchBackSymbols) {
+    public void onLoadKeyboard() {
         if (DEBUG_EVENT) {
             Log.d(TAG, "onLoadKeyboard: " + this);
         }
-        mLayoutSwitchBackSymbols = layoutSwitchBackSymbols;
         // Reset alphabet shift state.
         mAlphabetShiftState.setShiftLocked(false);
         mPrevMainKeyboardWasShiftLocked = false;
@@ -177,7 +175,7 @@
     private static final int AUTOMATIC_SHIFT = 2;
     private static final int SHIFT_LOCK_SHIFTED = 3;
 
-    private void setShifted(int shiftMode) {
+    private void setShifted(final int shiftMode) {
         if (DEBUG_ACTION) {
             Log.d(TAG, "setShifted: shiftMode=" + shiftModeToString(shiftMode) + " " + this);
         }
@@ -216,7 +214,7 @@
         }
     }
 
-    private void setShiftLocked(boolean shiftLocked) {
+    private void setShiftLocked(final boolean shiftLocked) {
         if (DEBUG_ACTION) {
             Log.d(TAG, "setShiftLocked: shiftLocked=" + shiftLocked + " " + this);
         }
@@ -313,7 +311,7 @@
         mSwitchState = SWITCH_STATE_SYMBOL_BEGIN;
     }
 
-    public void onPressKey(int code, boolean isSinglePointer, int autoCaps) {
+    public void onPressKey(final int code, final boolean isSinglePointer, final int autoCaps) {
         if (DEBUG_EVENT) {
             Log.d(TAG, "onPressKey: code=" + Constants.printableCode(code)
                    + " single=" + isSinglePointer + " autoCaps=" + autoCaps + " " + this);
@@ -346,7 +344,7 @@
         }
     }
 
-    public void onReleaseKey(int code, boolean withSliding) {
+    public void onReleaseKey(final int code, final boolean withSliding) {
         if (DEBUG_EVENT) {
             Log.d(TAG, "onReleaseKey: code=" + Constants.printableCode(code)
                     + " sliding=" + withSliding + " " + this);
@@ -364,7 +362,7 @@
         mSwitchState = SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL;
     }
 
-    private void onReleaseSymbol(boolean withSliding) {
+    private void onReleaseSymbol(final boolean withSliding) {
         if (mSymbolKeyState.isChording()) {
             // Switch back to the previous keyboard mode if the user chords the mode change key and
             // another key, then releases the mode change key.
@@ -378,7 +376,7 @@
         mSymbolKeyState.onRelease();
     }
 
-    public void onLongPressTimeout(int code) {
+    public void onLongPressTimeout(final int code) {
         if (DEBUG_EVENT) {
             Log.d(TAG, "onLongPressTimeout: code=" + Constants.printableCode(code) + " " + this);
         }
@@ -388,7 +386,7 @@
         }
     }
 
-    public void onUpdateShiftState(int autoCaps) {
+    public void onUpdateShiftState(final int autoCaps) {
         if (DEBUG_EVENT) {
             Log.d(TAG, "onUpdateShiftState: autoCaps=" + autoCaps + " " + this);
         }
@@ -404,7 +402,7 @@
         resetKeyboardStateToAlphabet();
     }
 
-    private void updateAlphabetShiftState(int autoCaps) {
+    private void updateAlphabetShiftState(final int autoCaps) {
         if (!mIsAlphabetMode) return;
         if (!mShiftKeyState.isReleasing()) {
             // Ignore update shift state event while the shift key is being pressed (including
@@ -468,7 +466,7 @@
         }
     }
 
-    private void onReleaseShift(boolean withSliding) {
+    private void onReleaseShift(final boolean withSliding) {
         if (mIsAlphabetMode) {
             final boolean isShiftLocked = mAlphabetShiftState.isShiftLocked();
             mIsInAlphabetUnshiftedFromShifted = false;
@@ -523,7 +521,7 @@
         mShiftKeyState.onRelease();
     }
 
-    public void onCancelInput(boolean isSinglePointer) {
+    public void onCancelInput(final boolean isSinglePointer) {
         if (DEBUG_EVENT) {
             Log.d(TAG, "onCancelInput: single=" + isSinglePointer + " " + this);
         }
@@ -542,17 +540,11 @@
                 || mSwitchState == SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE;
     }
 
-    private static boolean isSpaceCharacter(int c) {
+    private static boolean isSpaceCharacter(final int c) {
         return c == Constants.CODE_SPACE || c == Constants.CODE_ENTER;
     }
 
-    private boolean isLayoutSwitchBackCharacter(int c) {
-        if (TextUtils.isEmpty(mLayoutSwitchBackSymbols)) return false;
-        if (mLayoutSwitchBackSymbols.indexOf(c) >= 0) return true;
-        return false;
-    }
-
-    public void onCodeInput(int code, boolean isSinglePointer, int autoCaps) {
+    public void onCodeInput(final int code, final boolean isSinglePointer, final int autoCaps) {
         if (DEBUG_EVENT) {
             Log.d(TAG, "onCodeInput: code=" + Constants.printableCode(code)
                     + " single=" + isSinglePointer
@@ -592,17 +584,11 @@
                     || code == Constants.CODE_OUTPUT_TEXT)) {
                 mSwitchState = SWITCH_STATE_SYMBOL;
             }
-            // Switch back to alpha keyboard mode immediately if user types one of the switch back
-            // characters.
-            if (isLayoutSwitchBackCharacter(code)) {
-                toggleAlphabetAndSymbols();
-                mPrevSymbolsKeyboardWasShifted = false;
-            }
             break;
         case SWITCH_STATE_SYMBOL:
             // Switch back to alpha keyboard mode if user types one or more non-space/enter
-            // characters followed by a space/enter or one of the switch back characters.
-            if (isSpaceCharacter(code) || isLayoutSwitchBackCharacter(code)) {
+            // characters followed by a space/enter.
+            if (isSpaceCharacter(code)) {
                 toggleAlphabetAndSymbols();
                 mPrevSymbolsKeyboardWasShifted = false;
             }
@@ -615,7 +601,7 @@
         }
     }
 
-    private static String shiftModeToString(int shiftMode) {
+    private static String shiftModeToString(final int shiftMode) {
         switch (shiftMode) {
         case UNSHIFT: return "UNSHIFT";
         case MANUAL_SHIFT: return "MANUAL";
@@ -624,7 +610,7 @@
         }
     }
 
-    private static String switchStateToString(int switchState) {
+    private static String switchStateToString(final int switchState) {
         switch (switchState) {
         case SWITCH_STATE_ALPHA: return "ALPHA";
         case SWITCH_STATE_SYMBOL_BEGIN: return "SYMBOL-BEGIN";
diff --git a/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java b/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java
index 8a16131..f859d0b 100644
--- a/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java
+++ b/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java
@@ -29,8 +29,6 @@
  * complexity of settings and the like.
  */
 public final class AudioAndHapticFeedbackManager {
-    public static final int MAX_KEYPRESS_VIBRATION_DURATION = 250; // millisecond
-
     private AudioManager mAudioManager;
     private Vibrator mVibrator;
 
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index d02c4df..f4b7a17 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -773,7 +773,8 @@
         // to the user dictionary.
         if (null != mPositionalInfoForUserDictPendingAddition
                 && mPositionalInfoForUserDictPendingAddition.tryReplaceWithActualWord(
-                        mConnection, editorInfo, mLastSelectionEnd)) {
+                        mConnection, editorInfo, mLastSelectionEnd,
+                        mSubtypeSwitcher.getCurrentSubtypeLocale())) {
             mPositionalInfoForUserDictPendingAddition = null;
         }
         // If tryReplaceWithActualWord returns false, we don't know what word was
@@ -1223,11 +1224,17 @@
             mPositionalInfoForUserDictPendingAddition = null;
             return;
         }
+        final String wordToEdit;
+        if (StringUtils.isAutoCapsMode(mLastComposedWord.mCapitalizedMode)) {
+            wordToEdit = word.toLowerCase(mSubtypeSwitcher.getCurrentSubtypeLocale());
+        } else {
+            wordToEdit = word;
+        }
         mPositionalInfoForUserDictPendingAddition =
                 new PositionalInfoForUserDictPendingAddition(
-                        word, mLastSelectionEnd, getCurrentInputEditorInfo(),
+                        wordToEdit, mLastSelectionEnd, getCurrentInputEditorInfo(),
                         mLastComposedWord.mCapitalizedMode);
-        mUserDictionary.addWordToUserDictionary(word, 128);
+        mUserDictionary.addWordToUserDictionary(wordToEdit);
     }
 
     public void onWordAddedToUserDictionary(final String newSpelling) {
@@ -1240,7 +1247,8 @@
         }
         mPositionalInfoForUserDictPendingAddition.setActualWordBeingAdded(newSpelling);
         if (mPositionalInfoForUserDictPendingAddition.tryReplaceWithActualWord(
-                mConnection, getCurrentInputEditorInfo(), mLastSelectionEnd)) {
+                mConnection, getCurrentInputEditorInfo(), mLastSelectionEnd,
+                mSubtypeSwitcher.getCurrentSubtypeLocale())) {
             mPositionalInfoForUserDictPendingAddition = null;
         }
     }
@@ -1476,7 +1484,10 @@
                     Stats.onAutoCorrection("", mWordComposer.getTypedWord(), " ", mWordComposer);
                 }
             }
-            if (mWordComposer.size() <= 1) {
+            final int wordComposerSize = mWordComposer.size();
+            // Since isComposingWord() is true, the size is at least 1.
+            final int lastChar = mWordComposer.getCodeAt(wordComposerSize - 1);
+            if (wordComposerSize <= 1) {
                 // We auto-correct the previous (typed, not gestured) string iff it's one character
                 // long. The reason for this is, even in the middle of gesture typing, you'll still
                 // tap one-letter words and you want them auto-corrected (typically, "i" in English
@@ -1490,8 +1501,14 @@
             }
             mExpectingUpdateSelection = true;
             // The following is necessary for the case where the user typed something but didn't
-            // manual pick it and didn't input any separator.
-            mSpaceState = SPACE_STATE_PHANTOM;
+            // manual pick it and didn't input any separator: we want to put a space between what
+            // has been entered and the coming gesture input result, so we go into phantom space
+            // state, which will be promoted to a space when the gesture result is committed. But if
+            // the current input ends in a word connector on the other hand, then we want to have
+            // the next input stick to the current input so we don't switch to phantom space state.
+            if (!mSettings.getCurrent().isWordConnector(lastChar)) {
+                mSpaceState = SPACE_STATE_PHANTOM;
+            }
         } else {
             final int codePointBeforeCursor = mConnection.getCodePointBeforeCursor();
             if (mSettings.getCurrent().isUsuallyFollowedBySpace(codePointBeforeCursor)) {
diff --git a/java/src/com/android/inputmethod/latin/PositionalInfoForUserDictPendingAddition.java b/java/src/com/android/inputmethod/latin/PositionalInfoForUserDictPendingAddition.java
index a33cefc..8493ef6 100644
--- a/java/src/com/android/inputmethod/latin/PositionalInfoForUserDictPendingAddition.java
+++ b/java/src/com/android/inputmethod/latin/PositionalInfoForUserDictPendingAddition.java
@@ -18,6 +18,8 @@
 
 import android.view.inputmethod.EditorInfo;
 
+import java.util.Locale;
+
 /**
  * Holder class for data about a word already committed but that may still be edited.
  *
@@ -70,10 +72,11 @@
      * @param connection The RichInputConnection through which to contact the editor.
      * @param editorInfo Information pertaining to the editor we are currently in.
      * @param currentCursorPosition The current cursor position, for checking purposes.
+     * @param locale The locale for changing case, if necessary
      * @return true if the edit has been successfully made, false if we need to try again later
      */
     public boolean tryReplaceWithActualWord(final RichInputConnection connection,
-            final EditorInfo editorInfo, final int currentCursorPosition) {
+            final EditorInfo editorInfo, final int currentCursorPosition, final Locale locale) {
         // If we still don't know the actual word being added, we need to try again later.
         if (null == mActualWordBeingAdded) return false;
         // The entered text and the registered text were the same anyway : we can
@@ -92,9 +95,12 @@
         // so that it won't be tried again
         if (currentCursorPosition != mCursorPos) return true;
         // We have made all the checks : do the replacement and report success
+        // If this was auto-capitalized, we need to restore the case before committing
+        final String wordWithCaseFixed = StringUtils.applyAutoCapsMode(mActualWordBeingAdded,
+                mCapitalizedMode, locale);
         connection.setComposingRegion(currentCursorPosition - mOriginalWord.length(),
                 currentCursorPosition);
-        connection.commitText(mActualWordBeingAdded, mActualWordBeingAdded.length());
+        connection.commitText(wordWithCaseFixed, wordWithCaseFixed.length());
         return true;
     }
 }
diff --git a/java/src/com/android/inputmethod/latin/SeekBarDialog.java b/java/src/com/android/inputmethod/latin/SeekBarDialog.java
deleted file mode 100644
index c736d1b..0000000
--- a/java/src/com/android/inputmethod/latin/SeekBarDialog.java
+++ /dev/null
@@ -1,201 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.android.inputmethod.latin;
-
-import android.app.AlertDialog;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.SeekBar;
-import android.widget.SeekBar.OnSeekBarChangeListener;
-import android.widget.TextView;
-
-public final class SeekBarDialog implements DialogInterface.OnClickListener,
-        OnSeekBarChangeListener {
-    public interface Listener {
-        public void onPositiveButtonClick(final SeekBarDialog dialog);
-        public void onNegativeButtonClick(final SeekBarDialog dialog);
-        public void onNeutralButtonClick(final SeekBarDialog dialog);
-        public void onDismiss(final SeekBarDialog dialog);
-        public void onProgressChanged(final SeekBarDialog dialog);
-        public void onStartTrackingTouch(final SeekBarDialog dialog);
-        public void onStopTrackingTouch(final SeekBarDialog dialog);
-    }
-
-    public static class Adapter implements Listener {
-        @Override
-        public void onPositiveButtonClick(final SeekBarDialog dialog) {}
-        @Override
-        public void onNegativeButtonClick(final SeekBarDialog dialog) {}
-        @Override
-        public void onNeutralButtonClick(final SeekBarDialog dialog) {}
-        @Override
-        public void onDismiss(final SeekBarDialog dialog) {}
-        @Override
-        public void onProgressChanged(final SeekBarDialog dialog) {}
-        @Override
-        public void onStartTrackingTouch(final SeekBarDialog dialog) {}
-        @Override
-        public void onStopTrackingTouch(final SeekBarDialog dialog) {}
-    }
-
-    private static final Listener EMPTY_ADAPTER = new Adapter();
-
-    private final AlertDialog mDialog;
-    private final Listener mListener;
-    private final TextView mValueView;
-    private final SeekBar mSeekBar;
-    private final String mValueFormat;
-
-    private int mValue;
-
-    private SeekBarDialog(final Builder builder) {
-        final AlertDialog.Builder dialogBuilder = builder.mDialogBuilder;
-        dialogBuilder.setView(builder.mView);
-        dialogBuilder.setPositiveButton(android.R.string.ok, this);
-        dialogBuilder.setNegativeButton(android.R.string.cancel, this);
-        if (builder.mNeutralButtonTextResId != 0) {
-            dialogBuilder.setNeutralButton(builder.mNeutralButtonTextResId, this);
-        }
-        mDialog = dialogBuilder.create();
-        mListener = (builder.mListener == null) ? EMPTY_ADAPTER : builder.mListener;
-        mValueView = (TextView)builder.mView.findViewById(R.id.seek_bar_dialog_value);
-        mSeekBar = (SeekBar)builder.mView.findViewById(R.id.seek_bar_dialog_bar);
-        mSeekBar.setMax(builder.mMaxValue);
-        mSeekBar.setOnSeekBarChangeListener(this);
-        if (builder.mValueFormatResId == 0) {
-            mValueFormat = "%s";
-        } else {
-            mValueFormat = mDialog.getContext().getString(builder.mValueFormatResId);
-        }
-    }
-
-    public void setValue(final int value, final boolean fromUser) {
-        mValue = value;
-        mValueView.setText(String.format(mValueFormat, value));
-        if (!fromUser) {
-            mSeekBar.setProgress(value);
-        }
-    }
-
-    public int getValue() {
-        return mValue;
-    }
-
-    public CharSequence getValueText() {
-        return mValueView.getText();
-    }
-
-    public void show() {
-        mDialog.show();
-    }
-
-    public void dismiss() {
-        mDialog.dismiss();
-    }
-
-    @Override
-    public void onClick(final DialogInterface dialog, final int which) {
-        switch (which) {
-        case DialogInterface.BUTTON_POSITIVE:
-            mListener.onPositiveButtonClick(this);
-            break;
-        case DialogInterface.BUTTON_NEGATIVE:
-            mListener.onNegativeButtonClick(this);
-            break;
-        case DialogInterface.BUTTON_NEUTRAL:
-            mListener.onNeutralButtonClick(this);
-            break;
-        default:
-            return;
-        }
-        mListener.onDismiss(this);
-    }
-
-    @Override
-    public void onProgressChanged(final SeekBar seekBar, final int progress,
-            final boolean fromUser) {
-        setValue(progress, fromUser);
-        if (fromUser) {
-            mListener.onProgressChanged(this);
-        }
-    }
-
-    @Override
-    public void onStartTrackingTouch(final SeekBar seekBar) {
-        mListener.onStartTrackingTouch(this);
-    }
-
-    @Override
-    public void onStopTrackingTouch(final SeekBar seekBar) {
-        mListener.onStopTrackingTouch(this);
-    }
-
-    public static final class Builder {
-        final AlertDialog.Builder mDialogBuilder;
-        final View mView;
-
-        int mNeutralButtonTextResId;
-        int mMaxValue;
-        int mValueFormatResId;
-        int mValue;
-        Listener mListener;
-
-        public Builder(final Context context) {
-            mDialogBuilder = new AlertDialog.Builder(context);
-            mView = LayoutInflater.from(context).inflate(R.layout.seek_bar_dialog, null);
-        }
-
-        public Builder setTitle(final int resId) {
-            mDialogBuilder.setTitle(resId);
-            return this;
-        }
-
-        public Builder setNeutralButtonText(final int resId) {
-            mNeutralButtonTextResId = resId;
-            return this;
-        }
-
-        public Builder setMaxValue(final int max) {
-            mMaxValue = max;
-            mValue = Math.min(mValue, max);
-            return this;
-        }
-
-        public Builder setValueFromat(final int resId) {
-            mValueFormatResId = resId;
-            return this;
-        }
-
-        public Builder setValue(final int value) {
-            mValue = Math.min(value, mMaxValue);
-            return this;
-        }
-
-        public Builder setListener(final Listener listener) {
-            mListener = listener;
-            return this;
-        }
-
-        public SeekBarDialog create() {
-            final SeekBarDialog dialog = new SeekBarDialog(this);
-            dialog.setValue(mValue, false /* fromUser */);
-            return dialog;
-        }
-    }
-}
diff --git a/java/src/com/android/inputmethod/latin/SeekBarDialogPreference.java b/java/src/com/android/inputmethod/latin/SeekBarDialogPreference.java
new file mode 100644
index 0000000..56a6a9b
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/SeekBarDialogPreference.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.res.TypedArray;
+import android.preference.DialogPreference;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.SeekBar;
+import android.widget.TextView;
+
+public final class SeekBarDialogPreference extends DialogPreference
+        implements SeekBar.OnSeekBarChangeListener {
+    public interface ValueProxy {
+        public int readValue(final String key);
+        public int readDefaultValue(final String key);
+        public void writeValue(final int value, final String key);
+        public void feedbackValue(final int value);
+    }
+
+    private final int mValueFormatResId;
+    private final int mMaxValue;
+
+    private TextView mValueView;
+    private SeekBar mSeekBar;
+
+    private ValueProxy mValueProxy;
+
+    public SeekBarDialogPreference(final Context context, final AttributeSet attrs) {
+        super(context, attrs);
+        final TypedArray a = context.obtainStyledAttributes(
+                attrs, R.styleable.SeekBarDialogPreference, 0, 0);
+        mValueFormatResId = a.getResourceId(R.styleable.SeekBarDialogPreference_valueFormatText, 0);
+        mMaxValue = a.getInt(R.styleable.SeekBarDialogPreference_maxValue, 0);
+        a.recycle();
+        setDialogLayoutResource(R.layout.seek_bar_dialog);
+    }
+
+    public void setInterface(final ValueProxy proxy) {
+        mValueProxy = proxy;
+        setSummary(getValueText(proxy.readValue(getKey())));
+    }
+
+    private String getValueText(final int value) {
+        if (mValueFormatResId == 0) {
+            return Integer.toString(value);
+        } else {
+            return getContext().getString(mValueFormatResId, value);
+        }
+    }
+
+    @Override
+    protected View onCreateDialogView() {
+        final View view = super.onCreateDialogView();
+        mSeekBar = (SeekBar)view.findViewById(R.id.seek_bar_dialog_bar);
+        mSeekBar.setMax(mMaxValue);
+        mSeekBar.setOnSeekBarChangeListener(this);
+        mValueView = (TextView)view.findViewById(R.id.seek_bar_dialog_value);
+        return view;
+    }
+
+    private void setValue(final int value, final boolean fromUser) {
+        mValueView.setText(getValueText(value));
+        if (!fromUser) {
+            mSeekBar.setProgress(value);
+        }
+    }
+
+    @Override
+    protected void onBindDialogView(final View view) {
+        setValue(mValueProxy.readValue(getKey()), false /* fromUser */);
+    }
+
+    @Override
+    protected void onPrepareDialogBuilder(final AlertDialog.Builder builder) {
+        builder.setPositiveButton(android.R.string.ok, this)
+            .setNegativeButton(android.R.string.cancel, this)
+            .setNeutralButton(R.string.button_default, this);
+    }
+
+    @Override
+    public void onClick(final DialogInterface dialog, final int which) {
+        super.onClick(dialog, which);
+        if (which == DialogInterface.BUTTON_NEUTRAL) {
+            setValue(mValueProxy.readDefaultValue(getKey()), false /* fromUser */);
+        }
+        if (which != DialogInterface.BUTTON_NEGATIVE) {
+            setSummary(mValueView.getText());
+            mValueProxy.writeValue(mSeekBar.getProgress(), getKey());
+        }
+    }
+
+    @Override
+    public void onProgressChanged(final SeekBar seekBar, final int progress,
+            final boolean fromUser) {
+        setValue(progress, fromUser);
+    }
+
+    @Override
+    public void onStartTrackingTouch(final SeekBar seekBar) {}
+
+    @Override
+    public void onStopTrackingTouch(final SeekBar seekBar) {
+        mValueProxy.feedbackValue(seekBar.getProgress());
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/SettingsFragment.java b/java/src/com/android/inputmethod/latin/SettingsFragment.java
index 6a43718..3558d81 100644
--- a/java/src/com/android/inputmethod/latin/SettingsFragment.java
+++ b/java/src/com/android/inputmethod/latin/SettingsFragment.java
@@ -26,7 +26,6 @@
 import android.preference.CheckBoxPreference;
 import android.preference.ListPreference;
 import android.preference.Preference;
-import android.preference.Preference.OnPreferenceClickListener;
 import android.preference.PreferenceGroup;
 import android.preference.PreferenceScreen;
 import android.view.inputmethod.InputMethodSubtype;
@@ -36,8 +35,6 @@
 
 public final class SettingsFragment extends InputMethodSettingsFragment
         implements SharedPreferences.OnSharedPreferenceChangeListener {
-    private PreferenceScreen mKeypressVibrationDurationSettingsPref;
-    private PreferenceScreen mKeypressSoundVolumeSettingsPref;
     private ListPreference mVoicePreference;
     private ListPreference mShowCorrectionSuggestionsPreference;
     private ListPreference mAutoCorrectionThresholdPreference;
@@ -167,36 +164,8 @@
             getPreferenceScreen().removePreference(gestureTypingSettings);
         }
 
-        mKeypressVibrationDurationSettingsPref =
-                (PreferenceScreen) findPreference(Settings.PREF_VIBRATION_DURATION_SETTINGS);
-        if (mKeypressVibrationDurationSettingsPref != null) {
-            mKeypressVibrationDurationSettingsPref.setOnPreferenceClickListener(
-                    new OnPreferenceClickListener() {
-                        @Override
-                        public boolean onPreferenceClick(Preference arg0) {
-                            showKeypressVibrationDurationSettingsDialog();
-                            return true;
-                        }
-                    });
-            mKeypressVibrationDurationSettingsPref.setSummary(
-                    res.getString(R.string.settings_keypress_vibration_duration,
-                            Settings.readKeypressVibrationDuration(prefs, res)));
-        }
-
-        mKeypressSoundVolumeSettingsPref =
-                (PreferenceScreen) findPreference(Settings.PREF_KEYPRESS_SOUND_VOLUME);
-        if (mKeypressSoundVolumeSettingsPref != null) {
-            mKeypressSoundVolumeSettingsPref.setOnPreferenceClickListener(
-                    new OnPreferenceClickListener() {
-                        @Override
-                        public boolean onPreferenceClick(Preference arg0) {
-                            showKeypressSoundVolumeSettingDialog();
-                            return true;
-                        }
-                    });
-            mKeypressSoundVolumeSettingsPref.setSummary(String.valueOf(
-                    getCurrentKeyPressSoundVolumePercent(prefs, res)));
-        }
+        setupKeypressVibrationDurationSettings(prefs, res);
+        setupKeypressSoundVolumeSettings(prefs, res);
         refreshEnablingsOfKeypressSoundAndVibrationSettings(prefs, res);
     }
 
@@ -287,127 +256,86 @@
 
     private void refreshEnablingsOfKeypressSoundAndVibrationSettings(
             final SharedPreferences sp, final Resources res) {
-        if (mKeypressVibrationDurationSettingsPref != null) {
-            final boolean hasVibratorHardware =
-                    AudioAndHapticFeedbackManager.getInstance().hasVibrator();
-            final boolean vibrateOnByUser = sp.getBoolean(Settings.PREF_VIBRATE_ON,
-                    res.getBoolean(R.bool.config_default_vibration_enabled));
-            mKeypressVibrationDurationSettingsPref.setEnabled(
-                    hasVibratorHardware && vibrateOnByUser);
-        }
+        final boolean hasVibratorHardware =
+                AudioAndHapticFeedbackManager.getInstance().hasVibrator();
+        final boolean vibrateOnByUser = sp.getBoolean(Settings.PREF_VIBRATE_ON,
+                res.getBoolean(R.bool.config_default_vibration_enabled));
+        setPreferenceEnabled(Settings.PREF_VIBRATION_DURATION_SETTINGS,
+                hasVibratorHardware && vibrateOnByUser);
 
-        if (mKeypressSoundVolumeSettingsPref != null) {
-            final boolean soundOn = sp.getBoolean(Settings.PREF_SOUND_ON,
-                    res.getBoolean(R.bool.config_default_sound_enabled));
-            mKeypressSoundVolumeSettingsPref.setEnabled(soundOn);
-        }
+        final boolean soundOn = sp.getBoolean(Settings.PREF_SOUND_ON,
+                res.getBoolean(R.bool.config_default_sound_enabled));
+        setPreferenceEnabled(Settings.PREF_KEYPRESS_SOUND_VOLUME, soundOn);
     }
 
-    private void showKeypressVibrationDurationSettingsDialog() {
-        final SharedPreferences sp = getPreferenceManager().getSharedPreferences();
-        final Context context = getActivity();
-        final PreferenceScreen settingsPref = mKeypressVibrationDurationSettingsPref;
-        final SeekBarDialog.Listener listener = new SeekBarDialog.Adapter() {
-            private void writePreference(final SharedPreferences sp, final int value) {
-                sp.edit().putInt(Settings.PREF_VIBRATION_DURATION_SETTINGS, value).apply();
+    private void setupKeypressVibrationDurationSettings(final SharedPreferences sp,
+            final Resources res) {
+        final SeekBarDialogPreference pref = (SeekBarDialogPreference)findPreference(
+                Settings.PREF_VIBRATION_DURATION_SETTINGS);
+        if (pref == null) {
+            return;
+        }
+        pref.setInterface(new SeekBarDialogPreference.ValueProxy() {
+            @Override
+            public void writeValue(final int value, final String key) {
+                sp.edit().putInt(key, value).apply();
             }
 
-            private void feedbackSettingsValue(final int value) {
+            @Override
+            public int readValue(final String key) {
+                return Settings.readKeypressVibrationDuration(sp, res);
+            }
+
+            @Override
+            public int readDefaultValue(final String key) {
+                return Settings.readDefaultKeypressVibrationDuration(res);
+            }
+
+            @Override
+            public void feedbackValue(final int value) {
                 AudioAndHapticFeedbackManager.getInstance().vibrate(value);
             }
-
-            @Override
-            public void onPositiveButtonClick(final SeekBarDialog dialog) {
-                writePreference(sp, dialog.getValue());
-            }
-
-            @Override
-            public void onNeutralButtonClick(final SeekBarDialog dialog) {
-                final int defaultValue =
-                        Settings.readDefaultKeypressVibrationDuration(context.getResources());
-                dialog.setValue(defaultValue, false /* fromUser */);
-                writePreference(sp, defaultValue);
-            }
-
-            @Override
-            public void onDismiss(final SeekBarDialog dialog) {
-                if (settingsPref != null) {
-                    settingsPref.setSummary(dialog.getValueText());
-                }
-            }
-
-            @Override
-            public void onStopTrackingTouch(final SeekBarDialog dialog) {
-                feedbackSettingsValue(dialog.getValue());
-            }
-        };
-        final int currentMs = Settings.readKeypressVibrationDuration(sp, getResources());
-        final SeekBarDialog.Builder builder = new SeekBarDialog.Builder(context);
-        builder.setTitle(R.string.prefs_keypress_vibration_duration_settings)
-                .setNeutralButtonText(R.string.button_default)
-                .setListener(listener)
-                .setMaxValue(AudioAndHapticFeedbackManager.MAX_KEYPRESS_VIBRATION_DURATION)
-                .setValueFromat(R.string.settings_keypress_vibration_duration)
-                .setValue(currentMs)
-                .create()
-                .show();
+        });
     }
 
-    private static final int PERCENT_INT = 100;
-    private static final float PERCENT_FLOAT = 100.0f;
+    private void setupKeypressSoundVolumeSettings(final SharedPreferences sp, final Resources res) {
+        final SeekBarDialogPreference pref = (SeekBarDialogPreference)findPreference(
+                Settings.PREF_KEYPRESS_SOUND_VOLUME);
+        if (pref == null) {
+            return;
+        }
+        final AudioManager am = (AudioManager)getActivity().getSystemService(Context.AUDIO_SERVICE);
+        pref.setInterface(new SeekBarDialogPreference.ValueProxy() {
+            private static final float PERCENTAGE_FLOAT = 100.0f;
 
-    private static int getCurrentKeyPressSoundVolumePercent(final SharedPreferences sp,
-            final Resources res) {
-        return (int)(Settings.readKeypressSoundVolume(sp, res) * PERCENT_FLOAT);
-    }
-
-    private void showKeypressSoundVolumeSettingDialog() {
-        final SharedPreferences sp = getPreferenceManager().getSharedPreferences();
-        final Context context = getActivity();
-        final AudioManager am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
-        final PreferenceScreen settingsPref = mKeypressSoundVolumeSettingsPref;
-        final SeekBarDialog.Listener listener = new SeekBarDialog.Adapter() {
-            private void writePreference(final SharedPreferences sp, final float value) {
-                sp.edit().putFloat(Settings.PREF_KEYPRESS_SOUND_VOLUME, value).apply();
+            private float getValueFromPercentage(final int percentage) {
+                return percentage / PERCENTAGE_FLOAT;
             }
 
-            private void feedbackSettingsValue(final float value) {
-                am.playSoundEffect(AudioManager.FX_KEYPRESS_STANDARD, value);
+            private int getPercentageFromValue(final float floatValue) {
+                return (int)(floatValue * PERCENTAGE_FLOAT);
             }
 
             @Override
-            public void onPositiveButtonClick(final SeekBarDialog dialog) {
-                writePreference(sp, dialog.getValue() / PERCENT_FLOAT);
+            public void writeValue(final int value, final String key) {
+                sp.edit().putFloat(key, getValueFromPercentage(value)).apply();
             }
 
             @Override
-            public void onNeutralButtonClick(final SeekBarDialog dialog) {
-                final float defaultValue =
-                        Settings.readDefaultKeypressSoundVolume(context.getResources());
-                dialog.setValue((int)(defaultValue * PERCENT_INT), false /* fromUser */);
-                writePreference(sp, defaultValue);
+            public int readValue(final String key) {
+                return getPercentageFromValue(Settings.readKeypressSoundVolume(sp, res));
             }
 
             @Override
-            public void onDismiss(final SeekBarDialog dialog) {
-                if (settingsPref != null) {
-                    settingsPref.setSummary(dialog.getValueText());
-                }
+            public int readDefaultValue(final String key) {
+                return getPercentageFromValue(Settings.readDefaultKeypressSoundVolume(res));
             }
 
             @Override
-            public void onStopTrackingTouch(final SeekBarDialog dialog) {
-                feedbackSettingsValue(dialog.getValue() / PERCENT_FLOAT);
+            public void feedbackValue(final int value) {
+                am.playSoundEffect(
+                        AudioManager.FX_KEYPRESS_STANDARD, getValueFromPercentage(value));
             }
-        };
-        final SeekBarDialog.Builder builder = new SeekBarDialog.Builder(context);
-        final int currentVolumeInt = getCurrentKeyPressSoundVolumePercent(sp, getResources());
-        builder.setTitle(R.string.prefs_keypress_sound_volume_settings)
-                .setNeutralButtonText(R.string.button_default)
-                .setListener(listener)
-                .setMaxValue(PERCENT_INT)
-                .setValue(currentVolumeInt)
-                .create()
-                .show();
+        });
     }
 }
diff --git a/java/src/com/android/inputmethod/latin/StringUtils.java b/java/src/com/android/inputmethod/latin/StringUtils.java
index ddaa5ff..d00edbe 100644
--- a/java/src/com/android/inputmethod/latin/StringUtils.java
+++ b/java/src/com/android/inputmethod/latin/StringUtils.java
@@ -103,6 +103,37 @@
         }
     }
 
+    /**
+     * Apply an auto-caps mode to a string.
+     *
+     * This intentionally does NOT apply manual caps mode. It only changes the capitalization if
+     * the mode is one of the auto-caps modes.
+     * @param s The string to capitalize.
+     * @param capitalizeMode The mode in which to capitalize.
+     * @param locale The locale for capitalizing.
+     * @return The capitalized string.
+     */
+    public static String applyAutoCapsMode(final String s, final int capitalizeMode,
+            final Locale locale) {
+        if (WordComposer.CAPS_MODE_AUTO_SHIFT_LOCKED == capitalizeMode) {
+            return s.toUpperCase(locale);
+        } else if (WordComposer.CAPS_MODE_AUTO_SHIFTED == capitalizeMode) {
+            return toTitleCase(s, locale);
+        } else {
+            return s;
+        }
+    }
+
+    /**
+     * Return whether a constant represents an auto-caps mode (either auto-shift or auto-shift-lock)
+     * @param mode The mode to test for
+     * @return true if this represents an auto-caps mode, false otherwise
+     */
+    public static boolean isAutoCapsMode(final int mode) {
+        return WordComposer.CAPS_MODE_AUTO_SHIFTED == mode
+                || WordComposer.CAPS_MODE_AUTO_SHIFT_LOCKED == mode;
+    }
+
     public static String toTitleCase(final String s, final Locale locale) {
         if (s.length() <= 1) {
             // TODO: is this really correct? Shouldn't this be s.toUpperCase()?
diff --git a/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java b/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
index a167849..0d5bde6 100644
--- a/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
@@ -216,17 +216,13 @@
      *
      * @param word the word to add. If the word is capitalized, then the dictionary will
      * recognize it as a capitalized word when searched.
-     * @param frequency the frequency of occurrence of the word. A frequency of 255 is considered
-     * the highest.
-     * @TODO use a higher or float range for frequency
      */
-    public synchronized void addWordToUserDictionary(final String word, final int frequency) {
+    public synchronized void addWordToUserDictionary(final String word) {
         // TODO: do something for the UI. With the following, any sufficiently long word will
         // look like it will go to the user dictionary but it won't.
         // Safeguard against adding long words. Can cause stack overflow.
         if (word.length() >= MAX_WORD_LENGTH) return;
 
-        // TODO: Add an argument to the intent to specify the frequency.
         Intent intent = new Intent(ACTION_USER_DICTIONARY_INSERT);
         intent.putExtra(Words.WORD, word);
         intent.putExtra(Words.LOCALE, mLocale);
diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java
index b9ec497..01629fe 100644
--- a/java/src/com/android/inputmethod/latin/WordComposer.java
+++ b/java/src/com/android/inputmethod/latin/WordComposer.java
@@ -360,8 +360,10 @@
         mDigitsCount = 0;
         mIsBatchMode = false;
         mTypedWord.setLength(0);
+        mCodePointSize = 0;
         mTrailingSingleQuotesCount = 0;
         mIsFirstCharCapitalized = false;
+        mCapitalizedMode = CAPS_MODE_OFF;
         refreshSize();
         mAutoCorrection = null;
         mIsResumed = false;
diff --git a/java/src/com/android/inputmethod/research/LogUnit.java b/java/src/com/android/inputmethod/research/LogUnit.java
index cfba289..70bbf9d 100644
--- a/java/src/com/android/inputmethod/research/LogUnit.java
+++ b/java/src/com/android/inputmethod/research/LogUnit.java
@@ -60,6 +60,7 @@
     private String mWord;
     private boolean mMayContainDigit;
     private boolean mIsPartOfMegaword;
+    private boolean mContainsCorrection;
 
     public LogUnit() {
         mLogStatementList = new ArrayList<LogStatement>();
@@ -274,6 +275,14 @@
         return mMayContainDigit;
     }
 
+    public void setContainsCorrection() {
+        mContainsCorrection = true;
+    }
+
+    public boolean containsCorrection() {
+        return mContainsCorrection;
+    }
+
     public boolean isEmpty() {
         return mLogStatementList.isEmpty();
     }
@@ -301,6 +310,7 @@
                         true /* isPartOfMegaword */);
                 newLogUnit.mWord = null;
                 newLogUnit.mMayContainDigit = mMayContainDigit;
+                newLogUnit.mContainsCorrection = mContainsCorrection;
 
                 // Purge the logStatements and associated data from this LogUnit.
                 laterLogStatements.clear();
@@ -320,6 +330,7 @@
         mTimeList.addAll(logUnit.mTimeList);
         mWord = null;
         mMayContainDigit = mMayContainDigit || logUnit.mMayContainDigit;
+        mContainsCorrection = mContainsCorrection || logUnit.mContainsCorrection;
         mIsPartOfMegaword = false;
     }
 }
diff --git a/java/src/com/android/inputmethod/research/ResearchLogger.java b/java/src/com/android/inputmethod/research/ResearchLogger.java
index a2bcf44..a46216c 100644
--- a/java/src/com/android/inputmethod/research/ResearchLogger.java
+++ b/java/src/com/android/inputmethod/research/ResearchLogger.java
@@ -720,6 +720,10 @@
         mCurrentLogUnit.setMayContainDigit();
     }
 
+    private void setCurrentLogUnitContainsCorrection() {
+        mCurrentLogUnit.setContainsCorrection();
+    }
+
     /* package for test */ void commitCurrentLogUnit() {
         if (DEBUG) {
             Log.d(TAG, "commitCurrentLogUnit" + (mCurrentLogUnit.hasWord() ?
@@ -850,7 +854,7 @@
             mCurrentLogUnit.setWord(word);
             final boolean isDictionaryWord = dictionary != null
                     && dictionary.isValidWord(word);
-            mStatistics.recordWordEntered(isDictionaryWord);
+            mStatistics.recordWordEntered(isDictionaryWord, mCurrentLogUnit.containsCorrection());
         }
         final LogUnit newLogUnit = mCurrentLogUnit.splitByTime(maxTime);
         enqueueCommitText(word, isBatchMode);
@@ -1181,6 +1185,7 @@
                 scrubDigitsFromString(replacedWord), index,
                 suggestion == null ? null : scrubbedWord, Constants.SUGGESTION_STRIP_COORDINATE,
                 Constants.SUGGESTION_STRIP_COORDINATE);
+        researchLogger.setCurrentLogUnitContainsCorrection();
         researchLogger.commitCurrentLogUnitAsWord(scrubbedWord, Long.MAX_VALUE, isBatchMode);
         researchLogger.mStatistics.recordManualSuggestion(SystemClock.uptimeMillis());
     }
@@ -1340,6 +1345,9 @@
         researchLogger.enqueueEvent(logUnit != null ? logUnit : researchLogger.mCurrentLogUnit,
                 LOGSTATEMENT_LATINIME_REVERTCOMMIT, committedWord, originallyTypedWord,
                 separatorString);
+        if (logUnit != null) {
+            logUnit.setContainsCorrection();
+        }
         researchLogger.mStatistics.recordRevertCommit(SystemClock.uptimeMillis());
         researchLogger.commitCurrentLogUnitAsWord(originallyTypedWord, Long.MAX_VALUE, isBatchMode);
     }
@@ -1500,6 +1508,7 @@
         final ResearchLogger researchLogger = getInstance();
         final String scrubbedWord = scrubDigitsFromString(committedWord);
         researchLogger.enqueueEvent(LOGSTATEMENT_COMMIT_PARTIAL_TEXT);
+        researchLogger.mStatistics.recordAutoCorrection(SystemClock.uptimeMillis());
         researchLogger.commitCurrentLogUnitAsWord(scrubbedWord, lastTimestampOfWordData,
                 isBatchMode);
     }
@@ -1740,7 +1749,7 @@
                     "averageTimeDuringRepeatedDelete", "averageTimeAfterDelete",
                     "dictionaryWordCount", "splitWordsCount", "gestureInputCount",
                     "gestureCharsCount", "gesturesDeletedCount", "manualSuggestionsCount",
-                    "revertCommitsCount");
+                    "revertCommitsCount", "correctedWordsCount", "autoCorrectionsCount");
     private static void logStatistics() {
         final ResearchLogger researchLogger = getInstance();
         final Statistics statistics = researchLogger.mStatistics;
@@ -1754,6 +1763,7 @@
                 statistics.mDictionaryWordCount, statistics.mSplitWordsCount,
                 statistics.mGesturesInputCount, statistics.mGesturesCharsCount,
                 statistics.mGesturesDeletedCount, statistics.mManualSuggestionsCount,
-                statistics.mRevertCommitsCount);
+                statistics.mRevertCommitsCount, statistics.mCorrectedWordsCount,
+                statistics.mAutoCorrectionsCount);
     }
 }
diff --git a/java/src/com/android/inputmethod/research/Statistics.java b/java/src/com/android/inputmethod/research/Statistics.java
index a920265..f0cb157 100644
--- a/java/src/com/android/inputmethod/research/Statistics.java
+++ b/java/src/com/android/inputmethod/research/Statistics.java
@@ -25,6 +25,7 @@
     private static final String TAG = Statistics.class.getSimpleName();
     private static final boolean DEBUG = false && ProductionFlag.IS_EXPERIMENTAL_DEBUG;
 
+    // TODO: Cleanup comments to only including those giving meaningful information.
     // Number of characters entered during a typing session
     int mCharCount;
     // Number of letter characters entered during a typing session
@@ -41,6 +42,8 @@
     int mDictionaryWordCount;
     // Number of words split and spaces automatically entered.
     int mSplitWordsCount;
+    // Number of words entered during a session.
+    int mCorrectedWordsCount;
     // Number of gestures that were input.
     int mGesturesInputCount;
     // Number of gestures that were deleted.
@@ -49,6 +52,8 @@
     int mGesturesCharsCount;
     // Number of manual suggestions chosen.
     int mManualSuggestionsCount;
+    // Number of times that autocorrection was invoked.
+    int mAutoCorrectionsCount;
     // Number of times a commit was reverted in this session.
     int mRevertCommitsCount;
     // Whether the text field was empty upon editing
@@ -113,10 +118,12 @@
         mWordCount = 0;
         mDictionaryWordCount = 0;
         mSplitWordsCount = 0;
+        mCorrectedWordsCount = 0;
         mGesturesInputCount = 0;
         mGesturesDeletedCount = 0;
         mManualSuggestionsCount = 0;
         mRevertCommitsCount = 0;
+        mAutoCorrectionsCount = 0;
         mIsEmptyUponStarting = true;
         mIsEmptinessStateKnown = false;
         mKeyCounter.reset();
@@ -152,11 +159,15 @@
         }
     }
 
-    public void recordWordEntered(final boolean isDictionaryWord) {
+    public void recordWordEntered(final boolean isDictionaryWord,
+            final boolean containsCorrection) {
         mWordCount++;
         if (isDictionaryWord) {
             mDictionaryWordCount++;
         }
+        if (containsCorrection) {
+            mCorrectedWordsCount++;
+        }
     }
 
     public void recordSplitWords() {
@@ -184,6 +195,11 @@
         recordUserAction(time, false /* isDeletion */);
     }
 
+    public void recordAutoCorrection(final long time) {
+        mAutoCorrectionsCount++;
+        recordUserAction(time, false /* isDeletion */);
+    }
+
     public void recordRevertCommit(final long time) {
         mRevertCommitsCount++;
         recordUserAction(time, true /* isDeletion */);
diff --git a/native/jni/src/additional_proximity_chars.h b/native/jni/src/additional_proximity_chars.h
index 21eb0bf..a88fd6c 100644
--- a/native/jni/src/additional_proximity_chars.h
+++ b/native/jni/src/additional_proximity_chars.h
@@ -45,7 +45,7 @@
     }
 
  public:
-    static int getAdditionalCharsSize(const char *localeStr, const int c) {
+    static int getAdditionalCharsSize(const char *const localeStr, const int c) {
         if (!isEnLocale(localeStr)) {
             return 0;
         }
@@ -65,7 +65,7 @@
         }
     }
 
-    static const int *getAdditionalChars(const char *localeStr, const int c) {
+    static const int *getAdditionalChars(const char *const localeStr, const int c) {
         if (!isEnLocale(localeStr)) {
             return 0;
         }
diff --git a/native/jni/src/proximity_info.cpp b/native/jni/src/proximity_info.cpp
index 9b99554..08646af 100644
--- a/native/jni/src/proximity_info.cpp
+++ b/native/jni/src/proximity_info.cpp
@@ -94,11 +94,6 @@
     delete[] mProximityCharsArray;
 }
 
-inline int ProximityInfo::getStartIndexFromCoordinates(const int x, const int y) const {
-    return ((y / CELL_HEIGHT) * GRID_WIDTH + (x / CELL_WIDTH))
-            * MAX_PROXIMITY_CHARS_SIZE;
-}
-
 bool ProximityInfo::hasSpaceProximity(const int x, const int y) const {
     if (x < 0 || y < 0) {
         if (DEBUG_DICT) {
@@ -109,7 +104,8 @@
         return false;
     }
 
-    const int startIndex = getStartIndexFromCoordinates(x, y);
+    const int startIndex = ProximityInfoUtils::getStartIndexFromCoordinates(
+            MAX_PROXIMITY_CHARS_SIZE, x, y, CELL_HEIGHT, CELL_WIDTH, GRID_WIDTH);
     if (DEBUG_PROXIMITY_INFO) {
         AKLOGI("hasSpaceProximity: index %d, %d, %d", startIndex, x, y);
     }
@@ -147,100 +143,6 @@
     return getSquaredDistanceFloat(centerX, centerY, touchX, touchY) / SQUARE_FLOAT(keyWidth);
 }
 
-int ProximityInfo::squaredDistanceToEdge(const int keyId, const int x, const int y) const {
-    if (keyId < 0) return true; // NOT_A_ID is -1, but return whenever < 0 just in case
-    const int left = mKeyXCoordinates[keyId];
-    const int top = mKeyYCoordinates[keyId];
-    const int right = left + mKeyWidths[keyId];
-    const int bottom = top + mKeyHeights[keyId];
-    const int edgeX = x < left ? left : (x > right ? right : x);
-    const int edgeY = y < top ? top : (y > bottom ? bottom : y);
-    const int dx = x - edgeX;
-    const int dy = y - edgeY;
-    return dx * dx + dy * dy;
-}
-
-void ProximityInfo::calculateNearbyKeyCodes(
-        const int x, const int y, const int primaryKey, int *inputCodes) const {
-    int *proximityCharsArray = mProximityCharsArray;
-    int insertPos = 0;
-    inputCodes[insertPos++] = primaryKey;
-    const int startIndex = getStartIndexFromCoordinates(x, y);
-    if (startIndex >= 0) {
-        for (int i = 0; i < MAX_PROXIMITY_CHARS_SIZE; ++i) {
-            const int c = proximityCharsArray[startIndex + i];
-            if (c < KEYCODE_SPACE || c == primaryKey) {
-                continue;
-            }
-            const int keyIndex = getKeyIndexOf(c);
-            const bool onKey = isOnKey(keyIndex, x, y);
-            const int distance = squaredDistanceToEdge(keyIndex, x, y);
-            if (onKey || distance < MOST_COMMON_KEY_WIDTH_SQUARE) {
-                inputCodes[insertPos++] = c;
-                if (insertPos >= MAX_PROXIMITY_CHARS_SIZE) {
-                    if (DEBUG_DICT) {
-                        ASSERT(false);
-                    }
-                    return;
-                }
-            }
-        }
-        const int additionalProximitySize =
-                AdditionalProximityChars::getAdditionalCharsSize(mLocaleStr, primaryKey);
-        if (additionalProximitySize > 0) {
-            inputCodes[insertPos++] = ADDITIONAL_PROXIMITY_CHAR_DELIMITER_CODE;
-            if (insertPos >= MAX_PROXIMITY_CHARS_SIZE) {
-                if (DEBUG_DICT) {
-                    ASSERT(false);
-                }
-                return;
-            }
-
-            const int *additionalProximityChars =
-                    AdditionalProximityChars::getAdditionalChars(mLocaleStr, primaryKey);
-            for (int j = 0; j < additionalProximitySize; ++j) {
-                const int ac = additionalProximityChars[j];
-                int k = 0;
-                for (; k < insertPos; ++k) {
-                    if (ac == inputCodes[k]) {
-                        break;
-                    }
-                }
-                if (k < insertPos) {
-                    continue;
-                }
-                inputCodes[insertPos++] = ac;
-                if (insertPos >= MAX_PROXIMITY_CHARS_SIZE) {
-                    if (DEBUG_DICT) {
-                        ASSERT(false);
-                    }
-                    return;
-                }
-            }
-        }
-    }
-    // Add a delimiter for the proximity characters
-    for (int i = insertPos; i < MAX_PROXIMITY_CHARS_SIZE; ++i) {
-        inputCodes[i] = NOT_A_CODE_POINT;
-    }
-}
-
-int ProximityInfo::getKeyIndexOf(const int c) const {
-    if (KEY_COUNT == 0) {
-        // We do not have the coordinate data
-        return NOT_AN_INDEX;
-    }
-    if (c == NOT_A_CODE_POINT) {
-        return NOT_AN_INDEX;
-    }
-    const int lowerCode = toLowerCase(c);
-    hash_map_compat<int, int>::const_iterator mapPos = mCodeToKeyMap.find(lowerCode);
-    if (mapPos != mCodeToKeyMap.end()) {
-        return mapPos->second;
-    }
-    return NOT_AN_INDEX;
-}
-
 int ProximityInfo::getCodePointOf(const int keyIndex) const {
     if (keyIndex < 0 || keyIndex >= KEY_COUNT) {
         return NOT_A_CODE_POINT;
@@ -269,11 +171,13 @@
 }
 
 int ProximityInfo::getKeyCenterXOfCodePointG(int charCode) const {
-    return getKeyCenterXOfKeyIdG(getKeyIndexOf(charCode));
+    return getKeyCenterXOfKeyIdG(
+            ProximityInfoUtils::getKeyIndexOf(KEY_COUNT, charCode, &mCodeToKeyMap));
 }
 
 int ProximityInfo::getKeyCenterYOfCodePointG(int charCode) const {
-    return getKeyCenterYOfKeyIdG(getKeyIndexOf(charCode));
+    return getKeyCenterYOfKeyIdG(
+            ProximityInfoUtils::getKeyIndexOf(KEY_COUNT, charCode, &mCodeToKeyMap));
 }
 
 int ProximityInfo::getKeyCenterXOfKeyIdG(int keyId) const {
diff --git a/native/jni/src/proximity_info.h b/native/jni/src/proximity_info.h
index d002283..6be42a0 100644
--- a/native/jni/src/proximity_info.h
+++ b/native/jni/src/proximity_info.h
@@ -20,6 +20,7 @@
 #include "defines.h"
 #include "hash_map_compat.h"
 #include "jni.h"
+#include "proximity_info_utils.h"
 
 namespace latinime {
 
@@ -40,7 +41,6 @@
     float getNormalizedSquaredDistanceFromCenterFloatG(
             const int keyId, const int x, const int y) const;
     bool sameAsTyped(const unsigned short *word, int length) const;
-    int getKeyIndexOf(const int c) const;
     int getCodePointOf(const int keyIndex) const;
     bool hasSweetSpotData(const int keyIndex) const {
         // When there are no calibration data for a key,
@@ -109,23 +109,26 @@
     int getKeyCenterYOfKeyIdG(int keyId) const;
     int getKeyKeyDistanceG(int keyId0, int keyId1) const;
 
+    void initializeProximities(const int *const inputCodes, const int *const inputXCoordinates,
+            const int *const inputYCoordinates, const int inputSize, int *allInputCodes) const {
+        ProximityInfoUtils::initializeProximities(inputCodes, inputXCoordinates, inputYCoordinates,
+                inputSize, mKeyXCoordinates, mKeyYCoordinates, mKeyWidths, mKeyHeights,
+                mProximityCharsArray, MAX_PROXIMITY_CHARS_SIZE, CELL_HEIGHT, CELL_WIDTH,
+                GRID_WIDTH, MOST_COMMON_KEY_WIDTH, KEY_COUNT, mLocaleStr, &mCodeToKeyMap,
+                allInputCodes);
+    }
+
+    int getKeyIndexOf(const int c) const {
+        return ProximityInfoUtils::getKeyIndexOf(KEY_COUNT, c, &mCodeToKeyMap);
+    }
+
  private:
     DISALLOW_IMPLICIT_CONSTRUCTORS(ProximityInfo);
     static const float NOT_A_DISTANCE_FLOAT;
 
-    int getStartIndexFromCoordinates(const int x, const int y) const;
     void initializeG();
     float calculateNormalizedSquaredDistance(const int keyIndex, const int inputIndex) const;
     bool hasInputCoordinates() const;
-    int squaredDistanceToEdge(const int keyId, const int x, const int y) const;
-    bool isOnKey(const int keyId, const int x, const int y) const {
-        if (keyId < 0) return true; // NOT_A_ID is -1, but return whenever < 0 just in case
-        const int left = mKeyXCoordinates[keyId];
-        const int top = mKeyYCoordinates[keyId];
-        const int right = left + mKeyWidths[keyId] + 1;
-        const int bottom = top + mKeyHeights[keyId];
-        return left < right && top < bottom && x >= left && x < right && y >= top && y < bottom;
-    }
 
     const int MAX_PROXIMITY_CHARS_SIZE;
     const int GRID_WIDTH;
diff --git a/native/jni/src/proximity_info_state.cpp b/native/jni/src/proximity_info_state.cpp
index aa02929..1e1413a 100644
--- a/native/jni/src/proximity_info_state.cpp
+++ b/native/jni/src/proximity_info_state.cpp
@@ -53,33 +53,11 @@
     mGridHeight = proximityInfo->getGridWidth();
     mGridWidth = proximityInfo->getGridHeight();
 
-    memset(mInputCodes, 0, sizeof(mInputCodes));
+    memset(mInputProximities, 0, sizeof(mInputProximities));
 
     if (!isGeometric && pointerId == 0) {
-        // Initialize
-        // - mInputCodes
-        // - mNormalizedSquaredDistances
-        // TODO: Merge
-        for (int i = 0; i < inputSize; ++i) {
-            const int primaryKey = inputCodes[i];
-            const int x = xCoordinates[i];
-            const int y = yCoordinates[i];
-            int *proximities = &mInputCodes[i * MAX_PROXIMITY_CHARS_SIZE_INTERNAL];
-            mProximityInfo->calculateNearbyKeyCodes(x, y, primaryKey, proximities);
-        }
-
-        if (DEBUG_PROXIMITY_CHARS) {
-            for (int i = 0; i < inputSize; ++i) {
-                AKLOGI("---");
-                for (int j = 0; j < MAX_PROXIMITY_CHARS_SIZE_INTERNAL; ++j) {
-                    int icc = mInputCodes[i * MAX_PROXIMITY_CHARS_SIZE_INTERNAL + j];
-                    int icfjc = inputCodes[i * MAX_PROXIMITY_CHARS_SIZE_INTERNAL + j];
-                    icc += 0;
-                    icfjc += 0;
-                    AKLOGI("--- (%d)%c,%c", i, icc, icfjc); AKLOGI("--- A<%d>,B<%d>", icc, icfjc);
-                }
-            }
-        }
+        mProximityInfo->initializeProximities(inputCodes, xCoordinates, yCoordinates,
+                inputSize, mInputProximities);
     }
 
     ///////////////////////
diff --git a/native/jni/src/proximity_info_state.h b/native/jni/src/proximity_info_state.h
index d747bae..bc2cf50 100644
--- a/native/jni/src/proximity_info_state.h
+++ b/native/jni/src/proximity_info_state.h
@@ -61,7 +61,7 @@
               mInputIndice(), mLengthCache(), mBeelineSpeedPercentiles(), mDistanceCache_G(),
               mSpeedRates(), mDirections(), mCharProbabilities(), mNearKeysVector(),
               mSearchKeysVector(), mTouchPositionCorrectionEnabled(false), mSampledInputSize(0) {
-        memset(mInputCodes, 0, sizeof(mInputCodes));
+        memset(mInputProximities, 0, sizeof(mInputProximities));
         memset(mNormalizedSquaredDistances, 0, sizeof(mNormalizedSquaredDistances));
         memset(mPrimaryInputWord, 0, sizeof(mPrimaryInputWord));
     }
@@ -117,12 +117,12 @@
         if (length != mSampledInputSize) {
             return false;
         }
-        const int *inputCodes = mInputCodes;
+        const int *inputProximities = mInputProximities;
         while (length--) {
-            if (*inputCodes != *word) {
+            if (*inputProximities != *word) {
                 return false;
             }
-            inputCodes += MAX_PROXIMITY_CHARS_SIZE_INTERNAL;
+            inputProximities += MAX_PROXIMITY_CHARS_SIZE_INTERNAL;
             word++;
         }
         return true;
@@ -229,7 +229,7 @@
     }
 
     inline const int *getProximityCodePointsAt(const int index) const {
-        return mInputCodes + (index * MAX_PROXIMITY_CHARS_SIZE_INTERNAL);
+        return mInputProximities + (index * MAX_PROXIMITY_CHARS_SIZE_INTERNAL);
     }
 
     float updateNearKeysDistances(const int x, const int y,
@@ -289,7 +289,7 @@
     // inputs including the current input point.
     std::vector<NearKeycodesSet> mSearchKeysVector;
     bool mTouchPositionCorrectionEnabled;
-    int mInputCodes[MAX_PROXIMITY_CHARS_SIZE_INTERNAL * MAX_WORD_LENGTH];
+    int mInputProximities[MAX_PROXIMITY_CHARS_SIZE_INTERNAL * MAX_WORD_LENGTH];
     int mNormalizedSquaredDistances[MAX_PROXIMITY_CHARS_SIZE_INTERNAL * MAX_WORD_LENGTH];
     int mSampledInputSize;
     int mPrimaryInputWord[MAX_WORD_LENGTH];
diff --git a/native/jni/src/proximity_info_utils.h b/native/jni/src/proximity_info_utils.h
new file mode 100644
index 0000000..b9348d8
--- /dev/null
+++ b/native/jni/src/proximity_info_utils.h
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LATINIME_PROXIMITY_INFO_UTILS_H
+#define LATINIME_PROXIMITY_INFO_UTILS_H
+
+#include "additional_proximity_chars.h"
+#include "char_utils.h"
+#include "defines.h"
+#include "hash_map_compat.h"
+
+namespace latinime {
+class ProximityInfoUtils {
+ public:
+    static int getKeyIndexOf(const int keyCount, const int c,
+            const hash_map_compat<int, int> *const codeToKeyMap) {
+        if (keyCount == 0) {
+            // We do not have the coordinate data
+            return NOT_AN_INDEX;
+        }
+        if (c == NOT_A_CODE_POINT) {
+            return NOT_AN_INDEX;
+        }
+        const int lowerCode = toLowerCase(c);
+        hash_map_compat<int, int>::const_iterator mapPos = codeToKeyMap->find(lowerCode);
+        if (mapPos != codeToKeyMap->end()) {
+            return mapPos->second;
+        }
+        return NOT_AN_INDEX;
+    }
+
+    static void initializeProximities(const int *const inputCodes,
+            const int *const inputXCoordinates, const int *const inputYCoordinates,
+            const int inputSize, const int *const keyXCoordinates,
+            const int *const keyYCoordinates, const int *const keyWidths, const int *keyHeights,
+            const int *const proximityCharsArray, const int maxProximityCharsSize,
+            const int cellHeight, const int cellWidth, const int gridWidth,
+            const int mostCommonKeyWidth, const int keyCount, const char *const localeStr,
+            const hash_map_compat<int, int> *const codeToKeyMap, int *inputProximities) {
+        // Initialize
+        // - mInputCodes
+        // - mNormalizedSquaredDistances
+        // TODO: Merge
+        for (int i = 0; i < inputSize; ++i) {
+            const int primaryKey = inputCodes[i];
+            const int x = inputXCoordinates[i];
+            const int y = inputYCoordinates[i];
+            int *proximities = &inputProximities[i * MAX_PROXIMITY_CHARS_SIZE_INTERNAL];
+            calculateProximities(keyXCoordinates, keyYCoordinates, keyWidths, keyHeights,
+                    proximityCharsArray, maxProximityCharsSize, cellHeight, cellWidth, gridWidth,
+                    mostCommonKeyWidth, keyCount, x, y, primaryKey, localeStr, codeToKeyMap,
+                    proximities);
+        }
+
+        if (DEBUG_PROXIMITY_CHARS) {
+            for (int i = 0; i < inputSize; ++i) {
+                AKLOGI("---");
+                for (int j = 0; j < MAX_PROXIMITY_CHARS_SIZE_INTERNAL; ++j) {
+                    int proximityChar =
+                            inputProximities[i * MAX_PROXIMITY_CHARS_SIZE_INTERNAL + j];
+                    proximityChar += 0;
+                    AKLOGI("--- (%d)%c", i, proximityChar);
+                }
+            }
+        }
+    }
+
+    AK_FORCE_INLINE static int getStartIndexFromCoordinates(const int maxProximityCharsSize,
+            const int x, const int y, const int cellHeight, const int cellWidth,
+            const int gridWidth) {
+        return ((y / cellHeight) * gridWidth + (x / cellWidth)) * maxProximityCharsSize;
+    }
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(ProximityInfoUtils);
+
+    static bool isOnKey(const int *const keyXCoordinates, const int *const keyYCoordinates,
+            const int *const keyWidths, const int *keyHeights, const int keyId, const int x,
+            const int y) {
+        if (keyId < 0) return true; // NOT_A_ID is -1, but return whenever < 0 just in case
+        const int left = keyXCoordinates[keyId];
+        const int top = keyYCoordinates[keyId];
+        const int right = left + keyWidths[keyId] + 1;
+        const int bottom = top + keyHeights[keyId];
+        return left < right && top < bottom && x >= left && x < right && y >= top && y < bottom;
+    }
+
+    static void calculateProximities(
+            const int *const keyXCoordinates, const int *const keyYCoordinates,
+            const int *const keyWidths, const int *keyHeights,
+            const int *const proximityCharsArray,
+            const int maxProximityCharsSize, const int cellHeight, const int cellWidth,
+            const int gridWidth, const int mostCommonKeyWidth, const int keyCount,
+            const int x, const int y, const int primaryKey, const char *const localeStr,
+            const hash_map_compat<int, int> *const codeToKeyMap, int *proximities) {
+        const int mostCommonKeyWidthSquare = mostCommonKeyWidth * mostCommonKeyWidth;
+        int insertPos = 0;
+        proximities[insertPos++] = primaryKey;
+        const int startIndex = getStartIndexFromCoordinates(
+                maxProximityCharsSize, x, y, cellHeight, cellWidth, gridWidth);
+        if (startIndex >= 0) {
+            for (int i = 0; i < maxProximityCharsSize; ++i) {
+                const int c = proximityCharsArray[startIndex + i];
+                if (c < KEYCODE_SPACE || c == primaryKey) {
+                    continue;
+                }
+                const int keyIndex = getKeyIndexOf(keyCount, c, codeToKeyMap);
+                const bool onKey = isOnKey(keyXCoordinates, keyYCoordinates, keyWidths, keyHeights,
+                        keyIndex, x, y);
+                const int distance = squaredLengthToEdge(keyXCoordinates, keyYCoordinates,
+                        keyWidths, keyHeights, keyIndex, x, y);
+                if (onKey || distance < mostCommonKeyWidthSquare) {
+                    proximities[insertPos++] = c;
+                    if (insertPos >= maxProximityCharsSize) {
+                        if (DEBUG_DICT) {
+                            ASSERT(false);
+                        }
+                        return;
+                    }
+                }
+            }
+            const int additionalProximitySize =
+                    AdditionalProximityChars::getAdditionalCharsSize(localeStr, primaryKey);
+            if (additionalProximitySize > 0) {
+                proximities[insertPos++] = ADDITIONAL_PROXIMITY_CHAR_DELIMITER_CODE;
+                if (insertPos >= maxProximityCharsSize) {
+                    if (DEBUG_DICT) {
+                        ASSERT(false);
+                    }
+                    return;
+                }
+
+                const int *additionalProximityChars =
+                        AdditionalProximityChars::getAdditionalChars(localeStr, primaryKey);
+                for (int j = 0; j < additionalProximitySize; ++j) {
+                    const int ac = additionalProximityChars[j];
+                    int k = 0;
+                    for (; k < insertPos; ++k) {
+                        if (ac == proximities[k]) {
+                            break;
+                        }
+                    }
+                    if (k < insertPos) {
+                        continue;
+                    }
+                    proximities[insertPos++] = ac;
+                    if (insertPos >= maxProximityCharsSize) {
+                        if (DEBUG_DICT) {
+                            ASSERT(false);
+                        }
+                        return;
+                    }
+                }
+            }
+        }
+        // Add a delimiter for the proximity characters
+        for (int i = insertPos; i < maxProximityCharsSize; ++i) {
+            proximities[i] = NOT_A_CODE_POINT;
+        }
+    }
+
+    static int squaredLengthToEdge(const int *const keyXCoordinates,
+            const int *const keyYCoordinates, const int *const keyWidths, const int *keyHeights,
+            const int keyId, const int x, const int y) {
+        // NOT_A_ID is -1, but return whenever < 0 just in case
+        if (keyId < 0) return MAX_POINT_TO_KEY_LENGTH;
+        const int left = keyXCoordinates[keyId];
+        const int top = keyYCoordinates[keyId];
+        const int right = left + keyWidths[keyId];
+        const int bottom = top + keyHeights[keyId];
+        const int edgeX = x < left ? left : (x > right ? right : x);
+        const int edgeY = y < top ? top : (y > bottom ? bottom : y);
+        const int dx = x - edgeX;
+        const int dy = y - edgeY;
+        return dx * dx + dy * dy;
+    }
+};
+} // namespace latinime
+#endif // LATINIME_PROXIMITY_INFO_UTILS_H
diff --git a/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateSingleTouchTests.java b/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateSingleTouchTests.java
index a1ceb8e..74ff879 100644
--- a/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateSingleTouchTests.java
+++ b/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateSingleTouchTests.java
@@ -150,59 +150,6 @@
         pressAndReleaseKey(CODE_SPACE, SYMBOLS_SHIFTED, ALPHABET_SHIFT_LOCKED);
     }
 
-    // Automatic switch back to alphabet by registered letters.
-    public void testSwitchBackChar() {
-        // Set switch back chars.
-        final String switchBackSymbols = "'";
-        final int switchBackCode = switchBackSymbols.codePointAt(0);
-        setLayoutSwitchBackSymbols(switchBackSymbols);
-        loadKeyboard(ALPHABET_UNSHIFTED);
-
-        // Press/release "?123" key, enter into symbols.
-        pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
-        // Enter symbol letter.
-        pressAndReleaseKey('1', SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
-        // Enter switch back letter, switch back to alphabet.
-        pressAndReleaseKey(switchBackCode, SYMBOLS_UNSHIFTED, ALPHABET_UNSHIFTED);
-
-        // Press/release "?123" key, enter into symbols.
-        pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
-        // Press/release "=\<" key, enter into symbols shifted.
-        pressAndReleaseKey(CODE_SHIFT, SYMBOLS_SHIFTED, SYMBOLS_SHIFTED);
-        // Enter symbol shift letter.
-        pressAndReleaseKey('~', SYMBOLS_SHIFTED, SYMBOLS_SHIFTED);
-        // Enter switch abck letter, switch back to alphabet.
-        pressAndReleaseKey(switchBackCode, SYMBOLS_SHIFTED, ALPHABET_UNSHIFTED);
-    }
-
-    // Automatic switch back to alphabet shift locked by registered letters.
-    public void testSwitchBackCharShiftLocked() {
-        // Set switch back chars.
-        final String switchBackSymbols = "'";
-        final int switchBackCode = switchBackSymbols.codePointAt(0);
-        setLayoutSwitchBackSymbols(switchBackSymbols);
-        loadKeyboard(ALPHABET_UNSHIFTED);
-        // Long press shift key, enter alphabet shift locked.
-        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
-                ALPHABET_SHIFT_LOCKED);
-
-        // Press/release "?123" key, enter into symbols.
-        pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
-        // Enter symbol letter.
-        pressAndReleaseKey('1', SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
-        // Enter switch back letter, switch back to alphabet shift locked.
-        pressAndReleaseKey(switchBackCode, SYMBOLS_UNSHIFTED, ALPHABET_SHIFT_LOCKED);
-
-        // Press/release "?123" key, enter into symbols.
-        pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
-        // Press/release "=\<" key, enter into symbols shifted.
-        pressAndReleaseKey(CODE_SHIFT, SYMBOLS_SHIFTED, SYMBOLS_SHIFTED);
-        // Enter symbol shift letter.
-        pressAndReleaseKey(CODE_SPACE, SYMBOLS_SHIFTED, SYMBOLS_SHIFTED);
-        // Enter switch back letter, switch back to alphabet shift locked.
-        pressAndReleaseKey(switchBackCode, SYMBOLS_SHIFTED, ALPHABET_SHIFT_LOCKED);
-    }
-
     // Automatic upper case test
     public void testAutomaticUpperCase() {
         // Set capitalize the first character of all words mode.
diff --git a/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateTestsBase.java b/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateTestsBase.java
index c75f826..08199a0 100644
--- a/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateTestsBase.java
+++ b/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateTestsBase.java
@@ -22,8 +22,6 @@
         implements MockKeyboardSwitcher.MockConstants {
     protected MockKeyboardSwitcher mSwitcher;
 
-    private String mLayoutSwitchBackSymbols = "";
-
     @Override
     protected void setUp() throws Exception {
         super.setUp();
@@ -34,88 +32,86 @@
         loadKeyboard(ALPHABET_UNSHIFTED);
     }
 
-    public void setAutoCapsMode(int autoCaps) {
+    public void setAutoCapsMode(final int autoCaps) {
         mSwitcher.setAutoCapsMode(autoCaps);
     }
 
-    public void setLayoutSwitchBackSymbols(String switchBackSymbols) {
-        mLayoutSwitchBackSymbols = switchBackSymbols;
-    }
-
-    private static void assertLayout(String message, int expected, int actual) {
+    private static void assertLayout(final String message, final int expected, final int actual) {
         assertTrue(message + ": expected=" + MockKeyboardSwitcher.getLayoutName(expected)
                 + " actual=" + MockKeyboardSwitcher.getLayoutName(actual),
                 expected == actual);
     }
 
-    public void updateShiftState(int afterUpdate) {
+    public void updateShiftState(final int afterUpdate) {
         mSwitcher.updateShiftState();
         assertLayout("afterUpdate", afterUpdate, mSwitcher.getLayoutId());
     }
 
-    public void loadKeyboard(int afterLoad) {
-        mSwitcher.loadKeyboard(mLayoutSwitchBackSymbols);
+    public void loadKeyboard(final int afterLoad) {
+        mSwitcher.loadKeyboard();
         mSwitcher.updateShiftState();
         assertLayout("afterLoad", afterLoad, mSwitcher.getLayoutId());
     }
 
-    public void rotateDevice(int afterRotate) {
+    public void rotateDevice(final int afterRotate) {
         mSwitcher.saveKeyboardState();
-        mSwitcher.loadKeyboard(mLayoutSwitchBackSymbols);
+        mSwitcher.loadKeyboard();
         assertLayout("afterRotate", afterRotate, mSwitcher.getLayoutId());
     }
 
-    private void pressKeyWithoutTimerExpire(int code, boolean isSinglePointer, int afterPress) {
+    private void pressKeyWithoutTimerExpire(final int code, final boolean isSinglePointer,
+            final int afterPress) {
         mSwitcher.onPressKey(code, isSinglePointer);
         assertLayout("afterPress", afterPress, mSwitcher.getLayoutId());
     }
 
-    public void pressKey(int code, int afterPress) {
+    public void pressKey(final int code, final int afterPress) {
         mSwitcher.expireDoubleTapTimeout();
         pressKeyWithoutTimerExpire(code, true, afterPress);
     }
 
-    public void releaseKey(int code, int afterRelease) {
+    public void releaseKey(final int code, final int afterRelease) {
         mSwitcher.onCodeInput(code, SINGLE);
         mSwitcher.onReleaseKey(code, NOT_SLIDING);
         assertLayout("afterRelease", afterRelease, mSwitcher.getLayoutId());
     }
 
-    public void pressAndReleaseKey(int code, int afterPress, int afterRelease) {
+    public void pressAndReleaseKey(final int code, final int afterPress, final int afterRelease) {
         pressKey(code, afterPress);
         releaseKey(code, afterRelease);
     }
 
-    public void chordingPressKey(int code, int afterPress) {
+    public void chordingPressKey(final int code, final int afterPress) {
         mSwitcher.expireDoubleTapTimeout();
         pressKeyWithoutTimerExpire(code, false, afterPress);
     }
 
-    public void chordingReleaseKey(int code, int afterRelease) {
+    public void chordingReleaseKey(final int code, final int afterRelease) {
         mSwitcher.onCodeInput(code, MULTI);
         mSwitcher.onReleaseKey(code, NOT_SLIDING);
         assertLayout("afterRelease", afterRelease, mSwitcher.getLayoutId());
     }
 
-    public void chordingPressAndReleaseKey(int code, int afterPress, int afterRelease) {
+    public void chordingPressAndReleaseKey(final int code, final int afterPress,
+            final int afterRelease) {
         chordingPressKey(code, afterPress);
         chordingReleaseKey(code, afterRelease);
     }
 
-    public void pressAndSlideFromKey(int code, int afterPress, int afterSlide) {
+    public void pressAndSlideFromKey(final int code, final int afterPress, final int afterSlide) {
         pressKey(code, afterPress);
         mSwitcher.onReleaseKey(code, SLIDING);
         assertLayout("afterSlide", afterSlide, mSwitcher.getLayoutId());
     }
 
-    public void longPressKey(int code, int afterPress, int afterLongPress) {
+    public void longPressKey(final int code, final int afterPress, final int afterLongPress) {
         pressKey(code, afterPress);
         mSwitcher.onLongPressTimeout(code);
         assertLayout("afterLongPress", afterLongPress, mSwitcher.getLayoutId());
     }
 
-    public void longPressAndReleaseKey(int code, int afterPress, int afterLongPress,
-            int afterRelease) {
+    public void longPressAndReleaseKey(final int code, final int afterPress,
+            final int afterLongPress, final int afterRelease) {
         longPressKey(code, afterPress, afterLongPress);
         releaseKey(code, afterRelease);
     }
diff --git a/tests/src/com/android/inputmethod/keyboard/internal/MockKeyboardSwitcher.java b/tests/src/com/android/inputmethod/keyboard/internal/MockKeyboardSwitcher.java
index 0213744..ac35585 100644
--- a/tests/src/com/android/inputmethod/keyboard/internal/MockKeyboardSwitcher.java
+++ b/tests/src/com/android/inputmethod/keyboard/internal/MockKeyboardSwitcher.java
@@ -61,7 +61,7 @@
         return mLayout;
     }
 
-    public static String getLayoutName(int layoutId) {
+    public static String getLayoutName(final int layoutId) {
         switch (layoutId) {
         case MockConstants.ALPHABET_UNSHIFTED: return "ALPHABET_UNSHIFTED";
         case MockConstants.ALPHABET_MANUAL_SHIFTED: return "ALPHABET_MANUAL_SHIFTED";
@@ -74,7 +74,7 @@
         }
     }
 
-    public void setAutoCapsMode(int autoCaps) {
+    public void setAutoCapsMode(final int autoCaps) {
         mAutoCapsMode = autoCaps;
         mAutoCapsState = autoCaps;
     }
@@ -139,7 +139,7 @@
     }
 
     @Override
-    public void startLongPressTimer(int code) {
+    public void startLongPressTimer(final int code) {
         mLongPressTimeoutCode = code;
     }
 
@@ -149,11 +149,11 @@
     }
 
     @Override
-    public void hapticAndAudioFeedback(int code) {
+    public void hapticAndAudioFeedback(final int code) {
         // Nothing to do.
     }
 
-    public void onLongPressTimeout(int code) {
+    public void onLongPressTimeout(final int code) {
         // TODO: Handle simultaneous long presses.
         if (mLongPressTimeoutCode == code) {
             mLongPressTimeoutCode = 0;
@@ -165,26 +165,26 @@
         mState.onUpdateShiftState(mAutoCapsState);
     }
 
-    public void loadKeyboard(String layoutSwitchBackSymbols) {
-        mState.onLoadKeyboard(layoutSwitchBackSymbols);
+    public void loadKeyboard() {
+        mState.onLoadKeyboard();
     }
 
     public void saveKeyboardState() {
         mState.onSaveKeyboardState();
     }
 
-    public void onPressKey(int code, boolean isSinglePointer) {
+    public void onPressKey(final int code, final boolean isSinglePointer) {
         mState.onPressKey(code, isSinglePointer, mAutoCapsState);
     }
 
-    public void onReleaseKey(int code, boolean withSliding) {
+    public void onReleaseKey(final int code, final boolean withSliding) {
         mState.onReleaseKey(code, withSliding);
         if (mLongPressTimeoutCode == code) {
             mLongPressTimeoutCode = 0;
         }
     }
 
-    public void onCodeInput(int code, boolean isSinglePointer) {
+    public void onCodeInput(final int code, final boolean isSinglePointer) {
         if (mAutoCapsMode == MockConstants.CAP_MODE_WORDS) {
             if (Constants.isLetterCode(code)) {
                 mAutoCapsState = (code == MockConstants.CODE_AUTO_CAPS_TRIGGER)
@@ -196,7 +196,7 @@
         mState.onCodeInput(code, isSinglePointer, mAutoCapsState);
     }
 
-    public void onCancelInput(boolean isSinglePointer) {
+    public void onCancelInput(final boolean isSinglePointer) {
         mState.onCancelInput(isSinglePointer);
     }
 }
\ No newline at end of file