Merge "Import translations. DO NOT MERGE"
diff --git a/java/res/layout/emoji_keyboard_view.xml b/java/res/layout/emoji_keyboard_view.xml
index ccbcfdc..5fee419 100644
--- a/java/res/layout/emoji_keyboard_view.xml
+++ b/java/res/layout/emoji_keyboard_view.xml
@@ -76,18 +76,24 @@
         android:id="@+id/emoji_action_bar"
         android:orientation="horizontal"
         android:layout_width="match_parent"
-        android:layout_height="@dimen/suggestions_strip_height"
+        android:layout_height="0dip"
+        android:layout_weight="1"
     >
         <ImageButton
             android:id="@+id/emoji_keyboard_alphabet"
             android:layout_width="0dip"
-            android:layout_weight="0.825"
+            android:layout_weight="0.15"
             android:layout_height="match_parent"
             android:src="@drawable/ic_ime_light" />
         <ImageButton
+            android:id="@+id/emoji_keyboard_space"
+            android:layout_width="0dip"
+            android:layout_weight="0.70"
+            android:layout_height="match_parent" />
+        <ImageButton
             android:id="@+id/emoji_keyboard_send"
             android:layout_width="0dip"
-            android:layout_weight="0.125"
+            android:layout_weight="0.15"
             android:layout_height="match_parent"
             android:src="@drawable/sym_keyboard_return_holo" />
     </LinearLayout>
diff --git a/java/res/values-fr-rCA/donottranslate.xml b/java/res/values-fr-rCA/donottranslate.xml
new file mode 100644
index 0000000..21f18d8
--- /dev/null
+++ b/java/res/values-fr-rCA/donottranslate.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2009, 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 xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Symbols that are normally preceded by a space (used to add an auto-space before these) -->
+    <!-- This is similar to French with the exception of "!" "?" and ";" which do not take a space before in Canadian French. Note that ":" does take a space before according to Canadian rules. -->
+    <string name="symbols_preceded_by_space">([{&amp;:</string>
+    <!-- Symbols that are normally followed by a space (used to add an auto-space after these) -->
+    <string name="symbols_followed_by_space">.,;:!?)]}&amp;</string>
+    <!-- Symbols that separate words -->
+    <!-- Don't remove the enclosing double quotes, they protect whitespace (not just U+0020) -->
+    <string name="symbols_word_separators">"&#x0009;&#x0020;\n"()[]{}*&amp;&lt;&gt;+=|.,;:!?/_\"</string>
+    <!-- Word connectors -->
+    <string name="symbols_word_connectors">\'-</string>
+</resources>
diff --git a/java/res/values/attrs.xml b/java/res/values/attrs.xml
index 2a5334f..0978214 100644
--- a/java/res/values/attrs.xml
+++ b/java/res/values/attrs.xml
@@ -45,6 +45,8 @@
              possible states: normal, pressed, checkable, checkable+pressed, checkable+checked,
              checkable+checked+pressed. -->
         <attr name="keyBackground" format="reference" />
+        <!-- Image for the functional key used in Emoji layout. -->
+        <attr name="keyBackgroundEmojiFunctional" format="reference" />
 
         <!-- Horizontal padding of left/right aligned key label to the edge of the key. -->
         <attr name="keyLabelHorizontalPadding" format="dimension" />
diff --git a/java/res/values/themes-ics.xml b/java/res/values/themes-ics.xml
index f3b6b13..19fb4fd 100644
--- a/java/res/values/themes-ics.xml
+++ b/java/res/values/themes-ics.xml
@@ -103,7 +103,7 @@
         name="EmojiKeyboardView.ICS"
         parent="KeyboardView.ICS"
     >
-        <item name="keyBackground">@drawable/btn_keyboard_key_functional_ics</item>
+        <item name="keyBackgroundEmojiFunctional">@drawable/btn_keyboard_key_functional_ics</item>
         <item name="emojiTabLabelColor">@color/emoji_tab_label_color_ics</item>
     </style>
     <style
diff --git a/java/src/com/android/inputmethod/keyboard/EmojiKeyboardView.java b/java/src/com/android/inputmethod/keyboard/EmojiKeyboardView.java
index 25ff8d0..98c2925 100644
--- a/java/src/com/android/inputmethod/keyboard/EmojiKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/EmojiKeyboardView.java
@@ -22,6 +22,7 @@
 import android.content.res.ColorStateList;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
+import android.os.Build;
 import android.support.v4.view.PagerAdapter;
 import android.support.v4.view.ViewPager;
 import android.util.AttributeSet;
@@ -61,6 +62,7 @@
         ViewPager.OnPageChangeListener, View.OnClickListener,
         ScrollKeyboardView.OnKeyClickListener {
     private final int mKeyBackgroundId;
+    private final int mEmojiFunctionalKeyBackgroundId;
     private final ColorStateList mTabLabelColor;
     private final EmojiKeyboardAdapter mEmojiKeyboardAdapter;
 
@@ -116,6 +118,8 @@
                 R.styleable.KeyboardView, defStyle, R.style.KeyboardView);
         mKeyBackgroundId = keyboardViewAttr.getResourceId(
                 R.styleable.KeyboardView_keyBackground, 0);
+        mEmojiFunctionalKeyBackgroundId = keyboardViewAttr.getResourceId(
+                R.styleable.KeyboardView_keyBackgroundEmojiFunctional, 0);
         keyboardViewAttr.recycle();
         final TypedArray emojiKeyboardViewAttr = context.obtainStyledAttributes(attrs,
                 R.styleable.EmojiKeyboardView, defStyle, R.style.EmojiKeyboardView);
@@ -126,10 +130,9 @@
                 context, null /* editorInfo */);
         final Resources res = context.getResources();
         builder.setSubtype(SubtypeSwitcher.getInstance().getEmojiSubtype());
-        // TODO: Make Keyboard height variable.
         builder.setKeyboardGeometry(ResourceUtils.getDefaultKeyboardWidth(res),
-                (int)(ResourceUtils.getDefaultKeyboardHeight(res)
-                        - res.getDimension(R.dimen.suggestions_strip_height)));
+                (int)ResourceUtils.getDefaultKeyboardHeight(res)
+                        + res.getDimensionPixelSize(R.dimen.suggestions_strip_height));
         builder.setOptions(false, false, false /* lanuageSwitchKeyEnabled */);
         final KeyboardLayoutSet layoutSet = builder.build();
         mEmojiKeyboardAdapter = new EmojiKeyboardAdapter(layoutSet, this);
@@ -165,7 +168,6 @@
                     R.layout.emoji_keyboard_tab_label, null);
             textView.setText(sCategoryLabel[category]);
             textView.setTextColor(mTabLabelColor);
-            textView.setBackgroundResource(mKeyBackgroundId);
             tspec.setIndicator(textView);
         }
         host.addTab(tspec);
@@ -176,10 +178,12 @@
         mTabHost = (TabHost)findViewById(R.id.emoji_category_tabhost);
         mTabHost.setup();
         addTab(mTabHost, CATEGORY_RECENTS);
-        addTab(mTabHost, CATEGORY_PEOPLE);
-        addTab(mTabHost, CATEGORY_OBJECTS);
-        addTab(mTabHost, CATEGORY_NATURE);
-        addTab(mTabHost, CATEGORY_PLACES);
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
+            addTab(mTabHost, CATEGORY_PEOPLE);
+            addTab(mTabHost, CATEGORY_OBJECTS);
+            addTab(mTabHost, CATEGORY_NATURE);
+            addTab(mTabHost, CATEGORY_PLACES);
+        }
         addTab(mTabHost, CATEGORY_SYMBOLS);
         addTab(mTabHost, CATEGORY_EMOTICONS);
         mTabHost.setOnTabChangedListener(this);
@@ -189,27 +193,33 @@
         mEmojiPager.setAdapter(mEmojiKeyboardAdapter);
         mEmojiPager.setOnPageChangeListener(this);
         mEmojiPager.setOffscreenPageLimit(0);
-        final ViewGroup.LayoutParams lp = mEmojiPager.getLayoutParams();
         final Resources res = getResources();
-        lp.height = ResourceUtils.getDefaultKeyboardHeight(res)
-                - res.getDimensionPixelSize(R.dimen.suggestions_strip_height);
-        mEmojiPager.setLayoutParams(lp);
+        final EmojiLayoutParams emojiLp = new EmojiLayoutParams(res);
+        emojiLp.setPagerProps(mEmojiPager);
 
         // TODO: Record current category.
         final int category = CATEGORY_PEOPLE;
         setCurrentCategory(category, true /* force */);
 
+        final LinearLayout actionBar = (LinearLayout)findViewById(R.id.emoji_action_bar);
+        emojiLp.setActionBarProps(actionBar);
+
         // TODO: Implement auto repeat, using View.OnTouchListener?
-        final View deleteKey = findViewById(R.id.emoji_keyboard_delete);
-        deleteKey.setBackgroundResource(mKeyBackgroundId);
+        final ImageView deleteKey = (ImageView)findViewById(R.id.emoji_keyboard_delete);
+        deleteKey.setBackgroundResource(mEmojiFunctionalKeyBackgroundId);
         deleteKey.setTag(Constants.CODE_DELETE);
         deleteKey.setOnClickListener(this);
-        final View alphabetKey = findViewById(R.id.emoji_keyboard_alphabet);
-        alphabetKey.setBackgroundResource(mKeyBackgroundId);
+        final ImageView alphabetKey = (ImageView)findViewById(R.id.emoji_keyboard_alphabet);
+        alphabetKey.setBackgroundResource(mEmojiFunctionalKeyBackgroundId);
         alphabetKey.setTag(Constants.CODE_SWITCH_ALPHA_SYMBOL);
         alphabetKey.setOnClickListener(this);
-        final View sendKey = findViewById(R.id.emoji_keyboard_send);
-        sendKey.setBackgroundResource(mKeyBackgroundId);
+        final ImageView spaceKey = (ImageView)findViewById(R.id.emoji_keyboard_space);
+        spaceKey.setBackgroundResource(mKeyBackgroundId);
+        spaceKey.setTag(Constants.CODE_SPACE);
+        spaceKey.setOnClickListener(this);
+        emojiLp.setKeyProps(spaceKey);
+        final ImageView sendKey = (ImageView)findViewById(R.id.emoji_keyboard_send);
+        sendKey.setBackgroundResource(mEmojiFunctionalKeyBackgroundId);
         sendKey.setTag(Constants.CODE_ENTER);
         sendKey.setOnClickListener(this);
     }
diff --git a/java/src/com/android/inputmethod/keyboard/EmojiLayoutParams.java b/java/src/com/android/inputmethod/keyboard/EmojiLayoutParams.java
new file mode 100644
index 0000000..6486fc9
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/EmojiLayoutParams.java
@@ -0,0 +1,76 @@
+/*
+ * 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.keyboard;
+
+import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.utils.ResourceUtils;
+
+import android.content.res.Resources;
+import android.support.v4.view.ViewPager;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+
+public class EmojiLayoutParams {
+    private static final int DEFAULT_KEYBOARD_ROWS = 4;
+
+    public final int mEmojiKeyboardHeight;
+    public final int mEmojiActionBarHeight;
+    public final int mKeyVerticalGap;
+    private final int mKeyHorizontalGap;
+    private final int mBottomPadding;
+    private final int mTopPadding;
+
+    public EmojiLayoutParams(Resources res) {
+        final int defaultKeyboardHeight = ResourceUtils.getDefaultKeyboardHeight(res);
+        final int defaultKeyboardWidth = ResourceUtils.getDefaultKeyboardWidth(res);
+        mKeyVerticalGap = (int) res.getFraction(R.fraction.key_bottom_gap_ics,
+                (int) defaultKeyboardHeight, (int) defaultKeyboardHeight);
+        mBottomPadding = (int) res.getFraction(R.fraction.keyboard_bottom_padding_ics,
+                (int) defaultKeyboardHeight, (int) defaultKeyboardHeight);
+        mTopPadding = (int) res.getFraction(R.fraction.keyboard_top_padding_ics,
+                (int) defaultKeyboardHeight, (int) defaultKeyboardHeight);
+        mKeyHorizontalGap = (int) (res.getFraction(R.fraction.key_horizontal_gap_ics,
+                defaultKeyboardWidth, defaultKeyboardWidth));
+        final int baseheight = defaultKeyboardHeight - mBottomPadding - mTopPadding
+                + mKeyVerticalGap;
+        mEmojiActionBarHeight = ((int) baseheight) / DEFAULT_KEYBOARD_ROWS
+                - (mKeyVerticalGap - mBottomPadding) / 2;
+        mEmojiKeyboardHeight = defaultKeyboardHeight - mEmojiActionBarHeight;
+    }
+
+    public void setPagerProps(ViewPager vp) {
+        final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) vp.getLayoutParams();
+        lp.height = mEmojiKeyboardHeight - mKeyVerticalGap / 2;
+        lp.bottomMargin = mKeyVerticalGap / 2;
+        vp.setLayoutParams(lp);
+    }
+
+    public void setActionBarProps(LinearLayout ll) {
+        final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) ll.getLayoutParams();
+        lp.height = mEmojiActionBarHeight;
+        lp.topMargin = 0;
+        lp.bottomMargin = mBottomPadding;
+        ll.setLayoutParams(lp);
+    }
+
+    public void setKeyProps(ImageView ib) {
+        final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) ib.getLayoutParams();
+        lp.leftMargin = mKeyHorizontalGap / 2;
+        lp.rightMargin = mKeyHorizontalGap / 2;
+        ib.setLayoutParams(lp);
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
index e8b0657..834d3ed 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
@@ -43,6 +43,7 @@
 
     private long mNativeDict;
     private final Locale mLocale;
+    private final long mDictSize;
     private final int[] mInputCodePoints = new int[MAX_WORD_LENGTH];
     private final int[] mOutputCodePoints = new int[MAX_WORD_LENGTH * MAX_RESULTS];
     private final int[] mSpaceIndices = new int[MAX_RESULTS];
@@ -62,7 +63,7 @@
             if (traverseSession == null) {
                 traverseSession = mDicTraverseSessions.get(traverseSessionId);
                 if (traverseSession == null) {
-                    traverseSession = new DicTraverseSession(mLocale, mNativeDict);
+                    traverseSession = new DicTraverseSession(mLocale, mNativeDict, mDictSize);
                     mDicTraverseSessions.put(traverseSessionId, traverseSession);
                 }
             }
@@ -85,6 +86,7 @@
             final boolean isUpdatable) {
         super(dictType);
         mLocale = locale;
+        mDictSize = length;
         mNativeSuggestOptions.setUseFullEditDistance(useFullEditDistance);
         loadDictionary(filename, offset, length, isUpdatable);
     }
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
index 2b6d983..5661842 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
@@ -21,9 +21,10 @@
 import android.content.res.AssetFileDescriptor;
 import android.util.Log;
 
+import com.android.inputmethod.latin.makedict.DictDecoder;
+import com.android.inputmethod.latin.makedict.FormatSpec;
 import com.android.inputmethod.latin.makedict.FormatSpec.FileHeader;
 import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
-import com.android.inputmethod.latin.makedict.Ver3DictDecoder;
 import com.android.inputmethod.latin.utils.CollectionUtils;
 import com.android.inputmethod.latin.utils.DictionaryInfoUtils;
 import com.android.inputmethod.latin.utils.LocaleUtils;
@@ -228,7 +229,7 @@
     private static boolean hackCanUseDictionaryFile(final Locale locale, final File f) {
         try {
             // Read the version of the file
-            final Ver3DictDecoder dictDecoder = new Ver3DictDecoder(f);
+            final DictDecoder dictDecoder = FormatSpec.getDictDecoder(f);
             final FileHeader header = dictDecoder.readHeader();
 
             final String version = header.mDictionaryOptions.mAttributes.get(VERSION_KEY);
diff --git a/java/src/com/android/inputmethod/latin/DicTraverseSession.java b/java/src/com/android/inputmethod/latin/DicTraverseSession.java
index 45b2813..8d295ad 100644
--- a/java/src/com/android/inputmethod/latin/DicTraverseSession.java
+++ b/java/src/com/android/inputmethod/latin/DicTraverseSession.java
@@ -25,16 +25,16 @@
         JniUtils.loadNativeLibrary();
     }
 
-    private static native long setDicTraverseSessionNative(String locale);
+    private static native long setDicTraverseSessionNative(String locale, long dictSize);
     private static native void initDicTraverseSessionNative(long nativeDicTraverseSession,
             long dictionary, int[] previousWord, int previousWordLength);
     private static native void releaseDicTraverseSessionNative(long nativeDicTraverseSession);
 
     private long mNativeDicTraverseSession;
 
-    public DicTraverseSession(Locale locale, long dictionary) {
+    public DicTraverseSession(Locale locale, long dictionary, long dictSize) {
         mNativeDicTraverseSession = createNativeDicTraverseSession(
-                locale != null ? locale.toString() : "");
+                locale != null ? locale.toString() : "", dictSize);
         initSession(dictionary);
     }
 
@@ -51,8 +51,8 @@
                 mNativeDicTraverseSession, dictionary, previousWord, previousWordLength);
     }
 
-    private final long createNativeDicTraverseSession(String locale) {
-        return setDicTraverseSessionNative(locale);
+    private final long createNativeDicTraverseSession(String locale, long dictSize) {
+        return setDicTraverseSessionNative(locale, dictSize);
     }
 
     private void closeInternal() {
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 6c83ac7e..cdd6c92 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -95,7 +95,6 @@
 import com.android.inputmethod.latin.utils.IntentUtils;
 import com.android.inputmethod.latin.utils.JniUtils;
 import com.android.inputmethod.latin.utils.LatinImeLoggerUtils;
-import com.android.inputmethod.latin.utils.PositionalInfoForUserDictPendingAddition;
 import com.android.inputmethod.latin.utils.RecapitalizeStatus;
 import com.android.inputmethod.latin.utils.StaticInnerHandlerWrapper;
 import com.android.inputmethod.latin.utils.TargetPackageInfoGetterTask;
@@ -185,8 +184,6 @@
     private boolean mIsUserDictionaryAvailable;
 
     private LastComposedWord mLastComposedWord = LastComposedWord.NOT_A_COMPOSED_WORD;
-    private PositionalInfoForUserDictPendingAddition
-            mPositionalInfoForUserDictPendingAddition = null;
     private final WordComposer mWordComposer = new WordComposer();
     private final RichInputConnection mConnection = new RichInputConnection(this);
     private final RecapitalizeStatus mRecapitalizeStatus = new RecapitalizeStatus();
@@ -898,19 +895,6 @@
                 currentSettingsValues.mGestureTrailEnabled,
                 currentSettingsValues.mGestureFloatingPreviewTextEnabled);
 
-        // If we have a user dictionary addition in progress, we should check now if we should
-        // replace the previously committed string with the word that has actually been added
-        // to the user dictionary.
-        if (null != mPositionalInfoForUserDictPendingAddition
-                && mPositionalInfoForUserDictPendingAddition.tryReplaceWithActualWord(
-                        mConnection, editorInfo, mLastSelectionEnd, currentLocale)) {
-            mPositionalInfoForUserDictPendingAddition = null;
-        }
-        // If tryReplaceWithActualWord returns false, we don't know what word was
-        // added to the user dictionary yet, so we keep the data and defer processing. The word will
-        // be replaced when the user dictionary reports back with the actual word, which ends
-        // up calling #onWordAddedToUserDictionary() in this class.
-
         initPersonalizationDebugSettings(currentSettingsValues);
 
         if (TRACE) Debug.startMethodTracing("/data/trace/latinime");
@@ -1416,7 +1400,6 @@
     public void addWordToUserDictionary(final String word) {
         if (TextUtils.isEmpty(word)) {
             // Probably never supposed to happen, but just in case.
-            mPositionalInfoForUserDictPendingAddition = null;
             return;
         }
         final String wordToEdit;
@@ -1428,22 +1411,6 @@
         mUserDictionary.addWordToUserDictionary(wordToEdit);
     }
 
-    public void onWordAddedToUserDictionary(final String newSpelling) {
-        // If word was added but not by us, bail out
-        if (null == mPositionalInfoForUserDictPendingAddition) return;
-        if (mWordComposer.isComposingWord()) {
-            // We are late... give up and return
-            mPositionalInfoForUserDictPendingAddition = null;
-            return;
-        }
-        mPositionalInfoForUserDictPendingAddition.setActualWordBeingAdded(newSpelling);
-        if (mPositionalInfoForUserDictPendingAddition.tryReplaceWithActualWord(
-                mConnection, getCurrentInputEditorInfo(), mLastSelectionEnd,
-                mSubtypeSwitcher.getCurrentSubtypeLocale())) {
-            mPositionalInfoForUserDictPendingAddition = null;
-        }
-    }
-
     private void onSettingsKeyPressed() {
         if (isShowingOptionDialog()) return;
         showSubtypeSelectorAndSettings();
@@ -2704,6 +2671,8 @@
         // recorrection. This is a temporary, stopgap measure that will be removed later.
         // TODO: remove this.
         if (mAppWorkAroundsUtils.isBrokenByRecorrection()) return;
+        // A simple way to test for support from the TextView.
+        if (!isSuggestionsStripVisible()) return;
         // Recorrection is not supported in languages without spaces because we don't know
         // how to segment them yet.
         if (!mSettings.getCurrent().mCurrentLanguageHasSpaces) return;
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index 18ba158..9370757 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -48,8 +48,9 @@
 
     // Session id for
     // {@link #getSuggestedWords(WordComposer,String,ProximityInfo,boolean,int)}.
+    // We are sharing the same ID between typing and gesture to save RAM footprint.
     public static final int SESSION_TYPING = 0;
-    public static final int SESSION_GESTURE = 1;
+    public static final int SESSION_GESTURE = 0;
 
     // TODO: rename this to CORRECTION_OFF
     public static final int CORRECTION_NONE = 0;
diff --git a/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java b/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
index b2bb615..a241b55 100644
--- a/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
@@ -103,14 +103,6 @@
             @Override
             public void onChange(final boolean self, final Uri uri) {
                 setRequiresReload(true);
-                // We want to report back to Latin IME in case the user just entered the word.
-                // If the user changed the word in the dialog box, then we want to replace
-                // what was entered in the text field.
-                if (null == uri || !(context instanceof LatinIME)) return;
-                final long changedRowId = ContentUris.parseId(uri);
-                if (-1 == changedRowId) return; // Unknown content... Not sure why we're here
-                final String changedWord = getChangedWordForUri(uri);
-                ((LatinIME)context).onWordAddedToUserDictionary(changedWord);
             }
         };
         cres.registerContentObserver(Words.CONTENT_URI, true, mObserver);
@@ -118,19 +110,6 @@
         loadDictionary();
     }
 
-    private String getChangedWordForUri(final Uri uri) {
-        final Cursor cursor = mContext.getContentResolver().query(uri,
-                PROJECTION_QUERY, null, null, null);
-        if (cursor == null) return null;
-        try {
-            if (!cursor.moveToFirst()) return null;
-            final int indexWord = cursor.getColumnIndex(Words.WORD);
-            return cursor.getString(indexWord);
-        } finally {
-            cursor.close();
-        }
-    }
-
     @Override
     public synchronized void close() {
         if (mObserver != null) {
diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderUtils.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderUtils.java
index ceb8fa8..5b319ad 100644
--- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderUtils.java
+++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderUtils.java
@@ -342,13 +342,11 @@
      * @param formatOptions file format options.
      * @return the word with its frequency, as a weighted string.
      */
-    /* package for tests */ static WeightedString getWordAtPosition(
-            final Ver3DictDecoder dictDecoder, final int headerSize, final int pos,
-            final FormatOptions formatOptions) {
-        final DictBuffer dictBuffer = dictDecoder.getDictBuffer();
+    /* package for tests */ static WeightedString getWordAtPosition(final DictDecoder dictDecoder,
+            final int headerSize, final int pos, final FormatOptions formatOptions) {
         final WeightedString result;
-        final int originalPos = dictBuffer.position();
-        dictBuffer.position(pos);
+        final int originalPos = dictDecoder.getPosition();
+        dictDecoder.setPosition(pos);
 
         if (BinaryDictIOUtils.supportsDynamicUpdate(formatOptions)) {
             result = getWordAtPositionWithParentAddress(dictDecoder, pos, formatOptions);
@@ -357,14 +355,13 @@
                     formatOptions);
         }
 
-        dictBuffer.position(originalPos);
+        dictDecoder.setPosition(originalPos);
         return result;
     }
 
     @SuppressWarnings("unused")
-    private static WeightedString getWordAtPositionWithParentAddress(
-            final Ver3DictDecoder dictDecoder, final int pos, final FormatOptions options) {
-        final DictBuffer dictBuffer = dictDecoder.getDictBuffer();
+    private static WeightedString getWordAtPositionWithParentAddress(final DictDecoder dictDecoder,
+            final int pos, final FormatOptions options) {
         int currentPos = pos;
         int frequency = Integer.MIN_VALUE;
         final StringBuilder builder = new StringBuilder();
@@ -373,7 +370,7 @@
             PtNodeInfo currentInfo;
             int loopCounter = 0;
             do {
-                dictBuffer.position(currentPos);
+                dictDecoder.setPosition(currentPos);
                 currentInfo = dictDecoder.readPtNode(currentPos, options);
                 if (BinaryDictIOUtils.isMovedPtNode(currentInfo.mFlags, options)) {
                     currentPos = currentInfo.mParentAddress + currentInfo.mOriginalAddress;
@@ -392,11 +389,10 @@
     }
 
     private static WeightedString getWordAtPositionWithoutParentAddress(
-            final Ver3DictDecoder dictDecoder, final int headerSize, final int pos,
+            final DictDecoder dictDecoder, final int headerSize, final int pos,
             final FormatOptions options) {
-        final DictBuffer dictBuffer = dictDecoder.getDictBuffer();
-        dictBuffer.position(headerSize);
-        final int count = readPtNodeCount(dictBuffer);
+        dictDecoder.setPosition(headerSize);
+        final int count = dictDecoder.readPtNodeCount();
         int groupPos = headerSize + BinaryDictIOUtils.getPtNodeCountSize(count);
         final StringBuilder builder = new StringBuilder();
         WeightedString result = null;
@@ -414,8 +410,8 @@
                 if (info.mChildrenAddress > pos) {
                     if (null == last) continue;
                     builder.append(new String(last.mCharacters, 0, last.mCharacters.length));
-                    dictBuffer.position(last.mChildrenAddress);
-                    i = readPtNodeCount(dictBuffer);
+                    dictDecoder.setPosition(last.mChildrenAddress);
+                    i = dictDecoder.readPtNodeCount();
                     groupPos = last.mChildrenAddress + BinaryDictIOUtils.getPtNodeCountSize(i);
                     last = null;
                     continue;
@@ -424,8 +420,8 @@
             }
             if (0 == i && BinaryDictIOUtils.hasChildrenAddress(last.mChildrenAddress)) {
                 builder.append(new String(last.mCharacters, 0, last.mCharacters.length));
-                dictBuffer.position(last.mChildrenAddress);
-                i = readPtNodeCount(dictBuffer);
+                dictDecoder.setPosition(last.mChildrenAddress);
+                i = dictDecoder.readPtNodeCount();
                 groupPos = last.mChildrenAddress + BinaryDictIOUtils.getPtNodeCountSize(i);
                 last = null;
                 continue;
@@ -449,17 +445,16 @@
      * @param options file format options.
      * @return the read node array with all his children already read.
      */
-    private static PtNodeArray readNodeArray(final Ver3DictDecoder dictDecoder,
+    private static PtNodeArray readNodeArray(final DictDecoder dictDecoder,
             final int headerSize, final Map<Integer, PtNodeArray> reverseNodeArrayMap,
             final Map<Integer, PtNode> reversePtNodeMap, final FormatOptions options)
             throws IOException {
-        final DictBuffer dictBuffer = dictDecoder.getDictBuffer();
         final ArrayList<PtNode> nodeArrayContents = new ArrayList<PtNode>();
-        final int nodeArrayOriginPos = dictBuffer.position();
+        final int nodeArrayOriginPos = dictDecoder.getPosition();
 
         do { // Scan the linked-list node.
-            final int nodeArrayHeadPos = dictBuffer.position();
-            final int count = readPtNodeCount(dictBuffer);
+            final int nodeArrayHeadPos = dictDecoder.getPosition();
+            final int count = dictDecoder.readPtNodeCount();
             int groupOffsetPos = nodeArrayHeadPos + BinaryDictIOUtils.getPtNodeCountSize(count);
             for (int i = count; i > 0; --i) { // Scan the array of PtNode.
                 PtNodeInfo info = dictDecoder.readPtNode(groupOffsetPos, options);
@@ -480,11 +475,11 @@
                 if (BinaryDictIOUtils.hasChildrenAddress(info.mChildrenAddress)) {
                     PtNodeArray children = reverseNodeArrayMap.get(info.mChildrenAddress);
                     if (null == children) {
-                        final int currentPosition = dictBuffer.position();
-                        dictBuffer.position(info.mChildrenAddress);
+                        final int currentPosition = dictDecoder.getPosition();
+                        dictDecoder.setPosition(info.mChildrenAddress);
                         children = readNodeArray(dictDecoder, headerSize, reverseNodeArrayMap,
                                 reversePtNodeMap, options);
-                        dictBuffer.position(currentPosition);
+                        dictDecoder.setPosition(currentPosition);
                     }
                     nodeArrayContents.add(
                             new PtNode(info.mCharacters, shortcutTargets, bigrams,
@@ -503,15 +498,10 @@
 
             // reach the end of the array.
             if (options.mSupportsDynamicUpdate) {
-                final int nextAddress = dictBuffer.readUnsignedInt24();
-                if (nextAddress >= 0 && nextAddress < dictBuffer.limit()) {
-                    dictBuffer.position(nextAddress);
-                } else {
-                    break;
-                }
+                final boolean hasValidForwardLink = dictDecoder.readForwardLinkAndAdvancePosition();
+                if (!hasValidForwardLink) break;
             }
-        } while (options.mSupportsDynamicUpdate &&
-                dictBuffer.position() != FormatSpec.NO_FORWARD_LINK_ADDRESS);
+        } while (options.mSupportsDynamicUpdate && dictDecoder.hasNextPtNodeArray());
 
         final PtNodeArray nodeArray = new PtNodeArray(nodeArrayContents);
         nodeArray.mCachedAddressBeforeUpdate = nodeArrayOriginPos;
diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java
index 106f025..2c5e93e 100644
--- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java
+++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java
@@ -61,12 +61,11 @@
     /**
      * Retrieves all node arrays without recursive call.
      */
-    private static void readUnigramsAndBigramsBinaryInner(
-            final Ver3DictDecoder dictDecoder, final int headerSize,
-            final Map<Integer, String> words, final Map<Integer, Integer> frequencies,
+    private static void readUnigramsAndBigramsBinaryInner(final DictDecoder dictDecoder,
+            final int headerSize, final Map<Integer, String> words,
+            final Map<Integer, Integer> frequencies,
             final Map<Integer, ArrayList<PendingAttribute>> bigrams,
             final FormatOptions formatOptions) {
-        final DictBuffer dictBuffer = dictDecoder.getDictBuffer();
         int[] pushedChars = new int[FormatSpec.MAX_WORD_LENGTH + 1];
 
         Stack<Position> stack = new Stack<Position>();
@@ -83,11 +82,11 @@
                         p.mNumOfPtNode + ", position=" + p.mPosition + ", length=" + p.mLength);
             }
 
-            if (dictBuffer.position() != p.mAddress) dictBuffer.position(p.mAddress);
+            if (dictDecoder.getPosition() != p.mAddress) dictDecoder.setPosition(p.mAddress);
             if (index != p.mLength) index = p.mLength;
 
             if (p.mNumOfPtNode == Position.NOT_READ_PTNODE_COUNT) {
-                p.mNumOfPtNode = BinaryDictDecoderUtils.readPtNodeCount(dictBuffer);
+                p.mNumOfPtNode = dictDecoder.readPtNodeCount();
                 p.mAddress += getPtNodeCountSize(p.mNumOfPtNode);
                 p.mPosition = 0;
             }
@@ -114,11 +113,12 @@
 
             if (p.mPosition == p.mNumOfPtNode) {
                 if (formatOptions.mSupportsDynamicUpdate) {
-                    final int forwardLinkAddress = dictBuffer.readUnsignedInt24();
-                    if (forwardLinkAddress != FormatSpec.NO_FORWARD_LINK_ADDRESS) {
+                    final boolean hasValidForwardLinkAddress =
+                            dictDecoder.readForwardLinkAndAdvancePosition();
+                    if (hasValidForwardLinkAddress && dictDecoder.hasNextPtNodeArray()) {
                         // The node array has a forward link.
                         p.mNumOfPtNode = Position.NOT_READ_PTNODE_COUNT;
-                        p.mAddress = forwardLinkAddress;
+                        p.mAddress = dictDecoder.getPosition();
                     } else {
                         stack.pop();
                     }
@@ -127,7 +127,7 @@
                 }
             } else {
                 // The Ptnode array has more PtNodes.
-                p.mAddress = dictBuffer.position();
+                p.mAddress = dictDecoder.getPosition();
             }
 
             if (!isMovedPtNode && hasChildrenAddress(info.mChildrenAddress)) {
@@ -148,7 +148,7 @@
      * @throws IOException if the file can't be read.
      * @throws UnsupportedFormatException if the format of the file is not recognized.
      */
-    /* package */ static void readUnigramsAndBigramsBinary(final Ver3DictDecoder dictDecoder,
+    /* package */ static void readUnigramsAndBigramsBinary(final DictDecoder dictDecoder,
             final Map<Integer, String> words, final Map<Integer, Integer> frequencies,
             final Map<Integer, ArrayList<PendingAttribute>> bigrams) throws IOException,
             UnsupportedFormatException {
@@ -169,11 +169,10 @@
      * @throws UnsupportedFormatException if the format of the file is not recognized.
      */
     @UsedForTesting
-    /* package */ static int getTerminalPosition(final Ver3DictDecoder dictDecoder,
+    /* package */ static int getTerminalPosition(final DictDecoder dictDecoder,
             final String word) throws IOException, UnsupportedFormatException {
-        final DictBuffer dictBuffer = dictDecoder.getDictBuffer();
         if (word == null) return FormatSpec.NOT_VALID_WORD;
-        if (dictBuffer.position() != 0) dictBuffer.position(0);
+        dictDecoder.setPosition(0);
 
         final FileHeader header = dictDecoder.readHeader();
         int wordPos = 0;
@@ -182,10 +181,10 @@
             if (wordPos >= wordLen) return FormatSpec.NOT_VALID_WORD;
 
             do {
-                final int ptNodeCount = BinaryDictDecoderUtils.readPtNodeCount(dictBuffer);
+                final int ptNodeCount = dictDecoder.readPtNodeCount();
                 boolean foundNextPtNode = false;
                 for (int i = 0; i < ptNodeCount; ++i) {
-                    final int ptNodePos = dictBuffer.position();
+                    final int ptNodePos = dictDecoder.getPosition();
                     final PtNodeInfo currentInfo = dictDecoder.readPtNode(ptNodePos,
                             header.mFormatOptions);
                     final boolean isMovedNode = isMovedPtNode(currentInfo.mFlags,
@@ -219,7 +218,7 @@
                             return FormatSpec.NOT_VALID_WORD;
                         }
                         foundNextPtNode = true;
-                        dictBuffer.position(currentInfo.mChildrenAddress);
+                        dictDecoder.setPosition(currentInfo.mChildrenAddress);
                         break;
                     }
                 }
@@ -233,11 +232,11 @@
                     return FormatSpec.NOT_VALID_WORD;
                 }
 
-                final int forwardLinkAddress = dictBuffer.readUnsignedInt24();
-                if (forwardLinkAddress == FormatSpec.NO_FORWARD_LINK_ADDRESS) {
+                final boolean hasValidForwardLinkAddress =
+                        dictDecoder.readForwardLinkAndAdvancePosition();
+                if (!hasValidForwardLinkAddress || !dictDecoder.hasNextPtNodeArray()) {
                     return FormatSpec.NOT_VALID_WORD;
                 }
-                dictBuffer.position(forwardLinkAddress);
             } while(true);
         }
         return FormatSpec.NOT_VALID_WORD;
@@ -520,7 +519,7 @@
             final File file, final long offset, final long length)
             throws FileNotFoundException, IOException, UnsupportedFormatException {
         final byte[] buffer = new byte[HEADER_READING_BUFFER_SIZE];
-        final Ver3DictDecoder dictDecoder = new Ver3DictDecoder(file,
+        final DictDecoder dictDecoder = FormatSpec.getDictDecoder(file,
                 new DictDecoder.DictionaryBufferFactory() {
                     @Override
                     public DictBuffer getDictionaryBuffer(File file)
diff --git a/java/src/com/android/inputmethod/latin/makedict/DictDecoder.java b/java/src/com/android/inputmethod/latin/makedict/DictDecoder.java
index 64638fd..40e8524 100644
--- a/java/src/com/android/inputmethod/latin/makedict/DictDecoder.java
+++ b/java/src/com/android/inputmethod/latin/makedict/DictDecoder.java
@@ -91,6 +91,41 @@
             final TreeMap<Integer, ArrayList<PendingAttribute>> bigrams)
             throws IOException, UnsupportedFormatException;
 
+    /**
+     * Sets the position of the buffer to the given value.
+     *
+     * @param newPos the new position
+     */
+    public void setPosition(final int newPos);
+
+    /**
+     * Gets the position of the buffer.
+     *
+     * @return the position
+     */
+    public int getPosition();
+
+    /**
+     * Reads and returns the PtNode count out of a buffer and forwards the pointer.
+     */
+    public int readPtNodeCount();
+
+    /**
+     * Reads the forward link and advances the position.
+     *
+     * @return if this method advances the position then true else false.
+     */
+    public boolean readForwardLinkAndAdvancePosition();
+    public boolean hasNextPtNodeArray();
+
+    /**
+     * Opens the dictionary file and makes DictBuffer.
+     */
+    @UsedForTesting
+    public void openDictBuffer() throws FileNotFoundException, IOException;
+    @UsedForTesting
+    public boolean isOpenedDictBuffer();
+
     // Flags for DictionaryBufferFactory.
     public static final int USE_READONLY_BYTEBUFFER = 0x01000000;
     public static final int USE_BYTEARRAY = 0x02000000;
diff --git a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
index bf35f6a..44ae33d 100644
--- a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
+++ b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
@@ -18,8 +18,11 @@
 
 import com.android.inputmethod.annotations.UsedForTesting;
 import com.android.inputmethod.latin.Constants;
+import com.android.inputmethod.latin.makedict.DictDecoder.DictionaryBufferFactory;
 import com.android.inputmethod.latin.makedict.FusionDictionary.DictionaryOptions;
 
+import java.io.File;
+
 /**
  * Dictionary File Format Specification.
  */
@@ -341,6 +344,28 @@
         }
     }
 
+    /**
+     * Returns new dictionary decoder.
+     *
+     * @param dictFile the dictionary file.
+     * @param bufferType the flag indicating buffer type which is used by the dictionary decoder.
+     * @return new dictionary decoder if the dictionary file exists, otherwise null.
+     */
+    public static DictDecoder getDictDecoder(final File dictFile, final int bufferType) {
+        if (!dictFile.isFile()) return null;
+        return new Ver3DictDecoder(dictFile, bufferType);
+    }
+
+    public static DictDecoder getDictDecoder(final File dictFile,
+            final DictionaryBufferFactory factory) {
+        if (!dictFile.isFile()) return null;
+        return new Ver3DictDecoder(dictFile, factory);
+    }
+
+    public static DictDecoder getDictDecoder(final File dictFile) {
+        return getDictDecoder(dictFile, DictDecoder.USE_READONLY_BYTEBUFFER);
+    }
+
     private FormatSpec() {
         // This utility class is not publicly instantiable.
     }
diff --git a/java/src/com/android/inputmethod/latin/makedict/Ver3DictDecoder.java b/java/src/com/android/inputmethod/latin/makedict/Ver3DictDecoder.java
index b98aa0f..1a90a4b 100644
--- a/java/src/com/android/inputmethod/latin/makedict/Ver3DictDecoder.java
+++ b/java/src/com/android/inputmethod/latin/makedict/Ver3DictDecoder.java
@@ -173,11 +173,7 @@
     private final DictionaryBufferFactory mBufferFactory;
     private DictBuffer mDictBuffer;
 
-    public Ver3DictDecoder(final File file) {
-        this(file, USE_READONLY_BYTEBUFFER);
-    }
-
-    public Ver3DictDecoder(final File file, final int factoryFlag) {
+    /* package */ Ver3DictDecoder(final File file, final int factoryFlag) {
         mDictionaryBinaryFile = file;
         mDictBuffer = null;
 
@@ -192,15 +188,21 @@
         }
     }
 
-    public Ver3DictDecoder(final File file, final DictionaryBufferFactory factory) {
+    /* package */ Ver3DictDecoder(final File file, final DictionaryBufferFactory factory) {
         mDictionaryBinaryFile = file;
         mBufferFactory = factory;
     }
 
+    @Override
     public void openDictBuffer() throws FileNotFoundException, IOException {
         mDictBuffer = mBufferFactory.getDictionaryBuffer(mDictionaryBinaryFile);
     }
 
+    @Override
+    public boolean isOpenedDictBuffer() {
+        return mDictBuffer != null;
+    }
+
     /* package */ DictBuffer getDictBuffer() {
         return mDictBuffer;
     }
@@ -348,4 +350,33 @@
         BinaryDictIOUtils.readUnigramsAndBigramsBinary(this, words, frequencies, bigrams);
     }
 
+    @Override
+    public void setPosition(int newPos) {
+        mDictBuffer.position(newPos);
+    }
+
+    @Override
+    public int getPosition() {
+        return mDictBuffer.position();
+    }
+
+    @Override
+    public int readPtNodeCount() {
+        return BinaryDictDecoderUtils.readPtNodeCount(mDictBuffer);
+    }
+
+    @Override
+    public boolean readForwardLinkAndAdvancePosition() {
+        final int nextAddress = mDictBuffer.readUnsignedInt24();
+        if (nextAddress >= 0 && nextAddress < mDictBuffer.limit()) {
+            mDictBuffer.position(nextAddress);
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public boolean hasNextPtNodeArray() {
+        return mDictBuffer.position() != FormatSpec.NO_FORWARD_LINK_ADDRESS;
+    }
 }
diff --git a/java/src/com/android/inputmethod/latin/personalization/DynamicPredictionDictionaryBase.java b/java/src/com/android/inputmethod/latin/personalization/DynamicPredictionDictionaryBase.java
index 5b1d064..9364fb0 100644
--- a/java/src/com/android/inputmethod/latin/personalization/DynamicPredictionDictionaryBase.java
+++ b/java/src/com/android/inputmethod/latin/personalization/DynamicPredictionDictionaryBase.java
@@ -25,14 +25,13 @@
 import com.android.inputmethod.latin.ExpandableBinaryDictionary;
 import com.android.inputmethod.latin.LatinImeLogger;
 import com.android.inputmethod.latin.makedict.DictDecoder;
-import com.android.inputmethod.latin.makedict.Ver3DictDecoder;
+import com.android.inputmethod.latin.makedict.FormatSpec;
 import com.android.inputmethod.latin.settings.Settings;
 import com.android.inputmethod.latin.utils.CollectionUtils;
 import com.android.inputmethod.latin.utils.UserHistoryDictIOUtils;
 import com.android.inputmethod.latin.utils.UserHistoryDictIOUtils.OnAddWordListener;
 
 import java.io.File;
-import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.util.ArrayList;
 
@@ -173,16 +172,19 @@
 
         // Load the dictionary from binary file
         final File dictFile = new File(mContext.getFilesDir(), mFileName);
-        final Ver3DictDecoder dictDecoder = new Ver3DictDecoder(dictFile,
+        final DictDecoder dictDecoder = FormatSpec.getDictDecoder(dictFile,
                 DictDecoder.USE_BYTEARRAY);
+        if (dictDecoder == null) {
+            // This is an expected condition: we don't have a user history dictionary for this
+            // language yet. It will be created sometime later.
+            return;
+        }
+
         try {
             dictDecoder.openDictBuffer();
             UserHistoryDictIOUtils.readDictionaryBinary(dictDecoder, listener);
-        } catch (FileNotFoundException e) {
-            // This is an expected condition: we don't have a user history dictionary for this
-            // language yet. It will be created sometime later.
         } catch (IOException e) {
-            Log.e(TAG, "IOException on opening a bytebuffer", e);
+            Log.d(TAG, "IOException on opening a bytebuffer", e);
         } finally {
             if (PROFILE_SAVE_RESTORE) {
                 final long diff = System.currentTimeMillis() - now;
diff --git a/java/src/com/android/inputmethod/latin/utils/PositionalInfoForUserDictPendingAddition.java b/java/src/com/android/inputmethod/latin/utils/PositionalInfoForUserDictPendingAddition.java
deleted file mode 100644
index 1fc7ecc..0000000
--- a/java/src/com/android/inputmethod/latin/utils/PositionalInfoForUserDictPendingAddition.java
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.latin.utils;
-
-import android.view.inputmethod.EditorInfo;
-
-import com.android.inputmethod.latin.RichInputConnection;
-
-import java.util.Locale;
-
-/**
- * Holder class for data about a word already committed but that may still be edited.
- *
- * When the user chooses to add a word to the user dictionary by pressing the appropriate
- * suggestion, a dialog is presented to give a chance to edit the word before it is actually
- * registered as a user dictionary word. If the word is actually modified, the IME needs to
- * go back and replace the word that was committed with the amended version.
- * The word we need to replace with will only be known after it's actually committed, so
- * the IME needs to take a note of what it has to replace and where it is.
- * This class encapsulates this data.
- */
-public final class PositionalInfoForUserDictPendingAddition {
-    final private String mOriginalWord;
-    final private int mCursorPos; // Position of the cursor after the word
-    final private EditorInfo mEditorInfo; // On what binding this has been added
-    final private int mCapitalizedMode;
-    private String mActualWordBeingAdded;
-
-    public PositionalInfoForUserDictPendingAddition(final String word, final int cursorPos,
-            final EditorInfo editorInfo, final int capitalizedMode) {
-        mOriginalWord = word;
-        mCursorPos = cursorPos;
-        mEditorInfo = editorInfo;
-        mCapitalizedMode = capitalizedMode;
-    }
-
-    public void setActualWordBeingAdded(final String actualWordBeingAdded) {
-        mActualWordBeingAdded = actualWordBeingAdded;
-    }
-
-    /**
-     * Try to replace the string at the remembered position with the actual word being added.
-     *
-     * After the user validated the word being added, the IME has to replace the old version
-     * (which has been committed in the text view) with the amended version if it's different.
-     * This method tries to do that, but may fail because the IME is not yet ready to do so -
-     * for example, it is still waiting for the new string, or it is waiting to return to the text
-     * view in which the amendment should be made. In these cases, we should keep the data
-     * and wait until all conditions are met.
-     * This method returns true if the replacement has been successfully made and this data
-     * can be forgotten; it returns false if the replacement can't be made yet and we need to
-     * keep this until a later time.
-     * The IME knows about the actual word being added through a callback called by the
-     * user dictionary facility of the device. When this callback comes, the keyboard may still
-     * be connected to the edition dialog, or it may have already returned to the original text
-     * field. Replacement has to work in both cases.
-     * Accordingly, this method is called at two different points in time : upon getting the
-     * event that a new word was added to the user dictionary, and upon starting up in a
-     * new text field.
-     * @param connection The RichInputConnection through which to contact the editor.
-     * @param editorInfo Information pertaining to the editor we are currently in.
-     * @param currentCursorPosition The current cursor position, for checking purposes.
-     * @param locale The locale for changing case, if necessary
-     * @return true if the edit has been successfully made, false if we need to try again later
-     */
-    public boolean tryReplaceWithActualWord(final RichInputConnection connection,
-            final EditorInfo editorInfo, final int currentCursorPosition, final Locale locale) {
-        // If we still don't know the actual word being added, we need to try again later.
-        if (null == mActualWordBeingAdded) return false;
-        // The entered text and the registered text were the same anyway : we can
-        // return success right away even if focus has not returned yet to the text field we
-        // want to amend.
-        if (mActualWordBeingAdded.equals(mOriginalWord)) return true;
-        // Not the same text field : we need to try again later. This happens when the addition
-        // is reported by the user dictionary provider before the focus has moved back to the
-        // original text view, so the IME is still in the text view of the dialog and has no way to
-        // edit the original text view at this time.
-        if (!mEditorInfo.packageName.equals(editorInfo.packageName)
-                || mEditorInfo.fieldId != editorInfo.fieldId) {
-            return false;
-        }
-        // Same text field, but not the same cursor position : we give up, so we return success
-        // so that it won't be tried again
-        if (currentCursorPosition != mCursorPos) return true;
-        // We have made all the checks : do the replacement and report success
-        // If this was auto-capitalized, we need to restore the case before committing
-        final String wordWithCaseFixed = CapsModeUtils.applyAutoCapsMode(mActualWordBeingAdded,
-                mCapitalizedMode, locale);
-        connection.setComposingRegion(currentCursorPosition - mOriginalWord.length(),
-                currentCursorPosition);
-        connection.commitText(wordWithCaseFixed, wordWithCaseFixed.length());
-        return true;
-    }
-}
diff --git a/java/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtils.java b/java/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtils.java
index 05f3061..ea32a74 100644
--- a/java/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtils.java
@@ -20,13 +20,13 @@
 
 import com.android.inputmethod.annotations.UsedForTesting;
 import com.android.inputmethod.latin.makedict.BinaryDictIOUtils;
+import com.android.inputmethod.latin.makedict.DictDecoder;
 import com.android.inputmethod.latin.makedict.DictEncoder;
 import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions;
 import com.android.inputmethod.latin.makedict.FusionDictionary;
 import com.android.inputmethod.latin.makedict.FusionDictionary.PtNodeArray;
 import com.android.inputmethod.latin.makedict.PendingAttribute;
 import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
-import com.android.inputmethod.latin.makedict.Ver3DictDecoder;
 import com.android.inputmethod.latin.personalization.UserHistoryDictionaryBigramList;
 
 import java.io.IOException;
@@ -125,7 +125,7 @@
     /**
      * Reads dictionary from file.
      */
-    public static void readDictionaryBinary(final Ver3DictDecoder dictDecoder,
+    public static void readDictionaryBinary(final DictDecoder dictDecoder,
             final OnAddWordListener dict) {
         final TreeMap<Integer, String> unigrams = CollectionUtils.newTreeMap();
         final TreeMap<Integer, Integer> frequencies = CollectionUtils.newTreeMap();
diff --git a/native/jni/com_android_inputmethod_latin_DicTraverseSession.cpp b/native/jni/com_android_inputmethod_latin_DicTraverseSession.cpp
index 72e6258..3866433 100644
--- a/native/jni/com_android_inputmethod_latin_DicTraverseSession.cpp
+++ b/native/jni/com_android_inputmethod_latin_DicTraverseSession.cpp
@@ -25,8 +25,9 @@
 
 namespace latinime {
 class Dictionary;
-static jlong latinime_setDicTraverseSession(JNIEnv *env, jclass clazz, jstring localeJStr) {
-    void *traverseSession = DicTraverseSession::getSessionInstance(env, localeJStr);
+static jlong latinime_setDicTraverseSession(JNIEnv *env, jclass clazz, jstring localeJStr,
+        jlong dictSize) {
+    void *traverseSession = DicTraverseSession::getSessionInstance(env, localeJStr, dictSize);
     return reinterpret_cast<jlong>(traverseSession);
 }
 
@@ -53,7 +54,7 @@
 static const JNINativeMethod sMethods[] = {
     {
         const_cast<char *>("setDicTraverseSessionNative"),
-        const_cast<char *>("(Ljava/lang/String;)J"),
+        const_cast<char *>("(Ljava/lang/String;J)J"),
         reinterpret_cast<void *>(latinime_setDicTraverseSession)
     },
     {
diff --git a/native/jni/src/defines.h b/native/jni/src/defines.h
index 34a646f..4605890 100644
--- a/native/jni/src/defines.h
+++ b/native/jni/src/defines.h
@@ -322,13 +322,6 @@
 #define MAX_POINTER_COUNT 1
 #define MAX_POINTER_COUNT_G 2
 
-// Queue IDs and size for DicNodesCache
-#define DIC_NODES_CACHE_INITIAL_QUEUE_ID_ACTIVE 0
-#define DIC_NODES_CACHE_INITIAL_QUEUE_ID_NEXT_ACTIVE 1
-#define DIC_NODES_CACHE_INITIAL_QUEUE_ID_TERMINAL 2
-#define DIC_NODES_CACHE_INITIAL_QUEUE_ID_CACHE_FOR_CONTINUOUS_SUGGESTION 3
-#define DIC_NODES_CACHE_PRIORITY_QUEUES_SIZE 4
-
 template<typename T> AK_FORCE_INLINE const T &min(const T &a, const T &b) { return a < b ? a : b; }
 template<typename T> AK_FORCE_INLINE const T &max(const T &a, const T &b) { return a > b ? a : b; }
 
diff --git a/native/jni/src/suggest/core/dicnode/dic_node_priority_queue.h b/native/jni/src/suggest/core/dicnode/dic_node_priority_queue.h
index 2a486b8..7461f0c 100644
--- a/native/jni/src/suggest/core/dicnode/dic_node_priority_queue.h
+++ b/native/jni/src/suggest/core/dicnode/dic_node_priority_queue.h
@@ -24,20 +24,16 @@
 #include "suggest/core/dicnode/dic_node.h"
 #include "suggest/core/dicnode/dic_node_release_listener.h"
 
-// The biggest value among MAX_CACHE_DIC_NODE_SIZE, MAX_CACHE_DIC_NODE_SIZE_FOR_SINGLE_POINT, ...
-#define MAX_DIC_NODE_PRIORITY_QUEUE_CAPACITY 310
-
 namespace latinime {
 
 class DicNodePriorityQueue : public DicNodeReleaseListener {
  public:
-    AK_FORCE_INLINE DicNodePriorityQueue()
-            : MAX_CAPACITY(MAX_DIC_NODE_PRIORITY_QUEUE_CAPACITY),
-              mMaxSize(MAX_DIC_NODE_PRIORITY_QUEUE_CAPACITY), mDicNodesBuf(), mUnusedNodeIndices(),
-              mNextUnusedNodeId(0), mDicNodesQueue() {
-        mDicNodesBuf.resize(MAX_CAPACITY + 1);
-        mUnusedNodeIndices.resize(MAX_CAPACITY + 1);
-        reset();
+    AK_FORCE_INLINE explicit DicNodePriorityQueue(const int capacity)
+            : mCapacity(capacity), mMaxSize(capacity), mDicNodesBuf(),
+              mUnusedNodeIndices(), mNextUnusedNodeId(0), mDicNodesQueue() {
+        mDicNodesBuf.resize(mCapacity + 1);
+        mUnusedNodeIndices.resize(mCapacity + 1);
+        clearAndResizeToCapacity();
     }
 
     // Non virtual inline destructor -- never inherit this class
@@ -52,11 +48,12 @@
     }
 
     AK_FORCE_INLINE void setMaxSize(const int maxSize) {
-        mMaxSize = min(maxSize, MAX_CAPACITY);
+        ASSERT(maxSize <= mCapacity);
+        mMaxSize = min(maxSize, mCapacity);
     }
 
-    AK_FORCE_INLINE void reset() {
-        clearAndResize(MAX_CAPACITY);
+    AK_FORCE_INLINE void clearAndResizeToCapacity() {
+        clearAndResize(mCapacity);
     }
 
     AK_FORCE_INLINE void clear() {
@@ -64,27 +61,19 @@
     }
 
     AK_FORCE_INLINE void clearAndResize(const int maxSize) {
+        ASSERT(maxSize <= mCapacity);
         while (!mDicNodesQueue.empty()) {
             mDicNodesQueue.pop();
         }
         setMaxSize(maxSize);
-        for (int i = 0; i < MAX_CAPACITY + 1; ++i) {
+        for (int i = 0; i < mCapacity + 1; ++i) {
             mDicNodesBuf[i].remove();
             mDicNodesBuf[i].setReleaseListener(this);
-            mUnusedNodeIndices[i] = i == MAX_CAPACITY ? NOT_A_NODE_ID : static_cast<int>(i) + 1;
+            mUnusedNodeIndices[i] = i == mCapacity ? NOT_A_NODE_ID : static_cast<int>(i) + 1;
         }
         mNextUnusedNodeId = 0;
     }
 
-    AK_FORCE_INLINE DicNode *newDicNode(DicNode *dicNode) {
-        DicNode *newNode = searchEmptyDicNode();
-        if (newNode) {
-            DicNodeUtils::initByCopy(dicNode, newNode);
-            return newNode;
-        }
-        return 0;
-    }
-
     // Copy
     AK_FORCE_INLINE DicNode *copyPush(DicNode *dicNode) {
         return copyPush(dicNode, mMaxSize);
@@ -111,12 +100,12 @@
         }
         mUnusedNodeIndices[index] = mNextUnusedNodeId;
         mNextUnusedNodeId = index;
-        ASSERT(index >= 0 && index < (MAX_CAPACITY + 1));
+        ASSERT(index >= 0 && index < (mCapacity + 1));
     }
 
     AK_FORCE_INLINE void dump() const {
         AKLOGI("\n\n\n\n\n===========================");
-        for (int i = 0; i < MAX_CAPACITY + 1; ++i) {
+        for (int i = 0; i < mCapacity + 1; ++i) {
             if (mDicNodesBuf[i].isUsed()) {
                 mDicNodesBuf[i].dump("QUEUE: ");
             }
@@ -125,7 +114,7 @@
     }
 
  private:
-    DISALLOW_COPY_AND_ASSIGN(DicNodePriorityQueue);
+    DISALLOW_IMPLICIT_CONSTRUCTORS(DicNodePriorityQueue);
     static const int NOT_A_NODE_ID = -1;
 
     AK_FORCE_INLINE static bool compareDicNode(DicNode *left, DicNode *right) {
@@ -139,7 +128,7 @@
     };
 
     typedef std::priority_queue<DicNode *, std::vector<DicNode *>, DicNodeComparator> DicNodesQueue;
-    const int MAX_CAPACITY;
+    const int mCapacity;
     int mMaxSize;
     std::vector<DicNode> mDicNodesBuf; // of each element of mDicNodesBuf respectively
     std::vector<int> mUnusedNodeIndices;
@@ -163,13 +152,12 @@
     }
 
     AK_FORCE_INLINE DicNode *searchEmptyDicNode() {
-        // TODO: Currently O(n) but should be improved to O(1)
-        if (MAX_CAPACITY == 0) {
+        if (mCapacity == 0) {
             return 0;
         }
         if (mNextUnusedNodeId == NOT_A_NODE_ID) {
             AKLOGI("No unused node found.");
-            for (int i = 0; i < MAX_CAPACITY + 1; ++i) {
+            for (int i = 0; i < mCapacity + 1; ++i) {
                 AKLOGI("Dump node availability, %d, %d, %d",
                         i, mDicNodesBuf[i].isUsed(), mUnusedNodeIndices[i]);
             }
@@ -185,7 +173,7 @@
         const int index = static_cast<int>(dicNode - &mDicNodesBuf[0]);
         mNextUnusedNodeId = mUnusedNodeIndices[index];
         mUnusedNodeIndices[index] = NOT_A_NODE_ID;
-        ASSERT(index >= 0 && index < (MAX_CAPACITY + 1));
+        ASSERT(index >= 0 && index < (mCapacity + 1));
     }
 
     AK_FORCE_INLINE DicNode *pushPoolNodeWithMaxSize(DicNode *dicNode, const int maxSize) {
@@ -209,6 +197,15 @@
     AK_FORCE_INLINE DicNode *copyPush(DicNode *dicNode, const int maxSize) {
         return pushPoolNodeWithMaxSize(newDicNode(dicNode), maxSize);
     }
+
+    AK_FORCE_INLINE DicNode *newDicNode(DicNode *dicNode) {
+        DicNode *newNode = searchEmptyDicNode();
+        if (newNode) {
+            DicNodeUtils::initByCopy(dicNode, newNode);
+        }
+        return newNode;
+    }
+
 };
 } // namespace latinime
 #endif // LATINIME_DIC_NODE_PRIORITY_QUEUE_H
diff --git a/native/jni/src/suggest/core/dicnode/dic_nodes_cache.cpp b/native/jni/src/suggest/core/dicnode/dic_nodes_cache.cpp
index c3d2a2e..b6be47e 100644
--- a/native/jni/src/suggest/core/dicnode/dic_nodes_cache.cpp
+++ b/native/jni/src/suggest/core/dicnode/dic_nodes_cache.cpp
@@ -23,6 +23,11 @@
 
 namespace latinime {
 
+// The biggest value among MAX_CACHE_DIC_NODE_SIZE, MAX_CACHE_DIC_NODE_SIZE_FOR_SINGLE_POINT, ...
+const int DicNodesCache::LARGE_PRIORITY_QUEUE_CAPACITY = 310;
+// Capacity for reducing memory footprint.
+const int DicNodesCache::SMALL_PRIORITY_QUEUE_CAPACITY = 100;
+
 /**
  * Truncates all of the dicNodes so that they start at the given commit point.
  * Only called for multi-word typing input.
diff --git a/native/jni/src/suggest/core/dicnode/dic_nodes_cache.h b/native/jni/src/suggest/core/dicnode/dic_nodes_cache.h
index 7aab090..8493b6a 100644
--- a/native/jni/src/suggest/core/dicnode/dic_nodes_cache.h
+++ b/native/jni/src/suggest/core/dicnode/dic_nodes_cache.h
@@ -31,25 +31,32 @@
  */
 class DicNodesCache {
  public:
-    AK_FORCE_INLINE DicNodesCache()
-            : mActiveDicNodes(&mDicNodePriorityQueues[DIC_NODES_CACHE_INITIAL_QUEUE_ID_ACTIVE]),
-              mNextActiveDicNodes(&mDicNodePriorityQueues[
-                      DIC_NODES_CACHE_INITIAL_QUEUE_ID_NEXT_ACTIVE]),
-              mTerminalDicNodes(&mDicNodePriorityQueues[DIC_NODES_CACHE_INITIAL_QUEUE_ID_TERMINAL]),
-              mCachedDicNodesForContinuousSuggestion(&mDicNodePriorityQueues[
-                      DIC_NODES_CACHE_INITIAL_QUEUE_ID_CACHE_FOR_CONTINUOUS_SUGGESTION]),
-              mInputIndex(0), mLastCachedInputIndex(0) {
-    }
+    AK_FORCE_INLINE explicit DicNodesCache(const bool usesLargeCapacityCache)
+            : mUsesLargeCapacityCache(usesLargeCapacityCache),
+              mDicNodePriorityQueue0(getCacheCapacity()),
+              mDicNodePriorityQueue1(getCacheCapacity()),
+              mDicNodePriorityQueue2(getCacheCapacity()),
+              mDicNodePriorityQueueForTerminal(MAX_RESULTS),
+              mActiveDicNodes(&mDicNodePriorityQueue0),
+              mNextActiveDicNodes(&mDicNodePriorityQueue1),
+              mCachedDicNodesForContinuousSuggestion(&mDicNodePriorityQueue2),
+              mTerminalDicNodes(&mDicNodePriorityQueueForTerminal),
+              mInputIndex(0), mLastCachedInputIndex(0) {}
 
     AK_FORCE_INLINE virtual ~DicNodesCache() {}
 
     AK_FORCE_INLINE void reset(const int nextActiveSize, const int terminalSize) {
         mInputIndex = 0;
         mLastCachedInputIndex = 0;
-        mActiveDicNodes->reset();
-        mNextActiveDicNodes->clearAndResize(nextActiveSize);
+        // We want to use the max capacity for the current active dic node queue.
+        mActiveDicNodes->clearAndResizeToCapacity();
+        // nextActiveSize is used to limit the next iteration's active dic node size.
+        const int nextActiveSizeFittingToTheCapacity = min(nextActiveSize, getCacheCapacity());
+        mNextActiveDicNodes->clearAndResize(nextActiveSizeFittingToTheCapacity);
         mTerminalDicNodes->clearAndResize(terminalSize);
-        mCachedDicNodesForContinuousSuggestion->reset();
+        // We want to use the max capacity for the cached dic nodes that will be used for the
+        // continuous suggestion.
+        mCachedDicNodesForContinuousSuggestion->clearAndResizeToCapacity();
     }
 
     AK_FORCE_INLINE void continueSearch() {
@@ -157,21 +164,35 @@
         return tmp;
     }
 
+    AK_FORCE_INLINE int getCacheCapacity() const {
+        return mUsesLargeCapacityCache ?
+                LARGE_PRIORITY_QUEUE_CAPACITY : SMALL_PRIORITY_QUEUE_CAPACITY;
+    }
+
     AK_FORCE_INLINE void resetTemporaryCaches() {
         mActiveDicNodes->clear();
         mNextActiveDicNodes->clear();
         mTerminalDicNodes->clear();
     }
 
-    DicNodePriorityQueue mDicNodePriorityQueues[DIC_NODES_CACHE_PRIORITY_QUEUES_SIZE];
+    static const int LARGE_PRIORITY_QUEUE_CAPACITY;
+    static const int SMALL_PRIORITY_QUEUE_CAPACITY;
+
+    const bool mUsesLargeCapacityCache;
+    // Instances
+    DicNodePriorityQueue mDicNodePriorityQueue0;
+    DicNodePriorityQueue mDicNodePriorityQueue1;
+    DicNodePriorityQueue mDicNodePriorityQueue2;
+    DicNodePriorityQueue mDicNodePriorityQueueForTerminal;
+
     // Active dicNodes currently being expanded.
     DicNodePriorityQueue *mActiveDicNodes;
     // Next dicNodes to be expanded.
     DicNodePriorityQueue *mNextActiveDicNodes;
-    // Current top terminal dicNodes.
-    DicNodePriorityQueue *mTerminalDicNodes;
     // Cached dicNodes used for continuous suggestion.
     DicNodePriorityQueue *mCachedDicNodesForContinuousSuggestion;
+    // Current top terminal dicNodes.
+    DicNodePriorityQueue *mTerminalDicNodes;
     int mInputIndex;
     int mLastCachedInputIndex;
 };
diff --git a/native/jni/src/suggest/core/layout/proximity_info_state.cpp b/native/jni/src/suggest/core/layout/proximity_info_state.cpp
index 7780efd..fbabd92 100644
--- a/native/jni/src/suggest/core/layout/proximity_info_state.cpp
+++ b/native/jni/src/suggest/core/layout/proximity_info_state.cpp
@@ -36,8 +36,8 @@
         const int *const xCoordinates, const int *const yCoordinates, const int *const times,
         const int *const pointerIds, const bool isGeometric) {
     ASSERT(isGeometric || (inputSize < MAX_WORD_LENGTH));
-    mIsContinuousSuggestionPossible =
-            ProximityInfoStateUtils::checkAndReturnIsContinuousSuggestionPossible(
+    mIsContinuousSuggestionPossible = (mHasBeenUpdatedByGeometricInput != isGeometric) ?
+            false : ProximityInfoStateUtils::checkAndReturnIsContinuousSuggestionPossible(
                     inputSize, xCoordinates, yCoordinates, times, mSampledInputSize,
                     &mSampledInputXs, &mSampledInputYs, &mSampledTimes, &mSampledInputIndice);
     if (DEBUG_DICT) {
@@ -155,6 +155,7 @@
     if (DEBUG_GEO_FULL) {
         AKLOGI("ProximityState init finished: %d points out of %d", mSampledInputSize, inputSize);
     }
+    mHasBeenUpdatedByGeometricInput = isGeometric;
 }
 
 // This function basically converts from a length to an edit distance. Accordingly, it's obviously
diff --git a/native/jni/src/suggest/core/layout/proximity_info_state.h b/native/jni/src/suggest/core/layout/proximity_info_state.h
index dbcd544..01bf818 100644
--- a/native/jni/src/suggest/core/layout/proximity_info_state.h
+++ b/native/jni/src/suggest/core/layout/proximity_info_state.h
@@ -46,10 +46,11 @@
             : mProximityInfo(0), mMaxPointToKeyLength(0.0f), mAverageSpeed(0.0f),
               mHasTouchPositionCorrectionData(false), mMostCommonKeyWidthSquare(0),
               mKeyCount(0), mCellHeight(0), mCellWidth(0), mGridHeight(0), mGridWidth(0),
-              mIsContinuousSuggestionPossible(false), mSampledInputXs(), mSampledInputYs(),
-              mSampledTimes(), mSampledInputIndice(), mSampledLengthCache(),
-              mBeelineSpeedPercentiles(), mSampledNormalizedSquaredLengthCache(), mSpeedRates(),
-              mDirections(), mCharProbabilities(), mSampledNearKeySets(), mSampledSearchKeySets(),
+              mIsContinuousSuggestionPossible(false), mHasBeenUpdatedByGeometricInput(false),
+              mSampledInputXs(), mSampledInputYs(), mSampledTimes(), mSampledInputIndice(),
+              mSampledLengthCache(), mBeelineSpeedPercentiles(),
+              mSampledNormalizedSquaredLengthCache(), mSpeedRates(), mDirections(),
+              mCharProbabilities(), mSampledNearKeySets(), mSampledSearchKeySets(),
               mSampledSearchKeyVectors(), mTouchPositionCorrectionEnabled(false),
               mSampledInputSize(0), mMostProbableStringProbability(0.0f) {
         memset(mInputProximities, 0, sizeof(mInputProximities));
@@ -204,6 +205,7 @@
     int mGridHeight;
     int mGridWidth;
     bool mIsContinuousSuggestionPossible;
+    bool mHasBeenUpdatedByGeometricInput;
 
     std::vector<int> mSampledInputXs;
     std::vector<int> mSampledInputYs;
diff --git a/native/jni/src/suggest/core/session/dic_traverse_session.cpp b/native/jni/src/suggest/core/session/dic_traverse_session.cpp
index 0ca583f..2c22592 100644
--- a/native/jni/src/suggest/core/session/dic_traverse_session.cpp
+++ b/native/jni/src/suggest/core/session/dic_traverse_session.cpp
@@ -23,6 +23,11 @@
 
 namespace latinime {
 
+// 256K bytes threshold is heuristically used to distinguish dictionaries containing many unigrams
+// (e.g. main dictionary) from small dictionaries (e.g. contacts...)
+const int DicTraverseSession::DICTIONARY_SIZE_THRESHOLD_TO_USE_LARGE_CACHE_FOR_SUGGESTION =
+        256 * 1024;
+
 void DicTraverseSession::init(const Dictionary *const dictionary, const int *prevWord,
         int prevWordLength, const SuggestOptions *const suggestOptions) {
     mDictionary = dictionary;
@@ -59,8 +64,9 @@
     return mDictionary->getDictionaryStructurePolicy();
 }
 
-void DicTraverseSession::resetCache(const int nextActiveCacheSize, const int maxWords) {
-    mDicNodesCache.reset(nextActiveCacheSize, maxWords);
+void DicTraverseSession::resetCache(const int thresholdForNextActiveDicNodes, const int maxWords) {
+    mDicNodesCache.reset(thresholdForNextActiveDicNodes /* nextActiveSize */,
+            maxWords /* terminalSize */);
     mMultiBigramMap.clear();
     mPartiallyCommited = false;
 }
diff --git a/native/jni/src/suggest/core/session/dic_traverse_session.h b/native/jni/src/suggest/core/session/dic_traverse_session.h
index 23de5cc..fe88935 100644
--- a/native/jni/src/suggest/core/session/dic_traverse_session.h
+++ b/native/jni/src/suggest/core/session/dic_traverse_session.h
@@ -37,8 +37,12 @@
  public:
 
     // A factory method for DicTraverseSession
-    static AK_FORCE_INLINE void *getSessionInstance(JNIEnv *env, jstring localeStr) {
-        return new DicTraverseSession(env, localeStr);
+    static AK_FORCE_INLINE void *getSessionInstance(JNIEnv *env, jstring localeStr,
+            jlong dictSize) {
+        // To deal with the trade-off between accuracy and memory space, large cache is used for
+        // dictionaries larger that the threshold
+        return new DicTraverseSession(env, localeStr,
+                dictSize >= DICTIONARY_SIZE_THRESHOLD_TO_USE_LARGE_CACHE_FOR_SUGGESTION);
     }
 
     static AK_FORCE_INLINE void initSessionInstance(DicTraverseSession *traverseSession,
@@ -54,10 +58,10 @@
         delete traverseSession;
     }
 
-    AK_FORCE_INLINE DicTraverseSession(JNIEnv *env, jstring localeStr)
+    AK_FORCE_INLINE DicTraverseSession(JNIEnv *env, jstring localeStr, bool usesLargeCache)
             : mPrevWordPos(NOT_A_VALID_WORD_POS), mProximityInfo(0),
-              mDictionary(0), mSuggestOptions(0), mDicNodesCache(), mMultiBigramMap(),
-              mInputSize(0), mPartiallyCommited(false), mMaxPointerCount(1),
+              mDictionary(0), mSuggestOptions(0), mDicNodesCache(usesLargeCache),
+              mMultiBigramMap(), mInputSize(0), mPartiallyCommited(false), mMaxPointerCount(1),
               mMultiWordCostMultiplier(1.0f) {
         // NOTE: mProximityInfoStates is an array of instances.
         // No need to initialize it explicitly here.
@@ -73,7 +77,7 @@
             const int inputSize, const int *const inputXs, const int *const inputYs,
             const int *const times, const int *const pointerIds, const float maxSpatialDistance,
             const int maxPointerCount);
-    void resetCache(const int nextActiveCacheSize, const int maxWords);
+    void resetCache(const int thresholdForNextActiveDicNodes, const int maxWords);
 
     const DictionaryStructureWithBufferPolicy *getDictionaryStructurePolicy() const;
 
@@ -181,6 +185,7 @@
     DISALLOW_IMPLICIT_CONSTRUCTORS(DicTraverseSession);
     // threshold to start caching
     static const int CACHE_START_INPUT_LENGTH_THRESHOLD;
+    static const int DICTIONARY_SIZE_THRESHOLD_TO_USE_LARGE_CACHE_FOR_SUGGESTION;
     void initializeProximityInfoStates(const int *const inputCodePoints, const int *const inputXs,
             const int *const inputYs, const int *const times, const int *const pointerIds,
             const int inputSize, const float maxSpatialDistance, const int maxPointerCount);
diff --git a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderEncoderTests.java b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderEncoderTests.java
index 1864845..2d57100 100644
--- a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderEncoderTests.java
+++ b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderEncoderTests.java
@@ -132,18 +132,6 @@
     // Utilities for test
 
     /**
-     * Makes new DictDecoder according to BUFFER_TYPE.
-     */
-    private Ver3DictDecoder getDictDecoder(final File file, final int bufferType) {
-        if (bufferType == USE_BYTE_BUFFER) {
-            return new Ver3DictDecoder(file, DictDecoder.USE_READONLY_BYTEBUFFER);
-        } else  if (bufferType == USE_BYTE_ARRAY) {
-            return new Ver3DictDecoder(file, DictDecoder.USE_BYTEARRAY);
-        }
-        return null;
-    }
-
-    /**
      * Generates a random word.
      */
     private String generateWord(final Random random, final int[] codePointSet) {
@@ -285,9 +273,7 @@
 
         FusionDictionary dict = null;
         try {
-            final Ver3DictDecoder dictDecoder = getDictDecoder(file, bufferType);
-            dictDecoder.openDictBuffer();
-            assertNotNull(dictDecoder.getDictBuffer());
+            final DictDecoder dictDecoder = FormatSpec.getDictDecoder(file, bufferType);
             now = System.currentTimeMillis();
             dict = dictDecoder.readDictionaryBinary(null, false /* deleteDictIfBroken */);
             diff  = System.currentTimeMillis() - now;
@@ -443,9 +429,7 @@
 
         long now = -1, diff = -1;
         try {
-            final Ver3DictDecoder dictDecoder = getDictDecoder(file, bufferType);
-            dictDecoder.openDictBuffer();
-            assertNotNull("Can't get buffer.", dictDecoder.getDictBuffer());
+            final DictDecoder dictDecoder = FormatSpec.getDictDecoder(file, bufferType);
             now = System.currentTimeMillis();
             dictDecoder.readUnigramsAndBigramsBinary(resultWords, resultFreqs, resultBigrams);
             diff = System.currentTimeMillis() - now;
@@ -531,9 +515,8 @@
     }
 
     // Tests for getTerminalPosition
-    private String getWordFromBinary(final Ver3DictDecoder dictDecoder, final int address) {
-        final DictBuffer dictBuffer = dictDecoder.getDictBuffer();
-        if (dictBuffer.position() != 0) dictBuffer.position(0);
+    private String getWordFromBinary(final DictDecoder dictDecoder, final int address) {
+        if (dictDecoder.getPosition() != 0) dictDecoder.setPosition(0);
 
         FileHeader fileHeader = null;
         try {
@@ -548,7 +531,7 @@
                 address, fileHeader.mFormatOptions).mWord;
     }
 
-    private long runGetTerminalPosition(final Ver3DictDecoder dictDecoder, final String word,
+    private long runGetTerminalPosition(final DictDecoder dictDecoder, final String word,
             int index, boolean contained) {
         final int expectedFrequency = (UNIGRAM_FREQ + index) % 255;
         long diff = -1;
@@ -584,14 +567,14 @@
         addUnigrams(sWords.size(), dict, sWords, null /* shortcutMap */);
         timeWritingDictToFile(file, dict, VERSION3_WITH_DYNAMIC_UPDATE);
 
-        final Ver3DictDecoder dictDecoder = new Ver3DictDecoder(file, DictDecoder.USE_BYTEARRAY);
+        final DictDecoder dictDecoder = FormatSpec.getDictDecoder(file, DictDecoder.USE_BYTEARRAY);
         try {
             dictDecoder.openDictBuffer();
         } catch (IOException e) {
             // ignore
             Log.e(TAG, "IOException while opening the buffer", e);
         }
-        assertNotNull("Can't get the buffer", dictDecoder.getDictBuffer());
+        assertTrue("Can't get the buffer", dictDecoder.isOpenedDictBuffer());
 
         try {
             // too long word
@@ -648,7 +631,7 @@
             // ignore
             Log.e(TAG, "IOException while opening the buffer", e);
         }
-        assertNotNull("Can't get the buffer", dictDecoder.getDictBuffer());
+        assertTrue("Can't get the buffer", dictDecoder.isOpenedDictBuffer());
 
         try {
             MoreAsserts.assertNotEqual(FormatSpec.NOT_VALID_WORD,
diff --git a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtilsTests.java b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtilsTests.java
index 8e0c6df..a837494 100644
--- a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtilsTests.java
+++ b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtilsTests.java
@@ -140,7 +140,8 @@
         int position = FormatSpec.NOT_VALID_WORD;
 
         try {
-            final Ver3DictDecoder dictDecoder = new Ver3DictDecoder(file);
+            final Ver3DictDecoder dictDecoder = new Ver3DictDecoder(file,
+                    DictDecoder.USE_READONLY_BYTEBUFFER);
             position = dictDecoder.getTerminalPosition(word);
         } catch (IOException e) {
         } catch (UnsupportedFormatException e) {
@@ -149,7 +150,7 @@
     }
 
     /**
-     * Find a word using the Ver3DictDecoder.
+     * Find a word using the DictDecoder.
      *
      * @param dictDecoder the dict decoder
      * @param word the word searched
@@ -157,21 +158,20 @@
      * @throws IOException
      * @throws UnsupportedFormatException
      */
-    private static PtNodeInfo findWordByBinaryDictReader(final Ver3DictDecoder dictDecoder,
+    private static PtNodeInfo findWordByBinaryDictReader(final DictDecoder dictDecoder,
             final String word) throws IOException, UnsupportedFormatException {
         int position = dictDecoder.getTerminalPosition(word);
-        final DictBuffer dictBuffer = dictDecoder.getDictBuffer();
         if (position != FormatSpec.NOT_VALID_WORD) {
-            dictBuffer.position(0);
+            dictDecoder.setPosition(0);
             final FileHeader header = dictDecoder.readHeader();
-            dictBuffer.position(position);
+            dictDecoder.setPosition(position);
             return dictDecoder.readPtNode(position, header.mFormatOptions);
         }
         return null;
     }
 
     private PtNodeInfo findWordFromFile(final File file, final String word) {
-        final Ver3DictDecoder dictDecoder = new Ver3DictDecoder(file);
+        final DictDecoder dictDecoder = FormatSpec.getDictDecoder(file);
         PtNodeInfo info = null;
         try {
             dictDecoder.openDictBuffer();
@@ -234,7 +234,7 @@
     private void checkReverseLookup(final File file, final String word, final int position) {
 
         try {
-            final Ver3DictDecoder dictDecoder = new Ver3DictDecoder(file);
+            final DictDecoder dictDecoder = FormatSpec.getDictDecoder(file);
             final FileHeader fileHeader = dictDecoder.readHeader();
             assertEquals(word,
                     BinaryDictDecoderUtils.getWordAtPosition(dictDecoder, fileHeader.mHeaderSize,
diff --git a/tests/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtilsTests.java b/tests/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtilsTests.java
index 72b9478..3eabe2b 100644
--- a/tests/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtilsTests.java
+++ b/tests/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtilsTests.java
@@ -143,7 +143,7 @@
     }
 
     private void readDictFromFile(final File file, final OnAddWordListener listener) {
-        final Ver3DictDecoder dictDecoder = new Ver3DictDecoder(file, DictDecoder.USE_BYTEARRAY);
+        final DictDecoder dictDecoder = FormatSpec.getDictDecoder(file, DictDecoder.USE_BYTEARRAY);
         try {
             dictDecoder.openDictBuffer();
         } catch (FileNotFoundException e) {
diff --git a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/BinaryDictOffdeviceUtils.java b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/BinaryDictOffdeviceUtils.java
index 1417ca4..fa80385 100644
--- a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/BinaryDictOffdeviceUtils.java
+++ b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/BinaryDictOffdeviceUtils.java
@@ -18,9 +18,9 @@
 
 import com.android.inputmethod.latin.makedict.BinaryDictDecoderUtils;
 import com.android.inputmethod.latin.makedict.DictDecoder;
+import com.android.inputmethod.latin.makedict.FormatSpec;
 import com.android.inputmethod.latin.makedict.FusionDictionary;
 import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
-import com.android.inputmethod.latin.makedict.Ver3DictDecoder;
 
 import org.xml.sax.SAXException;
 
@@ -185,7 +185,7 @@
                     crash(filename, new RuntimeException(
                             filename + " does not seem to be a dictionary file"));
                 } else {
-                    final DictDecoder dictDecoder = new Ver3DictDecoder(decodedSpec.mFile,
+                    final DictDecoder dictDecoder = FormatSpec.getDictDecoder(file,
                             DictDecoder.USE_BYTEARRAY);
                     if (report) {
                         System.out.println("Format : Binary dictionary format");
diff --git a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/DictionaryMaker.java b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/DictionaryMaker.java
index 767c147..5302e97 100644
--- a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/DictionaryMaker.java
+++ b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/DictionaryMaker.java
@@ -267,7 +267,7 @@
     private static FusionDictionary readBinaryFile(final String binaryFilename)
             throws FileNotFoundException, IOException, UnsupportedFormatException {
         final File file = new File(binaryFilename);
-        final DictDecoder dictDecoder = new Ver3DictDecoder(file);
+        final DictDecoder dictDecoder = FormatSpec.getDictDecoder(file);
         return dictDecoder.readDictionaryBinary(null, false /* deleteDictIfBroken */);
     }
 
diff --git a/tools/dicttool/tests/com/android/inputmethod/latin/dicttool/BinaryDictOffdeviceUtilsTests.java b/tools/dicttool/tests/com/android/inputmethod/latin/dicttool/BinaryDictOffdeviceUtilsTests.java
index 6e82e37..1eff497 100644
--- a/tools/dicttool/tests/com/android/inputmethod/latin/dicttool/BinaryDictOffdeviceUtilsTests.java
+++ b/tools/dicttool/tests/com/android/inputmethod/latin/dicttool/BinaryDictOffdeviceUtilsTests.java
@@ -18,12 +18,12 @@
 
 import com.android.inputmethod.latin.makedict.DictDecoder;
 import com.android.inputmethod.latin.makedict.DictEncoder;
+import com.android.inputmethod.latin.makedict.FormatSpec;
 import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions;
 import com.android.inputmethod.latin.makedict.FusionDictionary;
 import com.android.inputmethod.latin.makedict.FusionDictionary.DictionaryOptions;
 import com.android.inputmethod.latin.makedict.FusionDictionary.PtNodeArray;
 import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
-import com.android.inputmethod.latin.makedict.Ver3DictDecoder;
 import com.android.inputmethod.latin.makedict.Ver3DictEncoder;
 
 import junit.framework.TestCase;
@@ -69,7 +69,7 @@
             assertEquals("Wrong decode spec", BinaryDictOffdeviceUtils.COMPRESSION, step);
         }
         assertEquals("Wrong decode spec", 3, decodeSpec.mDecoderSpec.size());
-        final DictDecoder dictDecoder = new Ver3DictDecoder(decodeSpec.mFile);
+        final DictDecoder dictDecoder = FormatSpec.getDictDecoder(decodeSpec.mFile);
         final FusionDictionary resultDict = dictDecoder.readDictionaryBinary(
                 null /* dict : an optional dictionary to add words to, or null */,
                 false /* deleteDictIfBroken */);