Merge "Add getDictDecoder."
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/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/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 07f1e52..4605890 100644
--- a/native/jni/src/defines.h
+++ b/native/jni/src/defines.h
@@ -32,8 +32,6 @@
 #define MAX_WORD_LENGTH 48
 // Must be equal to BinaryDictionary.MAX_RESULTS in Java
 #define MAX_RESULTS 18
-// 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
 // Must be equal to ProximityInfo.MAX_PROXIMITY_CHARS_SIZE in Java
 #define MAX_PROXIMITY_CHARS_SIZE 16
 #define ADDITIONAL_PROXIMITY_CHAR_DELIMITER_CODE 2
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 f085848..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,10 +31,11 @@
  */
 class DicNodesCache {
  public:
-    AK_FORCE_INLINE DicNodesCache()
-            : mDicNodePriorityQueue0(MAX_DIC_NODE_PRIORITY_QUEUE_CAPACITY),
-              mDicNodePriorityQueue1(MAX_DIC_NODE_PRIORITY_QUEUE_CAPACITY),
-              mDicNodePriorityQueue2(MAX_DIC_NODE_PRIORITY_QUEUE_CAPACITY),
+    AK_FORCE_INLINE explicit DicNodesCache(const bool usesLargeCapacityCache)
+            : mUsesLargeCapacityCache(usesLargeCapacityCache),
+              mDicNodePriorityQueue0(getCacheCapacity()),
+              mDicNodePriorityQueue1(getCacheCapacity()),
+              mDicNodePriorityQueue2(getCacheCapacity()),
               mDicNodePriorityQueueForTerminal(MAX_RESULTS),
               mActiveDicNodes(&mDicNodePriorityQueue0),
               mNextActiveDicNodes(&mDicNodePriorityQueue1),
@@ -50,7 +51,8 @@
         // 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.
-        mNextActiveDicNodes->clearAndResize(nextActiveSize);
+        const int nextActiveSizeFittingToTheCapacity = min(nextActiveSize, getCacheCapacity());
+        mNextActiveDicNodes->clearAndResize(nextActiveSizeFittingToTheCapacity);
         mTerminalDicNodes->clearAndResize(terminalSize);
         // We want to use the max capacity for the cached dic nodes that will be used for the
         // continuous suggestion.
@@ -162,12 +164,21 @@
         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();
     }
 
+    static const int LARGE_PRIORITY_QUEUE_CAPACITY;
+    static const int SMALL_PRIORITY_QUEUE_CAPACITY;
+
+    const bool mUsesLargeCapacityCache;
     // Instances
     DicNodePriorityQueue mDicNodePriorityQueue0;
     DicNodePriorityQueue mDicNodePriorityQueue1;
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 e7b386b..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;
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 b25580b..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.
@@ -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);