Merge "Introduce DynamicPatriciaTriePolicy for ver 3 dictionary."
diff --git a/java/res/values-ka/strings.xml b/java/res/values-ka/strings.xml
index bc8bda3..5719e6c 100644
--- a/java/res/values-ka/strings.xml
+++ b/java/res/values-ka/strings.xml
@@ -43,8 +43,7 @@
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"არ დაყოვნდეს"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"ნაგულისხმევი"</string>
     <string name="abbreviation_unit_milliseconds" msgid="8700286094028323363">"<xliff:g id="MILLISECONDS">%s</xliff:g>მწმ"</string>
-    <!-- no translation found for settings_system_default (6268225104743331821) -->
-    <skip />
+    <string name="settings_system_default" msgid="6268225104743331821">"სისტემის ნაგულისხმევი"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"კონტაქტის სახელების შეთავაზება"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"კონტაქტებიდან სახელების გამოყენება შეთავაზებებისთვის და კორექციისთვის"</string>
     <string name="use_double_space_period" msgid="8781529969425082860">"წერტილი ორმაგი შორისით"</string>
diff --git a/java/res/values-mn/strings.xml b/java/res/values-mn/strings.xml
index a75146f..04b5109 100644
--- a/java/res/values-mn/strings.xml
+++ b/java/res/values-mn/strings.xml
@@ -43,8 +43,7 @@
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Хүлээхгүй"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Үндсэн"</string>
     <string name="abbreviation_unit_milliseconds" msgid="8700286094028323363">"<xliff:g id="MILLISECONDS">%s</xliff:g>мс"</string>
-    <!-- no translation found for settings_system_default (6268225104743331821) -->
-    <skip />
+    <string name="settings_system_default" msgid="6268225104743331821">"Системийн үндсэн утга"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"Харилцагчдын нэрс санал болгох"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Санал болгох, залруулахда Харилцагчдын нэрсээс ашиглах"</string>
     <string name="use_double_space_period" msgid="8781529969425082860">"Давхар зайтай цэг"</string>
diff --git a/java/res/values/sudden-jumping-touch-event-device-list.xml b/java/res/values/sudden-jumping-touch-event-device-list.xml
deleted file mode 100644
index 3a9c379..0000000
--- a/java/res/values/sudden-jumping-touch-event-device-list.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2011, 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.
-*/
--->
-<resources>
-    <string-array name="sudden_jumping_touch_event_device_list" translatable="false">
-        <!-- "Build condition,true" that needs "sudden jump touch event" hack.
-             See {@link com.android.inputmethod.keyboard.SuddenJumpingTouchEventHandler}. -->
-        <!-- Nexus One -->
-        <item>HARDWARE=mahimahi,true</item>
-        <!-- Droid -->
-        <item>HARDWARE=sholes,true</item>
-        <!-- Default value for unknown device -->
-        <item>,false</item>
-    </string-array>
-</resources>
diff --git a/java/res/xml-sw600dp/key_azerty_quote.xml b/java/res/xml-sw600dp/key_azerty3_right.xml
similarity index 90%
rename from java/res/xml-sw600dp/key_azerty_quote.xml
rename to java/res/xml-sw600dp/key_azerty3_right.xml
index 0e4a8ec..a5a6e95 100644
--- a/java/res/xml-sw600dp/key_azerty_quote.xml
+++ b/java/res/xml-sw600dp/key_azerty3_right.xml
@@ -22,8 +22,8 @@
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
 >
     <Key
-        latin:keyLabel="\'"
-        latin:keyHintLabel=":"
-        latin:moreKeys=":"
+        latin:keyLabel=":"
+        latin:keyHintLabel=";"
+        latin:moreKeys=";"
         latin:keyStyle="hasShiftedLetterHintStyle" />
 </merge>
diff --git a/java/res/xml-sw600dp/rowkeys_symbols2.xml b/java/res/xml-sw600dp/rowkeys_symbols2.xml
index 7d7dcfe..14abb42 100644
--- a/java/res/xml-sw600dp/rowkeys_symbols2.xml
+++ b/java/res/xml-sw600dp/rowkeys_symbols2.xml
@@ -62,10 +62,11 @@
         latin:keyLabel="*"
         latin:moreKeys="!text/more_keys_for_star" />
     <!-- U+2013: "–" EN DASH
-         U+2014: "—" EM DASH -->
+         U+2014: "—" EM DASH
+         U+00B7: "·" MIDDLE DOT -->
     <Key
         latin:keyLabel="-"
-        latin:moreKeys="_,&#x2013;,&#x2014;" />
+        latin:moreKeys="_,&#x2013;,&#x2014;,&#x00B7;" />
     <Key
         latin:keyLabel="+"
         latin:moreKeys="!text/more_keys_for_plus" />
diff --git a/java/res/xml/key_azerty_quote.xml b/java/res/xml/key_azerty3_right.xml
similarity index 100%
rename from java/res/xml/key_azerty_quote.xml
rename to java/res/xml/key_azerty3_right.xml
diff --git a/java/res/xml/rowkeys_azerty3.xml b/java/res/xml/rowkeys_azerty3.xml
index 9f4c608..2643f32 100644
--- a/java/res/xml/rowkeys_azerty3.xml
+++ b/java/res/xml/rowkeys_azerty3.xml
@@ -38,5 +38,5 @@
         latin:keyLabel="n"
         latin:moreKeys="!text/more_keys_for_n" />
     <include
-        latin:keyboardLayout="@xml/key_azerty_quote" />
+        latin:keyboardLayout="@xml/key_azerty3_right" />
 </merge>
diff --git a/java/res/xml/rowkeys_symbols2.xml b/java/res/xml/rowkeys_symbols2.xml
index d3c1278..3e27f15 100644
--- a/java/res/xml/rowkeys_symbols2.xml
+++ b/java/res/xml/rowkeys_symbols2.xml
@@ -54,10 +54,11 @@
         latin:keyLabel="*"
         latin:moreKeys="!text/more_keys_for_star" />
     <!-- U+2013: "–" EN DASH
-         U+2014: "—" EM DASH -->
+         U+2014: "—" EM DASH
+         U+00B7: "·" MIDDLE DOT -->
     <Key
         latin:keyLabel="-"
-        latin:moreKeys="_,&#x2013;,&#x2014;" />
+        latin:moreKeys="_,&#x2013;,&#x2014;,&#x00B7;" />
     <Key
         latin:keyLabel="+"
         latin:moreKeys="!text/more_keys_for_plus" />
diff --git a/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java b/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java
index e195a54..55282c5 100644
--- a/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java
+++ b/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java
@@ -32,8 +32,6 @@
 import java.util.ArrayList;
 
 public final class SuggestionSpanUtils {
-    private static final String TAG = SuggestionSpanUtils.class.getSimpleName();
-
     // Note that SuggestionSpan.FLAG_AUTO_CORRECTION has been introduced
     // in API level 15 (Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1).
     public static final Field FIELD_FLAG_AUTO_CORRECTION = CompatUtils.getField(
@@ -60,7 +58,7 @@
         }
         final Spannable spannable = new SpannableString(text);
         final SuggestionSpan suggestionSpan = new SuggestionSpan(context, null /* locale */,
-                new String[] {} /* suggestions */, (int)OBJ_FLAG_AUTO_CORRECTION,
+                new String[] {} /* suggestions */, OBJ_FLAG_AUTO_CORRECTION,
                 SuggestionSpanPickedNotificationReceiver.class);
         spannable.setSpan(suggestionSpan, 0, text.length(),
                 Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING);
diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionaryProvider.java b/java/src/com/android/inputmethod/dictionarypack/DictionaryProvider.java
index 62b905d..1d9b999 100644
--- a/java/src/com/android/inputmethod/dictionarypack/DictionaryProvider.java
+++ b/java/src/com/android/inputmethod/dictionarypack/DictionaryProvider.java
@@ -54,7 +54,6 @@
     private static final String QUERY_PARAMETER_MAY_PROMPT_USER = "mayPrompt";
     private static final String QUERY_PARAMETER_TRUE = "true";
     private static final String QUERY_PARAMETER_DELETE_RESULT = "result";
-    private static final String QUERY_PARAMETER_SUCCESS = "success";
     private static final String QUERY_PARAMETER_FAILURE = "failure";
     public static final String QUERY_PARAMETER_PROTOCOL_VERSION = "protocol";
     private static final int NO_MATCH = 0;
diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionaryService.java b/java/src/com/android/inputmethod/dictionarypack/DictionaryService.java
index 939c25f..41916b6 100644
--- a/java/src/com/android/inputmethod/dictionarypack/DictionaryService.java
+++ b/java/src/com/android/inputmethod/dictionarypack/DictionaryService.java
@@ -50,8 +50,6 @@
  *     to access, and mark the current state as such.
  */
 public final class DictionaryService extends Service {
-    private static final String TAG = DictionaryService.class.getName();
-
     /**
      * The package name, to use in the intent actions.
      */
diff --git a/java/src/com/android/inputmethod/dictionarypack/EventHandler.java b/java/src/com/android/inputmethod/dictionarypack/EventHandler.java
index d8aa33b..859f1b3 100644
--- a/java/src/com/android/inputmethod/dictionarypack/EventHandler.java
+++ b/java/src/com/android/inputmethod/dictionarypack/EventHandler.java
@@ -21,8 +21,6 @@
 import android.content.Intent;
 
 public final class EventHandler extends BroadcastReceiver {
-    private static final String TAG = EventHandler.class.getName();
-
     /**
      * Receives a intent broadcast.
      *
diff --git a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
index 6aa43b9..43baf61 100644
--- a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
@@ -55,14 +55,12 @@
 import com.android.inputmethod.keyboard.internal.KeyPreviewDrawParams;
 import com.android.inputmethod.keyboard.internal.PreviewPlacerView;
 import com.android.inputmethod.keyboard.internal.SlidingKeyInputPreview;
-import com.android.inputmethod.keyboard.internal.TouchScreenRegulator;
 import com.android.inputmethod.latin.Constants;
 import com.android.inputmethod.latin.LatinImeLogger;
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.SuggestedWords;
 import com.android.inputmethod.latin.define.ProductionFlag;
 import com.android.inputmethod.latin.settings.DebugSettings;
-import com.android.inputmethod.latin.settings.Settings;
 import com.android.inputmethod.latin.utils.CollectionUtils;
 import com.android.inputmethod.latin.utils.CoordinateUtils;
 import com.android.inputmethod.latin.utils.StaticInnerHandlerWrapper;
@@ -116,13 +114,9 @@
  * @attr ref R.styleable#MainKeyboardView_suppressKeyPreviewAfterBatchInputDuration
  */
 public final class MainKeyboardView extends KeyboardView implements PointerTracker.KeyEventHandler,
-        PointerTracker.DrawingProxy, MoreKeysPanel.Controller,
-        TouchScreenRegulator.ProcessMotionEvent {
+        PointerTracker.DrawingProxy, MoreKeysPanel.Controller {
     private static final String TAG = MainKeyboardView.class.getSimpleName();
 
-    // TODO: Kill process when the usability study mode was changed.
-    private static final boolean ENABLE_USABILITY_STUDY_LOG = LatinImeLogger.sUsabilityStudy;
-
     /** Listener for {@link KeyboardActionListener}. */
     private KeyboardActionListener mKeyboardActionListener;
 
@@ -184,8 +178,6 @@
     // TODO: Make this parameter customizable by user via settings.
     private int mGestureFloatingPreviewTextLingerTimeout;
 
-    private final TouchScreenRegulator mTouchScreenRegulator;
-
     private KeyDetector mKeyDetector;
     private final boolean mHasDistinctMultitouch;
     private int mOldPointerCount = 1;
@@ -203,7 +195,6 @@
 
         private final int mKeyRepeatStartTimeout;
         private final int mKeyRepeatInterval;
-        private final int mLongPressShiftLockTimeout;
         private final int mIgnoreAltCodeKeyTimeout;
         private final int mGestureRecognitionUpdateTime;
 
@@ -215,8 +206,6 @@
                     R.styleable.MainKeyboardView_keyRepeatStartTimeout, 0);
             mKeyRepeatInterval = mainKeyboardViewAttr.getInt(
                     R.styleable.MainKeyboardView_keyRepeatInterval, 0);
-            mLongPressShiftLockTimeout = mainKeyboardViewAttr.getInt(
-                    R.styleable.MainKeyboardView_longPressShiftLockTimeout, 0);
             mIgnoreAltCodeKeyTimeout = mainKeyboardViewAttr.getInt(
                     R.styleable.MainKeyboardView_ignoreAltCodeKeyTimeout, 0);
             mGestureRecognitionUpdateTime = mainKeyboardViewAttr.getInt(
@@ -280,31 +269,10 @@
         }
 
         @Override
-        public void startLongPressTimer(final PointerTracker tracker) {
+        public void startLongPressTimer(final PointerTracker tracker, final int delay) {
             cancelLongPressTimer();
-            if (tracker == null) {
-                return;
-            }
-            final Key key = tracker.getKey();
-            final int delay;
-            switch (key.mCode) {
-            case Constants.CODE_SHIFT:
-                delay = mLongPressShiftLockTimeout;
-                break;
-            default:
-                final int longpressTimeout =
-                        Settings.getInstance().getCurrent().mKeyLongpressTimeout;
-                if (tracker.isInSlidingKeyInputFromModifier()) {
-                    // We use longer timeout for sliding finger input started from the modifier key.
-                    delay = longpressTimeout * 3;
-                } else {
-                    delay = longpressTimeout;
-                }
-                break;
-            }
-            if (delay > 0) {
-                sendMessageDelayed(obtainMessage(MSG_LONGPRESS_KEY, tracker), delay);
-            }
+            if (delay <= 0) return;
+            sendMessageDelayed(obtainMessage(MSG_LONGPRESS_KEY, tracker), delay);
         }
 
         @Override
@@ -475,8 +443,6 @@
     public MainKeyboardView(final Context context, final AttributeSet attrs, final int defStyle) {
         super(context, attrs, defStyle);
 
-        mTouchScreenRegulator = new TouchScreenRegulator(context, this);
-
         final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
         final boolean forceNonDistinctMultitouch = prefs.getBoolean(
                 DebugSettings.PREF_FORCE_NON_DISTINCT_MULTITOUCH, false);
@@ -653,7 +619,6 @@
         mKeyDetector.setKeyboard(
                 keyboard, -getPaddingLeft(), -getPaddingTop() + getVerticalCorrection());
         PointerTracker.setKeyDetector(mKeyDetector);
-        mTouchScreenRegulator.setKeyboardGeometry(keyboard.mOccupiedWidth);
         mMoreKeysKeyboardCache.clear();
 
         mSpaceKey = keyboard.getKey(Constants.CODE_SPACE);
@@ -901,9 +866,12 @@
     }
 
     @Override
-    public void showGestureTrail(final PointerTracker tracker) {
+    public void showGestureTrail(final PointerTracker tracker,
+            final boolean showsFloatingPreviewText) {
         locatePreviewPlacerView();
-        mGestureFloatingPreviewText.setPreviewPosition(tracker);
+        if (showsFloatingPreviewText) {
+            mGestureFloatingPreviewText.setPreviewPosition(tracker);
+        }
         mGestureTrailsPreview.setPreviewPosition(tracker);
     }
 
@@ -1072,10 +1040,11 @@
         if (getKeyboard() == null) {
             return false;
         }
-        return mTouchScreenRegulator.onTouchEvent(me);
+        // TODO: Add multi-touch to single-touch event converter for non-distinct multi-touch
+        // device.
+        return processMotionEvent(me);
     }
 
-    @Override
     public boolean processMotionEvent(final MotionEvent me) {
         final boolean nonDistinctMultitouch = !mHasDistinctMultitouch;
         final int action = me.getActionMasked();
@@ -1097,8 +1066,8 @@
         final int y = (int)me.getY(index);
 
         // TODO: This might be moved to the tracker.processMotionEvent() call below.
-        if (ENABLE_USABILITY_STUDY_LOG && action != MotionEvent.ACTION_MOVE) {
-            writeUsabilityStudyLog(me, action, eventTime, index, id, x, y);
+        if (LatinImeLogger.sUsabilityStudy) {
+            UsabilityStudyLogUtils.writeMotionEvent(me);
         }
         // TODO: This should be moved to the tracker.processMotionEvent() call below.
         // Currently the same "move" event is being logged twice.
@@ -1159,15 +1128,6 @@
                 final int px = (int)me.getX(i);
                 final int py = (int)me.getY(i);
                 tracker.onMoveEvent(px, py, eventTime, me);
-                if (ENABLE_USABILITY_STUDY_LOG) {
-                    writeUsabilityStudyLog(me, action, eventTime, i, pointerId, px, py);
-                }
-                // TODO: This seems to be no longer necessary, and confusing because it leads to
-                // duplicate MotionEvents being recorded.
-                // if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
-                //     ResearchLogger.mainKeyboardView_processMotionEvent(
-                //             me, action, eventTime, i, pointerId, px, py);
-                // }
             }
         } else {
             final PointerTracker tracker = PointerTracker.getPointerTracker(id, this);
@@ -1177,35 +1137,6 @@
         return true;
     }
 
-    private static void writeUsabilityStudyLog(final MotionEvent me, final int action,
-            final long eventTime, final int index, final int id, final int x, final int y) {
-        final String eventTag;
-        switch (action) {
-        case MotionEvent.ACTION_UP:
-            eventTag = "[Up]";
-            break;
-        case MotionEvent.ACTION_DOWN:
-            eventTag = "[Down]";
-            break;
-        case MotionEvent.ACTION_POINTER_UP:
-            eventTag = "[PointerUp]";
-            break;
-        case MotionEvent.ACTION_POINTER_DOWN:
-            eventTag = "[PointerDown]";
-            break;
-        case MotionEvent.ACTION_MOVE:
-            eventTag = "[Move]";
-            break;
-        default:
-            eventTag = "[Action" + action + "]";
-            break;
-        }
-        final float size = me.getSize(index);
-        final float pressure = me.getPressure(index);
-        UsabilityStudyLogUtils.getInstance().write(
-                eventTag + eventTime + "," + id + "," + x + "," + y + "," + size + "," + pressure);
-    }
-
     public void cancelAllOngoingEvents() {
         mKeyTimerHandler.cancelAllMessages();
         mDrawingHandler.cancelAllMessages();
@@ -1398,4 +1329,8 @@
             drawIcon(canvas, mSpaceIcon, x, y, iconWidth, iconHeight);
         }
     }
+
+    public void deallocateMemory() {
+        mGestureTrailsPreview.deallocateMemory();
+    }
 }
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
index c7b0964..20fc109 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
@@ -34,6 +34,7 @@
 import com.android.inputmethod.latin.LatinImeLogger;
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.define.ProductionFlag;
+import com.android.inputmethod.latin.settings.Settings;
 import com.android.inputmethod.latin.utils.CollectionUtils;
 import com.android.inputmethod.latin.utils.CoordinateUtils;
 import com.android.inputmethod.latin.utils.ResourceUtils;
@@ -87,14 +88,14 @@
         public void dismissKeyPreview(PointerTracker tracker);
         public void showSlidingKeyInputPreview(PointerTracker tracker);
         public void dismissSlidingKeyInputPreview();
-        public void showGestureTrail(PointerTracker tracker);
+        public void showGestureTrail(PointerTracker tracker, boolean showsFloatingPreviewText);
     }
 
     public interface TimerProxy {
         public void startTypingStateTimer(Key typedKey);
         public boolean isTypingState();
         public void startKeyRepeatTimer(PointerTracker tracker);
-        public void startLongPressTimer(PointerTracker tracker);
+        public void startLongPressTimer(PointerTracker tracker, int delay);
         public void cancelLongPressTimer();
         public void startDoubleTapShiftKeyTimer();
         public void cancelDoubleTapShiftKeyTimer();
@@ -112,7 +113,7 @@
             @Override
             public void startKeyRepeatTimer(PointerTracker tracker) {}
             @Override
-            public void startLongPressTimer(PointerTracker tracker) {}
+            public void startLongPressTimer(PointerTracker tracker, int delay) {}
             @Override
             public void cancelLongPressTimer() {}
             @Override
@@ -137,6 +138,7 @@
         public final int mTouchNoiseThresholdTime;
         public final int mTouchNoiseThresholdDistance;
         public final int mSuppressKeyPreviewAfterBatchInputDuration;
+        public final int mLongPressShiftLockTimeout;
 
         public static final PointerTrackerParams DEFAULT = new PointerTrackerParams();
 
@@ -145,6 +147,7 @@
             mTouchNoiseThresholdTime = 0;
             mTouchNoiseThresholdDistance = 0;
             mSuppressKeyPreviewAfterBatchInputDuration = 0;
+            mLongPressShiftLockTimeout = 0;
         }
 
         public PointerTrackerParams(final TypedArray mainKeyboardViewAttr) {
@@ -156,6 +159,8 @@
                     R.styleable.MainKeyboardView_touchNoiseThresholdDistance, 0);
             mSuppressKeyPreviewAfterBatchInputDuration = mainKeyboardViewAttr.getInt(
                     R.styleable.MainKeyboardView_suppressKeyPreviewAfterBatchInputDuration, 0);
+            mLongPressShiftLockTimeout = mainKeyboardViewAttr.getInt(
+                    R.styleable.MainKeyboardView_longPressShiftLockTimeout, 0);
         }
     }
 
@@ -327,6 +332,7 @@
     // the more keys panel currently being shown. equals null if no panel is active.
     private MoreKeysPanel mMoreKeysPanel;
 
+    private static final int MULTIPLIER_FOR_LONG_PRESS_TIMEOUT_IN_SLIDING_INPUT = 3;
     // true if this pointer is in a sliding key input.
     boolean mIsInSlidingKeyInput;
     // true if this pointer is in a sliding key input from a modifier key,
@@ -602,10 +608,6 @@
         return mIsInSlidingKeyInput;
     }
 
-    public boolean isInSlidingKeyInputFromModifier() {
-        return mIsInSlidingKeyInputFromModifier;
-    }
-
     public Key getKey() {
         return mCurrentKey;
     }
@@ -753,7 +755,7 @@
         return sPointerTrackerQueue.size();
     }
 
-    public boolean isOldestTrackerInQueue() {
+    private boolean isOldestTrackerInQueue() {
         return sPointerTrackerQueue.getOldestElement() == this;
     }
 
@@ -776,7 +778,9 @@
             dismissAllMoreKeysPanels();
         }
         mTimerProxy.cancelLongPressTimer();
-        mDrawingProxy.showGestureTrail(this);
+        // A gesture floating preview text will be shown at the oldest pointer/finger on the screen.
+        mDrawingProxy.showGestureTrail(
+                this, isOldestTrackerInQueue() /* showsFloatingPreviewText */);
     }
 
     public void updateBatchInputByTimer(final long eventTime) {
@@ -792,7 +796,9 @@
         if (mIsTrackingForActionDisabled) {
             return;
         }
-        mDrawingProxy.showGestureTrail(this);
+        // A gesture floating preview text will be shown at the oldest pointer/finger on the screen.
+        mDrawingProxy.showGestureTrail(
+                this, isOldestTrackerInQueue() /* showsFloatingPreviewText */);
     }
 
     private void updateBatchInput(final long eventTime) {
@@ -833,7 +839,9 @@
         if (mIsTrackingForActionDisabled) {
             return;
         }
-        mDrawingProxy.showGestureTrail(this);
+        // A gesture floating preview text will be shown at the oldest pointer/finger on the screen.
+        mDrawingProxy.showGestureTrail(
+                this, isOldestTrackerInQueue() /* showsFloatingPreviewText */);
     }
 
     private void cancelBatchInput() {
@@ -1013,7 +1021,9 @@
             final int translatedY = mMoreKeysPanel.translateY(y);
             mMoreKeysPanel.onMoveEvent(translatedX, translatedY, mPointerId, eventTime);
             onMoveKey(x, y);
-            mDrawingProxy.showSlidingKeyInputPreview(this);
+            if (mIsInSlidingKeyInputFromModifier) {
+                mDrawingProxy.showSlidingKeyInputPreview(this);
+            }
             return;
         }
         onMoveEventInternal(x, y, eventTime);
@@ -1168,7 +1178,9 @@
                 slideOutFromOldKey(oldKey, x, y);
             }
         }
-        mDrawingProxy.showSlidingKeyInputPreview(this);
+        if (mIsInSlidingKeyInputFromModifier) {
+            mDrawingProxy.showSlidingKeyInputPreview(this);
+        }
     }
 
     public void onUpEvent(final int x, final int y, final long eventTime) {
@@ -1353,7 +1365,22 @@
         // We always need to start the long press timer if the key has its more keys regardless of
         // whether or not we are in the sliding input mode.
         if (mIsInSlidingKeyInput && key.mMoreKeys == null) return;
-        mTimerProxy.startLongPressTimer(this);
+        final int delay;
+        switch (key.mCode) {
+        case Constants.CODE_SHIFT:
+            delay = sParams.mLongPressShiftLockTimeout;
+            break;
+        default:
+            final int longpressTimeout = Settings.getInstance().getCurrent().mKeyLongpressTimeout;
+            if (mIsInSlidingKeyInputFromModifier) {
+                // We use longer timeout for sliding finger input started from the modifier key.
+                delay = longpressTimeout * MULTIPLIER_FOR_LONG_PRESS_TIMEOUT_IN_SLIDING_INPUT;
+            } else {
+                delay = longpressTimeout;
+            }
+            break;
+        }
+        mTimerProxy.startLongPressTimer(this, delay);
     }
 
     private void detectAndSendKey(final Key key, final int x, final int y, final long eventTime) {
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureFloatingPreviewText.java b/java/src/com/android/inputmethod/keyboard/internal/GestureFloatingPreviewText.java
index 9bfddba..c6dd9e1 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/GestureFloatingPreviewText.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/GestureFloatingPreviewText.java
@@ -115,9 +115,7 @@
 
     @Override
     public void setPreviewPosition(final PointerTracker tracker) {
-        final boolean needsToUpdateLastPointer =
-                tracker.isOldestTrackerInQueue() && isPreviewEnabled();
-        if (!needsToUpdateLastPointer) {
+        if (!isPreviewEnabled()) {
             return;
         }
         tracker.getLastCoordinates(mLastPointerCoords);
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureTrailsPreview.java b/java/src/com/android/inputmethod/keyboard/internal/GestureTrailsPreview.java
index dff5177..d4c2594 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/GestureTrailsPreview.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/GestureTrailsPreview.java
@@ -104,7 +104,12 @@
         freeOffscreenBuffer();
     }
 
+    public void deallocateMemory() {
+        freeOffscreenBuffer();
+    }
+
     private void freeOffscreenBuffer() {
+        mOffscreenCanvas.setBitmap(null);
         if (mOffscreenBuffer != null) {
             mOffscreenBuffer.recycle();
             mOffscreenBuffer = null;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
index 8ead44c..164910d 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
@@ -29,8 +29,8 @@
  *
  * The input events are {@link #onLoadKeyboard()}, {@link #onSaveKeyboardState()},
  * {@link #onPressKey(int,boolean,int)}, {@link #onReleaseKey(int,boolean)},
- * {@link #onCodeInput(int,int)}, {@link #onFinishSlidingInput()}, {@link #onCancelInput()},
- * {@link #onUpdateShiftState(int,int)}.
+ * {@link #onCodeInput(int,int)}, {@link #onFinishSlidingInput()},
+ * {@link #onUpdateShiftState(int,int)}, {@link #onResetKeyboardStateToAlphabet()}.
  *
  * The actions are {@link SwitchActions}'s methods.
  */
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java
index 1594df7..7bb7442 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java
@@ -374,8 +374,7 @@
         /* 115 */ "w",
         /* 116 */ "y",
         /* 117 */ "x",
-        // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
-        /* 118 */ "\u00F1",
+        /* 118 */ EMPTY,
         /* 119 */ "!fixedColumnOrder!2,!hasLabels!,!text/label_time_am,!text/label_time_pm",
         /* 120 */ "!icon/settings_key|!code/key_settings",
         /* 121 */ "!icon/shortcut_key|!code/key_shortcut",
@@ -625,7 +624,8 @@
 
     /* Language az: Azerbaijani */
     private static final String[] LANGUAGE_az = {
-        /* 0 */ null,
+        // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX
+        /* 0 */ "\u00E2",
         // U+0259: "ə" LATIN SMALL LETTER SCHWA
         /* 1 */ "\u0259",
         // U+0131: "ı" LATIN SMALL LETTER DOTLESS I
@@ -776,9 +776,28 @@
         /* 8~ */
         null, null, null, null, null, null,
         /* ~13 */
-        // U+0140: "ŀ" LATIN SMALL LETTER L WITH MIDDLE DOT
+        // U+00B7: "·" MIDDLE DOT
         // U+0142: "ł" LATIN SMALL LETTER L WITH STROKE
-        /* 14 */ "\u0140,\u0142",
+        /* 14 */ "l\u00B7l,\u0142",
+        /* 15~ */
+        null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+        null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+        null, null, null, null, null, null, null, null,
+        /* ~52 */
+        // U+00B7: "·" MIDDLE DOT
+        /* 53 */ "!fixedColumnOrder!9,\u00B7,\",\',#,-,:,!,\\,,?,@,&,\\%,+,;,/,(,)",
+        /* 54~ */
+        null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+        null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+        null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+        null, null, null, null, null, null, null, null, null,
+        /* ~107 */
+        /* 108 */ "?,\u00B7",
+        /* 109~ */
+        null, null, null, null, null, null, null, null, null,
+        /* ~117 */
+        // U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
+        /* 118 */ "\u00E7",
     };
 
     /* Language cs: Czech */
@@ -1253,6 +1272,11 @@
         /* 109 */ "\"",
         /* 110 */ "\'",
         /* 111 */ "\'",
+        /* 112~ */
+        null, null, null, null, null, null,
+        /* ~117 */
+        // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
+        /* 118 */ "\u00F1",
     };
 
     /* Language et: Estonian */
diff --git a/java/src/com/android/inputmethod/keyboard/internal/MatrixUtils.java b/java/src/com/android/inputmethod/keyboard/internal/MatrixUtils.java
index 4916a15..c1f3749 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/MatrixUtils.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/MatrixUtils.java
@@ -30,7 +30,6 @@
 public class MatrixUtils {
     private static final String TAG = MatrixUtils.class.getSimpleName();
     public static class MatrixOperationFailedException extends Exception {
-        private static final String TAG = MatrixOperationFailedException.class.getSimpleName();
         private static final long serialVersionUID = 4384485606788583829L;
 
         public MatrixOperationFailedException(String msg) {
diff --git a/java/src/com/android/inputmethod/keyboard/internal/SlidingKeyInputPreview.java b/java/src/com/android/inputmethod/keyboard/internal/SlidingKeyInputPreview.java
index 5c9d367..2787ebf 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/SlidingKeyInputPreview.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/SlidingKeyInputPreview.java
@@ -32,7 +32,7 @@
 public final class SlidingKeyInputPreview extends AbstractDrawingPreview {
     private final float mPreviewBodyRadius;
 
-    private boolean mShowSlidingKeyInputPreview;
+    private boolean mShowsSlidingKeyInputPreview;
     private final int[] mPreviewFrom = CoordinateUtils.newInstance();
     private final int[] mPreviewTo = CoordinateUtils.newInstance();
 
@@ -62,7 +62,7 @@
     }
 
     public void dismissSlidingKeyInputPreview() {
-        mShowSlidingKeyInputPreview = false;
+        mShowsSlidingKeyInputPreview = false;
         getDrawingView().invalidate();
     }
 
@@ -72,7 +72,7 @@
      */
     @Override
     public void drawPreview(final Canvas canvas) {
-        if (!isPreviewEnabled() || !mShowSlidingKeyInputPreview) {
+        if (!isPreviewEnabled() || !mShowsSlidingKeyInputPreview) {
             return;
         }
 
@@ -90,13 +90,9 @@
      */
     @Override
     public void setPreviewPosition(final PointerTracker tracker) {
-        if (!tracker.isInSlidingKeyInputFromModifier()) {
-            mShowSlidingKeyInputPreview = false;
-            return;
-        }
         tracker.getDownCoordinates(mPreviewFrom);
         tracker.getLastCoordinates(mPreviewTo);
-        mShowSlidingKeyInputPreview = true;
+        mShowsSlidingKeyInputPreview = true;
         getDrawingView().invalidate();
     }
 }
diff --git a/java/src/com/android/inputmethod/keyboard/internal/SmoothingUtils.java b/java/src/com/android/inputmethod/keyboard/internal/SmoothingUtils.java
index e5665bc..10847f6 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/SmoothingUtils.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/SmoothingUtils.java
@@ -62,7 +62,7 @@
             for (int j = 0; j < COEFF_COUNT; ++j) {
                 final int pow = i + j;
                 for (int k = 0; k < N; ++k) {
-                    m0[i][j] += (float) Math.pow((double) xs[k], pow);
+                    m0[i][j] += (float) Math.pow(xs[k], pow);
                 }
             }
         }
diff --git a/java/src/com/android/inputmethod/keyboard/internal/TouchScreenRegulator.java b/java/src/com/android/inputmethod/keyboard/internal/TouchScreenRegulator.java
deleted file mode 100644
index fddd985..0000000
--- a/java/src/com/android/inputmethod/keyboard/internal/TouchScreenRegulator.java
+++ /dev/null
@@ -1,155 +0,0 @@
-/*
- * Copyright (C) 2011 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.keyboard.internal;
-
-import android.content.Context;
-import android.util.Log;
-import android.view.MotionEvent;
-
-import com.android.inputmethod.keyboard.MainKeyboardView;
-import com.android.inputmethod.latin.LatinImeLogger;
-import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.define.ProductionFlag;
-import com.android.inputmethod.latin.utils.ResourceUtils;
-import com.android.inputmethod.research.ResearchLogger;
-
-public final class TouchScreenRegulator {
-    private static final String TAG = TouchScreenRegulator.class.getSimpleName();
-    private static boolean DEBUG_MODE = LatinImeLogger.sDBG;
-
-    public interface ProcessMotionEvent {
-        public boolean processMotionEvent(MotionEvent me);
-    }
-
-    private final ProcessMotionEvent mView;
-    private final boolean mNeedsSuddenJumpingHack;
-
-    /** Whether we've started dropping move events because we found a big jump */
-    private boolean mDroppingEvents;
-    /**
-     * Whether multi-touch disambiguation needs to be disabled if a real multi-touch event has
-     * occured
-     */
-    private boolean mDisableDisambiguation;
-    /** The distance threshold at which we start treating the touch session as a multi-touch */
-    private int mJumpThresholdSquare = Integer.MAX_VALUE;
-    private int mLastX;
-    private int mLastY;
-    // One-seventh of the keyboard width seems like a reasonable threshold
-    private static final float JUMP_THRESHOLD_RATIO_TO_KEYBOARD_WIDTH = 1.0f / 7.0f;
-
-    public TouchScreenRegulator(final Context context, final ProcessMotionEvent view) {
-        mView = view;
-        mNeedsSuddenJumpingHack = Boolean.parseBoolean(ResourceUtils.getDeviceOverrideValue(
-                context.getResources(), R.array.sudden_jumping_touch_event_device_list));
-    }
-
-    public void setKeyboardGeometry(final int keyboardWidth) {
-        final float jumpThreshold = keyboardWidth * JUMP_THRESHOLD_RATIO_TO_KEYBOARD_WIDTH;
-        mJumpThresholdSquare = (int)(jumpThreshold * jumpThreshold);
-    }
-
-    /**
-     * This function checks to see if we need to handle any sudden jumps in the pointer location
-     * that could be due to a multi-touch being treated as a move by the firmware or hardware.
-     * Once a sudden jump is detected, all subsequent move events are discarded
-     * until an UP is received.<P>
-     * When a sudden jump is detected, an UP event is simulated at the last position and when
-     * the sudden moves subside, a DOWN event is simulated for the second key.
-     * @param me the motion event
-     * @return true if the event was consumed, so that it doesn't continue to be handled by
-     * {@link MainKeyboardView}.
-     */
-    private boolean handleSuddenJumping(final MotionEvent me) {
-        if (!mNeedsSuddenJumpingHack)
-            return false;
-        final int action = me.getAction();
-        final int x = (int) me.getX();
-        final int y = (int) me.getY();
-        boolean result = false;
-
-        // Real multi-touch event? Stop looking for sudden jumps
-        if (me.getPointerCount() > 1) {
-            mDisableDisambiguation = true;
-        }
-        if (mDisableDisambiguation) {
-            // If UP, reset the multi-touch flag
-            if (action == MotionEvent.ACTION_UP) mDisableDisambiguation = false;
-            return false;
-        }
-
-        switch (action) {
-        case MotionEvent.ACTION_DOWN:
-            // Reset the "session"
-            mDroppingEvents = false;
-            mDisableDisambiguation = false;
-            break;
-        case MotionEvent.ACTION_MOVE:
-            // Is this a big jump?
-            final int distanceSquare = (mLastX - x) * (mLastX - x) + (mLastY - y) * (mLastY - y);
-            // Check the distance.
-            if (distanceSquare > mJumpThresholdSquare) {
-                // If we're not yet dropping events, start dropping and send an UP event
-                if (!mDroppingEvents) {
-                    mDroppingEvents = true;
-                    // Send an up event
-                    MotionEvent translated = MotionEvent.obtain(
-                            me.getEventTime(), me.getEventTime(),
-                            MotionEvent.ACTION_UP,
-                            mLastX, mLastY, me.getMetaState());
-                    mView.processMotionEvent(translated);
-                    translated.recycle();
-                }
-                result = true;
-            } else if (mDroppingEvents) {
-                // If moves are small and we're already dropping events, continue dropping
-                result = true;
-            }
-            break;
-        case MotionEvent.ACTION_UP:
-            if (mDroppingEvents) {
-                // Send a down event first, as we dropped a bunch of sudden jumps and assume that
-                // the user is releasing the touch on the second key.
-                MotionEvent translated = MotionEvent.obtain(me.getEventTime(), me.getEventTime(),
-                        MotionEvent.ACTION_DOWN,
-                        x, y, me.getMetaState());
-                mView.processMotionEvent(translated);
-                translated.recycle();
-                mDroppingEvents = false;
-                // Let the up event get processed as well, result = false
-            }
-            break;
-        }
-        // Track the previous coordinate
-        mLastX = x;
-        mLastY = y;
-        return result;
-    }
-
-    public boolean onTouchEvent(final MotionEvent me) {
-        // If there was a sudden jump, return without processing the actual motion event.
-        if (handleSuddenJumping(me)) {
-            if (DEBUG_MODE)
-                Log.w(TAG, "onTouchEvent: ignore sudden jump " + me);
-            if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
-                ResearchLogger.suddenJumpingTouchEventHandler_onTouchEvent(me);
-            }
-            return true;
-        }
-        return mView.processMotionEvent(me);
-    }
-}
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
index 31a892e..fa301b5 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
@@ -18,7 +18,6 @@
 
 import android.content.Context;
 import android.content.SharedPreferences;
-import android.content.pm.PackageManager;
 import android.content.res.AssetFileDescriptor;
 import android.util.Log;
 
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 9366abd..614c143 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -76,7 +76,7 @@
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
 import com.android.inputmethod.latin.define.ProductionFlag;
 import com.android.inputmethod.latin.personalization.PersonalizationDictionaryHelper;
-import com.android.inputmethod.latin.personalization.UserHistoryDictionary;
+import com.android.inputmethod.latin.personalization.UserHistoryPredictionDictionary;
 import com.android.inputmethod.latin.settings.Settings;
 import com.android.inputmethod.latin.settings.SettingsActivity;
 import com.android.inputmethod.latin.settings.SettingsValues;
@@ -169,7 +169,7 @@
 
     private boolean mIsMainDictionaryAvailable;
     private UserBinaryDictionary mUserDictionary;
-    private UserHistoryDictionary mUserHistoryDictionary;
+    private UserHistoryPredictionDictionary mUserHistoryPredictionDictionary;
     private boolean mIsUserDictionaryAvailable;
 
     private LastComposedWord mLastComposedWord = LastComposedWord.NOT_A_COMPOSED_WORD;
@@ -539,34 +539,32 @@
         final Locale subtypeLocale = mSubtypeSwitcher.getCurrentSubtypeLocale();
         final String localeStr = subtypeLocale.toString();
 
-        final ContactsBinaryDictionary oldContactsDictionary;
-        if (mSuggest != null) {
-            oldContactsDictionary = mSuggest.getContactsDictionary();
-            mSuggest.close();
-        } else {
-            oldContactsDictionary = null;
-        }
-        mSuggest = new Suggest(this /* Context */, subtypeLocale,
+        final Suggest newSuggest = new Suggest(this /* Context */, subtypeLocale,
                 this /* SuggestInitializationListener */);
-        if (mSettings.getCurrent().mCorrectionEnabled) {
-            mSuggest.setAutoCorrectionThreshold(mSettings.getCurrent().mAutoCorrectionThreshold);
+        final SettingsValues settingsValues = mSettings.getCurrent();
+        if (settingsValues.mCorrectionEnabled) {
+            newSuggest.setAutoCorrectionThreshold(settingsValues.mAutoCorrectionThreshold);
         }
 
         mIsMainDictionaryAvailable = DictionaryFactory.isDictionaryAvailable(this, subtypeLocale);
         if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
-            ResearchLogger.getInstance().initSuggest(mSuggest);
+            ResearchLogger.getInstance().initSuggest(newSuggest);
         }
 
         mUserDictionary = new UserBinaryDictionary(this, localeStr);
         mIsUserDictionaryAvailable = mUserDictionary.isEnabled();
-        mSuggest.setUserDictionary(mUserDictionary);
-
-        resetContactsDictionary(oldContactsDictionary);
+        newSuggest.setUserDictionary(mUserDictionary);
 
         final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
-        mUserHistoryDictionary =
-                PersonalizationDictionaryHelper.getUserHistoryDictionary(this, localeStr, prefs);
-        mSuggest.setUserHistoryDictionary(mUserHistoryDictionary);
+
+        mUserHistoryPredictionDictionary = PersonalizationDictionaryHelper
+                .getUserHistoryPredictionDictionary(this, localeStr, prefs);
+        newSuggest.setUserHistoryPredictionDictionary(mUserHistoryPredictionDictionary);
+
+        final Suggest oldSuggest = mSuggest;
+        resetContactsDictionary(null != oldSuggest ? oldSuggest.getContactsDictionary() : null);
+        mSuggest = newSuggest;
+        if (oldSuggest != null) oldSuggest.close();
     }
 
     /**
@@ -578,8 +576,9 @@
      * @param oldContactsDictionary an optional dictionary to use, or null
      */
     private void resetContactsDictionary(final ContactsBinaryDictionary oldContactsDictionary) {
+        final Suggest suggest = mSuggest;
         final boolean shouldSetDictionary =
-                (null != mSuggest && mSettings.getCurrent().mUseContactsDict);
+                (null != suggest && mSettings.getCurrent().mUseContactsDict);
 
         final ContactsBinaryDictionary dictionaryToUse;
         if (!shouldSetDictionary) {
@@ -606,8 +605,8 @@
             }
         }
 
-        if (null != mSuggest) {
-            mSuggest.setContactsDictionary(dictionaryToUse);
+        if (null != suggest) {
+            suggest.setContactsDictionary(dictionaryToUse);
         }
     }
 
@@ -619,8 +618,9 @@
 
     @Override
     public void onDestroy() {
-        if (mSuggest != null) {
-            mSuggest.close();
+        final Suggest suggest = mSuggest;
+        if (suggest != null) {
+            suggest.close();
             mSuggest = null;
         }
         mSettings.onDestroy();
@@ -714,7 +714,9 @@
         super.onStartInputView(editorInfo, restarting);
         final KeyboardSwitcher switcher = mKeyboardSwitcher;
         final MainKeyboardView mainKeyboardView = switcher.getMainKeyboardView();
-        final SettingsValues currentSettingsValues = mSettings.getCurrent();
+        // If we are starting input in a different text field from before, we'll have to reload
+        // settings, so currentSettingsValues can't be final.
+        SettingsValues currentSettingsValues = mSettings.getCurrent();
 
         if (editorInfo == null) {
             Log.e(TAG, "Null EditorInfo in onStartInputView()");
@@ -792,7 +794,8 @@
 
         // Note: the following does a round-trip IPC on the main thread: be careful
         final Locale currentLocale = mSubtypeSwitcher.getCurrentSubtypeLocale();
-        if (null != mSuggest && null != currentLocale && !currentLocale.equals(mSuggest.mLocale)) {
+        final Suggest suggest = mSuggest;
+        if (null != suggest && null != currentLocale && !currentLocale.equals(suggest.mLocale)) {
             initSuggest();
         }
         if (mSuggestionStripView != null) {
@@ -808,9 +811,10 @@
         if (isDifferentTextField) {
             mainKeyboardView.closing();
             loadSettings();
-            // TODO: Need to update currentSettingsValues after loadSettings()
-            if (mSuggest != null && currentSettingsValues.mCorrectionEnabled) {
-                mSuggest.setAutoCorrectionThreshold(currentSettingsValues.mAutoCorrectionThreshold);
+            currentSettingsValues = mSettings.getCurrent();
+
+            if (suggest != null && currentSettingsValues.mCorrectionEnabled) {
+                suggest.setAutoCorrectionThreshold(currentSettingsValues.mAutoCorrectionThreshold);
             }
 
             switcher.loadKeyboard(editorInfo, currentSettingsValues);
@@ -890,6 +894,7 @@
         final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
         if (mainKeyboardView != null) {
             mainKeyboardView.cancelAllOngoingEvents();
+            mainKeyboardView.deallocateMemory();
         }
         // Remove pending messages related to update suggestions
         mHandler.cancelUpdateSuggestionStrip();
@@ -1213,10 +1218,11 @@
     private void resetEntireInputState(final int newCursorPosition) {
         final boolean shouldFinishComposition = mWordComposer.isComposingWord();
         resetComposingState(true /* alsoResetLastComposedWord */);
-        if (mSettings.getCurrent().mBigramPredictionEnabled) {
+        final SettingsValues settingsValues = mSettings.getCurrent();
+        if (settingsValues.mBigramPredictionEnabled) {
             clearSuggestionStrip();
         } else {
-            setSuggestedWords(mSettings.getCurrent().mSuggestPuncList, false);
+            setSuggestedWords(settingsValues.mSuggestPuncList, false);
         }
         mConnection.resetCachesUponCursorMove(newCursorPosition, shouldFinishComposition);
     }
@@ -1290,8 +1296,9 @@
     }
 
     private boolean maybeDoubleSpacePeriod() {
-        if (!mSettings.getCurrent().mCorrectionEnabled) return false;
-        if (!mSettings.getCurrent().mUseDoubleSpacePeriod) return false;
+        final SettingsValues settingsValues = mSettings.getCurrent();
+        if (!settingsValues.mCorrectionEnabled) return false;
+        if (!settingsValues.mUseDoubleSpacePeriod) return false;
         if (!mHandler.isAcceptingDoubleSpacePeriod()) return false;
         final CharSequence lastThree = mConnection.getTextBeforeCursor(3, 0);
         if (lastThree != null && lastThree.length() == 3
@@ -1551,12 +1558,13 @@
             final int spaceState) {
         mSpaceState = SPACE_STATE_NONE;
         final boolean didAutoCorrect;
-        if (mSettings.getCurrent().isWordSeparator(primaryCode)) {
+        final SettingsValues settingsValues = mSettings.getCurrent();
+        if (settingsValues.isWordSeparator(primaryCode)) {
             didAutoCorrect = handleSeparator(primaryCode, x, y, spaceState);
         } else {
             didAutoCorrect = false;
             if (SPACE_STATE_PHANTOM == spaceState) {
-                if (mSettings.isInternal()) {
+                if (settingsValues.mIsInternal) {
                     if (mWordComposer.isComposingWord() && mWordComposer.isBatchMode()) {
                         LatinImeLoggerUtils.onAutoCorrection(
                                 "", mWordComposer.getTypedWord(), " ", mWordComposer);
@@ -1616,8 +1624,9 @@
         BatchInputUpdater.getInstance().onStartBatchInput(this);
         mHandler.cancelUpdateSuggestionStrip();
         mConnection.beginBatchEdit();
+        final SettingsValues settingsValues = mSettings.getCurrent();
         if (mWordComposer.isComposingWord()) {
-            if (mSettings.isInternal()) {
+            if (settingsValues.mIsInternal) {
                 if (mWordComposer.isBatchMode()) {
                     LatinImeLoggerUtils.onAutoCorrection(
                             "", mWordComposer.getTypedWord(), " ", mWordComposer);
@@ -1646,7 +1655,7 @@
         }
         final int codePointBeforeCursor = mConnection.getCodePointBeforeCursor();
         if (Character.isLetterOrDigit(codePointBeforeCursor)
-                || mSettings.getCurrent().isUsuallyFollowedBySpace(codePointBeforeCursor)) {
+                || settingsValues.isUsuallyFollowedBySpace(codePointBeforeCursor)) {
             mSpaceState = SPACE_STATE_PHANTOM;
         }
         mConnection.endBatchEdit();
@@ -1866,8 +1875,9 @@
                 mConnection.deleteSurroundingText(1, 0);
             }
         } else {
+            final SettingsValues currentSettings = mSettings.getCurrent();
             if (mLastComposedWord.canRevertCommit()) {
-                if (mSettings.isInternal()) {
+                if (currentSettings.mIsInternal) {
                     LatinImeLoggerUtils.onAutoCorrectionCancellation();
                 }
                 revertCommit();
@@ -1944,7 +1954,7 @@
                     }
                 }
             }
-            if (mSettings.getCurrent().isSuggestionsRequested(mDisplayOrientation)) {
+            if (currentSettings.isSuggestionsRequested(mDisplayOrientation)) {
                 restartSuggestionsOnWordBeforeCursorIfAtEndOfWord();
             }
         }
@@ -1961,8 +1971,9 @@
         }
         if ((SPACE_STATE_WEAK == spaceState || SPACE_STATE_SWAP_PUNCTUATION == spaceState)
                 && isFromSuggestionStrip) {
-            if (mSettings.getCurrent().isUsuallyPrecededBySpace(code)) return false;
-            if (mSettings.getCurrent().isUsuallyFollowedBySpace(code)) return true;
+            final SettingsValues currentSettings = mSettings.getCurrent();
+            if (currentSettings.isUsuallyPrecededBySpace(code)) return false;
+            if (currentSettings.isUsuallyFollowedBySpace(code)) return true;
             mConnection.removeTrailingSpace();
         }
         return false;
@@ -1974,8 +1985,8 @@
 
         // TODO: remove isWordConnector() and use isUsuallyFollowedBySpace() instead.
         // See onStartBatchInput() to see how to do it.
-        if (SPACE_STATE_PHANTOM == spaceState &&
-                !mSettings.getCurrent().isWordConnector(primaryCode)) {
+        final SettingsValues currentSettings = mSettings.getCurrent();
+        if (SPACE_STATE_PHANTOM == spaceState && !currentSettings.isWordConnector(primaryCode)) {
             if (isComposingWord) {
                 // Sanity check
                 throw new RuntimeException("Should not be composing here");
@@ -1993,9 +2004,9 @@
         // dozen milliseconds. Avoid calling it as much as possible, since we are on the UI
         // thread here.
         if (!isComposingWord && (isAlphabet(primaryCode)
-                || mSettings.getCurrent().isWordConnector(primaryCode))
-                && mSettings.getCurrent().isSuggestionsRequested(mDisplayOrientation) &&
-                !mConnection.isCursorTouchingWord(mSettings.getCurrent())) {
+                || currentSettings.isWordConnector(primaryCode))
+                && currentSettings.isSuggestionsRequested(mDisplayOrientation) &&
+                !mConnection.isCursorTouchingWord(currentSettings)) {
             // Reset entirely the composing state anyway, then start composing a new word unless
             // the character is a single quote. The idea here is, single quote is not a
             // separator and it should be treated as a normal character, except in the first
@@ -2038,7 +2049,7 @@
             if (null != mSuggestionStripView) mSuggestionStripView.dismissAddToDictionaryHint();
         }
         mHandler.postUpdateSuggestionStrip();
-        if (mSettings.isInternal()) {
+        if (currentSettings.mIsInternal) {
             LatinImeLoggerUtils.onNonSeparator((char)primaryCode, x, y);
         }
     }
@@ -2051,9 +2062,10 @@
             final CharSequence selectedText =
                     mConnection.getSelectedText(0 /* flags, 0 for no styles */);
             if (TextUtils.isEmpty(selectedText)) return; // Race condition with the input connection
+            final SettingsValues currentSettings = mSettings.getCurrent();
             mRecapitalizeStatus.initialize(mLastSelectionStart, mLastSelectionEnd,
-                    selectedText.toString(), mSettings.getCurrentLocale(),
-                    mSettings.getWordSeparators());
+                    selectedText.toString(), currentSettings.mLocale,
+                    currentSettings.mWordSeparators);
             // We trim leading and trailing whitespace.
             mRecapitalizeStatus.trim();
             // Trimming the object may have changed the length of the string, and we need to
@@ -2087,8 +2099,9 @@
             // first so that we can insert the separator at the current cursor position.
             resetEntireInputState(mLastSelectionStart);
         }
+        final SettingsValues currentSettings = mSettings.getCurrent();
         if (mWordComposer.isComposingWord()) {
-            if (mSettings.getCurrent().mCorrectionEnabled) {
+            if (currentSettings.mCorrectionEnabled) {
                 // TODO: maybe cache Strings in an <String> sparse array or something
                 commitCurrentAutoCorrection(new String(new int[]{primaryCode}, 0, 1));
                 didAutoCorrect = true;
@@ -2101,7 +2114,7 @@
                 Constants.SUGGESTION_STRIP_COORDINATE == x);
 
         if (SPACE_STATE_PHANTOM == spaceState &&
-                mSettings.getCurrent().isUsuallyPrecededBySpace(primaryCode)) {
+                currentSettings.isUsuallyPrecededBySpace(primaryCode)) {
             promotePhantomSpace();
         }
         if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
@@ -2110,7 +2123,7 @@
         sendKeyCodePoint(primaryCode);
 
         if (Constants.CODE_SPACE == primaryCode) {
-            if (mSettings.getCurrent().isSuggestionsRequested(mDisplayOrientation)) {
+            if (currentSettings.isSuggestionsRequested(mDisplayOrientation)) {
                 if (maybeDoubleSpacePeriod()) {
                     mSpaceState = SPACE_STATE_DOUBLE;
                 } else if (!isShowingPunctuationList()) {
@@ -2125,7 +2138,7 @@
                 swapSwapperAndSpace();
                 mSpaceState = SPACE_STATE_SWAP_PUNCTUATION;
             } else if (SPACE_STATE_PHANTOM == spaceState
-                    && mSettings.getCurrent().isUsuallyFollowedBySpace(primaryCode)) {
+                    && currentSettings.isUsuallyFollowedBySpace(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
@@ -2143,7 +2156,7 @@
             // already displayed or not, so it's okay.
             setPunctuationSuggestions();
         }
-        if (mSettings.isInternal()) {
+        if (currentSettings.mIsInternal) {
             LatinImeLoggerUtils.onSeparator((char)primaryCode, x, y);
         }
 
@@ -2176,17 +2189,18 @@
     }
 
     private boolean isSuggestionsStripVisible() {
+        final SettingsValues currentSettings = mSettings.getCurrent();
         if (mSuggestionStripView == null)
             return false;
         if (mSuggestionStripView.isShowingAddToDictionaryHint())
             return true;
-        if (null == mSettings.getCurrent())
+        if (null == currentSettings)
             return false;
-        if (!mSettings.getCurrent().isSuggestionStripVisibleInOrientation(mDisplayOrientation))
+        if (!currentSettings.isSuggestionStripVisibleInOrientation(mDisplayOrientation))
             return false;
-        if (mSettings.getCurrent().isApplicationSpecifiedCompletionsOn())
+        if (currentSettings.isApplicationSpecifiedCompletionsOn())
             return true;
-        return mSettings.getCurrent().isSuggestionsRequested(mDisplayOrientation);
+        return currentSettings.isSuggestionsRequested(mDisplayOrientation);
     }
 
     private void clearSuggestionStrip() {
@@ -2219,10 +2233,11 @@
 
     private void updateSuggestionStrip() {
         mHandler.cancelUpdateSuggestionStrip();
+        final SettingsValues currentSettings = mSettings.getCurrent();
 
         // Check if we have a suggestion engine attached.
         if (mSuggest == null
-                || !mSettings.getCurrent().isSuggestionsRequested(mDisplayOrientation)) {
+                || !currentSettings.isSuggestionsRequested(mDisplayOrientation)) {
             if (mWordComposer.isComposingWord()) {
                 Log.w(TAG, "Called updateSuggestionsOrPredictions but suggestions were not "
                         + "requested!");
@@ -2230,7 +2245,7 @@
             return;
         }
 
-        if (!mWordComposer.isComposingWord() && !mSettings.getCurrent().mBigramPredictionEnabled) {
+        if (!mWordComposer.isComposingWord() && !currentSettings.mBigramPredictionEnabled) {
             setPunctuationSuggestions();
             return;
         }
@@ -2251,12 +2266,13 @@
         // whatever is *before* the half-committed word in the buffer, hence 2; if we aren't, we
         // should just skip whitespace if any, so 1.
         // TODO: this is slow (2-way IPC) - we should probably cache this instead.
+        final SettingsValues currentSettings = mSettings.getCurrent();
         final String prevWord =
-                mConnection.getNthPreviousWord(mSettings.getCurrent().mWordSeparators,
+                mConnection.getNthPreviousWord(currentSettings.mWordSeparators,
                 mWordComposer.isComposingWord() ? 2 : 1);
         return suggest.getSuggestedWords(mWordComposer, prevWord, keyboard.getProximityInfo(),
-                mSettings.getBlockPotentiallyOffensive(),
-                mSettings.getCurrent().mCorrectionEnabled, sessionId);
+                currentSettings.mBlockPotentiallyOffensive,
+                currentSettings.mCorrectionEnabled, sessionId);
     }
 
     private SuggestedWords getSuggestedWordsOrOlderSuggestions(final int sessionId) {
@@ -2382,18 +2398,19 @@
         }
 
         mConnection.beginBatchEdit();
+        final SettingsValues currentSettings = mSettings.getCurrent();
         if (SPACE_STATE_PHANTOM == mSpaceState && suggestion.length() > 0
                 // In the batch input mode, a manually picked suggested word should just replace
                 // the current batch input text and there is no need for a phantom space.
                 && !mWordComposer.isBatchMode()) {
             final int firstChar = Character.codePointAt(suggestion, 0);
-            if (!mSettings.getCurrent().isWordSeparator(firstChar)
-                    || mSettings.getCurrent().isUsuallyPrecededBySpace(firstChar)) {
+            if (!currentSettings.isWordSeparator(firstChar)
+                    || currentSettings.isUsuallyPrecededBySpace(firstChar)) {
                 promotePhantomSpace();
             }
         }
 
-        if (mSettings.getCurrent().isApplicationSpecifiedCompletionsOn()
+        if (currentSettings.isApplicationSpecifiedCompletionsOn()
                 && mApplicationSpecifiedCompletions != null
                 && index >= 0 && index < mApplicationSpecifiedCompletions.length) {
             mSuggestedWords = SuggestedWords.EMPTY;
@@ -2431,20 +2448,21 @@
         // AND it's in none of our current dictionaries (main, user or otherwise).
         // Please note that if mSuggest is null, it means that everything is off: suggestion
         // and correction, so we shouldn't try to show the hint
+        final Suggest suggest = mSuggest;
         final boolean showingAddToDictionaryHint =
                 (SuggestedWordInfo.KIND_TYPED == suggestionInfo.mKind
                         || SuggestedWordInfo.KIND_OOV_CORRECTION == suggestionInfo.mKind)
-                        && mSuggest != null
+                        && suggest != null
                         // If the suggestion is not in the dictionary, the hint should be shown.
-                        && !AutoCorrectionUtils.isValidWord(mSuggest, suggestion, true);
+                        && !AutoCorrectionUtils.isValidWord(suggest, suggestion, true);
 
-        if (mSettings.isInternal()) {
+        if (currentSettings.mIsInternal) {
             LatinImeLoggerUtils.onSeparator((char)Constants.CODE_SPACE,
                     Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
         }
         if (showingAddToDictionaryHint && mIsUserDictionaryAvailable) {
             mSuggestionStripView.showAddToDictionaryHint(
-                    suggestion, mSettings.getCurrent().mHintToSaveText);
+                    suggestion, currentSettings.mHintToSaveText);
         } else {
             // If we're not showing the "Touch again to save", then update the suggestion strip.
             mHandler.postUpdateSuggestionStrip();
@@ -2470,10 +2488,11 @@
     }
 
     private void setPunctuationSuggestions() {
-        if (mSettings.getCurrent().mBigramPredictionEnabled) {
+        final SettingsValues currentSettings = mSettings.getCurrent();
+        if (currentSettings.mBigramPredictionEnabled) {
             clearSuggestionStrip();
         } else {
-            setSuggestedWords(mSettings.getCurrent().mSuggestPuncList, false);
+            setSuggestedWords(currentSettings.mSuggestPuncList, false);
         }
         setAutoCorrectionIndicator(false);
         setSuggestionStripShown(isSuggestionsStripVisible());
@@ -2481,21 +2500,20 @@
 
     private String addToUserHistoryDictionary(final String suggestion) {
         if (TextUtils.isEmpty(suggestion)) return null;
-        if (mSuggest == null) return null;
+        final Suggest suggest = mSuggest;
+        if (suggest == null) return null;
 
         // If correction is not enabled, we don't add words to the user history dictionary.
         // That's to avoid unintended additions in some sensitive fields, or fields that
         // expect to receive non-words.
-        if (!mSettings.getCurrent().mCorrectionEnabled) return null;
+        final SettingsValues currentSettings = mSettings.getCurrent();
+        if (!currentSettings.mCorrectionEnabled) return null;
 
-        final Suggest suggest = mSuggest;
-        final UserHistoryDictionary userHistoryDictionary = mUserHistoryDictionary;
-        if (suggest == null || userHistoryDictionary == null) {
-            // Avoid concurrent issue
-            return null;
-        }
-        final String prevWord
-                = mConnection.getNthPreviousWord(mSettings.getCurrent().mWordSeparators, 2);
+        final UserHistoryPredictionDictionary userHistoryDictionary =
+                mUserHistoryPredictionDictionary;
+        if (userHistoryDictionary == null) return null;
+
+        final String prevWord = mConnection.getNthPreviousWord(currentSettings.mWordSeparators, 2);
         final String secondWord;
         if (mWordComposer.wasAutoCapitalized() && !mWordComposer.isMostlyCaps()) {
             secondWord = suggestion.toLowerCase(mSubtypeSwitcher.getCurrentSubtypeLocale());
@@ -2524,8 +2542,9 @@
         if (mLastSelectionStart != mLastSelectionEnd) return;
         // If we don't know the cursor location, return.
         if (mLastSelectionStart < 0) return;
-        if (!mConnection.isCursorTouchingWord(mSettings.getCurrent())) return;
-        final TextRange range = mConnection.getWordRangeAtCursor(mSettings.getWordSeparators(),
+        final SettingsValues currentSettings = mSettings.getCurrent();
+        if (!mConnection.isCursorTouchingWord(currentSettings)) return;
+        final TextRange range = mConnection.getWordRangeAtCursor(currentSettings.mWordSeparators,
                 0 /* additionalPrecedingWordsCount */);
         if (null == range) return; // Happens if we don't have an input connection at all
         // If for some strange reason (editor bug or so) we measure the text before the cursor as
@@ -2639,7 +2658,7 @@
         }
         mConnection.deleteSurroundingText(deleteLength, 0);
         if (!TextUtils.isEmpty(previousWord) && !TextUtils.isEmpty(committedWord)) {
-            mUserHistoryDictionary.cancelAddingUserHistory(previousWord, committedWord);
+            mUserHistoryPredictionDictionary.cancelAddingUserHistory(previousWord, committedWord);
         }
         mConnection.commitText(originallyTypedWord + mLastComposedWord.mSeparatorString, 1);
         if (mSettings.isInternal()) {
diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java
index d07fa47..b69e3f8 100644
--- a/java/src/com/android/inputmethod/latin/RichInputConnection.java
+++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java
@@ -56,11 +56,14 @@
     private static final int INVALID_CURSOR_POSITION = -1;
 
     /**
-     * This variable contains the value LatinIME thinks the cursor position should be at now.
-     * This is a few steps in advance of what the TextView thinks it is, because TextView will
-     * only know after the IPC calls gets through.
+     * This variable contains an expected value for the cursor position. This is where the
+     * cursor may end up after all the keyboard-triggered updates have passed. We keep this to
+     * compare it to the actual cursor position to guess whether the move was caused by a
+     * keyboard command or not.
+     * It's not really the cursor position: the cursor may not be there yet, and it's also expected 
+     * there be cases where it never actually comes to be there.
      */
-    private int mCurrentCursorPosition = INVALID_CURSOR_POSITION; // in chars, not code points
+    private int mExpectedCursorPosition = INVALID_CURSOR_POSITION; // in chars, not code points
     /**
      * This contains the committed text immediately preceding the cursor and the composing
      * text if any. It is refreshed when the cursor moves by calling upon the TextView.
@@ -101,16 +104,16 @@
         final String reference = (beforeCursor.length() <= actualLength) ? beforeCursor.toString()
                 : beforeCursor.subSequence(beforeCursor.length() - actualLength,
                         beforeCursor.length()).toString();
-        if (et.selectionStart != mCurrentCursorPosition
+        if (et.selectionStart != mExpectedCursorPosition
                 || !(reference.equals(internal.toString()))) {
-            final String context = "Expected cursor position = " + mCurrentCursorPosition
+            final String context = "Expected cursor position = " + mExpectedCursorPosition
                     + "\nActual cursor position = " + et.selectionStart
                     + "\nExpected text = " + internal.length() + " " + internal
                     + "\nActual text = " + reference.length() + " " + reference;
             ((LatinIME)mParent).debugDumpStateAndCrashWithException(context);
         } else {
             Log.e(TAG, DebugLogUtils.getStackTrace(2));
-            Log.e(TAG, "Exp <> Actual : " + mCurrentCursorPosition + " <> " + et.selectionStart);
+            Log.e(TAG, "Exp <> Actual : " + mExpectedCursorPosition + " <> " + et.selectionStart);
         }
     }
 
@@ -141,7 +144,7 @@
 
     public void resetCachesUponCursorMove(final int newCursorPosition,
             final boolean shouldFinishComposition) {
-        mCurrentCursorPosition = newCursorPosition;
+        mExpectedCursorPosition = newCursorPosition;
         mComposingText.setLength(0);
         mCommittedTextBeforeComposingText.setLength(0);
         final CharSequence textBeforeCursor = getTextBeforeCursor(DEFAULT_TEXT_CACHE_SIZE, 0);
@@ -166,7 +169,7 @@
         if (DEBUG_BATCH_NESTING) checkBatchEdit();
         if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
         mCommittedTextBeforeComposingText.append(mComposingText);
-        mCurrentCursorPosition += mComposingText.length();
+        mExpectedCursorPosition += mComposingText.length();
         mComposingText.setLength(0);
         if (null != mIC) {
             mIC.finishComposingText();
@@ -180,7 +183,7 @@
         if (DEBUG_BATCH_NESTING) checkBatchEdit();
         if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
         mCommittedTextBeforeComposingText.append(text);
-        mCurrentCursorPosition += text.length() - mComposingText.length();
+        mExpectedCursorPosition += text.length() - mComposingText.length();
         mComposingText.setLength(0);
         if (null != mIC) {
             mIC.commitText(text, i);
@@ -193,7 +196,7 @@
     }
 
     public boolean canDeleteCharacters() {
-        return mCurrentCursorPosition > 0;
+        return mExpectedCursorPosition > 0;
     }
 
     /**
@@ -230,7 +233,7 @@
         // heavy pressing of delete, for example DEFAULT_TEXT_CACHE_SIZE - 5 times or so.
         // getCapsMode should be updated to be able to return a "not enough info" result so that
         // we can get more context only when needed.
-        if (TextUtils.isEmpty(mCommittedTextBeforeComposingText) && 0 != mCurrentCursorPosition) {
+        if (TextUtils.isEmpty(mCommittedTextBeforeComposingText) && 0 != mExpectedCursorPosition) {
             mCommittedTextBeforeComposingText.append(
                     getTextBeforeCursor(DEFAULT_TEXT_CACHE_SIZE, 0));
         }
@@ -251,7 +254,7 @@
                 mCommittedTextBeforeComposingText.length() + mComposingText.length();
         // If we have enough characters to satisfy the request, or if we have all characters in
         // the text field, then we can return the cached version right away.
-        if (cachedLength >= n || cachedLength >= mCurrentCursorPosition) {
+        if (cachedLength >= n || cachedLength >= mExpectedCursorPosition) {
             final StringBuilder s = new StringBuilder(mCommittedTextBeforeComposingText);
             s.append(mComposingText);
             if (s.length() > n) {
@@ -284,10 +287,10 @@
                     + remainingChars, 0);
             mCommittedTextBeforeComposingText.setLength(len);
         }
-        if (mCurrentCursorPosition > beforeLength) {
-            mCurrentCursorPosition -= beforeLength;
+        if (mExpectedCursorPosition > beforeLength) {
+            mExpectedCursorPosition -= beforeLength;
         } else {
-            mCurrentCursorPosition = 0;
+            mExpectedCursorPosition = 0;
         }
         if (null != mIC) {
             mIC.deleteSurroundingText(beforeLength, afterLength);
@@ -321,7 +324,7 @@
             switch (keyEvent.getKeyCode()) {
             case KeyEvent.KEYCODE_ENTER:
                 mCommittedTextBeforeComposingText.append("\n");
-                mCurrentCursorPosition += 1;
+                mExpectedCursorPosition += 1;
                 break;
             case KeyEvent.KEYCODE_DEL:
                 if (0 == mComposingText.length()) {
@@ -333,18 +336,18 @@
                 } else {
                     mComposingText.delete(mComposingText.length() - 1, mComposingText.length());
                 }
-                if (mCurrentCursorPosition > 0) mCurrentCursorPosition -= 1;
+                if (mExpectedCursorPosition > 0) mExpectedCursorPosition -= 1;
                 break;
             case KeyEvent.KEYCODE_UNKNOWN:
                 if (null != keyEvent.getCharacters()) {
                     mCommittedTextBeforeComposingText.append(keyEvent.getCharacters());
-                    mCurrentCursorPosition += keyEvent.getCharacters().length();
+                    mExpectedCursorPosition += keyEvent.getCharacters().length();
                 }
                 break;
             default:
                 final String text = new String(new int[] { keyEvent.getUnicodeChar() }, 0, 1);
                 mCommittedTextBeforeComposingText.append(text);
-                mCurrentCursorPosition += text.length();
+                mExpectedCursorPosition += text.length();
                 break;
             }
         }
@@ -378,7 +381,7 @@
     public void setComposingText(final CharSequence text, final int newCursorPosition) {
         if (DEBUG_BATCH_NESTING) checkBatchEdit();
         if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
-        mCurrentCursorPosition += text.length() - mComposingText.length();
+        mExpectedCursorPosition += text.length() - mComposingText.length();
         mComposingText.setLength(0);
         mComposingText.append(text);
         // TODO: support values of i != 1. At this time, this is never called with i != 1.
@@ -400,7 +403,7 @@
                 ResearchLogger.richInputConnection_setSelection(start, end);
             }
         }
-        mCurrentCursorPosition = start;
+        mExpectedCursorPosition = start;
         mCommittedTextBeforeComposingText.setLength(0);
         mCommittedTextBeforeComposingText.append(getTextBeforeCursor(DEFAULT_TEXT_CACHE_SIZE, 0));
     }
@@ -423,7 +426,7 @@
         // text should never be null, but just in case, it's better to insert nothing than to crash
         if (null == text) text = "";
         mCommittedTextBeforeComposingText.append(text);
-        mCurrentCursorPosition += text.length() - mComposingText.length();
+        mExpectedCursorPosition += text.length() - mComposingText.length();
         mComposingText.setLength(0);
         if (null != mIC) {
             mIC.commitCompletion(completionInfo);
@@ -705,14 +708,14 @@
      */
     public boolean isBelatedExpectedUpdate(final int oldSelStart, final int newSelStart) {
         // If this is an update that arrives at our expected position, it's a belated update.
-        if (newSelStart == mCurrentCursorPosition) return true;
+        if (newSelStart == mExpectedCursorPosition) return true;
         // If this is an update that moves the cursor from our expected position, it must be
         // an explicit move.
-        if (oldSelStart == mCurrentCursorPosition) return false;
+        if (oldSelStart == mExpectedCursorPosition) return false;
         // The following returns true if newSelStart is between oldSelStart and
         // mCurrentCursorPosition. We assume that if the updated position is between the old
         // position and the expected position, then it must be a belated update.
-        return (newSelStart - oldSelStart) * (mCurrentCursorPosition - newSelStart) >= 0;
+        return (newSelStart - oldSelStart) * (mExpectedCursorPosition - newSelStart) >= 0;
     }
 
     /**
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index 647c6f6..6b01667 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -22,7 +22,7 @@
 import com.android.inputmethod.annotations.UsedForTesting;
 import com.android.inputmethod.keyboard.ProximityInfo;
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
-import com.android.inputmethod.latin.personalization.UserHistoryDictionary;
+import com.android.inputmethod.latin.personalization.UserHistoryPredictionDictionary;
 import com.android.inputmethod.latin.utils.AutoCorrectionUtils;
 import com.android.inputmethod.latin.utils.BoundedTreeSet;
 import com.android.inputmethod.latin.utils.CollectionUtils;
@@ -168,8 +168,10 @@
         addOrReplaceDictionary(mDictionaries, Dictionary.TYPE_CONTACTS, contactsDictionary);
     }
 
-    public void setUserHistoryDictionary(final UserHistoryDictionary userHistoryDictionary) {
-        addOrReplaceDictionary(mDictionaries, Dictionary.TYPE_USER_HISTORY, userHistoryDictionary);
+    public void setUserHistoryPredictionDictionary(
+            final UserHistoryPredictionDictionary userHistoryPredictionDictionary) {
+        addOrReplaceDictionary(mDictionaries, Dictionary.TYPE_USER_HISTORY,
+                userHistoryPredictionDictionary);
     }
 
     public void setAutoCorrectionThreshold(float threshold) {
diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java
index 000c252..167c691 100644
--- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java
+++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java
@@ -982,6 +982,7 @@
         return null;
     }
 
+    private static final int HEADER_READING_BUFFER_SIZE = 16384;
     /**
      * Convenience method to read the header of a binary file.
      *
@@ -991,7 +992,6 @@
      * @param offset The offset in the file where to start reading the data.
      * @param length The length of the data file.
      */
-    private static final int HEADER_READING_BUFFER_SIZE = 16384;
     public static FileHeader getDictionaryFileHeader(
             final File file, final long offset, final long length)
             throws FileNotFoundException, IOException, UnsupportedFormatException {
diff --git a/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java b/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java
index 5a2b24c..118dc22 100644
--- a/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java
+++ b/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java
@@ -34,6 +34,8 @@
 public final class FusionDictionary implements Iterable<Word> {
     private static final boolean DBG = MakedictLog.DBG;
 
+    private static int CHARACTER_NOT_FOUND_INDEX = -1;
+
     /**
      * A node of the dictionary, containing several CharGroups.
      *
@@ -473,7 +475,7 @@
         CharGroup currentGroup = null;
         int differentCharIndex = 0; // Set by the loop to the index of the char that differs
         int nodeIndex = findIndexOfChar(mRoot, word[charIndex]);
-        while (CHARACTER_NOT_FOUND != nodeIndex) {
+        while (CHARACTER_NOT_FOUND_INDEX != nodeIndex) {
             currentGroup = currentNode.mData.get(nodeIndex);
             differentCharIndex = compareArrays(currentGroup.mChars, word, charIndex);
             if (ARRAYS_ARE_EQUAL != differentCharIndex
@@ -485,7 +487,7 @@
             nodeIndex = findIndexOfChar(currentNode, word[charIndex]);
         }
 
-        if (-1 == nodeIndex) {
+        if (CHARACTER_NOT_FOUND_INDEX == nodeIndex) {
             // No node at this point to accept the word. Create one.
             final int insertionIndex = findInsertionIndex(currentNode, word[charIndex]);
             final CharGroup newGroup = new CharGroup(
@@ -612,20 +614,18 @@
         return result >= 0 ? result : -result - 1;
     }
 
-    private static int CHARACTER_NOT_FOUND = -1;
-
     /**
      * Find the index of a char in a node, if it exists.
      *
      * @param node the node to search in.
      * @param character the character to search for.
-     * @return the position of the character if it's there, or CHARACTER_NOT_FOUND = -1 else.
+     * @return the position of the character if it's there, or CHARACTER_NOT_FOUND_INDEX = -1 else.
      */
     private static int findIndexOfChar(final Node node, int character) {
         final int insertionIndex = findInsertionIndex(node, character);
-        if (node.mData.size() <= insertionIndex) return CHARACTER_NOT_FOUND;
+        if (node.mData.size() <= insertionIndex) return CHARACTER_NOT_FOUND_INDEX;
         return character == node.mData.get(insertionIndex).mChars[0] ? insertionIndex
-                : CHARACTER_NOT_FOUND;
+                : CHARACTER_NOT_FOUND_INDEX;
     }
 
     /**
@@ -640,7 +640,7 @@
         CharGroup currentGroup;
         do {
             int indexOfGroup = findIndexOfChar(node, codePoints[index]);
-            if (CHARACTER_NOT_FOUND == indexOfGroup) return null;
+            if (CHARACTER_NOT_FOUND_INDEX == indexOfGroup) return null;
             currentGroup = node.mData.get(indexOfGroup);
 
             if (codePoints.length - index < currentGroup.mChars.length) return null;
diff --git a/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java b/java/src/com/android/inputmethod/latin/personalization/DynamicPredictionDictionaryBase.java
similarity index 83%
rename from java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java
rename to java/src/com/android/inputmethod/latin/personalization/DynamicPredictionDictionaryBase.java
index c76dea0..9d041f4 100644
--- a/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/personalization/DynamicPredictionDictionaryBase.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2010 The Android Open Source Project
+ * 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.
@@ -24,7 +24,6 @@
 import com.android.inputmethod.annotations.UsedForTesting;
 import com.android.inputmethod.keyboard.ProximityInfo;
 import com.android.inputmethod.latin.Constants;
-import com.android.inputmethod.latin.Dictionary;
 import com.android.inputmethod.latin.ExpandableDictionary;
 import com.android.inputmethod.latin.LatinImeLogger;
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
@@ -47,16 +46,17 @@
 import java.util.concurrent.locks.ReentrantLock;
 
 /**
- * Locally gathers stats about the words user types and various other signals like auto-correction
- * cancellation or manual picks. This allows the keyboard to adapt to the typist over time.
+ * This class is a base class of a dictionary for the personalized prediction language model.
  */
-public class UserHistoryDictionary extends ExpandableDictionary {
-    private static final String TAG = UserHistoryDictionary.class.getSimpleName();
-    private static final String NAME = UserHistoryDictionary.class.getSimpleName();
+public abstract class DynamicPredictionDictionaryBase extends ExpandableDictionary {
+    public static void registerUpdateListener(PersonalizationDictionaryUpdateListener listener) {
+        // TODO: Implement
+    }
+
+    private static final String TAG = DynamicPredictionDictionaryBase.class.getSimpleName();
     public static final boolean DBG_SAVE_RESTORE = false;
-    public static final boolean DBG_STRESS_TEST = false;
-    public static final boolean DBG_ALWAYS_WRITE = false;
-    public static final boolean PROFILE_SAVE_RESTORE = LatinImeLogger.sDBG;
+    private static final boolean DBG_STRESS_TEST = false;
+    private static final boolean PROFILE_SAVE_RESTORE = LatinImeLogger.sDBG;
 
     private static final FormatOptions VERSION3 = new FormatOptions(3,
             true /* supportsDynamicUpdate */);
@@ -65,14 +65,7 @@
     private static final int FREQUENCY_FOR_TYPED = 2;
 
     /** Maximum number of pairs. Pruning will start when databases goes above this number. */
-    public static final int MAX_HISTORY_BIGRAMS = 10000;
-
-    /**
-     * When it hits maximum bigram pair, it will delete until you are left with
-     * only (sMaxHistoryBigrams - sDeleteHistoryBigrams) pairs.
-     * Do not keep this number small to avoid deleting too often.
-     */
-    public static final int DELETE_HISTORY_BIGRAMS = 1000;
+    private static final int MAX_HISTORY_BIGRAMS = 10000;
 
     /** Locale for which this user history dictionary is storing words */
     private final String mLocale;
@@ -85,9 +78,9 @@
     // Should always be false except when we use this class for test
     @UsedForTesting boolean isTest = false;
 
-    /* package */ UserHistoryDictionary(final Context context, final String locale,
-            final SharedPreferences sp) {
-        super(context, Dictionary.TYPE_USER_HISTORY);
+    /* package */ DynamicPredictionDictionaryBase(final Context context, final String locale,
+            final SharedPreferences sp, final String dictionaryType) {
+        super(context, dictionaryType);
         mLocale = locale;
         mPrefs = sp;
         if (mLocale != null && mLocale.length() > 1) {
@@ -102,8 +95,8 @@
         // Also, the database is written to somewhat frequently, so it needs to be kept alive
         // throughout the life of the process.
         // mOpenHelper.close();
-        // Ignore close because we cache UserHistoryDictionary for each language. See getInstance()
-        // above.
+        // Ignore close because we cache PersonalizationPredictionDictionary for each language.
+        // See getInstance() above.
         // super.close();
     }
 
@@ -184,7 +177,7 @@
     }
 
     @Override
-    public void loadDictionaryAsync() {
+    public final void loadDictionaryAsync() {
         // This must be run on non-main thread
         mBigramListLock.lock();
         try {
@@ -194,48 +187,47 @@
         }
     }
 
-    private int profTotal;
-
     private void loadDictionaryAsyncLocked() {
+        final int[] profTotalCount = { 0 };
+        final String locale = getLocale();
         if (DBG_STRESS_TEST) {
             try {
-                Log.w(TAG, "Start stress in loading: " + mLocale);
+                Log.w(TAG, "Start stress in loading: " + locale);
                 Thread.sleep(15000);
                 Log.w(TAG, "End stress in loading");
             } catch (InterruptedException e) {
             }
         }
-        final long last = Settings.readLastUserHistoryWriteTime(mPrefs, mLocale);
+        final long last = Settings.readLastUserHistoryWriteTime(mPrefs, locale);
         final boolean initializing = last == 0;
         final long now = System.currentTimeMillis();
-        profTotal = 0;
-        final String fileName = NAME + "." + mLocale + ".dict";
+        final String fileName = getDictionaryFileName();
         final ExpandableDictionary dictionary = this;
         final OnAddWordListener listener = new OnAddWordListener() {
             @Override
             public void setUnigram(final String word, final String shortcutTarget,
                     final int frequency) {
-                profTotal++;
                 if (DBG_SAVE_RESTORE) {
                     Log.d(TAG, "load unigram: " + word + "," + frequency);
                 }
                 dictionary.addWord(word, shortcutTarget, frequency);
-                mBigramList.addBigram(null, word, (byte)frequency);
+                ++profTotalCount[0];
+                addToBigramListLocked(null, word, (byte)frequency);
             }
 
             @Override
             public void setBigram(final String word1, final String word2, final int frequency) {
                 if (word1.length() < Constants.DICTIONARY_MAX_WORD_LENGTH
                         && word2.length() < Constants.DICTIONARY_MAX_WORD_LENGTH) {
-                    profTotal++;
                     if (DBG_SAVE_RESTORE) {
                         Log.d(TAG, "load bigram: " + word1 + "," + word2 + "," + frequency);
                     }
+                    ++profTotalCount[0];
                     dictionary.setBigramAndGetFrequency(
                             word1, word2, initializing ? new ForgettingCurveParams(true)
                             : new ForgettingCurveParams(frequency, now, last));
                 }
-                mBigramList.addBigram(word1, word2, (byte)frequency);
+                addToBigramListLocked(word1, word2, (byte)frequency);
             }
         };
 
@@ -264,11 +256,21 @@
             if (PROFILE_SAVE_RESTORE) {
                 final long diff = System.currentTimeMillis() - now;
                 Log.d(TAG, "PROF: Load UserHistoryDictionary: "
-                        + mLocale + ", " + diff + "ms. load " + profTotal + "entries.");
+                        + locale + ", " + diff + "ms. load " + profTotalCount[0] + "entries.");
             }
         }
     }
 
+    protected abstract String getDictionaryFileName();
+
+    protected String getLocale() {
+        return mLocale;
+    }
+
+    private void addToBigramListLocked(String word0, String word1, byte fcValue) {
+        mBigramList.addBigram(word0, word1, fcValue);
+    }
+
     /**
      * Async task to write pending words to the binarydicts.
      */
@@ -277,16 +279,16 @@
         private final UserHistoryDictionaryBigramList mBigramList;
         private final boolean mAddLevel0Bigrams;
         private final String mLocale;
-        private final UserHistoryDictionary mUserHistoryDictionary;
+        private final DynamicPredictionDictionaryBase mDynamicPredictionDictionary;
         private final SharedPreferences mPrefs;
         private final Context mContext;
 
         public UpdateBinaryTask(final UserHistoryDictionaryBigramList pendingWrites,
-                final String locale, final UserHistoryDictionary dict,
+                final String locale, final DynamicPredictionDictionaryBase dict,
                 final SharedPreferences prefs, final Context context) {
             mBigramList = pendingWrites;
             mLocale = locale;
-            mUserHistoryDictionary = dict;
+            mDynamicPredictionDictionary = dict;
             mPrefs = prefs;
             mContext = context;
             mAddLevel0Bigrams = mBigramList.size() <= MAX_HISTORY_BIGRAMS;
@@ -294,19 +296,19 @@
 
         @Override
         protected Void doInBackground(final Void... v) {
-            if (mUserHistoryDictionary.isTest) {
+            if (mDynamicPredictionDictionary.isTest) {
                 // If isTest == true, wait until the lock is released.
-                mUserHistoryDictionary.mBigramListLock.lock();
+                mDynamicPredictionDictionary.mBigramListLock.lock();
                 try {
                     doWriteTaskLocked();
                 } finally {
-                    mUserHistoryDictionary.mBigramListLock.unlock();
+                    mDynamicPredictionDictionary.mBigramListLock.unlock();
                 }
-            } else if (mUserHistoryDictionary.mBigramListLock.tryLock()) {
+            } else if (mDynamicPredictionDictionary.mBigramListLock.tryLock()) {
                 try {
                     doWriteTaskLocked();
                 } finally {
-                    mUserHistoryDictionary.mBigramListLock.unlock();
+                    mDynamicPredictionDictionary.mBigramListLock.unlock();
                 }
             }
             return null;
@@ -324,7 +326,8 @@
             }
 
             final long now = PROFILE_SAVE_RESTORE ? System.currentTimeMillis() : 0;
-            final String fileName = NAME + "." + mLocale + ".dict";
+            final String fileName =
+                    mDynamicPredictionDictionary.getDictionaryFileName();
             final File file = new File(mContext.getFilesDir(), fileName);
             FileOutputStream out = null;
 
@@ -360,7 +363,8 @@
                 freq = FREQUENCY_FOR_TYPED;
                 final byte prevFc = mBigramList.getBigrams(word1).get(word2);
             } else { // bigram
-                final NextWord nw = mUserHistoryDictionary.getBigramWord(word1, word2);
+                final NextWord nw =
+                        mDynamicPredictionDictionary.getBigramWord(word1, word2);
                 if (nw != null) {
                     final ForgettingCurveParams fcp = nw.getFcParams();
                     final byte prevFc = mBigramList.getBigrams(word1).get(word2);
diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDicitonary.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionary.java
similarity index 92%
rename from java/src/com/android/inputmethod/latin/personalization/PersonalizationDicitonary.java
rename to java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionary.java
index d3e2dfe..19554d6 100644
--- a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDicitonary.java
+++ b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionary.java
@@ -24,7 +24,7 @@
 /**
  * This class is a dictionary for the personalized language model that uses binary dictionary.
  */
-public class PersonalizationDicitonary extends ExpandableBinaryDictionary {
+public class PersonalizationDictionary extends ExpandableBinaryDictionary {
     private static final String NAME = "personalization";
 
     public static void registerUpdateListener(PersonalizationDictionaryUpdateListener listener) {
@@ -35,7 +35,7 @@
     private final String mLocale;
 
     // Singleton
-    private PersonalizationDicitonary(final Context context, final String locale) {
+    private PersonalizationDictionary(final Context context, final String locale) {
         super(context, getFilenameWithLocale(NAME, locale), Dictionary.TYPE_PERSONALIZATION);
         mLocale = locale;
     }
diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryHelper.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryHelper.java
index e09e834..f5dae99 100644
--- a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryHelper.java
+++ b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryHelper.java
@@ -29,15 +29,16 @@
     private static final String TAG = PersonalizationDictionaryHelper.class.getSimpleName();
     private static final boolean DEBUG = false;
 
-    private static final ConcurrentHashMap<String, SoftReference<UserHistoryDictionary>>
+    private static final ConcurrentHashMap<String, SoftReference<UserHistoryPredictionDictionary>>
             sLangDictCache = CollectionUtils.newConcurrentHashMap();
 
-    public static UserHistoryDictionary getUserHistoryDictionary(
+    public static UserHistoryPredictionDictionary getUserHistoryPredictionDictionary(
             final Context context, final String locale, final SharedPreferences sp) {
         synchronized (sLangDictCache) {
             if (sLangDictCache.containsKey(locale)) {
-                final SoftReference<UserHistoryDictionary> ref = sLangDictCache.get(locale);
-                final UserHistoryDictionary dict = ref == null ? null : ref.get();
+                final SoftReference<UserHistoryPredictionDictionary> ref =
+                        sLangDictCache.get(locale);
+                final UserHistoryPredictionDictionary dict = ref == null ? null : ref.get();
                 if (dict != null) {
                     if (DEBUG) {
                         Log.w(TAG, "Use cached UserHistoryDictionary for " + locale);
@@ -45,8 +46,9 @@
                     return dict;
                 }
             }
-            final UserHistoryDictionary dict = new UserHistoryDictionary(context, locale, sp);
-            sLangDictCache.put(locale, new SoftReference<UserHistoryDictionary>(dict));
+            final UserHistoryPredictionDictionary dict =
+                    new UserHistoryPredictionDictionary(context, locale, sp);
+            sLangDictCache.put(locale, new SoftReference<UserHistoryPredictionDictionary>(dict));
             return dict;
         }
     }
diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryUpdateListener.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryUpdateListener.java
index 2ec0dc0..c78e5a9 100644
--- a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryUpdateListener.java
+++ b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryUpdateListener.java
@@ -16,6 +16,6 @@
 
 package com.android.inputmethod.latin.personalization;
 
-public class PersonalizationDictionaryUpdateListener {
+public interface PersonalizationDictionaryUpdateListener {
     // TODO: Implement
 }
diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationPredictionDicitonary.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationPredictionDicitonary.java
deleted file mode 100644
index 3e77725..0000000
--- a/java/src/com/android/inputmethod/latin/personalization/PersonalizationPredictionDicitonary.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * 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.personalization;
-
-import com.android.inputmethod.latin.Dictionary;
-import com.android.inputmethod.latin.ExpandableDictionary;
-
-import android.content.Context;
-import android.content.SharedPreferences;
-
-/**
- * This class is a dictionary for the personalized prediction language model implemented in Java.
- */
-public class PersonalizationPredictionDicitonary extends ExpandableDictionary {
-    public static void registerUpdateListener(PersonalizationDictionaryUpdateListener listener) {
-        // TODO: Implement
-    }
-
-    /** Locale for which this user history dictionary is storing words */
-    private final String mLocale;
-    private final SharedPreferences mPrefs;
-
-    // Singleton
-    private PersonalizationPredictionDicitonary(final Context context, final String locale,
-            final SharedPreferences sp) {
-        super(context, Dictionary.TYPE_PERSONALIZATION_PREDICTION_IN_JAVA);
-        mLocale = locale;
-        mPrefs = sp;
-    }
-
-    // TODO: Implement
-}
diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationPredictionDictionary.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationPredictionDictionary.java
new file mode 100644
index 0000000..955bd27
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/personalization/PersonalizationPredictionDictionary.java
@@ -0,0 +1,36 @@
+/*
+ * 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.personalization;
+
+import com.android.inputmethod.latin.Dictionary;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+
+public class PersonalizationPredictionDictionary extends DynamicPredictionDictionaryBase {
+    private static final String NAME = PersonalizationPredictionDictionary.class.getSimpleName();
+
+    /* package */ PersonalizationPredictionDictionary(final Context context, final String locale,
+            final SharedPreferences sp) {
+        super(context, locale, sp, Dictionary.TYPE_PERSONALIZATION_PREDICTION_IN_JAVA);
+    }
+
+    @Override
+    protected String getDictionaryFileName() {
+        return NAME + "." + getLocale() + ".dict";
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryBigramList.java b/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryBigramList.java
index b93630a..f21db25 100644
--- a/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryBigramList.java
+++ b/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryBigramList.java
@@ -53,7 +53,7 @@
      * Called when loaded from the SQL DB.
      */
     public void addBigram(String word1, String word2, byte fcValue) {
-        if (UserHistoryDictionary.DBG_SAVE_RESTORE) {
+        if (UserHistoryPredictionDictionary.DBG_SAVE_RESTORE) {
             Log.d(TAG, "--- add bigram: " + word1 + ", " + word2 + ", " + fcValue);
         }
         final HashMap<String, Byte> map;
@@ -73,7 +73,7 @@
      * Called when inserted to the SQL DB.
      */
     public void updateBigram(String word1, String word2, byte fcValue) {
-        if (UserHistoryDictionary.DBG_SAVE_RESTORE) {
+        if (UserHistoryPredictionDictionary.DBG_SAVE_RESTORE) {
             Log.d(TAG, "--- update bigram: " + word1 + ", " + word2 + ", " + fcValue);
         }
         final HashMap<String, Byte> map;
diff --git a/java/src/com/android/inputmethod/latin/personalization/UserHistoryPredictionDictionary.java b/java/src/com/android/inputmethod/latin/personalization/UserHistoryPredictionDictionary.java
new file mode 100644
index 0000000..d117844
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/personalization/UserHistoryPredictionDictionary.java
@@ -0,0 +1,39 @@
+/*
+ * 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.personalization;
+
+import com.android.inputmethod.latin.Dictionary;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+
+/**
+ * Locally gathers stats about the words user types and various other signals like auto-correction
+ * cancellation or manual picks. This allows the keyboard to adapt to the typist over time.
+ */
+public class UserHistoryPredictionDictionary extends DynamicPredictionDictionaryBase {
+    private static final String NAME = UserHistoryPredictionDictionary.class.getSimpleName();
+    /* package */ UserHistoryPredictionDictionary(final Context context, final String locale,
+            final SharedPreferences sp) {
+        super(context, locale, sp, Dictionary.TYPE_USER_HISTORY);
+    }
+
+    @Override
+    protected String getDictionaryFileName() {
+        return NAME + "." + getLocale() + ".dict";
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/settings/DebugSettings.java b/java/src/com/android/inputmethod/latin/settings/DebugSettings.java
index 34ea227..e675f42 100644
--- a/java/src/com/android/inputmethod/latin/settings/DebugSettings.java
+++ b/java/src/com/android/inputmethod/latin/settings/DebugSettings.java
@@ -32,7 +32,6 @@
 
 public final class DebugSettings extends PreferenceFragment
         implements SharedPreferences.OnSharedPreferenceChangeListener {
-    private static final String TAG = DebugSettings.class.getSimpleName();
 
     public static final String PREF_DEBUG_MODE = "debug_mode";
     public static final String PREF_FORCE_NON_DISTINCT_MULTITOUCH = "force_non_distinct_multitouch";
diff --git a/java/src/com/android/inputmethod/latin/settings/Settings.java b/java/src/com/android/inputmethod/latin/settings/Settings.java
index b690fed..2631cd3 100644
--- a/java/src/com/android/inputmethod/latin/settings/Settings.java
+++ b/java/src/com/android/inputmethod/latin/settings/Settings.java
@@ -93,7 +93,6 @@
 
     private Resources mRes;
     private SharedPreferences mPrefs;
-    private Locale mCurrentLocale;
     private SettingsValues mSettingsValues;
 
     private static final Settings sInstance = new Settings();
@@ -128,16 +127,15 @@
             Log.w(TAG, "onSharedPreferenceChanged called before loadSettings.");
             return;
         }
-        loadSettings(mCurrentLocale, mSettingsValues.mInputAttributes);
+        loadSettings(mSettingsValues.mLocale, mSettingsValues.mInputAttributes);
     }
 
     public void loadSettings(final Locale locale, final InputAttributes inputAttributes) {
-        mCurrentLocale = locale;
         final SharedPreferences prefs = mPrefs;
         final RunInLocale<SettingsValues> job = new RunInLocale<SettingsValues>() {
             @Override
             protected SettingsValues job(final Resources res) {
-                return new SettingsValues(prefs, res, inputAttributes);
+                return new SettingsValues(prefs, locale, res, inputAttributes);
             }
         };
         mSettingsValues = job.runInLocale(mRes, locale);
@@ -160,10 +158,6 @@
         return mSettingsValues.isWordSeparator(code);
     }
 
-    public Locale getCurrentLocale() {
-        return mCurrentLocale;
-    }
-
     public boolean getBlockPotentiallyOffensive() {
         return mSettingsValues.mBlockPotentiallyOffensive;
     }
diff --git a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
index 3fa00bb..8aafb07 100644
--- a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
+++ b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
@@ -32,11 +32,11 @@
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
 import com.android.inputmethod.latin.utils.CollectionUtils;
 import com.android.inputmethod.latin.utils.InputTypeUtils;
-import com.android.inputmethod.latin.utils.RunInLocale;
 import com.android.inputmethod.latin.utils.StringUtils;
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Locale;
 
 /**
  * When you call the constructor of this class, you may want to change the current system locale by
@@ -75,6 +75,7 @@
     public final boolean mGestureFloatingPreviewTextEnabled;
     public final boolean mSlidingKeyInputPreviewEnabled;
     public final int mKeyLongpressTimeout;
+    public final Locale mLocale;
 
     // From the input box
     public final InputAttributes mInputAttributes;
@@ -97,8 +98,9 @@
     // Debug settings
     public final boolean mIsInternal;
 
-    public SettingsValues(final SharedPreferences prefs, final Resources res,
+    public SettingsValues(final SharedPreferences prefs, final Locale locale, final Resources res,
             final InputAttributes inputAttributes) {
+        mLocale = locale;
         // Get the resources
         mDelayUpdateOldSuggestions = res.getInteger(R.integer.config_delay_update_old_suggestions);
         mSymbolsPrecededBySpace =
diff --git a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordContents.java b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordContents.java
index ba5a684..21426d1 100644
--- a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordContents.java
+++ b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordContents.java
@@ -274,4 +274,8 @@
         localesList.add(new LocaleRenderer(activity, null)); // meaning: select another locale
         return localesList;
     }
+
+    public String getCurrentUserDictionaryLocale() {
+        return mLocale;
+    }
 }
diff --git a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordFragment.java b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordFragment.java
index 8b8bd5e..4fc132f 100644
--- a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordFragment.java
+++ b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordFragment.java
@@ -60,6 +60,7 @@
     public void onActivityCreated(final Bundle savedInstanceState) {
         super.onActivityCreated(savedInstanceState);
         setHasOptionsMenu(true);
+        getActivity().getActionBar().setTitle(R.string.edit_personal_dictionary);
         // Keep the instance so that we remember mContents when configuration changes (eg rotation)
         setRetainInstance(true);
     }
@@ -82,6 +83,8 @@
             mContents = new UserDictionaryAddWordContents(mRootView,
                     mContents /* oldInstanceToBeEdited */);
         }
+        getActivity().getActionBar().setSubtitle(UserDictionarySettingsUtils.getLocaleDisplayName(
+                getActivity(), mContents.getCurrentUserDictionaryLocale()));
         return mRootView;
     }
 
@@ -100,7 +103,7 @@
     /**
      * Callback for the framework when a menu option is pressed.
      *
-     * @param MenuItem the item that was pressed
+     * @param item the item that was pressed
      * @return false to allow normal menu processing to proceed, true to consume it here
      */
     @Override
diff --git a/java/src/com/android/inputmethod/latin/utils/CsvUtils.java b/java/src/com/android/inputmethod/latin/utils/CsvUtils.java
index 159ebb1..36b927e 100644
--- a/java/src/com/android/inputmethod/latin/utils/CsvUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/CsvUtils.java
@@ -22,7 +22,7 @@
 
 /**
  * Utility methods for parsing and serializing Comma-Separated Values. The public APIs of this
- * utility class are {@link #split(String)}, {@link #split(int,String)}, {@link #join(String)},
+ * utility class are {@link #split(String)}, {@link #split(int,String)}, {@link #join(String...)},
  * {@link #join(int,String...)}, and {@link #join(int,int[],String...)}.
  *
  * This class implements CSV parsing and serializing methods conforming to RFC 4180 with an
diff --git a/java/src/com/android/inputmethod/latin/utils/UsabilityStudyLogUtils.java b/java/src/com/android/inputmethod/latin/utils/UsabilityStudyLogUtils.java
index ef9cacf..06826da 100644
--- a/java/src/com/android/inputmethod/latin/utils/UsabilityStudyLogUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/UsabilityStudyLogUtils.java
@@ -25,6 +25,7 @@
 import android.os.HandlerThread;
 import android.os.Process;
 import android.util.Log;
+import android.view.MotionEvent;
 
 import com.android.inputmethod.latin.LatinImeLogger;
 
@@ -109,6 +110,43 @@
         LatinImeLogger.onPrintAllUsabilityStudyLogs();
     }
 
+    public static void writeMotionEvent(final MotionEvent me) {
+        final int action = me.getActionMasked();
+        final long eventTime = me.getEventTime();
+        final int pointerCount = me.getPointerCount();
+        for (int index = 0; index < pointerCount; index++) {
+            final int id = me.getPointerId(index);
+            final int x = (int)me.getX(index);
+            final int y = (int)me.getY(index);
+            final float size = me.getSize(index);
+            final float pressure = me.getPressure(index);
+
+            final String eventTag;
+            switch (action) {
+            case MotionEvent.ACTION_UP:
+                eventTag = "[Up]";
+                break;
+            case MotionEvent.ACTION_DOWN:
+                eventTag = "[Down]";
+                break;
+            case MotionEvent.ACTION_POINTER_UP:
+                eventTag = "[PointerUp]";
+                break;
+            case MotionEvent.ACTION_POINTER_DOWN:
+                eventTag = "[PointerDown]";
+                break;
+            case MotionEvent.ACTION_MOVE:
+                eventTag = "[Move]";
+                break;
+            default:
+                eventTag = "[Action" + action + "]";
+                break;
+            }
+            getInstance().write(eventTag + eventTime + "," + id + "," + x + "," + y + "," + size
+                    + "," + pressure);
+        }
+    }
+
     public void write(final String log) {
         mLoggingHandler.post(new Runnable() {
             @Override
@@ -191,7 +229,7 @@
                     Log.w(USABILITY_TAG, e2);
                     return;
                 }
-                if (destFile == null || !destFile.exists()) {
+                if (!destFile.exists()) {
                     Log.w(USABILITY_TAG, "Dest file doesn't exist.");
                     return;
                 }
diff --git a/java/src/com/android/inputmethod/research/MainLogBuffer.java b/java/src/com/android/inputmethod/research/MainLogBuffer.java
index 3482153..6df7c17 100644
--- a/java/src/com/android/inputmethod/research/MainLogBuffer.java
+++ b/java/src/com/android/inputmethod/research/MainLogBuffer.java
@@ -119,9 +119,9 @@
      *
      * @param logUnits a LogUnit list to check for publishability
      * @param nGramSize the smallest n-gram acceptable to be published.  if
-     * {@link ResearchLogger.IS_LOGGING_EVERYTHING} is true, then publish if there are more than
+     * {@link ResearchLogger#IS_LOGGING_EVERYTHING} is true, then publish if there are more than
      * {@code minNGramSize} words in the logUnits, otherwise wait.  if {@link
-     * ResearchLogger.IS_LOGGING_EVERYTHING} is false, then ensure that there are exactly nGramSize
+     * ResearchLogger#IS_LOGGING_EVERYTHING} is false, then ensure that there are exactly nGramSize
      * words in the LogUnits.
      *
      * @return one of the {@code PUBLISHABILITY_*} result codes defined in this class.
diff --git a/java/src/com/android/inputmethod/research/MotionEventReader.java b/java/src/com/android/inputmethod/research/MotionEventReader.java
index fbfd9b5..3388645 100644
--- a/java/src/com/android/inputmethod/research/MotionEventReader.java
+++ b/java/src/com/android/inputmethod/research/MotionEventReader.java
@@ -315,16 +315,6 @@
         return pointerCoords;
     }
 
-    /**
-     * Tests that {@code x} is uninitialized.
-     *
-     * Assumes that {@code x} will never be given a valid value less than 0, and that
-     * UNINITIALIZED_FLOAT is less than 0.0f.
-     */
-    private boolean isUninitializedFloat(final float x) {
-        return x < 0.0f;
-    }
-
     private void addMotionEventData(final ReplayData replayData, final int actionType,
             final long time, final PointerProperties[] pointerProperties,
             final PointerCoords[] pointerCoords) {
diff --git a/java/src/com/android/inputmethod/research/ResearchLogger.java b/java/src/com/android/inputmethod/research/ResearchLogger.java
index ed047e1..fc8615b 100644
--- a/java/src/com/android/inputmethod/research/ResearchLogger.java
+++ b/java/src/com/android/inputmethod/research/ResearchLogger.java
@@ -180,7 +180,6 @@
     private ResearchLogDirectory mResearchLogDirectory;
     private SplashScreen mSplashScreen;
 
-    private Intent mUploadIntent;
     private Intent mUploadNowIntent;
 
     /* package for test */ LogUnit mCurrentLogUnit = new LogUnit();
@@ -233,7 +232,6 @@
         resetLogBuffers();
 
         // Initialize external services
-        mUploadIntent = new Intent(mLatinIME, UploaderService.class);
         mUploadNowIntent = new Intent(mLatinIME, UploaderService.class);
         mUploadNowIntent.putExtra(UploaderService.EXTRA_UPLOAD_UNCONDITIONALLY, true);
         if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
@@ -319,12 +317,6 @@
         restart();
     }
 
-    private void setLoggingAllowed(final boolean enableLogging) {
-        if (mPrefs == null) return;
-        sIsLogging = enableLogging;
-        ResearchSettings.writeResearchLoggerEnabledFlag(mPrefs, enableLogging);
-    }
-
     private void checkForEmptyEditor() {
         if (mLatinIME == null) {
             return;
@@ -1261,10 +1253,23 @@
     private static final LogStatement LOGSTATEMENT_LATINIME_PICKSUGGESTIONMANUALLY =
             new LogStatement("LatinIMEPickSuggestionManually", true, false, "replacedWord", "index",
                     "suggestion", "x", "y", "isBatchMode", "score", "kind", "sourceDict");
+    /**
+     * Log a call to LatinIME.pickSuggestionManually().
+     *
+     * @param replacedWord the typed word that this manual suggestion replaces. May not be null.
+     * @param index the index in the suggestion strip
+     * @param suggestion the committed suggestion. May not be null.
+     * @param isBatchMode whether this was input in batch mode, aka gesture.
+     * @param score the internal score of the suggestion, as output by the dictionary
+     * @param kind the kind of suggestion, as one of the SuggestedWordInfo#KIND_* constants
+     * @param sourceDict the source origin of this word, as one of the Dictionary#TYPE_* constants.
+     */
     public static void latinIME_pickSuggestionManually(final String replacedWord,
             final int index, final String suggestion, final boolean isBatchMode,
             final int score, final int kind, final String sourceDict) {
         final ResearchLogger researchLogger = getInstance();
+        // Note : suggestion can't be null here, because it's only called in a place where it
+        // can't be null.
         if (!replacedWord.equals(suggestion.toString())) {
             // The user chose something other than what was already there.
             researchLogger.setCurrentLogUnitContainsUserDeletions();
@@ -1273,7 +1278,7 @@
         final String scrubbedWord = scrubDigitsFromString(suggestion);
         researchLogger.enqueueEvent(LOGSTATEMENT_LATINIME_PICKSUGGESTIONMANUALLY,
                 scrubDigitsFromString(replacedWord), index,
-                suggestion == null ? null : scrubbedWord, Constants.SUGGESTION_STRIP_COORDINATE,
+                scrubbedWord, Constants.SUGGESTION_STRIP_COORDINATE,
                 Constants.SUGGESTION_STRIP_COORDINATE, isBatchMode, score, kind, sourceDict);
         researchLogger.commitCurrentLogUnitAsWord(scrubbedWord, Long.MAX_VALUE, isBatchMode);
         researchLogger.mStatistics.recordManualSuggestion(SystemClock.uptimeMillis());
diff --git a/native/jni/src/suggest/core/dicnode/dic_node_utils.cpp b/native/jni/src/suggest/core/dicnode/dic_node_utils.cpp
index f3b110b..6b4ef2f 100644
--- a/native/jni/src/suggest/core/dicnode/dic_node_utils.cpp
+++ b/native/jni/src/suggest/core/dicnode/dic_node_utils.cpp
@@ -150,7 +150,7 @@
         }
         actualLength1 = i + 1;
     }
-    actualLength1 = min(actualLength1, MAX_WORD_LENGTH - actualLength0 - 1);
+    actualLength1 = min(actualLength1, MAX_WORD_LENGTH - actualLength0);
     memcpy(&dest[actualLength0], src1, actualLength1 * sizeof(dest[0]));
     return actualLength0 + actualLength1;
 }
diff --git a/native/jni/src/suggest/core/dictionary/bigram_dictionary.cpp b/native/jni/src/suggest/core/dictionary/bigram_dictionary.cpp
index 09eecd3..532c769 100644
--- a/native/jni/src/suggest/core/dictionary/bigram_dictionary.cpp
+++ b/native/jni/src/suggest/core/dictionary/bigram_dictionary.cpp
@@ -23,7 +23,6 @@
 #include "defines.h"
 #include "suggest/core/dictionary/binary_dictionary_bigrams_iterator.h"
 #include "suggest/core/dictionary/binary_dictionary_info.h"
-#include "suggest/core/dictionary/binary_format.h"
 #include "suggest/core/dictionary/dictionary.h"
 #include "suggest/core/dictionary/probability_utils.h"
 #include "utils/char_utils.h"
diff --git a/native/jni/src/suggest/core/dictionary/dictionary.cpp b/native/jni/src/suggest/core/dictionary/dictionary.cpp
index 52e6359..4f5d29f 100644
--- a/native/jni/src/suggest/core/dictionary/dictionary.cpp
+++ b/native/jni/src/suggest/core/dictionary/dictionary.cpp
@@ -24,7 +24,6 @@
 #include "defines.h"
 #include "jni.h"
 #include "suggest/core/dictionary/bigram_dictionary.h"
-#include "suggest/core/dictionary/binary_format.h"
 #include "suggest/core/session/dic_traverse_session.h"
 #include "suggest/core/suggest.h"
 #include "suggest/core/suggest_options.h"
diff --git a/native/jni/src/suggest/core/dictionary/binary_format.h b/native/jni/src/suggest/policyimpl/dictionary/binary_format.h
similarity index 100%
rename from native/jni/src/suggest/core/dictionary/binary_format.h
rename to native/jni/src/suggest/policyimpl/dictionary/binary_format.h
diff --git a/native/jni/src/suggest/policyimpl/dictionary/patricia_trie_policy.cpp b/native/jni/src/suggest/policyimpl/dictionary/patricia_trie_policy.cpp
index 450f6d2..2a9a5ce 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/patricia_trie_policy.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/patricia_trie_policy.cpp
@@ -21,7 +21,7 @@
 #include "suggest/core/dicnode/dic_node.h"
 #include "suggest/core/dicnode/dic_node_vector.h"
 #include "suggest/core/dictionary/binary_dictionary_info.h"
-#include "suggest/core/dictionary/binary_format.h"
+#include "suggest/policyimpl/dictionary/binary_format.h"
 
 namespace latinime {
 
diff --git a/tests/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryTests.java b/tests/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryTests.java
index 7af83d0..8f9ef1d 100644
--- a/tests/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryTests.java
+++ b/tests/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryTests.java
@@ -70,7 +70,7 @@
         return new ArrayList<String>(wordSet);
     }
 
-    private void addToDict(final UserHistoryDictionary dict, final List<String> words) {
+    private void addToDict(final UserHistoryPredictionDictionary dict, final List<String> words) {
         String prevWord = null;
         for (String word : words) {
             dict.forceAddWordForTest(prevWord, word, true);
@@ -90,8 +90,8 @@
             final String locale = "testRandomWords";
             final String fileName = "UserHistoryDictionary." + locale + ".dict";
             dictFile = new File(getContext().getFilesDir(), fileName);
-            final UserHistoryDictionary dict =
-                    PersonalizationDictionaryHelper.getUserHistoryDictionary(
+            final UserHistoryPredictionDictionary dict =
+                    PersonalizationDictionaryHelper.getUserHistoryPredictionDictionary(
                             getContext(), locale, mPrefs);
             dict.isTest = true;
 
@@ -142,8 +142,8 @@
             for (int i = 0; i < numberOfLanguageSwitching; i++) {
                 final int index = i % numberOfLanguages;
                 // Switch languages to locales[index].
-                final UserHistoryDictionary dict =
-                        PersonalizationDictionaryHelper.getUserHistoryDictionary(
+                final UserHistoryPredictionDictionary dict =
+                        PersonalizationDictionaryHelper.getUserHistoryPredictionDictionary(
                                 getContext(), locales[index], mPrefs);
                 final List<String> words = generateWords(
                         numberOfWordsIntertedForEachLanguageSwitch, random);
diff --git a/tools/maketext/res/values-ca/donottranslate-more-keys.xml b/tools/maketext/res/values-ca/donottranslate-more-keys.xml
index baa23bf..8624dfb 100644
--- a/tools/maketext/res/values-ca/donottranslate-more-keys.xml
+++ b/tools/maketext/res/values-ca/donottranslate-more-keys.xml
@@ -67,7 +67,12 @@
          U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE
          U+010D: "č" LATIN SMALL LETTER C WITH CARON -->
     <string name="more_keys_for_c">&#x00E7;,&#x0107;,&#x010D;</string>
-    <!-- U+0140: "ŀ" LATIN SMALL LETTER L WITH MIDDLE DOT
+    <!-- U+00B7: "·" MIDDLE DOT
          U+0142: "ł" LATIN SMALL LETTER L WITH STROKE -->
-    <string name="more_keys_for_l">&#x0140;,&#x0142;</string>
+    <string name="more_keys_for_l">l&#x00B7;l,&#x0142;</string>
+    <!-- U+00B7: "·" MIDDLE DOT -->
+    <string name="more_keys_for_punctuation">"!fixedColumnOrder!9,&#x00B7;,\",\',#,-,:,!,\\,,\?,\@,&amp;,\\%,+,;,/,(,)"</string>
+    <string name="more_keys_for_tablet_period">\?,&#x00B7;</string>
+    <!-- U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA -->
+    <string name="keylabel_for_spanish_row2_10">&#x00E7;</string>
 </resources>
diff --git a/tools/maketext/res/values-es/donottranslate-more-keys.xml b/tools/maketext/res/values-es/donottranslate-more-keys.xml
index 961193b..0e58c14 100644
--- a/tools/maketext/res/values-es/donottranslate-more-keys.xml
+++ b/tools/maketext/res/values-es/donottranslate-more-keys.xml
@@ -67,6 +67,8 @@
          U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE
          U+010D: "č" LATIN SMALL LETTER C WITH CARON -->
     <string name="more_keys_for_c">&#x00E7;,&#x0107;,&#x010D;</string>
+    <!-- U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE -->
+    <string name="keylabel_for_spanish_row2_10">&#x00F1;</string>
     <!-- U+00A1: "¡" INVERTED EXCLAMATION MARK
          U+00BF: "¿" INVERTED QUESTION MARK -->
     <string name="more_keys_for_punctuation">"!fixedColumnOrder!9,&#x00A1;,\",\',#,-,:,!,\\,,\?,&#x00BF;,\@,&amp;,\\%,+,;,/,(,)"</string>
diff --git a/tools/maketext/res/values/donottranslate-more-keys.xml b/tools/maketext/res/values/donottranslate-more-keys.xml
index b53a369..4cf2650 100644
--- a/tools/maketext/res/values/donottranslate-more-keys.xml
+++ b/tools/maketext/res/values/donottranslate-more-keys.xml
@@ -180,8 +180,7 @@
     <string name="keylabel_for_w">w</string>
     <string name="keylabel_for_y">y</string>
     <string name="keylabel_for_x">x</string>
-    <!-- U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE -->
-    <string name="keylabel_for_spanish_row2_10">&#x00F1;</string>
+    <string name="keylabel_for_spanish_row2_10"></string>
     <string name="more_keys_for_am_pm">!fixedColumnOrder!2,!hasLabels!,\@string/label_time_am,\@string/label_time_pm</string>
     <string name="settings_as_more_key">!icon/settings_key|!code/key_settings</string>
     <string name="shortcut_as_more_key">!icon/shortcut_key|!code/key_shortcut</string>