Merge "Add a user preference for split layout within Appearance & Layouts"
diff --git a/java-overridable/src/com/android/inputmethod/latin/define/ProductionFlags.java b/java-overridable/src/com/android/inputmethod/latin/define/ProductionFlags.java
index c22c577..9d7258d 100644
--- a/java-overridable/src/com/android/inputmethod/latin/define/ProductionFlags.java
+++ b/java-overridable/src/com/android/inputmethod/latin/define/ProductionFlags.java
@@ -45,4 +45,9 @@
      * When {@code false}, the split keyboard is not yet ready to be enabled.
      */
     public static final boolean IS_SPLIT_KEYBOARD_SUPPORTED = true;
+
+    /**
+     * When {@code false}, account sign-in in keyboard is not yet ready to be enabled.
+     */
+    public static final boolean ENABLE_ACCOUNT_SIGN_IN = false;
 }
diff --git a/java-overridable/src/com/android/inputmethod/latin/utils/StatsUtils.java b/java-overridable/src/com/android/inputmethod/latin/utils/StatsUtils.java
index 38735ec..2274852 100644
--- a/java-overridable/src/com/android/inputmethod/latin/utils/StatsUtils.java
+++ b/java-overridable/src/com/android/inputmethod/latin/utils/StatsUtils.java
@@ -20,6 +20,8 @@
 import com.android.inputmethod.latin.SuggestedWords;
 import com.android.inputmethod.latin.settings.SettingsValues;
 
+import javax.annotation.Nullable;
+
 public final class StatsUtils {
 
     private StatsUtils() {
@@ -63,4 +65,8 @@
 
     public static void onStartInputView(int inputType, int displayOrientation, boolean restarting) {
     }
+
+    public static void onAutoCorrection(final String typedWord, final String autoCorrectionWord,
+            final boolean isBatchInput, @Nullable final String dictionaryType) {
+    }
 }
diff --git a/java/AndroidManifest.xml b/java/AndroidManifest.xml
index a1ffe5a..054c415 100644
--- a/java/AndroidManifest.xml
+++ b/java/AndroidManifest.xml
@@ -18,7 +18,7 @@
         coreApp="true"
         package="com.android.inputmethod.latin">
 
-    <uses-sdk android:minSdkVersion="14" android:targetSdkVersion="19" />
+    <uses-sdk android:minSdkVersion="14" android:targetSdkVersion="21" />
 
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
     <uses-permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" />
diff --git a/java/res/values/attrs.xml b/java/res/values/attrs.xml
index f1253b4..8ee859b 100644
--- a/java/res/values/attrs.xml
+++ b/java/res/values/attrs.xml
@@ -483,6 +483,8 @@
         <attr name="localeCode" format="string" />
         <attr name="languageCode" format="string" />
         <attr name="countryCode" format="string" />
+        <!-- Enable split keyboard layout. Disabled by default. -->
+        <attr name="isSplitLayout" format="boolean" />
     </declare-styleable>
 
     <declare-styleable name="Keyboard_KeyStyle">
@@ -514,6 +516,8 @@
         <attr name="elementKeyboard" format="reference"/>
         <!-- Enable proximity characters correction. Disabled by default. -->
         <attr name="enableProximityCharsCorrection" format="boolean" />
+        <!-- Indicates if the keyboard layout supports being split or not. false by default -->
+        <attr name="supportsSplitLayout" format="boolean" />
     </declare-styleable>
 
     <declare-styleable name="KeyboardLayoutSet_Feature">
diff --git a/java/res/values/keyboard-themes.xml b/java/res/values/keyboard-themes.xml
index 9d772c4..b0bae96 100644
--- a/java/res/values/keyboard-themes.xml
+++ b/java/res/values/keyboard-themes.xml
@@ -26,10 +26,10 @@
         <item>@string/keyboard_theme_holo_blue</item>
     </string-array>
     <!-- An element must be a keyboard theme id of {@link KeyboardTheme#THEME_ID_*}. -->
-    <string-array name="keyboard_theme_ids" translatable="false">
+    <integer-array name="keyboard_theme_ids" translatable="false">
         <item>3</item>
         <item>4</item>
         <item>2</item>
         <item>0</item>
-    </string-array>
+    </integer-array>
 </resources>
diff --git a/java/res/xml-sw600dp-land/key_space_3kw.xml b/java/res/xml-sw600dp-land/key_space_3kw.xml
new file mode 100644
index 0000000..47c4e48
--- /dev/null
+++ b/java/res/xml-sw600dp-land/key_space_3kw.xml
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2014, 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.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <!-- TODO: Consolidate the layout specification between protrait and landscape.
+         Ideally just the keyWidth should be different -->
+    <switch>
+        <!-- fa: Perisan
+             kn: Kannada
+             ne: Nepali
+             te: Telugu -->
+        <case
+            latin:languageCode="fa|kn|ne|te"
+            latin:languageSwitchKeyEnabled="true"
+        >
+            <Key
+                latin:keyStyle="languageSwitchKeyStyle" />
+            <Key
+                latin:keyStyle="spaceKeyStyle"
+                latin:keyWidth="7.0%p" />
+            <Key
+                latin:keyStyle="zwnjKeyStyle" />
+        </case>
+        <case
+            latin:languageCode="fa|kn|ne|te"
+            latin:languageSwitchKeyEnabled="false"
+        >
+            <Key
+                latin:keyStyle="spaceKeyStyle"
+                latin:keyWidth="14.0%p" />
+            <Key
+                latin:keyStyle="zwnjKeyStyle" />
+        </case>
+        <case
+            latin:languageSwitchKeyEnabled="true"
+        >
+            <Key
+                latin:keyStyle="languageSwitchKeyStyle" />
+            <Key
+                latin:keyStyle="spaceKeyStyle"
+                latin:keyWidth="14.0%p" />
+        </case>
+        <!-- languageSwitchKeyEnabled="false" -->
+        <default>
+            <Key
+                latin:keyStyle="spaceKeyStyle"
+                latin:keyWidth="21.0%p" />
+        </default>
+    </switch>
+</merge>
diff --git a/java/res/xml-sw600dp-land/row_qwerty4.xml b/java/res/xml-sw600dp-land/row_qwerty4.xml
new file mode 100644
index 0000000..0fdb5c6
--- /dev/null
+++ b/java/res/xml-sw600dp-land/row_qwerty4.xml
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2014, 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.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <switch>
+        <!-- Split the 4th row for split layouts -->
+        <case
+            latin:isSplitLayout="true"
+        >
+            <Row
+                latin:keyWidth="7.0%p"
+                latin:backgroundType="functional"
+            >
+                <Key
+                    latin:keyStyle="toSymbolKeyStyle" />
+                <include
+                    latin:keyboardLayout="@xml/key_comma" />
+                <!-- Space key. -->
+                <include
+                    latin:keyboardLayout="@xml/key_space_3kw"
+                    latin:backgroundType="normal" />
+                <Spacer
+                    latin:keyWidth="28.0%p" />
+                <Key
+                    latin:keyStyle="spaceKeyStyle"
+                    latin:keyWidth="21.0%p" />
+                <include
+                    latin:keyboardLayout="@xml/key_period" />
+                <include
+                    latin:keyboardLayout="@xml/key_emoji" />
+            </Row>
+        </case>
+        <default>
+            <Row
+                latin:keyWidth="9.0%p"
+                latin:backgroundType="functional"
+            >
+                <Key
+                    latin:keyStyle="toSymbolKeyStyle"
+                    latin:keyWidth="10.0%p" />
+                <include
+                    latin:keyboardLayout="@xml/key_comma" />
+                <!-- Space key. -->
+                <include
+                    latin:keyXPos="19.0%p"
+                    latin:keyboardLayout="@xml/key_space_7kw"
+                    latin:backgroundType="normal" />
+                <include
+                    latin:keyboardLayout="@xml/key_period" />
+                <include
+                    latin:keyboardLayout="@xml/key_emoji" />
+            </Row>
+        </default>
+    </switch>
+</merge>
diff --git a/java/res/xml-sw600dp-land/rows_qwerty.xml b/java/res/xml-sw600dp-land/rows_qwerty.xml
new file mode 100644
index 0000000..b580dcf
--- /dev/null
+++ b/java/res/xml-sw600dp-land/rows_qwerty.xml
@@ -0,0 +1,133 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <include
+        latin:keyboardLayout="@xml/key_styles_common" />
+    <!-- First row -->
+    <Row>
+        <switch>
+            <!-- Split keyboard layout for the first row -->
+            <case
+                latin:isSplitLayout="true"
+            >
+                <include
+                    latin:keyboardLayout="@xml/rowkeys_qwerty1_left5"
+                    latin:keyWidth="7.0%p" />
+                <Spacer
+                    latin:keyWidth="20.0%p" />
+                <include
+                    latin:keyboardLayout="@xml/rowkeys_qwerty1_right5"
+                    latin:keyWidth="7.0%p" />
+                <Key
+                    latin:keyStyle="deleteKeyStyle"
+                    latin:keyWidth="10.0%p" />
+            </case>
+            <!-- Regular layout for the first row -->
+            <default>
+                <include
+                    latin:keyboardLayout="@xml/rowkeys_qwerty1"
+                    latin:keyWidth="9.0%p" />
+                <Key
+                    latin:keyStyle="deleteKeyStyle"
+                    latin:keyWidth="fillRight" />
+            </default>
+        </switch>
+    </Row>
+    <!-- Second row -->
+    <Row>
+        <switch>
+            <!-- Split keyboard layout for the second row -->
+            <case
+                latin:isSplitLayout="true"
+            >
+                <include
+                    latin:keyboardLayout="@xml/rowkeys_qwerty2_left5"
+                    latin:keyXPos="4.0%p"
+                    latin:keyWidth="7.0%p" />
+                <Spacer
+                    latin:keyWidth="23.0%p" />
+                <include
+                    latin:keyboardLayout="@xml/rowkeys_qwerty2_right4"
+                    latin:keyWidth="7.0%p" />
+                <Key
+                    latin:keyStyle="enterKeyStyle"
+                    latin:keyWidth="10.0%p" />
+            </case>
+            <!-- Regular layout for the second row -->
+            <default>
+                <include
+                    latin:keyboardLayout="@xml/rowkeys_qwerty2"
+                    latin:keyXPos="4.5%p"
+                    latin:keyWidth="9.0%p" />
+                <Key
+                    latin:keyStyle="enterKeyStyle"
+                    latin:keyWidth="fillRight" />
+            </default>
+        </switch>
+    </Row>
+    <!-- Third row -->
+    <Row>
+        <switch>
+            <!-- Split keyboard layout for the third row -->
+            <case
+                latin:isSplitLayout="true"
+            >
+                <Key
+                    latin:keyStyle="shiftKeyStyle"
+                    latin:keyWidth="10.0%p" />
+                <include
+                    latin:keyboardLayout="@xml/rowkeys_qwerty3_left4"
+                    latin:keyWidth="7.0%p" />
+                <Spacer
+                    latin:keyWidth="17.0%p" />
+                <include
+                    latin:keyboardLayout="@xml/rowkeys_qwerty3_right3"
+                    latin:keyWidth="7.0%p" />
+                <include
+                    latin:keyboardLayout="@xml/keys_exclamation_question"
+                    latin:keyWidth="7.0%p" />
+                <Key
+                    latin:keyStyle="shiftKeyStyle"
+                    latin:keyWidth="10.0%p" />
+            </case>
+            <!-- Regular layout for the third row -->
+            <default>
+                <Key
+                    latin:keyStyle="shiftKeyStyle"
+                    latin:keyWidth="10.0%p" />
+                <include
+                    latin:keyboardLayout="@xml/rowkeys_qwerty3"
+                    latin:keyWidth="9.0%p" />
+                <include
+                    latin:keyboardLayout="@xml/keys_exclamation_question"
+                    latin:keyWidth="9.0%p" />
+                <Key
+                    latin:keyStyle="shiftKeyStyle"
+                    latin:keyWidth="fillRight" />
+            </default>
+        </switch>
+    </Row>
+    <!-- Fourth row -->
+    <include
+        latin:keyboardLayout="@xml/row_qwerty4" />
+</merge>
diff --git a/java/res/xml-sw600dp/key_space_3kw.xml b/java/res/xml-sw600dp/key_space_3kw.xml
new file mode 100644
index 0000000..9932d34
--- /dev/null
+++ b/java/res/xml-sw600dp/key_space_3kw.xml
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2014, 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.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <switch>
+        <!-- fa: Perisan
+             kn: Kannada
+             ne: Nepali
+             te: Telugu -->
+        <case
+            latin:languageCode="fa|kn|ne|te"
+            latin:languageSwitchKeyEnabled="true"
+        >
+            <Key
+                latin:keyStyle="languageSwitchKeyStyle" />
+            <Key
+                latin:keyStyle="spaceKeyStyle"
+                latin:keyWidth="8.0%p" />
+            <Key
+                latin:keyStyle="zwnjKeyStyle" />
+        </case>
+        <case
+            latin:languageCode="fa|kn|ne|te"
+            latin:languageSwitchKeyEnabled="false"
+        >
+            <Key
+                latin:keyStyle="spaceKeyStyle"
+                latin:keyWidth="16.0%p" />
+            <Key
+                latin:keyStyle="zwnjKeyStyle" />
+        </case>
+        <case
+            latin:languageSwitchKeyEnabled="true"
+        >
+            <Key
+                latin:keyStyle="languageSwitchKeyStyle" />
+            <Key
+                latin:keyStyle="spaceKeyStyle"
+                latin:keyWidth="16.0%p" />
+        </case>
+        <!-- languageSwitchKeyEnabled="false" -->
+        <default>
+            <Key
+                latin:keyStyle="spaceKeyStyle"
+                latin:keyWidth="24.0%p" />
+        </default>
+    </switch>
+</merge>
diff --git a/java/res/xml-sw600dp/row_qwerty4.xml b/java/res/xml-sw600dp/row_qwerty4.xml
index ed7150d..bcfd2cb 100644
--- a/java/res/xml-sw600dp/row_qwerty4.xml
+++ b/java/res/xml-sw600dp/row_qwerty4.xml
@@ -21,23 +21,54 @@
 <merge
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
 >
-    <Row
-        latin:keyWidth="9.0%p"
-        latin:backgroundType="functional"
-    >
-        <Key
-            latin:keyStyle="toSymbolKeyStyle"
-            latin:keyWidth="10.0%p" />
-        <include
-            latin:keyboardLayout="@xml/key_comma" />
-        <!-- Space key. -->
-        <include
-            latin:keyXPos="19.0%p"
-            latin:keyboardLayout="@xml/key_space_7kw"
-            latin:backgroundType="normal" />
-        <include
-            latin:keyboardLayout="@xml/key_period" />
-        <include
-            latin:keyboardLayout="@xml/key_emoji" />
-    </Row>
+    <switch>
+        <!-- Split the 4th row for split layouts -->
+        <case
+            latin:isSplitLayout="true"
+        >
+            <Row
+                latin:keyWidth="8.0%p"
+                latin:backgroundType="functional"
+            >
+                <Key
+                    latin:keyStyle="toSymbolKeyStyle" />
+                <include
+                    latin:keyboardLayout="@xml/key_comma" />
+                <!-- Space key. -->
+                <include
+                    latin:keyboardLayout="@xml/key_space_3kw"
+                    latin:backgroundType="normal" />
+                <Spacer
+                    latin:keyWidth="20.0%p" />
+                <Key
+                    latin:keyStyle="spaceKeyStyle"
+                    latin:keyWidth="24.0%p" />
+                <include
+                    latin:keyboardLayout="@xml/key_period" />
+                <include
+                    latin:keyboardLayout="@xml/key_emoji" />
+            </Row>
+        </case>
+        <default>
+            <Row
+                latin:keyWidth="9.0%p"
+                latin:backgroundType="functional"
+            >
+                <Key
+                    latin:keyStyle="toSymbolKeyStyle"
+                    latin:keyWidth="10.0%p" />
+                <include
+                    latin:keyboardLayout="@xml/key_comma" />
+                <!-- Space key. -->
+                <include
+                    latin:keyXPos="19.0%p"
+                    latin:keyboardLayout="@xml/key_space_7kw"
+                    latin:backgroundType="normal" />
+                <include
+                    latin:keyboardLayout="@xml/key_period" />
+                <include
+                    latin:keyboardLayout="@xml/key_emoji" />
+            </Row>
+        </default>
+    </switch>
 </merge>
diff --git a/java/res/xml-sw600dp/rows_qwerty.xml b/java/res/xml-sw600dp/rows_qwerty.xml
index 58ba1d7..51df4b0 100644
--- a/java/res/xml-sw600dp/rows_qwerty.xml
+++ b/java/res/xml-sw600dp/rows_qwerty.xml
@@ -23,39 +23,114 @@
 >
     <include
         latin:keyboardLayout="@xml/key_styles_common" />
-    <Row
-        latin:keyWidth="9.0%p"
-    >
-        <include
-            latin:keyboardLayout="@xml/rowkeys_qwerty1" />
-        <Key
-            latin:keyStyle="deleteKeyStyle"
-            latin:keyWidth="fillRight" />
+    <!-- TODO: Consolidate the layout specification between protrait and landscape.
+         Ideally just the keyWidth should be different and the spacer should adjust to fill
+         the available space. -->
+    <!-- First row -->
+    <Row>
+        <switch>
+            <!-- Split keyboard layout for the first row -->
+            <case
+                latin:isSplitLayout="true"
+            >
+                <include
+                    latin:keyboardLayout="@xml/rowkeys_qwerty1_left5"
+                    latin:keyWidth="8.0%p" />
+                <Spacer
+                    latin:keyWidth="10.0%p" />
+                <include
+                    latin:keyboardLayout="@xml/rowkeys_qwerty1_right5"
+                    latin:keyWidth="8.0%p" />
+                <Key
+                    latin:keyStyle="deleteKeyStyle"
+                    latin:keyWidth="10.0%p" />
+            </case>
+            <!-- Regular layout for the first row -->
+            <default>
+                <include
+                    latin:keyboardLayout="@xml/rowkeys_qwerty1"
+                    latin:keyWidth="9.0%p" />
+                <Key
+                    latin:keyStyle="deleteKeyStyle"
+                    latin:keyWidth="fillRight" />
+            </default>
+        </switch>
     </Row>
-    <Row
-        latin:keyWidth="9.0%p"
-    >
-        <include
-            latin:keyboardLayout="@xml/rowkeys_qwerty2"
-            latin:keyXPos="4.5%p" />
-        <Key
-            latin:keyStyle="enterKeyStyle"
-            latin:keyWidth="fillRight" />
+    <!-- Second row -->
+    <Row>
+        <switch>
+            <!-- Split keyboard layout for the second row -->
+            <case
+                latin:isSplitLayout="true"
+            >
+                <include
+                    latin:keyboardLayout="@xml/rowkeys_qwerty2_left5"
+                    latin:keyXPos="4.0%p"
+                    latin:keyWidth="8.0%p" />
+                <Spacer
+                    latin:keyWidth="14.0%p" />
+                <include
+                    latin:keyboardLayout="@xml/rowkeys_qwerty2_right4"
+                    latin:keyWidth="8.0%p" />
+                <Key
+                    latin:keyStyle="enterKeyStyle"
+                    latin:keyWidth="10.0%p" />
+            </case>
+            <!-- Regular layout for the second row -->
+            <default>
+                <include
+                    latin:keyboardLayout="@xml/rowkeys_qwerty2"
+                    latin:keyXPos="4.5%p"
+                    latin:keyWidth="9.0%p" />
+                <Key
+                    latin:keyStyle="enterKeyStyle"
+                    latin:keyWidth="fillRight" />
+            </default>
+        </switch>
     </Row>
-    <Row
-        latin:keyWidth="9.0%p"
-    >
-        <Key
-            latin:keyStyle="shiftKeyStyle"
-            latin:keyWidth="10.0%p" />
-        <include
-            latin:keyboardLayout="@xml/rowkeys_qwerty3" />
-        <include
-            latin:keyboardLayout="@xml/keys_exclamation_question" />
-        <Key
-            latin:keyStyle="shiftKeyStyle"
-            latin:keyWidth="fillRight" />
+    <!-- Third row -->
+    <Row>
+        <switch>
+            <!-- Split keyboard layout for the third row -->
+            <case
+                latin:isSplitLayout="true"
+            >
+                <Key
+                    latin:keyStyle="shiftKeyStyle"
+                    latin:keyWidth="10.0%p" />
+                <include
+                    latin:keyboardLayout="@xml/rowkeys_qwerty3_left4"
+                    latin:keyWidth="8.0%p" />
+                <Spacer
+                    latin:keyWidth="8.0%p" />
+                <include
+                    latin:keyboardLayout="@xml/rowkeys_qwerty3_right3"
+                    latin:keyWidth="8.0%p" />
+                <include
+                    latin:keyboardLayout="@xml/keys_exclamation_question"
+                    latin:keyWidth="8.0%p" />
+                <Key
+                    latin:keyStyle="shiftKeyStyle"
+                    latin:keyWidth="10.0%p" />
+            </case>
+            <!-- Regular layout for the third row -->
+            <default>
+                <Key
+                    latin:keyStyle="shiftKeyStyle"
+                    latin:keyWidth="10.0%p" />
+                <include
+                    latin:keyboardLayout="@xml/rowkeys_qwerty3"
+                    latin:keyWidth="9.0%p" />
+                <include
+                    latin:keyboardLayout="@xml/keys_exclamation_question"
+                    latin:keyWidth="9.0%p" />
+                <Key
+                    latin:keyStyle="shiftKeyStyle"
+                    latin:keyWidth="fillRight" />
+            </default>
+        </switch>
     </Row>
+    <!-- Fourth row -->
     <include
         latin:keyboardLayout="@xml/row_qwerty4" />
 </merge>
diff --git a/java/res/xml/keyboard_layout_set_qwerty.xml b/java/res/xml/keyboard_layout_set_qwerty.xml
index 8215170..1aa6f01 100644
--- a/java/res/xml/keyboard_layout_set_qwerty.xml
+++ b/java/res/xml/keyboard_layout_set_qwerty.xml
@@ -23,7 +23,8 @@
     <Element
         latin:elementName="alphabet"
         latin:elementKeyboard="@xml/kbd_qwerty"
-        latin:enableProximityCharsCorrection="true" />
+        latin:enableProximityCharsCorrection="true"
+        latin:supportsSplitLayout="false" />
     <Element
         latin:elementName="symbols"
         latin:elementKeyboard="@xml/kbd_symbols" />
diff --git a/java/res/xml/rowkeys_qwerty1.xml b/java/res/xml/rowkeys_qwerty1.xml
index 8f3b160..b8e4a4c 100644
--- a/java/res/xml/rowkeys_qwerty1.xml
+++ b/java/res/xml/rowkeys_qwerty1.xml
@@ -21,53 +21,10 @@
 <merge
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
 >
-    <Key
-        latin:keySpec="!text/keyspec_q"
-        latin:keyHintLabel="1"
-        latin:additionalMoreKeys="1"
-        latin:moreKeys="!text/morekeys_q" />
-    <Key
-        latin:keySpec="!text/keyspec_w"
-        latin:keyHintLabel="2"
-        latin:additionalMoreKeys="2"
-        latin:moreKeys="!text/morekeys_w" />
-    <Key
-        latin:keySpec="e"
-        latin:keyHintLabel="3"
-        latin:additionalMoreKeys="3"
-        latin:moreKeys="!text/morekeys_e" />
-    <Key
-        latin:keySpec="r"
-        latin:keyHintLabel="4"
-        latin:additionalMoreKeys="4"
-        latin:moreKeys="!text/morekeys_r" />
-    <Key
-        latin:keySpec="t"
-        latin:keyHintLabel="5"
-        latin:additionalMoreKeys="5"
-        latin:moreKeys="!text/morekeys_t" />
-    <Key
-        latin:keySpec="!text/keyspec_y"
-        latin:keyHintLabel="6"
-        latin:additionalMoreKeys="6"
-        latin:moreKeys="!text/morekeys_y" />
-    <Key
-        latin:keySpec="u"
-        latin:keyHintLabel="7"
-        latin:additionalMoreKeys="7"
-        latin:moreKeys="!text/morekeys_u" />
-    <Key
-        latin:keySpec="i"
-        latin:keyHintLabel="8"
-        latin:additionalMoreKeys="8"
-        latin:moreKeys="!text/morekeys_i" />
-    <Key
-        latin:keySpec="o"
-        latin:keyHintLabel="9"
-        latin:additionalMoreKeys="9"
-        latin:moreKeys="!text/morekeys_o" />
-    <Key
-        latin:keySpec="p"
-        latin:keyHintLabel="0"
-        latin:additionalMoreKeys="0" />
+    <!-- q,w,e,r,t -->
+    <include
+        latin:keyboardLayout="@xml/rowkeys_qwerty1_left5" />
+    <!-- y,u,i,o,p -->
+    <include
+        latin:keyboardLayout="@xml/rowkeys_qwerty1_right5" />
 </merge>
diff --git a/java/res/xml/rowkeys_qwerty1_left5.xml b/java/res/xml/rowkeys_qwerty1_left5.xml
new file mode 100644
index 0000000..ff9f1b2
--- /dev/null
+++ b/java/res/xml/rowkeys_qwerty1_left5.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2014, 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.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <Key
+        latin:keySpec="!text/keyspec_q"
+        latin:keyHintLabel="1"
+        latin:additionalMoreKeys="1"
+        latin:moreKeys="!text/morekeys_q" />
+    <Key
+        latin:keySpec="!text/keyspec_w"
+        latin:keyHintLabel="2"
+        latin:additionalMoreKeys="2"
+        latin:moreKeys="!text/morekeys_w" />
+    <Key
+        latin:keySpec="e"
+        latin:keyHintLabel="3"
+        latin:additionalMoreKeys="3"
+        latin:moreKeys="!text/morekeys_e" />
+    <Key
+        latin:keySpec="r"
+        latin:keyHintLabel="4"
+        latin:additionalMoreKeys="4"
+        latin:moreKeys="!text/morekeys_r" />
+    <Key
+        latin:keySpec="t"
+        latin:keyHintLabel="5"
+        latin:additionalMoreKeys="5"
+        latin:moreKeys="!text/morekeys_t" />
+</merge>
diff --git a/java/res/xml/rowkeys_qwerty1_right5.xml b/java/res/xml/rowkeys_qwerty1_right5.xml
new file mode 100644
index 0000000..2b3cae2
--- /dev/null
+++ b/java/res/xml/rowkeys_qwerty1_right5.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2014, 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.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <Key
+        latin:keySpec="!text/keyspec_y"
+        latin:keyHintLabel="6"
+        latin:additionalMoreKeys="6"
+        latin:moreKeys="!text/morekeys_y" />
+    <Key
+        latin:keySpec="u"
+        latin:keyHintLabel="7"
+        latin:additionalMoreKeys="7"
+        latin:moreKeys="!text/morekeys_u" />
+    <Key
+        latin:keySpec="i"
+        latin:keyHintLabel="8"
+        latin:additionalMoreKeys="8"
+        latin:moreKeys="!text/morekeys_i" />
+    <Key
+        latin:keySpec="o"
+        latin:keyHintLabel="9"
+        latin:additionalMoreKeys="9"
+        latin:moreKeys="!text/morekeys_o" />
+    <Key
+        latin:keySpec="p"
+        latin:keyHintLabel="0"
+        latin:additionalMoreKeys="0" />
+</merge>
diff --git a/java/res/xml/rowkeys_qwerty2.xml b/java/res/xml/rowkeys_qwerty2.xml
index 4077bea..550db3b 100644
--- a/java/res/xml/rowkeys_qwerty2.xml
+++ b/java/res/xml/rowkeys_qwerty2.xml
@@ -21,30 +21,10 @@
 <merge
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
 >
-    <Key
-        latin:keySpec="a"
-        latin:moreKeys="!text/morekeys_a" />
-    <Key
-        latin:keySpec="s"
-        latin:moreKeys="!text/morekeys_s" />
-    <Key
-        latin:keySpec="d"
-        latin:moreKeys="!text/morekeys_d" />
-    <Key
-        latin:keySpec="f" />
-    <Key
-        latin:keySpec="g"
-        latin:moreKeys="!text/morekeys_g" />
-    <Key
-        latin:keySpec="h"
-        latin:moreKeys="!text/morekeys_h" />
-    <Key
-        latin:keySpec="j"
-        latin:moreKeys="!text/morekeys_j" />
-    <Key
-        latin:keySpec="k"
-        latin:moreKeys="!text/morekeys_k" />
-    <Key
-        latin:keySpec="l"
-        latin:moreKeys="!text/morekeys_l" />
+    <!-- a,s,d,f,g -->
+    <include
+        latin:keyboardLayout="@xml/rowkeys_qwerty2_left5" />
+    <!-- h,j,k,l -->
+    <include
+        latin:keyboardLayout="@xml/rowkeys_qwerty2_right4" />
 </merge>
diff --git a/java/res/xml/rowkeys_qwerty2_left5.xml b/java/res/xml/rowkeys_qwerty2_left5.xml
new file mode 100644
index 0000000..1803bf2
--- /dev/null
+++ b/java/res/xml/rowkeys_qwerty2_left5.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2014, 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.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <Key
+        latin:keySpec="a"
+        latin:moreKeys="!text/morekeys_a" />
+    <Key
+        latin:keySpec="s"
+        latin:moreKeys="!text/morekeys_s" />
+    <Key
+        latin:keySpec="d"
+        latin:moreKeys="!text/morekeys_d" />
+    <Key
+        latin:keySpec="f" />
+    <Key
+        latin:keySpec="g"
+        latin:moreKeys="!text/morekeys_g" />
+</merge>
diff --git a/java/res/xml/rowkeys_qwerty2_right4.xml b/java/res/xml/rowkeys_qwerty2_right4.xml
new file mode 100644
index 0000000..99936b7
--- /dev/null
+++ b/java/res/xml/rowkeys_qwerty2_right4.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2014, 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.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <Key
+        latin:keySpec="h"
+        latin:moreKeys="!text/morekeys_h" />
+    <Key
+        latin:keySpec="j"
+        latin:moreKeys="!text/morekeys_j" />
+    <Key
+        latin:keySpec="k"
+        latin:moreKeys="!text/morekeys_k" />
+    <Key
+        latin:keySpec="l"
+        latin:moreKeys="!text/morekeys_l" />
+</merge>
diff --git a/java/res/xml/rowkeys_qwerty3.xml b/java/res/xml/rowkeys_qwerty3.xml
index 8562003..7a523f1 100644
--- a/java/res/xml/rowkeys_qwerty3.xml
+++ b/java/res/xml/rowkeys_qwerty3.xml
@@ -21,23 +21,10 @@
 <merge
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
 >
-    <Key
-        latin:keySpec="z"
-        latin:moreKeys="!text/morekeys_z" />
-    <Key
-        latin:keySpec="!text/keyspec_x"
-        latin:moreKeys="!text/morekeys_x" />
-    <Key
-        latin:keySpec="c"
-        latin:moreKeys="!text/morekeys_c" />
-    <Key
-        latin:keySpec="v"
-        latin:moreKeys="!text/morekeys_v" />
-    <Key
-        latin:keySpec="b" />
-    <Key
-        latin:keySpec="n"
-        latin:moreKeys="!text/morekeys_n" />
-    <Key
-        latin:keySpec="m" />
+    <!-- z,x,c,v -->
+    <include
+        latin:keyboardLayout="@xml/rowkeys_qwerty3_left4" />
+    <!-- b,n,m -->
+    <include
+        latin:keyboardLayout="@xml/rowkeys_qwerty3_right3" />
 </merge>
diff --git a/java/res/xml/rowkeys_qwerty3_left4.xml b/java/res/xml/rowkeys_qwerty3_left4.xml
new file mode 100644
index 0000000..6043c3b
--- /dev/null
+++ b/java/res/xml/rowkeys_qwerty3_left4.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2014, 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.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <Key
+        latin:keySpec="z"
+        latin:moreKeys="!text/morekeys_z" />
+    <Key
+        latin:keySpec="!text/keyspec_x"
+        latin:moreKeys="!text/morekeys_x" />
+    <Key
+        latin:keySpec="c"
+        latin:moreKeys="!text/morekeys_c" />
+    <Key
+        latin:keySpec="v"
+        latin:moreKeys="!text/morekeys_v" />
+</merge>
diff --git a/java/res/xml/rowkeys_qwerty3_right3.xml b/java/res/xml/rowkeys_qwerty3_right3.xml
new file mode 100644
index 0000000..f699103
--- /dev/null
+++ b/java/res/xml/rowkeys_qwerty3_right3.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2014, 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.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <Key
+        latin:keySpec="b" />
+    <Key
+        latin:keySpec="n"
+        latin:moreKeys="!text/morekeys_n" />
+    <Key
+        latin:keySpec="m" />
+</merge>
diff --git a/java/src/com/android/inputmethod/keyboard/Key.java b/java/src/com/android/inputmethod/keyboard/Key.java
index bd1c147..99a34be 100644
--- a/java/src/com/android/inputmethod/keyboard/Key.java
+++ b/java/src/com/android/inputmethod/keyboard/Key.java
@@ -702,6 +702,10 @@
         return ((mLabelFlags | defaultFlags) & LABEL_FLAGS_KEEP_BACKGROUND_ASPECT_RATIO) != 0;
     }
 
+    public final boolean hasCustomActionLabel() {
+        return (mLabelFlags & LABEL_FLAGS_FROM_CUSTOM_ACTION_LABEL) != 0;
+    }
+
     private final boolean isShiftedLetterActivated() {
         return (mLabelFlags & LABEL_FLAGS_SHIFTED_LETTER_ACTIVATED) != 0
                 && !TextUtils.isEmpty(mHintLabel);
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardId.java b/java/src/com/android/inputmethod/keyboard/KeyboardId.java
index 538e515..43c6144 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardId.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardId.java
@@ -73,10 +73,12 @@
     public final boolean mLanguageSwitchKeyEnabled;
     public final String mCustomActionLabel;
     public final boolean mHasShortcutKey;
+    public final boolean mIsSplitLayout;
 
     private final int mHashCode;
 
-    public KeyboardId(final int elementId, final KeyboardLayoutSet.Params params) {
+    public KeyboardId(final int elementId, final KeyboardLayoutSet.Params params,
+        boolean isSplitLayout) {
         mSubtype = params.mSubtype;
         mLocale = SubtypeLocaleUtils.getSubtypeLocale(mSubtype);
         mWidth = params.mKeyboardWidth;
@@ -89,6 +91,7 @@
         mCustomActionLabel = (mEditorInfo.actionLabel != null)
                 ? mEditorInfo.actionLabel.toString() : null;
         mHasShortcutKey = params.mVoiceInputKeyEnabled;
+        mIsSplitLayout = isSplitLayout;
 
         mHashCode = computeHashCode(this);
     }
@@ -108,7 +111,8 @@
                 id.mCustomActionLabel,
                 id.navigateNext(),
                 id.navigatePrevious(),
-                id.mSubtype
+                id.mSubtype,
+                id.mIsSplitLayout
         });
     }
 
@@ -128,7 +132,8 @@
                 && TextUtils.equals(other.mCustomActionLabel, mCustomActionLabel)
                 && other.navigateNext() == navigateNext()
                 && other.navigatePrevious() == navigatePrevious()
-                && other.mSubtype.equals(mSubtype);
+                && other.mSubtype.equals(mSubtype)
+                && other.mIsSplitLayout == mIsSplitLayout;
     }
 
     private static boolean isAlphabetKeyboard(final int elementId) {
@@ -175,7 +180,7 @@
 
     @Override
     public String toString() {
-        return String.format(Locale.ROOT, "[%s %s:%s %dx%d %s %s%s%s%s%s%s%s%s]",
+        return String.format(Locale.ROOT, "[%s %s:%s %dx%d %s %s%s%s%s%s%s%s%s%s]",
                 elementIdToName(mElementId),
                 mLocale, mSubtype.getExtraValueOf(KEYBOARD_LAYOUT_SET),
                 mWidth, mHeight,
@@ -187,7 +192,8 @@
                 (passwordInput() ? " passwordInput" : ""),
                 (mHasShortcutKey ? " hasShortcutKey" : ""),
                 (mLanguageSwitchKeyEnabled ? " languageSwitchKeyEnabled" : ""),
-                (isMultiLine() ? " isMultiLine" : "")
+                (isMultiLine() ? " isMultiLine" : ""),
+                (mIsSplitLayout ? " isSplitLayout" : "")
         );
     }
 
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
index 3f43673..1dbecdc 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
@@ -96,6 +96,7 @@
     private static final class ElementParams {
         int mKeyboardXmlId;
         boolean mProximityCharsCorrectionEnabled;
+        boolean mSupportsSplitLayout;
         public ElementParams() {}
     }
 
@@ -168,7 +169,10 @@
         // attribute in a keyboard_layout_set XML file.  Also each keyboard layout XML resource is
         // specified as an elementKeyboard attribute in the file.
         // The KeyboardId is an internal key for a Keyboard object.
-        final KeyboardId id = new KeyboardId(keyboardLayoutSetElementId, mParams);
+
+        // TODO: AND mSupportsSplitLayout with the user preference that forces a split.
+        final KeyboardId id = new KeyboardId(keyboardLayoutSetElementId, mParams,
+                elementParams.mSupportsSplitLayout);
         try {
             return getKeyboard(elementParams, id);
         } catch (final RuntimeException e) {
@@ -376,6 +380,8 @@
                 elementParams.mProximityCharsCorrectionEnabled = a.getBoolean(
                         R.styleable.KeyboardLayoutSet_Element_enableProximityCharsCorrection,
                         false);
+                elementParams.mSupportsSplitLayout = a.getBoolean(
+                        R.styleable.KeyboardLayoutSet_Element_supportsSplitLayout, false);
                 mParams.mKeyboardLayoutSetElementIdToParamsMap.put(elementName, elementParams);
             } finally {
                 a.recycle();
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
index d36c199..6cd7955 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
@@ -17,9 +17,7 @@
 package com.android.inputmethod.keyboard;
 
 import android.content.Context;
-import android.content.SharedPreferences;
 import android.content.res.Resources;
-import android.preference.PreferenceManager;
 import android.util.Log;
 import android.view.ContextThemeWrapper;
 import android.view.LayoutInflater;
@@ -47,7 +45,6 @@
     private static final String TAG = KeyboardSwitcher.class.getSimpleName();
 
     private SubtypeSwitcher mSubtypeSwitcher;
-    private SharedPreferences mPrefs;
 
     private InputView mCurrentInputView;
     private View mMainKeyboardFrame;
@@ -76,13 +73,11 @@
     }
 
     public static void init(final LatinIME latinIme) {
-        final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(latinIme);
-        sInstance.initInternal(latinIme, prefs);
+        sInstance.initInternal(latinIme);
     }
 
-    private void initInternal(final LatinIME latinIme, final SharedPreferences prefs) {
+    private void initInternal(final LatinIME latinIme) {
         mLatinIME = latinIme;
-        mPrefs = prefs;
         mSubtypeSwitcher = SubtypeSwitcher.getInstance();
         mState = new KeyboardState(this);
         mIsHardwareAcceleratedDrawingEnabled =
@@ -91,7 +86,7 @@
 
     public void updateKeyboardTheme() {
         final boolean themeUpdated = updateKeyboardThemeAndContextThemeWrapper(
-                mLatinIME, KeyboardTheme.getKeyboardTheme(mPrefs));
+                mLatinIME, KeyboardTheme.getKeyboardTheme(mLatinIME /* context */));
         if (themeUpdated && mKeyboardView != null) {
             mLatinIME.setInputView(onCreateInputView(mIsHardwareAcceleratedDrawingEnabled));
         }
@@ -348,7 +343,7 @@
         }
 
         updateKeyboardThemeAndContextThemeWrapper(
-                mLatinIME, KeyboardTheme.getKeyboardTheme(mPrefs));
+                mLatinIME, KeyboardTheme.getKeyboardTheme(mLatinIME /* context */));
         mCurrentInputView = (InputView)LayoutInflater.from(mThemeContext).inflate(
                 R.layout.input_view, null);
         mMainKeyboardFrame = mCurrentInputView.findViewById(R.id.main_keyboard_frame);
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardTheme.java b/java/src/com/android/inputmethod/keyboard/KeyboardTheme.java
index 7161d3f..6d8c8b7 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardTheme.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardTheme.java
@@ -16,14 +16,17 @@
 
 package com.android.inputmethod.keyboard;
 
+import android.content.Context;
 import android.content.SharedPreferences;
 import android.os.Build.VERSION_CODES;
+import android.preference.PreferenceManager;
 import android.util.Log;
 
 import com.android.inputmethod.annotations.UsedForTesting;
 import com.android.inputmethod.compat.BuildCompatUtils;
 import com.android.inputmethod.latin.R;
 
+import java.util.ArrayList;
 import java.util.Arrays;
 
 public final class KeyboardTheme implements Comparable<KeyboardTheme> {
@@ -40,7 +43,10 @@
     public static final int THEME_ID_LXX_DARK = 4;
     public static final int DEFAULT_THEME_ID = THEME_ID_KLP;
 
-    private static final KeyboardTheme[] KEYBOARD_THEMES = {
+    private static KeyboardTheme[] AVAILABLE_KEYBOARD_THEMES;
+
+    @UsedForTesting
+    static final KeyboardTheme[] KEYBOARD_THEMES = {
         new KeyboardTheme(THEME_ID_ICS, "ICS", R.style.KeyboardTheme_ICS,
                 // This has never been selected because we support ICS or later.
                 VERSION_CODES.BASE),
@@ -93,9 +99,10 @@
     }
 
     @UsedForTesting
-    static KeyboardTheme searchKeyboardThemeById(final int themeId) {
+    static KeyboardTheme searchKeyboardThemeById(final int themeId,
+            final KeyboardTheme[] availableThemeIds) {
         // TODO: This search algorithm isn't optimal if there are many themes.
-        for (final KeyboardTheme theme : KEYBOARD_THEMES) {
+        for (final KeyboardTheme theme : availableThemeIds) {
             if (theme.mThemeId == themeId) {
                 return theme;
             }
@@ -105,13 +112,14 @@
 
     @UsedForTesting
     static KeyboardTheme getDefaultKeyboardTheme(final SharedPreferences prefs,
-            final int sdkVersion) {
+            final int sdkVersion, final KeyboardTheme[] availableThemeArray) {
         final String klpThemeIdString = prefs.getString(KLP_KEYBOARD_THEME_KEY, null);
         if (klpThemeIdString != null) {
             if (sdkVersion <= VERSION_CODES.KITKAT) {
                 try {
                     final int themeId = Integer.parseInt(klpThemeIdString);
-                    final KeyboardTheme theme = searchKeyboardThemeById(themeId);
+                    final KeyboardTheme theme = searchKeyboardThemeById(themeId,
+                            availableThemeArray);
                     if (theme != null) {
                         return theme;
                     }
@@ -125,22 +133,21 @@
             prefs.edit().remove(KLP_KEYBOARD_THEME_KEY).apply();
         }
         // TODO: This search algorithm isn't optimal if there are many themes.
-        for (final KeyboardTheme theme : KEYBOARD_THEMES) {
+        for (final KeyboardTheme theme : availableThemeArray) {
             if (sdkVersion >= theme.mMinApiVersion) {
                 return theme;
             }
         }
-        return searchKeyboardThemeById(DEFAULT_THEME_ID);
+        return searchKeyboardThemeById(DEFAULT_THEME_ID, availableThemeArray);
     }
 
     public static String getKeyboardThemeName(final int themeId) {
-        final KeyboardTheme theme = searchKeyboardThemeById(themeId);
+        final KeyboardTheme theme = searchKeyboardThemeById(themeId, KEYBOARD_THEMES);
         return theme.mThemeName;
     }
 
-    public static void saveKeyboardThemeId(final String themeIdString,
-            final SharedPreferences prefs) {
-        saveKeyboardThemeId(themeIdString, prefs, BuildCompatUtils.EFFECTIVE_SDK_INT);
+    public static void saveKeyboardThemeId(final int themeId, final SharedPreferences prefs) {
+        saveKeyboardThemeId(themeId, prefs, BuildCompatUtils.EFFECTIVE_SDK_INT);
     }
 
     @UsedForTesting
@@ -152,25 +159,45 @@
     }
 
     @UsedForTesting
-    static void saveKeyboardThemeId(final String themeIdString,
-            final SharedPreferences prefs, final int sdkVersion) {
+    static void saveKeyboardThemeId(final int themeId, final SharedPreferences prefs,
+            final int sdkVersion) {
         final String prefKey = getPreferenceKey(sdkVersion);
-        prefs.edit().putString(prefKey, themeIdString).apply();
+        prefs.edit().putString(prefKey, Integer.toString(themeId)).apply();
     }
 
-    public static KeyboardTheme getKeyboardTheme(final SharedPreferences prefs) {
-        return getKeyboardTheme(prefs, BuildCompatUtils.EFFECTIVE_SDK_INT);
+    public static KeyboardTheme getKeyboardTheme(final Context context) {
+        final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+        final KeyboardTheme[] availableThemeArray = getAvailableThemeArray(context);
+        return getKeyboardTheme(prefs, BuildCompatUtils.EFFECTIVE_SDK_INT, availableThemeArray);
+    }
+
+    static KeyboardTheme[] getAvailableThemeArray(final Context context) {
+        if (AVAILABLE_KEYBOARD_THEMES == null) {
+            final int[] availableThemeIdStringArray = context.getResources().getIntArray(
+                    R.array.keyboard_theme_ids);
+            final ArrayList<KeyboardTheme> availableThemeList = new ArrayList<>();
+            for (final int id : availableThemeIdStringArray) {
+                final KeyboardTheme theme = searchKeyboardThemeById(id, KEYBOARD_THEMES);
+                if (theme != null) {
+                    availableThemeList.add(theme);
+                }
+            }
+            AVAILABLE_KEYBOARD_THEMES = availableThemeList.toArray(
+                    new KeyboardTheme[availableThemeList.size()]);
+        }
+        return AVAILABLE_KEYBOARD_THEMES;
     }
 
     @UsedForTesting
-    static KeyboardTheme getKeyboardTheme(final SharedPreferences prefs, final int sdkVersion) {
+    static KeyboardTheme getKeyboardTheme(final SharedPreferences prefs, final int sdkVersion,
+            final KeyboardTheme[] availableThemeArray) {
         final String lxxThemeIdString = prefs.getString(LXX_KEYBOARD_THEME_KEY, null);
         if (lxxThemeIdString == null) {
-            return getDefaultKeyboardTheme(prefs, sdkVersion);
+            return getDefaultKeyboardTheme(prefs, sdkVersion, availableThemeArray);
         }
         try {
             final int themeId = Integer.parseInt(lxxThemeIdString);
-            final KeyboardTheme theme = searchKeyboardThemeById(themeId);
+            final KeyboardTheme theme = searchKeyboardThemeById(themeId, availableThemeArray);
             if (theme != null) {
                 return theme;
             }
@@ -180,6 +207,6 @@
         }
         // Remove preference that contains unknown or illegal theme id.
         prefs.edit().remove(LXX_KEYBOARD_THEME_KEY).apply();
-        return getDefaultKeyboardTheme(prefs, sdkVersion);
+        return getDefaultKeyboardTheme(prefs, sdkVersion, availableThemeArray);
     }
 }
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
index bb3cbb0..98cd1da 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
@@ -343,7 +343,9 @@
         final int keyWidth = key.getDrawWidth();
         final int keyHeight = key.getHeight();
         final int bgWidth, bgHeight, bgX, bgY;
-        if (key.needsToKeepBackgroundAspectRatio(mDefaultKeyLabelFlags)) {
+        if (key.needsToKeepBackgroundAspectRatio(mDefaultKeyLabelFlags)
+                // HACK: To disable expanding normal/functional key background.
+                && !key.hasCustomActionLabel()) {
             final int intrinsicWidth = background.getIntrinsicWidth();
             final int intrinsicHeight = background.getIntrinsicHeight();
             final float minScale = Math.min(
diff --git a/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java b/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java
index c205466..eced45e 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java
@@ -61,9 +61,9 @@
     // dictionary.
     private static final int CAPITALIZED_FORM_MAX_PROBABILITY_FOR_INSERT = 140;
 
-    private DictionaryGroup mDictionaryGroup = new DictionaryGroup();
+    private DictionaryGroup[] mDictionaryGroups = new DictionaryGroup[] { new DictionaryGroup() };
     private boolean mIsUserDictEnabled = false;
-    private volatile CountDownLatch mLatchForWaitingLoadingMainDictionary = new CountDownLatch(0);
+    private volatile CountDownLatch mLatchForWaitingLoadingMainDictionaries = new CountDownLatch(0);
     // To synchronize assigning mDictionaryGroup to ensure closing dictionaries.
     private final Object mLock = new Object();
     private final DistracterFilter mDistracterFilter;
@@ -193,8 +193,9 @@
         mPersonalizationHelper.setIsMonolingualUser(isMonolingualUser);
     }
 
+    // TODO: remove this, replace with version returning multiple locales
     public Locale getLocale() {
-        return mDictionaryGroup.mLocale;
+        return mDictionaryGroups[0].mLocale;
     }
 
     private static ExpandableBinaryDictionary getSubDict(final String dictType,
@@ -226,6 +227,21 @@
                 usePersonalizedDicts, forceReloadMainDictionary, listener, "" /* dictNamePrefix */);
     }
 
+    private DictionaryGroup findDictionaryGroupWithLocale(final DictionaryGroup[] dictionaryGroups,
+            final Locale locale) {
+        for (int i = 0; i < dictionaryGroups.length; ++i) {
+            if (locale.equals(dictionaryGroups[i].mLocale)) {
+                return dictionaryGroups[i];
+            }
+        }
+        return null;
+    }
+
+    private DictionaryGroup getDictionaryGroupForActiveLanguage() {
+        // TODO: implement this
+        return mDictionaryGroups[0];
+    }
+
     public void resetDictionariesWithDictNamePrefix(final Context context,
             final Locale newLocaleToUse,
             final boolean useContactsDict, final boolean usePersonalizedDicts,
@@ -252,7 +268,7 @@
             final ArrayList<String> dictsForLocale = new ArrayList<>();
             existingDictsToCleanup.put(newLocale, dictsForLocale);
             final DictionaryGroup currentDictionaryGroupForLocale =
-                    newLocale.equals(mDictionaryGroup.mLocale) ? mDictionaryGroup : null;
+                    findDictionaryGroupWithLocale(mDictionaryGroups, newLocale);
             if (null == currentDictionaryGroupForLocale) {
                 continue;
             }
@@ -266,10 +282,11 @@
             }
         }
 
-        final HashMap<Locale, DictionaryGroup> newDictionaryGroups = new HashMap<>();
-        for (final Locale newLocale : newLocales) {
+        final DictionaryGroup[] newDictionaryGroups = new DictionaryGroup[newLocales.length];
+        for (int i = 0; i < newLocales.length; ++i) {
+            final Locale newLocale = newLocales[i];
             final DictionaryGroup dictionaryGroupForLocale =
-                    newLocale.equals(mDictionaryGroup.mLocale) ? mDictionaryGroup : null;
+                    findDictionaryGroupWithLocale(mDictionaryGroups, newLocale);
             final ArrayList<String> dictsToCleanupForLocale = existingDictsToCleanup.get(newLocale);
             final boolean noExistingDictsForThisLocale = (null == dictionaryGroupForLocale);
 
@@ -297,30 +314,29 @@
                 }
                 subDicts.put(subDictType, subDict);
             }
-            newDictionaryGroups.put(newLocale, new DictionaryGroup(newLocale, mainDict, subDicts));
+            newDictionaryGroups[i] = new DictionaryGroup(newLocale, mainDict, subDicts);
         }
 
         // Replace Dictionaries.
-        // TODO: use multiple locales.
-        final DictionaryGroup newDictionaryGroup = newDictionaryGroups.get(newLocaleToUse);
-        final DictionaryGroup oldDictionaryGroup;
+        final DictionaryGroup[] oldDictionaryGroups;
         synchronized (mLock) {
-            oldDictionaryGroup = mDictionaryGroup;
-            mDictionaryGroup = newDictionaryGroup;
+            oldDictionaryGroups = mDictionaryGroups;
+            mDictionaryGroups = newDictionaryGroups;
             mIsUserDictEnabled = UserBinaryDictionary.isEnabled(context);
-            if (null == newDictionaryGroup.getDict(Dictionary.TYPE_MAIN)) {
+            if (hasAtLeastOneUninitializedMainDictionary()) {
                 asyncReloadUninitializedMainDictionaries(context, newLocales, listener);
             }
         }
         if (listener != null) {
-            listener.onUpdateMainDictionaryAvailability(hasInitializedMainDictionary());
+            listener.onUpdateMainDictionaryAvailability(hasAtLeastOneInitializedMainDictionary());
         }
 
         // Clean up old dictionaries.
         for (final Locale localeToCleanUp : existingDictsToCleanup.keySet()) {
             final ArrayList<String> dictTypesToCleanUp =
                     existingDictsToCleanup.get(localeToCleanUp);
-            final DictionaryGroup dictionarySetToCleanup = oldDictionaryGroup;
+            final DictionaryGroup dictionarySetToCleanup =
+                    findDictionaryGroupWithLocale(oldDictionaryGroups, localeToCleanUp);
             for (final String dictType : dictTypesToCleanUp) {
                 dictionarySetToCleanup.closeDict(dictType);
             }
@@ -330,12 +346,18 @@
     private void asyncReloadUninitializedMainDictionaries(final Context context,
             final Locale[] locales, final DictionaryInitializationListener listener) {
         final CountDownLatch latchForWaitingLoadingMainDictionary = new CountDownLatch(1);
-        mLatchForWaitingLoadingMainDictionary = latchForWaitingLoadingMainDictionary;
+        mLatchForWaitingLoadingMainDictionaries = latchForWaitingLoadingMainDictionary;
         ExecutorUtils.getExecutor("InitializeBinaryDictionary").execute(new Runnable() {
             @Override
             public void run() {
                 for (final Locale locale : locales) {
-                    final DictionaryGroup dictionaryGroup = mDictionaryGroup;
+                    final DictionaryGroup dictionaryGroup =
+                            findDictionaryGroupWithLocale(mDictionaryGroups, locale);
+                    if (null == dictionaryGroup) {
+                        // This should never happen, but better safe than crashy
+                        Log.w(TAG, "Expected a dictionary group for " + locale + " but none found");
+                        continue;
+                    }
                     final Dictionary mainDict =
                             DictionaryFactory.createMainDictionaryFromManager(context, locale);
                     synchronized (mLock) {
@@ -348,7 +370,8 @@
                     }
                 }
                 if (listener != null) {
-                    listener.onUpdateMainDictionaryAvailability(hasInitializedMainDictionary());
+                    listener.onUpdateMainDictionaryAvailability(
+                            hasAtLeastOneInitializedMainDictionary());
                 }
                 latchForWaitingLoadingMainDictionary.countDown();
             }
@@ -381,17 +404,20 @@
                 subDicts.put(dictType, dict);
             }
         }
-        mDictionaryGroup = new DictionaryGroup(locale, mainDictionary, subDicts);
+        mDictionaryGroups = new DictionaryGroup[] {
+                new DictionaryGroup(locale, mainDictionary, subDicts) };
     }
 
     public void closeDictionaries() {
-        final DictionaryGroup dictionaryGroup;
+        final DictionaryGroup[] dictionaryGroups;
         synchronized (mLock) {
-            dictionaryGroup = mDictionaryGroup;
-            mDictionaryGroup = new DictionaryGroup();
+            dictionaryGroups = mDictionaryGroups;
+            mDictionaryGroups = new DictionaryGroup[] { new DictionaryGroup() };
         }
-        for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS) {
-            dictionaryGroup.closeDict(dictType);
+        for (final DictionaryGroup dictionaryGroup : dictionaryGroups) {
+            for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS) {
+                dictionaryGroup.closeDict(dictType);
+            }
         }
         mDistracterFilter.close();
         if (mPersonalizationHelper != null) {
@@ -401,40 +427,71 @@
 
     @UsedForTesting
     public ExpandableBinaryDictionary getSubDictForTesting(final String dictName) {
-        return mDictionaryGroup.getSubDict(dictName);
+        return mDictionaryGroups[0].getSubDict(dictName);
     }
 
-    // The main dictionary could have been loaded asynchronously.  Don't cache the return value
-    // of this method.
-    public boolean hasInitializedMainDictionary() {
-        final Dictionary mainDict = mDictionaryGroup.getDict(Dictionary.TYPE_MAIN);
-        return mainDict != null && mainDict.isInitialized();
+    // The main dictionaries are loaded asynchronously.  Don't cache the return value
+    // of these methods.
+    public boolean hasAtLeastOneInitializedMainDictionary() {
+        final DictionaryGroup[] dictionaryGroups = mDictionaryGroups;
+        for (final DictionaryGroup dictionaryGroup : dictionaryGroups) {
+            final Dictionary mainDict = dictionaryGroup.getDict(Dictionary.TYPE_MAIN);
+            if (mainDict != null && mainDict.isInitialized()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public boolean hasAtLeastOneUninitializedMainDictionary() {
+        final DictionaryGroup[] dictionaryGroups = mDictionaryGroups;
+        for (final DictionaryGroup dictionaryGroup : dictionaryGroups) {
+            final Dictionary mainDict = dictionaryGroup.getDict(Dictionary.TYPE_MAIN);
+            if (mainDict == null || !mainDict.isInitialized()) {
+                return true;
+            }
+        }
+        return false;
     }
 
     public boolean hasPersonalizationDictionary() {
-        return mDictionaryGroup.hasDict(Dictionary.TYPE_PERSONALIZATION);
+        final DictionaryGroup[] dictionaryGroups = mDictionaryGroups;
+        for (final DictionaryGroup dictionaryGroup : dictionaryGroups) {
+            if (dictionaryGroup.hasDict(Dictionary.TYPE_PERSONALIZATION)) {
+                return true;
+            }
+        }
+        return false;
     }
 
     public void flushPersonalizationDictionary() {
-        final ExpandableBinaryDictionary personalizationDictUsedForSuggestion =
-                mDictionaryGroup.getSubDict(Dictionary.TYPE_PERSONALIZATION);
+        final HashSet<ExpandableBinaryDictionary> personalizationDictsUsedForSuggestion =
+                new HashSet<>();
+        final DictionaryGroup[] dictionaryGroups = mDictionaryGroups;
+        for (final DictionaryGroup dictionaryGroup : dictionaryGroups) {
+            final ExpandableBinaryDictionary personalizationDictUsedForSuggestion =
+                    dictionaryGroup.getSubDict(Dictionary.TYPE_PERSONALIZATION);
+            personalizationDictsUsedForSuggestion.add(personalizationDictUsedForSuggestion);
+        }
         mPersonalizationHelper.flushPersonalizationDictionariesToUpdate(
-                personalizationDictUsedForSuggestion);
+                personalizationDictsUsedForSuggestion);
         mDistracterFilter.close();
     }
 
-    public void waitForLoadingMainDictionary(final long timeout, final TimeUnit unit)
+    public void waitForLoadingMainDictionaries(final long timeout, final TimeUnit unit)
             throws InterruptedException {
-        mLatchForWaitingLoadingMainDictionary.await(timeout, unit);
+        mLatchForWaitingLoadingMainDictionaries.await(timeout, unit);
     }
 
     @UsedForTesting
     public void waitForLoadingDictionariesForTesting(final long timeout, final TimeUnit unit)
             throws InterruptedException {
-        waitForLoadingMainDictionary(timeout, unit);
-        final Map<String, ExpandableBinaryDictionary> dictMap = mDictionaryGroup.mSubDictMap;
-        for (final ExpandableBinaryDictionary dict : dictMap.values()) {
-            dict.waitAllTasksForTests();
+        waitForLoadingMainDictionaries(timeout, unit);
+        final DictionaryGroup[] dictionaryGroups = mDictionaryGroups;
+        for (final DictionaryGroup dictionaryGroup : dictionaryGroups) {
+            for (final ExpandableBinaryDictionary dict : dictionaryGroup.mSubDictMap.values()) {
+                dict.waitAllTasksForTests();
+            }
         }
     }
 
@@ -453,7 +510,7 @@
     public void addToUserHistory(final String suggestion, final boolean wasAutoCapitalized,
             final PrevWordsInfo prevWordsInfo, final int timeStampInSeconds,
             final boolean blockPotentiallyOffensive) {
-        final DictionaryGroup dictionaryGroup = mDictionaryGroup;
+        final DictionaryGroup dictionaryGroup = getDictionaryGroupForActiveLanguage();
         final String[] words = suggestion.split(Constants.WORD_SEPARATOR);
         PrevWordsInfo prevWordsInfoForCurrentWord = prevWordsInfo;
         for (int i = 0; i < words.length; i++) {
@@ -520,7 +577,8 @@
     }
 
     private void removeWord(final String dictName, final String word) {
-        final ExpandableBinaryDictionary dictionary = mDictionaryGroup.getSubDict(dictName);
+        final ExpandableBinaryDictionary dictionary =
+                getDictionaryGroupForActiveLanguage().getSubDict(dictName);
         if (dictionary != null) {
             dictionary.removeUnigramEntryDynamically(word);
         }
@@ -536,20 +594,22 @@
     public SuggestionResults getSuggestionResults(final WordComposer composer,
             final PrevWordsInfo prevWordsInfo, final ProximityInfo proximityInfo,
             final SettingsValuesForSuggestion settingsValuesForSuggestion, final int sessionId) {
-        final DictionaryGroup dictionaryGroup = mDictionaryGroup;
+        final DictionaryGroup[] dictionaryGroups = mDictionaryGroups;
         final SuggestionResults suggestionResults =
                 new SuggestionResults(SuggestedWords.MAX_SUGGESTIONS);
         final float[] languageWeight = new float[] { Dictionary.NOT_A_LANGUAGE_WEIGHT };
-        for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS) {
-            final Dictionary dictionary = dictionaryGroup.getDict(dictType);
-            if (null == dictionary) continue;
-            final ArrayList<SuggestedWordInfo> dictionarySuggestions =
-                    dictionary.getSuggestions(composer, prevWordsInfo, proximityInfo,
-                            settingsValuesForSuggestion, sessionId, languageWeight);
-            if (null == dictionarySuggestions) continue;
-            suggestionResults.addAll(dictionarySuggestions);
-            if (null != suggestionResults.mRawSuggestions) {
-                suggestionResults.mRawSuggestions.addAll(dictionarySuggestions);
+        for (final DictionaryGroup dictionaryGroup : dictionaryGroups) {
+            for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS) {
+                final Dictionary dictionary = dictionaryGroup.getDict(dictType);
+                if (null == dictionary) continue;
+                final ArrayList<SuggestedWordInfo> dictionarySuggestions =
+                        dictionary.getSuggestions(composer, prevWordsInfo, proximityInfo,
+                                settingsValuesForSuggestion, sessionId, languageWeight);
+                if (null == dictionarySuggestions) continue;
+                suggestionResults.addAll(dictionarySuggestions);
+                if (null != suggestionResults.mRawSuggestions) {
+                    suggestionResults.mRawSuggestions.addAll(dictionarySuggestions);
+                }
             }
         }
         return suggestionResults;
@@ -559,20 +619,22 @@
         if (TextUtils.isEmpty(word)) {
             return false;
         }
-        final DictionaryGroup dictionaryGroup = mDictionaryGroup;
-        if (dictionaryGroup.mLocale == null) {
-            return false;
-        }
-        final String lowerCasedWord = word.toLowerCase(dictionaryGroup.mLocale);
-        for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS) {
-            final Dictionary dictionary = dictionaryGroup.getDict(dictType);
-            // Ideally the passed map would come out of a {@link java.util.concurrent.Future} and
-            // would be immutable once it's finished initializing, but concretely a null test is
-            // probably good enough for the time being.
-            if (null == dictionary) continue;
-            if (dictionary.isValidWord(word)
-                    || (ignoreCase && dictionary.isValidWord(lowerCasedWord))) {
-                return true;
+        final DictionaryGroup[] dictionaryGroups = mDictionaryGroups;
+        for (final DictionaryGroup dictionaryGroup : dictionaryGroups) {
+            if (dictionaryGroup.mLocale == null) {
+                continue;
+            }
+            final String lowerCasedWord = word.toLowerCase(dictionaryGroup.mLocale);
+            for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS) {
+                final Dictionary dictionary = dictionaryGroup.getDict(dictType);
+                // Ideally the passed map would come out of a {@link java.util.concurrent.Future} and
+                // would be immutable once it's finished initializing, but concretely a null test is
+                // probably good enough for the time being.
+                if (null == dictionary) continue;
+                if (dictionary.isValidWord(word)
+                        || (ignoreCase && dictionary.isValidWord(lowerCasedWord))) {
+                    return true;
+                }
             }
         }
         return false;
@@ -584,18 +646,20 @@
             return Dictionary.NOT_A_PROBABILITY;
         }
         int maxFreq = Dictionary.NOT_A_PROBABILITY;
-        final DictionaryGroup dictionaryGroup = mDictionaryGroup;
-        for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS) {
-            final Dictionary dictionary = dictionaryGroup.getDict(dictType);
-            if (dictionary == null) continue;
-            final int tempFreq;
-            if (isGettingMaxFrequencyOfExactMatches) {
-                tempFreq = dictionary.getMaxFrequencyOfExactMatches(word);
-            } else {
-                tempFreq = dictionary.getFrequency(word);
-            }
-            if (tempFreq >= maxFreq) {
-                maxFreq = tempFreq;
+        final DictionaryGroup[] dictionaryGroups = mDictionaryGroups;
+        for (final DictionaryGroup dictionaryGroup : dictionaryGroups) {
+            for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS) {
+                final Dictionary dictionary = dictionaryGroup.getDict(dictType);
+                if (dictionary == null) continue;
+                final int tempFreq;
+                if (isGettingMaxFrequencyOfExactMatches) {
+                    tempFreq = dictionary.getMaxFrequencyOfExactMatches(word);
+                } else {
+                    tempFreq = dictionary.getFrequency(word);
+                }
+                if (tempFreq >= maxFreq) {
+                    maxFreq = tempFreq;
+                }
             }
         }
         return maxFreq;
@@ -610,9 +674,12 @@
     }
 
     private void clearSubDictionary(final String dictName) {
-        final ExpandableBinaryDictionary dictionary = mDictionaryGroup.getSubDict(dictName);
-        if (dictionary != null) {
-            dictionary.clear();
+        final DictionaryGroup[] dictionaryGroups = mDictionaryGroups;
+        for (final DictionaryGroup dictionaryGroup : dictionaryGroups) {
+            final ExpandableBinaryDictionary dictionary = dictionaryGroup.getSubDict(dictName);
+            if (dictionary != null) {
+                dictionary.clear();
+            }
         }
     }
 
@@ -641,8 +708,10 @@
 
     public void addPhraseToContextualDictionary(final String[] phrase, final int probability,
             final int bigramProbabilityForWords, final int bigramProbabilityForPhrases) {
+        // TODO: we're inserting the phrase into the dictionary for the active language. Rethink
+        // this a bit from a theoretical point of view.
         final ExpandableBinaryDictionary contextualDict =
-                mDictionaryGroup.getSubDict(Dictionary.TYPE_CONTEXTUAL);
+                getDictionaryGroupForActiveLanguage().getSubDict(Dictionary.TYPE_CONTEXTUAL);
         if (contextualDict == null) {
             return;
         }
@@ -675,22 +744,27 @@
     }
 
     public void dumpDictionaryForDebug(final String dictName) {
-        final ExpandableBinaryDictionary dictToDump = mDictionaryGroup.getSubDict(dictName);
-        if (dictToDump == null) {
-            Log.e(TAG, "Cannot dump " + dictName + ". "
-                    + "The dictionary is not being used for suggestion or cannot be dumped.");
-            return;
+        final DictionaryGroup[] dictionaryGroups = mDictionaryGroups;
+        for (final DictionaryGroup dictionaryGroup : dictionaryGroups) {
+            final ExpandableBinaryDictionary dictToDump = dictionaryGroup.getSubDict(dictName);
+            if (dictToDump == null) {
+                Log.e(TAG, "Cannot dump " + dictName + ". "
+                        + "The dictionary is not being used for suggestion or cannot be dumped.");
+                return;
+            }
+            dictToDump.dumpAllWordsForDebug();
         }
-        dictToDump.dumpAllWordsForDebug();
     }
 
     public ArrayList<Pair<String, DictionaryStats>> getStatsOfEnabledSubDicts() {
         final ArrayList<Pair<String, DictionaryStats>> statsOfEnabledSubDicts = new ArrayList<>();
-        final DictionaryGroup dictionaryGroup = mDictionaryGroup;
-        for (final String dictType : SUB_DICT_TYPES) {
-            final ExpandableBinaryDictionary dictionary = dictionaryGroup.getSubDict(dictType);
-            if (dictionary == null) continue;
-            statsOfEnabledSubDicts.add(new Pair<>(dictType, dictionary.getDictionaryStats()));
+        final DictionaryGroup[] dictionaryGroups = mDictionaryGroups;
+        for (final DictionaryGroup dictionaryGroup : dictionaryGroups) {
+            for (final String dictType : SUB_DICT_TYPES) {
+                final ExpandableBinaryDictionary dictionary = dictionaryGroup.getSubDict(dictType);
+                if (dictionary == null) continue;
+                statsOfEnabledSubDicts.add(new Pair<>(dictType, dictionary.getDictionaryStats()));
+            }
         }
         return statsOfEnabledSubDicts;
     }
diff --git a/java/src/com/android/inputmethod/latin/DictionaryFacilitatorLruCache.java b/java/src/com/android/inputmethod/latin/DictionaryFacilitatorLruCache.java
index fa0265d..ff4a6bd 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryFacilitatorLruCache.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryFacilitatorLruCache.java
@@ -84,7 +84,7 @@
     private void waitForLoadingMainDictionary(final DictionaryFacilitator dictionaryFacilitator) {
         for (int i = 0; i < MAX_RETRY_COUNT_FOR_WAITING_FOR_LOADING_DICT; i++) {
             try {
-                dictionaryFacilitator.waitForLoadingMainDictionary(
+                dictionaryFacilitator.waitForLoadingMainDictionaries(
                         WAIT_FOR_LOADING_MAIN_DICT_IN_MILLISECONDS, TimeUnit.MILLISECONDS);
                 return;
             } catch (final InterruptedException e) {
diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
index a1dd67f..671ba67 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
@@ -156,23 +156,25 @@
     }
 
     private void asyncExecuteTaskWithWriteLock(final Runnable task) {
-        asyncExecuteTaskWithLock(mLock.writeLock(), task);
+        asyncExecuteTaskWithLock(mLock.writeLock(), mDictName /* executorName */, task);
     }
 
-    private void asyncExecuteTaskWithLock(final Lock lock, final Runnable task) {
-        asyncPreCheckAndExecuteTaskWithLock(lock, null /* preCheckTask */, task);
+    private void asyncExecuteTaskWithLock(final Lock lock, final String executorName,
+            final Runnable task) {
+        asyncPreCheckAndExecuteTaskWithLock(lock, null /* preCheckTask */, executorName, task);
     }
 
     private void asyncPreCheckAndExecuteTaskWithWriteLock(
             final Callable<Boolean> preCheckTask, final Runnable task) {
-        asyncPreCheckAndExecuteTaskWithLock(mLock.writeLock(), preCheckTask, task);
+        asyncPreCheckAndExecuteTaskWithLock(mLock.writeLock(), preCheckTask,
+                mDictName /* executorName */, task);
 
     }
 
     // Execute task with lock when the result of preCheckTask is true or preCheckTask is null.
     private void asyncPreCheckAndExecuteTaskWithLock(final Lock lock,
-            final Callable<Boolean> preCheckTask, final Runnable task) {
-        ExecutorUtils.getExecutor(mDictName).execute(new Runnable() {
+            final Callable<Boolean> preCheckTask, final String executorName, final Runnable task) {
+        ExecutorUtils.getExecutor(executorName).execute(new Runnable() {
             @Override
             public void run() {
                 if (preCheckTask != null) {
@@ -676,10 +678,10 @@
 
     public void dumpAllWordsForDebug() {
         reloadDictionaryIfRequired();
-        asyncExecuteTaskWithLock(mLock.readLock(), new Runnable() {
+        asyncExecuteTaskWithLock(mLock.readLock(), "dumpAllWordsForDebug", new Runnable() {
             @Override
             public void run() {
-                Log.d(TAG, "Dump dictionary: " + mDictName);
+                Log.d(TAG, "Dump dictionary: " + mDictName + " for " + mLocale);
                 try {
                     final DictionaryHeader header = mBinaryDictionary.getHeader();
                     Log.d(TAG, "Format version: " + mBinaryDictionary.getFormatVersion());
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 0a64c4c..4757820 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -1000,7 +1000,7 @@
         mHandler.cancelUpdateSuggestionStrip();
 
         mainKeyboardView.setMainDictionaryAvailability(
-                mDictionaryFacilitator.hasInitializedMainDictionary());
+                mDictionaryFacilitator.hasAtLeastOneInitializedMainDictionary());
         mainKeyboardView.setKeyPreviewPopupEnabled(currentSettingsValues.mKeyPreviewPopupOn,
                 currentSettingsValues.mKeyPreviewPopupDismissDelay);
         mainKeyboardView.setSlidingKeyInputPreviewEnabled(
diff --git a/java/src/com/android/inputmethod/latin/PersonalizationHelperForDictionaryFacilitator.java b/java/src/com/android/inputmethod/latin/PersonalizationHelperForDictionaryFacilitator.java
index 43cebdf..396d062 100644
--- a/java/src/com/android/inputmethod/latin/PersonalizationHelperForDictionaryFacilitator.java
+++ b/java/src/com/android/inputmethod/latin/PersonalizationHelperForDictionaryFacilitator.java
@@ -88,17 +88,17 @@
 
     /**
      * Flush personalization dictionaries to dictionary files. Close dictionaries after writing
-     * files except the dictionary that is used for generating suggestions.
+     * files except the dictionaries that is used for generating suggestions.
      *
-     * @param personalizationDictUsedForSuggestion the personalization dictionary used for
+     * @param personalizationDictsUsedForSuggestion the personalization dictionaries used for
      * generating suggestions that won't be closed.
      */
     public void flushPersonalizationDictionariesToUpdate(
-            final ExpandableBinaryDictionary personalizationDictUsedForSuggestion) {
+            final HashSet<ExpandableBinaryDictionary> personalizationDictsUsedForSuggestion) {
         for (final ExpandableBinaryDictionary personalizationDict :
                 mPersonalizationDictsToUpdate.values()) {
             personalizationDict.asyncFlushBinaryDictionary();
-            if (personalizationDict != personalizationDictUsedForSuggestion) {
+            if (!personalizationDictsUsedForSuggestion.contains(personalizationDict)) {
                 // Close if the dictionary is not being used for suggestion.
                 personalizationDict.close();
             }
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index 9e4aa40..4ad5ba6 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -157,7 +157,7 @@
         if (!isCorrectionEnabled || !allowsToBeAutoCorrected || resultsArePredictions
                 || suggestionResults.isEmpty() || wordComposer.hasDigits()
                 || wordComposer.isMostlyCaps() || wordComposer.isResumed()
-                || !mDictionaryFacilitator.hasInitializedMainDictionary()
+                || !mDictionaryFacilitator.hasAtLeastOneInitializedMainDictionary()
                 || suggestionResults.first().isKindOf(SuggestedWordInfo.KIND_SHORTCUT)) {
             // If we don't have a main dictionary, we never want to auto-correct. The reason for
             // this is, the user may have a contact whose name happens to match a valid word in
@@ -235,7 +235,7 @@
         SuggestedWordInfo.removeDups(null /* typedWord */, suggestionsContainer);
 
         // For some reason some suggestions with MIN_VALUE are making their way here.
-        // TODO: Find a more robust way to detect distractors.
+        // TODO: Find a more robust way to detect distracters.
         for (int i = suggestionsContainer.size() - 1; i >= 0; --i) {
             if (suggestionsContainer.get(i).mScore < SUPPRESS_SUGGEST_THRESHOLD) {
                 suggestionsContainer.remove(i);
diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java
index 32d1fe3..567aa07 100644
--- a/java/src/com/android/inputmethod/latin/WordComposer.java
+++ b/java/src/com/android/inputmethod/latin/WordComposer.java
@@ -49,6 +49,7 @@
     private final ArrayList<Event> mEvents;
     private final InputPointers mInputPointers = new InputPointers(MAX_WORD_LENGTH);
     private String mAutoCorrection;
+    private String mAutoCorrectionDictionaryType;
     private boolean mIsResumed;
     private boolean mIsBatchMode;
     // A memory of the last rejected batch mode suggestion, if any. This goes like this: the user
@@ -418,8 +419,9 @@
     /**
      * Sets the auto-correction for this word.
      */
-    public void setAutoCorrection(final String correction) {
+    public void setAutoCorrection(final String correction, String dictType) {
         mAutoCorrection = correction;
+        mAutoCorrectionDictionaryType = dictType;
     }
 
     /**
@@ -430,6 +432,13 @@
     }
 
     /**
+     * @return the auto-correction dictionary type or null if none.
+     */
+    public String getAutoCorrectionDictionaryTypeOrNull() {
+        return mAutoCorrectionDictionaryType;
+    }
+
+    /**
      * @return whether we started composing this word by resuming suggestion on an existing string
      */
     public boolean isResumed() {
diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
index c5e60d6..0942c07 100644
--- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
+++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
@@ -612,14 +612,21 @@
             final SettingsValues settingsValues, final LatinIME.UIHandler handler) {
         if (SuggestedWords.EMPTY != suggestedWords) {
             final String autoCorrection;
+            final String dictType;
             if (suggestedWords.mWillAutoCorrect) {
-                autoCorrection = suggestedWords.getWord(SuggestedWords.INDEX_OF_AUTO_CORRECTION);
+                SuggestedWordInfo info = suggestedWords.getInfo(
+                        SuggestedWords.INDEX_OF_AUTO_CORRECTION);
+                autoCorrection = info.mWord;
+                dictType = info.mSourceDict.mDictType;
             } else {
                 // We can't use suggestedWords.getWord(SuggestedWords.INDEX_OF_TYPED_WORD)
                 // because it may differ from mWordComposer.mTypedWord.
                 autoCorrection = suggestedWords.mTypedWord;
+                dictType = Dictionary.TYPE_USER_TYPED;
             }
-            mWordComposer.setAutoCorrection(autoCorrection);
+            // TODO: Use the SuggestedWordInfo to set the auto correction when
+            // user typed word is available via SuggestedWordInfo.
+            mWordComposer.setAutoCorrection(autoCorrection, dictType);
         }
         mSuggestedWords = suggestedWords;
         final boolean newAutoCorrectionIndicator = suggestedWords.mWillAutoCorrect;
@@ -2100,6 +2107,8 @@
                 mConnection.commitCorrection(new CorrectionInfo(
                         mConnection.getExpectedSelectionEnd() - autoCorrection.length(),
                         typedWord, autoCorrection));
+                StatsUtils.onAutoCorrection(typedWord, autoCorrection, mWordComposer.isBatchMode(),
+                        mWordComposer.getAutoCorrectionDictionaryTypeOrNull());
             }
         }
     }
diff --git a/java/src/com/android/inputmethod/latin/settings/ThemeSettingsFragment.java b/java/src/com/android/inputmethod/latin/settings/ThemeSettingsFragment.java
index 5a3fc36..29289ae 100644
--- a/java/src/com/android/inputmethod/latin/settings/ThemeSettingsFragment.java
+++ b/java/src/com/android/inputmethod/latin/settings/ThemeSettingsFragment.java
@@ -17,7 +17,6 @@
 package com.android.inputmethod.latin.settings;
 
 import android.content.Context;
-import android.content.SharedPreferences;
 import android.content.res.Resources;
 import android.os.Bundle;
 import android.preference.Preference;
@@ -32,12 +31,12 @@
  */
 public final class ThemeSettingsFragment extends SubScreenFragment
         implements OnRadioButtonClickedListener {
-    private String mSelectedThemeId;
+    private int mSelectedThemeId;
 
     static class KeyboardThemePreference extends RadioButtonPreference {
-        final String mThemeId;
+        final int mThemeId;
 
-        KeyboardThemePreference(final Context context, final String name, final String id) {
+        KeyboardThemePreference(final Context context, final String name, final int id) {
             super(context);
             setTitle(name);
             mThemeId = id;
@@ -45,14 +44,13 @@
     }
 
     static void updateKeyboardThemeSummary(final Preference pref) {
-        final Resources res = pref.getContext().getResources();
-        final SharedPreferences prefs = pref.getSharedPreferences();
-        final KeyboardTheme keyboardTheme = KeyboardTheme.getKeyboardTheme(prefs);
-        final String keyboardThemeId = String.valueOf(keyboardTheme.mThemeId);
+        final Context context = pref.getContext();
+        final Resources res = context.getResources();
+        final KeyboardTheme keyboardTheme = KeyboardTheme.getKeyboardTheme(context);
         final String[] keyboardThemeNames = res.getStringArray(R.array.keyboard_theme_names);
-        final String[] keyboardThemeIds = res.getStringArray(R.array.keyboard_theme_ids);
+        final int[] keyboardThemeIds = res.getIntArray(R.array.keyboard_theme_ids);
         for (int index = 0; index < keyboardThemeNames.length; index++) {
-            if (keyboardThemeId.equals(keyboardThemeIds[index])) {
+            if (keyboardTheme.mThemeId == keyboardThemeIds[index]) {
                 pref.setSummary(keyboardThemeNames[index]);
                 return;
             }
@@ -64,18 +62,18 @@
         super.onCreate(icicle);
         addPreferencesFromResource(R.xml.prefs_screen_theme);
         final PreferenceScreen screen = getPreferenceScreen();
+        final Context context = getActivity();
         final Resources res = getResources();
         final String[] keyboardThemeNames = res.getStringArray(R.array.keyboard_theme_names);
-        final String[] keyboardThemeIds = res.getStringArray(R.array.keyboard_theme_ids);
+        final int[] keyboardThemeIds = res.getIntArray(R.array.keyboard_theme_ids);
         for (int index = 0; index < keyboardThemeNames.length; index++) {
             final KeyboardThemePreference pref = new KeyboardThemePreference(
-                    getActivity(), keyboardThemeNames[index], keyboardThemeIds[index]);
+                    context, keyboardThemeNames[index], keyboardThemeIds[index]);
             screen.addPreference(pref);
             pref.setOnRadioButtonClickedListener(this);
         }
-        final SharedPreferences prefs = getSharedPreferences();
-        final KeyboardTheme keyboardTheme = KeyboardTheme.getKeyboardTheme(prefs);
-        mSelectedThemeId = String.valueOf(keyboardTheme.mThemeId);
+        final KeyboardTheme keyboardTheme = KeyboardTheme.getKeyboardTheme(context);
+        mSelectedThemeId = keyboardTheme.mThemeId;
     }
 
     @Override
@@ -106,7 +104,7 @@
             final Preference preference = screen.getPreference(index);
             if (preference instanceof KeyboardThemePreference) {
                 final KeyboardThemePreference pref = (KeyboardThemePreference)preference;
-                final boolean selected = mSelectedThemeId.equals(pref.mThemeId);
+                final boolean selected = (mSelectedThemeId == pref.mThemeId);
                 pref.setSelected(selected);
             }
         }
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
index 49b34d3..3523916 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
@@ -185,7 +185,7 @@
         try {
             final DictionaryFacilitator dictionaryFacilitator =
                     mDictionaryFacilitatorCache.get(locale);
-            return dictionaryFacilitator.hasInitializedMainDictionary();
+            return dictionaryFacilitator.hasAtLeastOneInitializedMainDictionary();
         } finally {
             mSemaphore.release();
         }
diff --git a/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatchesAndSuggestions.java b/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatchesAndSuggestions.java
index 1db5255..f8a8453 100644
--- a/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatchesAndSuggestions.java
+++ b/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatchesAndSuggestions.java
@@ -64,9 +64,9 @@
     private final Object mLock = new Object();
 
     // If the score of the top suggestion exceeds this value, the tested word (e.g.,
-    // an OOV, a misspelling, or an in-vocabulary word) would be considered as a distractor to
+    // an OOV, a misspelling, or an in-vocabulary word) would be considered as a distracter to
     // words in dictionary. The greater the threshold is, the less likely the tested word would
-    // become a distractor, which means the tested word will be more likely to be added to
+    // become a distracter, which means the tested word will be more likely to be added to
     // the dictionary.
     private static final float DISTRACTER_WORD_SCORE_THRESHOLD = 0.4f;
 
@@ -196,7 +196,7 @@
         }
         final boolean Word = dictionaryFacilitator.isValidWord(testedWord, false /* ignoreCase */);
         if (Word) {
-            // Valid word is not a distractor.
+            // Valid word is not a distracter.
             if (DEBUG) {
                 Log.d(TAG, "isDistracter: false (valid word)");
             }
@@ -257,12 +257,12 @@
             return false;
         }
         final SuggestedWordInfo firstSuggestion = suggestionResults.first();
-        final boolean isDistractor = suggestionExceedsDistracterThreshold(
+        final boolean isDistracter = suggestionExceedsDistracterThreshold(
                 firstSuggestion, consideredWord, DISTRACTER_WORD_SCORE_THRESHOLD);
         if (DEBUG) {
-            Log.d(TAG, "isDistracter: " + isDistractor);
+            Log.d(TAG, "isDistracter: " + isDistracter);
         }
-        return isDistractor;
+        return isDistracter;
     }
 
     private static boolean suggestionExceedsDistracterThreshold(final SuggestedWordInfo suggestion,
diff --git a/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
index 81e2ff5..440b963 100644
--- a/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
+++ b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
@@ -259,20 +259,21 @@
         jintArray word) {
     Dictionary *dictionary = reinterpret_cast<Dictionary *>(dict);
     if (!dictionary) return NOT_A_PROBABILITY;
-    const jsize wordLength = env->GetArrayLength(word);
-    int codePoints[wordLength];
-    env->GetIntArrayRegion(word, 0, wordLength, codePoints);
-    return dictionary->getProbability(codePoints, wordLength);
+    const jsize codePointCount = env->GetArrayLength(word);
+    int codePoints[codePointCount];
+    env->GetIntArrayRegion(word, 0, codePointCount, codePoints);
+    return dictionary->getProbability(CodePointArrayView(codePoints, codePointCount));
 }
 
 static jint latinime_BinaryDictionary_getMaxProbabilityOfExactMatches(
         JNIEnv *env, jclass clazz, jlong dict, jintArray word) {
     Dictionary *dictionary = reinterpret_cast<Dictionary *>(dict);
     if (!dictionary) return NOT_A_PROBABILITY;
-    const jsize wordLength = env->GetArrayLength(word);
-    int codePoints[wordLength];
-    env->GetIntArrayRegion(word, 0, wordLength, codePoints);
-    return dictionary->getMaxProbabilityOfExactMatches(codePoints, wordLength);
+    const jsize codePointCount = env->GetArrayLength(word);
+    int codePoints[codePointCount];
+    env->GetIntArrayRegion(word, 0, codePointCount, codePoints);
+    return dictionary->getMaxProbabilityOfExactMatches(
+            CodePointArrayView(codePoints, codePointCount));
 }
 
 static jint latinime_BinaryDictionary_getNgramProbability(JNIEnv *env, jclass clazz,
@@ -285,7 +286,8 @@
     env->GetIntArrayRegion(word, 0, wordLength, wordCodePoints);
     const PrevWordsInfo prevWordsInfo = JniDataUtils::constructPrevWordsInfo(env,
             prevWordCodePointArrays, isBeginningOfSentenceArray);
-    return dictionary->getNgramProbability(&prevWordsInfo, wordCodePoints, wordLength);
+    return dictionary->getNgramProbability(&prevWordsInfo,
+            CodePointArrayView(wordCodePoints, wordLength));
 }
 
 // Method to iterate all words in the dictionary for makedict.
@@ -340,7 +342,8 @@
             return;
         }
     }
-    const WordProperty wordProperty = dictionary->getWordProperty(wordCodePoints, codePointCount);
+    const WordProperty wordProperty = dictionary->getWordProperty(
+            CodePointArrayView(wordCodePoints, codePointCount));
     wordProperty.outputProperties(env, outCodePoints, outFlags, outProbabilityInfo,
             outBigramTargets, outBigramProbabilityInfo, outShortcutTargets,
             outShortcutProbabilities);
@@ -366,7 +369,8 @@
     // Use 1 for count to indicate the word has inputted.
     const UnigramProperty unigramProperty(isBeginningOfSentence, isNotAWord,
             isBlacklisted, probability, timestamp, 0 /* level */, 1 /* count */, &shortcuts);
-    return dictionary->addUnigramEntry(codePoints, codePointCount, &unigramProperty);
+    return dictionary->addUnigramEntry(CodePointArrayView(codePoints, codePointCount),
+            &unigramProperty);
 }
 
 static bool latinime_BinaryDictionary_removeUnigramEntry(JNIEnv *env, jclass clazz, jlong dict,
@@ -378,7 +382,7 @@
     jsize codePointCount = env->GetArrayLength(word);
     int codePoints[codePointCount];
     env->GetIntArrayRegion(word, 0, codePointCount, codePoints);
-    return dictionary->removeUnigramEntry(codePoints, codePointCount);
+    return dictionary->removeUnigramEntry(CodePointArrayView(codePoints, codePointCount));
 }
 
 static bool latinime_BinaryDictionary_addNgramEntry(JNIEnv *env, jclass clazz, jlong dict,
@@ -410,10 +414,11 @@
     }
     const PrevWordsInfo prevWordsInfo = JniDataUtils::constructPrevWordsInfo(env,
             prevWordCodePointArrays, isBeginningOfSentenceArray);
-    jsize wordLength = env->GetArrayLength(word);
-    int wordCodePoints[wordLength];
-    env->GetIntArrayRegion(word, 0, wordLength, wordCodePoints);
-    return dictionary->removeNgramEntry(&prevWordsInfo, wordCodePoints, wordLength);
+    jsize codePointCount = env->GetArrayLength(word);
+    int wordCodePoints[codePointCount];
+    env->GetIntArrayRegion(word, 0, codePointCount, wordCodePoints);
+    return dictionary->removeNgramEntry(&prevWordsInfo,
+            CodePointArrayView(wordCodePoints, codePointCount));
 }
 
 // Returns how many language model params are processed.
@@ -484,7 +489,8 @@
         const UnigramProperty unigramProperty(false /* isBeginningOfSentence */, isNotAWord,
                 isBlacklisted, unigramProbability, timestamp, 0 /* level */, 1 /* count */,
                 &shortcuts);
-        dictionary->addUnigramEntry(word1CodePoints, word1Length, &unigramProperty);
+        dictionary->addUnigramEntry(CodePointArrayView(word1CodePoints, word1Length),
+                &unigramProperty);
         if (word0) {
             jint bigramProbability = env->GetIntField(languageModelParam, bigramProbabilityFieldId);
             const std::vector<int> bigramTargetCodePoints(
@@ -568,8 +574,8 @@
     // Add unigrams.
     do {
         token = dictionary->getNextWordAndNextToken(token, wordCodePoints, &wordCodePointCount);
-        const WordProperty wordProperty = dictionary->getWordProperty(wordCodePoints,
-                wordCodePointCount);
+        const WordProperty wordProperty = dictionary->getWordProperty(
+                CodePointArrayView(wordCodePoints, wordCodePointCount));
         if (wordCodePoints[0] == CODE_POINT_BEGINNING_OF_SENTENCE) {
             // Skip beginning-of-sentence unigram.
             continue;
@@ -593,8 +599,8 @@
     // Add bigrams.
     do {
         token = dictionary->getNextWordAndNextToken(token, wordCodePoints, &wordCodePointCount);
-        const WordProperty wordProperty = dictionary->getWordProperty(wordCodePoints,
-                wordCodePointCount);
+        const WordProperty wordProperty = dictionary->getWordProperty(
+                CodePointArrayView(wordCodePoints, wordCodePointCount));
         if (dictionaryStructureWithBufferPolicy->needsToRunGC(true /* mindsBlockByGC */)) {
             dictionaryStructureWithBufferPolicy = runGCAndGetNewStructurePolicy(
                     std::move(dictionaryStructureWithBufferPolicy), dictFilePathChars);
diff --git a/native/jni/src/suggest/core/dicnode/internal/dic_node_properties.h b/native/jni/src/suggest/core/dicnode/internal/dic_node_properties.h
index cecfc7a..1b796b5 100644
--- a/native/jni/src/suggest/core/dicnode/internal/dic_node_properties.h
+++ b/native/jni/src/suggest/core/dicnode/internal/dic_node_properties.h
@@ -32,7 +32,7 @@
  public:
     AK_FORCE_INLINE DicNodeProperties()
             : mChildrenPtNodeArrayPos(NOT_A_DICT_POS), mDicNodeCodePoint(NOT_A_CODE_POINT),
-              mWordId(NOT_A_WORD_ID), mDepth(0), mLeavingDepth(0) {}
+              mWordId(NOT_A_WORD_ID), mDepth(0), mLeavingDepth(0), mPrevWordCount(0) {}
 
     ~DicNodeProperties() {}
 
@@ -45,6 +45,7 @@
         mDepth = depth;
         mLeavingDepth = leavingDepth;
         prevWordIds.copyToArray(&mPrevWordIds, 0 /* offset */);
+        mPrevWordCount = prevWordIds.size();
     }
 
     // Init for root with prevWordsPtNodePos which is used for n-gram
@@ -55,6 +56,7 @@
         mDepth = 0;
         mLeavingDepth = 0;
         prevWordIds.copyToArray(&mPrevWordIds, 0 /* offset */);
+        mPrevWordCount = prevWordIds.size();
     }
 
     void initByCopy(const DicNodeProperties *const dicNodeProp) {
@@ -63,8 +65,9 @@
         mWordId = dicNodeProp->mWordId;
         mDepth = dicNodeProp->mDepth;
         mLeavingDepth = dicNodeProp->mLeavingDepth;
-        WordIdArrayView::fromArray(dicNodeProp->mPrevWordIds)
-                .copyToArray(&mPrevWordIds, 0 /* offset */);
+        const WordIdArrayView prevWordIdArrayView = dicNodeProp->getPrevWordIds();
+        prevWordIdArrayView.copyToArray(&mPrevWordIds, 0 /* offset */);
+        mPrevWordCount = prevWordIdArrayView.size();
     }
 
     // Init as passing child
@@ -74,8 +77,9 @@
         mWordId = dicNodeProp->mWordId;
         mDepth = dicNodeProp->mDepth + 1; // Increment the depth of a passing child
         mLeavingDepth = dicNodeProp->mLeavingDepth;
-        WordIdArrayView::fromArray(dicNodeProp->mPrevWordIds)
-                .copyToArray(&mPrevWordIds, 0 /* offset */);
+        const WordIdArrayView prevWordIdArrayView = dicNodeProp->getPrevWordIds();
+        prevWordIdArrayView.copyToArray(&mPrevWordIds, 0 /* offset */);
+        mPrevWordCount = prevWordIdArrayView.size();
     }
 
     int getChildrenPtNodeArrayPos() const {
@@ -104,7 +108,7 @@
     }
 
     const WordIdArrayView getPrevWordIds() const {
-        return WordIdArrayView::fromArray(mPrevWordIds);
+        return WordIdArrayView::fromArray(mPrevWordIds).limit(mPrevWordCount);
     }
 
     int getWordId() const {
@@ -121,6 +125,7 @@
     uint16_t mDepth;
     uint16_t mLeavingDepth;
     WordIdArray<MAX_PREV_WORD_COUNT_FOR_N_GRAM> mPrevWordIds;
+    size_t mPrevWordCount;
 };
 } // namespace latinime
 #endif // LATINIME_DIC_NODE_PROPERTIES_H
diff --git a/native/jni/src/suggest/core/dictionary/dictionary.cpp b/native/jni/src/suggest/core/dictionary/dictionary.cpp
index ec261cf..f9f36ce 100644
--- a/native/jni/src/suggest/core/dictionary/dictionary.cpp
+++ b/native/jni/src/suggest/core/dictionary/dictionary.cpp
@@ -93,42 +93,42 @@
 void Dictionary::getPredictions(const PrevWordsInfo *const prevWordsInfo,
         SuggestionResults *const outSuggestionResults) const {
     TimeKeeper::setCurrentTime();
-    WordIdArray<MAX_PREV_WORD_COUNT_FOR_N_GRAM> prevWordIds;
-    prevWordsInfo->getPrevWordIds(mDictionaryStructureWithBufferPolicy.get(), prevWordIds.data(),
+    WordIdArray<MAX_PREV_WORD_COUNT_FOR_N_GRAM> prevWordIdArray;
+    const WordIdArrayView prevWordIds = prevWordsInfo->getPrevWordIds(
+            mDictionaryStructureWithBufferPolicy.get(), &prevWordIdArray,
             true /* tryLowerCaseSearch */);
-    const WordIdArrayView prevWordIdArrayView = WordIdArrayView::fromArray(prevWordIds);
-    NgramListenerForPrediction listener(prevWordsInfo, prevWordIdArrayView, outSuggestionResults,
+    NgramListenerForPrediction listener(prevWordsInfo, prevWordIds, outSuggestionResults,
             mDictionaryStructureWithBufferPolicy.get());
-    mDictionaryStructureWithBufferPolicy->iterateNgramEntries(prevWordIdArrayView, &listener);
+    mDictionaryStructureWithBufferPolicy->iterateNgramEntries(prevWordIds, &listener);
 }
 
-int Dictionary::getProbability(const int *word, int length) const {
-    return getNgramProbability(nullptr /* prevWordsInfo */, word, length);
+int Dictionary::getProbability(const CodePointArrayView codePoints) const {
+    return getNgramProbability(nullptr /* prevWordsInfo */, codePoints);
 }
 
-int Dictionary::getMaxProbabilityOfExactMatches(const int *word, int length) const {
+int Dictionary::getMaxProbabilityOfExactMatches(const CodePointArrayView codePoints) const {
     TimeKeeper::setCurrentTime();
     return DictionaryUtils::getMaxProbabilityOfExactMatches(
-            mDictionaryStructureWithBufferPolicy.get(), word, length);
+            mDictionaryStructureWithBufferPolicy.get(), codePoints);
 }
 
-int Dictionary::getNgramProbability(const PrevWordsInfo *const prevWordsInfo, const int *word,
-        int length) const {
+int Dictionary::getNgramProbability(const PrevWordsInfo *const prevWordsInfo,
+        const CodePointArrayView codePoints) const {
     TimeKeeper::setCurrentTime();
-    int wordId = mDictionaryStructureWithBufferPolicy->getWordId(
-            CodePointArrayView(word, length), false /* forceLowerCaseSearch */);
+    const int wordId = mDictionaryStructureWithBufferPolicy->getWordId(codePoints,
+            false /* forceLowerCaseSearch */);
     if (wordId == NOT_A_WORD_ID) return NOT_A_PROBABILITY;
     if (!prevWordsInfo) {
         return getDictionaryStructurePolicy()->getProbabilityOfWord(WordIdArrayView(), wordId);
     }
-    WordIdArray<MAX_PREV_WORD_COUNT_FOR_N_GRAM> prevWordIds;
-    prevWordsInfo->getPrevWordIds(mDictionaryStructureWithBufferPolicy.get(), prevWordIds.data(),
+    WordIdArray<MAX_PREV_WORD_COUNT_FOR_N_GRAM> prevWordIdArray;
+    const WordIdArrayView prevWordIds = prevWordsInfo->getPrevWordIds
+            (mDictionaryStructureWithBufferPolicy.get(), &prevWordIdArray,
             true /* tryLowerCaseSearch */);
-    return getDictionaryStructurePolicy()->getProbabilityOfWord(
-            IntArrayView::fromArray(prevWordIds), wordId);
+    return getDictionaryStructurePolicy()->getProbabilityOfWord(prevWordIds, wordId);
 }
 
-bool Dictionary::addUnigramEntry(const int *const word, const int length,
+bool Dictionary::addUnigramEntry(const CodePointArrayView codePoints,
         const UnigramProperty *const unigramProperty) {
     if (unigramProperty->representsBeginningOfSentence()
             && !mDictionaryStructureWithBufferPolicy->getHeaderStructurePolicy()
@@ -137,14 +137,12 @@
         return false;
     }
     TimeKeeper::setCurrentTime();
-    return mDictionaryStructureWithBufferPolicy->addUnigramEntry(CodePointArrayView(word, length),
-            unigramProperty);
+    return mDictionaryStructureWithBufferPolicy->addUnigramEntry(codePoints, unigramProperty);
 }
 
-bool Dictionary::removeUnigramEntry(const int *const codePoints, const int codePointCount) {
+bool Dictionary::removeUnigramEntry(const CodePointArrayView codePoints) {
     TimeKeeper::setCurrentTime();
-    return mDictionaryStructureWithBufferPolicy->removeUnigramEntry(
-            CodePointArrayView(codePoints, codePointCount));
+    return mDictionaryStructureWithBufferPolicy->removeUnigramEntry(codePoints);
 }
 
 bool Dictionary::addNgramEntry(const PrevWordsInfo *const prevWordsInfo,
@@ -154,10 +152,9 @@
 }
 
 bool Dictionary::removeNgramEntry(const PrevWordsInfo *const prevWordsInfo,
-        const int *const word, const int length) {
+        const CodePointArrayView codePoints) {
     TimeKeeper::setCurrentTime();
-    return mDictionaryStructureWithBufferPolicy->removeNgramEntry(prevWordsInfo,
-            CodePointArrayView(word, length));
+    return mDictionaryStructureWithBufferPolicy->removeNgramEntry(prevWordsInfo, codePoints);
 }
 
 bool Dictionary::flush(const char *const filePath) {
@@ -182,11 +179,9 @@
             maxResultLength);
 }
 
-const WordProperty Dictionary::getWordProperty(const int *const codePoints,
-        const int codePointCount) {
+const WordProperty Dictionary::getWordProperty(const CodePointArrayView codePoints) {
     TimeKeeper::setCurrentTime();
-    return mDictionaryStructureWithBufferPolicy->getWordProperty(
-            CodePointArrayView(codePoints, codePointCount));
+    return mDictionaryStructureWithBufferPolicy->getWordProperty(codePoints);
 }
 
 int Dictionary::getNextWordAndNextToken(const int token, int *const outCodePoints,
diff --git a/native/jni/src/suggest/core/dictionary/dictionary.h b/native/jni/src/suggest/core/dictionary/dictionary.h
index 0b54f30..f6482ab 100644
--- a/native/jni/src/suggest/core/dictionary/dictionary.h
+++ b/native/jni/src/suggest/core/dictionary/dictionary.h
@@ -72,23 +72,23 @@
     void getPredictions(const PrevWordsInfo *const prevWordsInfo,
             SuggestionResults *const outSuggestionResults) const;
 
-    int getProbability(const int *word, int length) const;
+    int getProbability(const CodePointArrayView codePoints) const;
 
-    int getMaxProbabilityOfExactMatches(const int *word, int length) const;
+    int getMaxProbabilityOfExactMatches(const CodePointArrayView codePoints) const;
 
     int getNgramProbability(const PrevWordsInfo *const prevWordsInfo,
-            const int *word, int length) const;
+            const CodePointArrayView codePoints) const;
 
-    bool addUnigramEntry(const int *const codePoints, const int codePointCount,
+    bool addUnigramEntry(const CodePointArrayView codePoints,
             const UnigramProperty *const unigramProperty);
 
-    bool removeUnigramEntry(const int *const codePoints, const int codePointCount);
+    bool removeUnigramEntry(const CodePointArrayView codePoints);
 
     bool addNgramEntry(const PrevWordsInfo *const prevWordsInfo,
             const BigramProperty *const bigramProperty);
 
-    bool removeNgramEntry(const PrevWordsInfo *const prevWordsInfo, const int *const word,
-            const int length);
+    bool removeNgramEntry(const PrevWordsInfo *const prevWordsInfo,
+            const CodePointArrayView codePoints);
 
     bool flush(const char *const filePath);
 
@@ -99,7 +99,7 @@
     void getProperty(const char *const query, const int queryLength, char *const outResult,
             const int maxResultLength);
 
-    const WordProperty getWordProperty(const int *const codePoints, const int codePointCount);
+    const WordProperty getWordProperty(const CodePointArrayView codePoints);
 
     // Method to iterate all words in the dictionary.
     // The returned token has to be used to get the next word. If token is 0, this method newly
diff --git a/native/jni/src/suggest/core/dictionary/dictionary_utils.cpp b/native/jni/src/suggest/core/dictionary/dictionary_utils.cpp
index 617bf5a..b85f362 100644
--- a/native/jni/src/suggest/core/dictionary/dictionary_utils.cpp
+++ b/native/jni/src/suggest/core/dictionary/dictionary_utils.cpp
@@ -29,28 +29,27 @@
 
 /* static */ int DictionaryUtils::getMaxProbabilityOfExactMatches(
         const DictionaryStructureWithBufferPolicy *const dictionaryStructurePolicy,
-        const int *const codePoints, const int codePointCount) {
+        const CodePointArrayView codePoints) {
     std::vector<DicNode> current;
     std::vector<DicNode> next;
 
     // No prev words information.
     PrevWordsInfo emptyPrevWordsInfo;
-    WordIdArray<MAX_PREV_WORD_COUNT_FOR_N_GRAM> prevWordIds;
-    emptyPrevWordsInfo.getPrevWordIds(dictionaryStructurePolicy, prevWordIds.data(),
-            false /* tryLowerCaseSearch */);
+    WordIdArray<MAX_PREV_WORD_COUNT_FOR_N_GRAM> prevWordIdArray;
+    const WordIdArrayView prevWordIds = emptyPrevWordsInfo.getPrevWordIds(
+            dictionaryStructurePolicy, &prevWordIdArray, false /* tryLowerCaseSearch */);
     current.emplace_back();
-    DicNodeUtils::initAsRoot(dictionaryStructurePolicy,
-            IntArrayView::fromArray(prevWordIds), &current.front());
-    for (int i = 0; i < codePointCount; ++i) {
+    DicNodeUtils::initAsRoot(dictionaryStructurePolicy, prevWordIds, &current.front());
+    for (const int codePoint : codePoints) {
         // The base-lower input is used to ignore case errors and accent errors.
-        const int codePoint = CharUtils::toBaseLowerCase(codePoints[i]);
+        const int baseLowerCodePoint = CharUtils::toBaseLowerCase(codePoint);
         for (const DicNode &dicNode : current) {
-            if (dicNode.isInDigraph() && dicNode.getNodeCodePoint() == codePoint) {
+            if (dicNode.isInDigraph() && dicNode.getNodeCodePoint() == baseLowerCodePoint) {
                 next.emplace_back(dicNode);
                 next.back().advanceDigraphIndex();
                 continue;
             }
-            processChildDicNodes(dictionaryStructurePolicy, codePoint, &dicNode, &next);
+            processChildDicNodes(dictionaryStructurePolicy, baseLowerCodePoint, &dicNode, &next);
         }
         current.clear();
         current.swap(next);
diff --git a/native/jni/src/suggest/core/dictionary/dictionary_utils.h b/native/jni/src/suggest/core/dictionary/dictionary_utils.h
index 358ebf6..4dd21c9 100644
--- a/native/jni/src/suggest/core/dictionary/dictionary_utils.h
+++ b/native/jni/src/suggest/core/dictionary/dictionary_utils.h
@@ -20,6 +20,7 @@
 #include <vector>
 
 #include "defines.h"
+#include "utils/int_array_view.h"
 
 namespace latinime {
 
@@ -30,7 +31,7 @@
  public:
     static int getMaxProbabilityOfExactMatches(
             const DictionaryStructureWithBufferPolicy *const dictionaryStructurePolicy,
-            const int *const codePoints, const int codePointCount);
+            const CodePointArrayView codePoints);
 
  private:
     DISALLOW_IMPLICIT_CONSTRUCTORS(DictionaryUtils);
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 4f58c0c..4d7505a 100644
--- a/native/jni/src/suggest/core/session/dic_traverse_session.cpp
+++ b/native/jni/src/suggest/core/session/dic_traverse_session.cpp
@@ -35,8 +35,8 @@
     mMultiWordCostMultiplier = getDictionaryStructurePolicy()->getHeaderStructurePolicy()
             ->getMultiWordCostMultiplier();
     mSuggestOptions = suggestOptions;
-    prevWordsInfo->getPrevWordIds(getDictionaryStructurePolicy(), mPrevWordsIds.data(),
-            true /* tryLowerCaseSearch */);
+    mPrevWordIdCount = prevWordsInfo->getPrevWordIds(getDictionaryStructurePolicy(),
+            &mPrevWordIdArray, true /* tryLowerCaseSearch */).size();
 }
 
 void DicTraverseSession::setupForGetSuggestions(const ProximityInfo *pInfo,
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 af19907..9f841aa 100644
--- a/native/jni/src/suggest/core/session/dic_traverse_session.h
+++ b/native/jni/src/suggest/core/session/dic_traverse_session.h
@@ -51,12 +51,11 @@
     }
 
     AK_FORCE_INLINE DicTraverseSession(JNIEnv *env, jstring localeStr, bool usesLargeCache)
-            : mProximityInfo(nullptr), mDictionary(nullptr), mSuggestOptions(nullptr),
-              mDicNodesCache(usesLargeCache), mMultiBigramMap(), mInputSize(0), mMaxPointerCount(1),
-              mMultiWordCostMultiplier(1.0f) {
+            : mPrevWordIdCount(0), mProximityInfo(nullptr), mDictionary(nullptr),
+              mSuggestOptions(nullptr), mDicNodesCache(usesLargeCache), mMultiBigramMap(),
+              mInputSize(0), mMaxPointerCount(1), mMultiWordCostMultiplier(1.0f) {
         // NOTE: mProximityInfoStates is an array of instances.
         // No need to initialize it explicitly here.
-        mPrevWordsIds.fill(NOT_A_DICT_POS);
     }
 
     // Non virtual inline destructor -- never inherit this class
@@ -78,7 +77,9 @@
     //--------------------
     const ProximityInfo *getProximityInfo() const { return mProximityInfo; }
     const SuggestOptions *getSuggestOptions() const { return mSuggestOptions; }
-    const WordIdArrayView getPrevWordIds() const { return IntArrayView::fromArray(mPrevWordsIds); }
+    const WordIdArrayView getPrevWordIds() const {
+        return WordIdArrayView::fromArray(mPrevWordIdArray).limit(mPrevWordIdCount);
+    }
     DicNodesCache *getDicTraverseCache() { return &mDicNodesCache; }
     MultiBigramMap *getMultiBigramMap() { return &mMultiBigramMap; }
     const ProximityInfoState *getProximityInfoState(int id) const {
@@ -165,7 +166,8 @@
             const int *const inputYs, const int *const times, const int *const pointerIds,
             const int inputSize, const float maxSpatialDistance, const int maxPointerCount);
 
-    WordIdArray<MAX_PREV_WORD_COUNT_FOR_N_GRAM> mPrevWordsIds;
+    WordIdArray<MAX_PREV_WORD_COUNT_FOR_N_GRAM> mPrevWordIdArray;
+    size_t mPrevWordIdCount;
     const ProximityInfo *mProximityInfo;
     const Dictionary *mDictionary;
     const SuggestOptions *mSuggestOptions;
diff --git a/native/jni/src/suggest/core/session/prev_words_info.h b/native/jni/src/suggest/core/session/prev_words_info.h
index fc9a359..02e82a8 100644
--- a/native/jni/src/suggest/core/session/prev_words_info.h
+++ b/native/jni/src/suggest/core/session/prev_words_info.h
@@ -17,6 +17,8 @@
 #ifndef LATINIME_PREV_WORDS_INFO_H
 #define LATINIME_PREV_WORDS_INFO_H
 
+#include <array>
+
 #include "defines.h"
 #include "suggest/core/policy/dictionary_structure_with_buffer_policy.h"
 #include "utils/char_utils.h"
@@ -27,12 +29,13 @@
 class PrevWordsInfo {
  public:
     // No prev word information.
-    PrevWordsInfo() {
+    PrevWordsInfo() : mPrevWordCount(0) {
         clear();
     }
 
-    PrevWordsInfo(PrevWordsInfo &&prevWordsInfo) {
-        for (size_t i = 0; i < NELEMS(mPrevWordCodePoints); ++i) {
+    PrevWordsInfo(PrevWordsInfo &&prevWordsInfo)
+            : mPrevWordCount(prevWordsInfo.mPrevWordCount) {
+        for (size_t i = 0; i < mPrevWordCount; ++i) {
             mPrevWordCodePointCount[i] = prevWordsInfo.mPrevWordCodePointCount[i];
             memmove(mPrevWordCodePoints[i], prevWordsInfo.mPrevWordCodePoints[i],
                     sizeof(mPrevWordCodePoints[i][0]) * mPrevWordCodePointCount[i]);
@@ -43,9 +46,10 @@
     // Construct from previous words.
     PrevWordsInfo(const int prevWordCodePoints[][MAX_WORD_LENGTH],
             const int *const prevWordCodePointCount, const bool *const isBeginningOfSentence,
-            const size_t prevWordCount) {
+            const size_t prevWordCount)
+            : mPrevWordCount(std::min(NELEMS(mPrevWordCodePoints), prevWordCount)) {
         clear();
-        for (size_t i = 0; i < std::min(NELEMS(mPrevWordCodePoints), prevWordCount); ++i) {
+        for (size_t i = 0; i < mPrevWordCount; ++i) {
             if (prevWordCodePointCount[i] < 0 || prevWordCodePointCount[i] > MAX_WORD_LENGTH) {
                 continue;
             }
@@ -58,7 +62,7 @@
 
     // Construct from a previous word.
     PrevWordsInfo(const int *const prevWordCodePoints, const int prevWordCodePointCount,
-            const bool isBeginningOfSentence) {
+            const bool isBeginningOfSentence) : mPrevWordCount(1) {
         clear();
         if (prevWordCodePointCount > MAX_WORD_LENGTH || !prevWordCodePoints) {
             return;
@@ -79,26 +83,29 @@
         return false;
     }
 
-    void getPrevWordIds(const DictionaryStructureWithBufferPolicy *const dictStructurePolicy,
-            int *const outPrevWordIds, const bool tryLowerCaseSearch) const {
-        for (size_t i = 0; i < NELEMS(mPrevWordCodePoints); ++i) {
-            outPrevWordIds[i] = getWordId(dictStructurePolicy,
+    template<size_t N>
+    const WordIdArrayView getPrevWordIds(
+            const DictionaryStructureWithBufferPolicy *const dictStructurePolicy,
+            std::array<int, N> *const prevWordIdBuffer, const bool tryLowerCaseSearch) const {
+        for (size_t i = 0; i < std::min(mPrevWordCount, N); ++i) {
+            prevWordIdBuffer->at(i) = getWordId(dictStructurePolicy,
                     mPrevWordCodePoints[i], mPrevWordCodePointCount[i],
                     mIsBeginningOfSentence[i], tryLowerCaseSearch);
         }
+        return WordIdArrayView::fromArray(*prevWordIdBuffer).limit(mPrevWordCount);
     }
 
     // n is 1-indexed.
-    const CodePointArrayView getNthPrevWordCodePoints(const int n) const {
-        if (n <= 0 || n > MAX_PREV_WORD_COUNT_FOR_N_GRAM) {
+    const CodePointArrayView getNthPrevWordCodePoints(const size_t n) const {
+        if (n <= 0 || n > mPrevWordCount) {
             return CodePointArrayView();
         }
         return CodePointArrayView(mPrevWordCodePoints[n - 1], mPrevWordCodePointCount[n - 1]);
     }
 
     // n is 1-indexed.
-    bool isNthPrevWordBeginningOfSentence(const int n) const {
-        if (n <= 0 || n > MAX_PREV_WORD_COUNT_FOR_N_GRAM) {
+    bool isNthPrevWordBeginningOfSentence(const size_t n) const {
+        if (n <= 0 || n > mPrevWordCount) {
             return false;
         }
         return mIsBeginningOfSentence[n - 1];
@@ -142,6 +149,7 @@
         }
     }
 
+    const size_t mPrevWordCount;
     int mPrevWordCodePoints[MAX_PREV_WORD_COUNT_FOR_N_GRAM][MAX_WORD_LENGTH];
     int mPrevWordCodePointCount[MAX_PREV_WORD_COUNT_FOR_N_GRAM];
     bool mIsBeginningOfSentence[MAX_PREV_WORD_COUNT_FOR_N_GRAM];
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/backward/v402/ver4_patricia_trie_policy.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/backward/v402/ver4_patricia_trie_policy.cpp
index dfc3d2d..ee14037 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/backward/v402/ver4_patricia_trie_policy.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/backward/v402/ver4_patricia_trie_policy.cpp
@@ -268,8 +268,8 @@
         return false;
     }
     const CodePointArrayView codePointArrayView(codePointsToAdd, codePointCountToAdd);
-    if (mUpdatingHelper.addUnigramWord(&readingHelper, codePointArrayView.data(),
-            codePointArrayView.size(), unigramProperty, &addedNewUnigram)) {
+    if (mUpdatingHelper.addUnigramWord(&readingHelper, codePointArrayView, unigramProperty,
+            &addedNewUnigram)) {
         if (addedNewUnigram && !unigramProperty->representsBeginningOfSentence()) {
             mUnigramCount++;
         }
@@ -283,8 +283,8 @@
             }
             for (const auto &shortcut : unigramProperty->getShortcuts()) {
                 if (!mUpdatingHelper.addShortcutTarget(wordPos,
-                        shortcut.getTargetCodePoints()->data(),
-                        shortcut.getTargetCodePoints()->size(), shortcut.getProbability())) {
+                        CodePointArrayView(*shortcut.getTargetCodePoints()),
+                        shortcut.getProbability())) {
                     AKLOGE("Cannot add new shortcut target. PtNodePos: %d, length: %zd, "
                             "probability: %d", wordPos, shortcut.getTargetCodePoints()->size(),
                             shortcut.getProbability());
@@ -332,8 +332,12 @@
                 "length: %zd", bigramProperty->getTargetCodePoints()->size());
         return false;
     }
-    int prevWordIds[MAX_PREV_WORD_COUNT_FOR_N_GRAM];
-    prevWordsInfo->getPrevWordIds(this, prevWordIds, false /* tryLowerCaseSearch */);
+    WordIdArray<MAX_PREV_WORD_COUNT_FOR_N_GRAM> prevWordIdArray;
+    const WordIdArrayView prevWordIds = prevWordsInfo->getPrevWordIds(this, &prevWordIdArray,
+            false /* tryLowerCaseSearch */);
+    if (prevWordIds.empty()) {
+        return false;
+    }
     if (prevWordIds[0] == NOT_A_WORD_ID) {
         if (prevWordsInfo->isNthPrevWordBeginningOfSentence(1 /* n */)) {
             const std::vector<UnigramProperty::ShortcutProperty> shortcuts;
@@ -347,7 +351,7 @@
                 return false;
             }
             // Refresh word ids.
-            prevWordsInfo->getPrevWordIds(this, prevWordIds, false /* tryLowerCaseSearch */);
+            prevWordsInfo->getPrevWordIds(this, &prevWordIdArray, false /* tryLowerCaseSearch */);
         } else {
             return false;
         }
@@ -390,9 +394,10 @@
         AKLOGE("word is too long to remove n-gram entry form the dictionary. length: %zd",
                 wordCodePoints.size());
     }
-    int prevWordIds[MAX_PREV_WORD_COUNT_FOR_N_GRAM];
-    prevWordsInfo->getPrevWordIds(this, prevWordIds, false /* tryLowerCaseSerch */);
-    if (prevWordIds[0] == NOT_A_WORD_ID) {
+    WordIdArray<MAX_PREV_WORD_COUNT_FOR_N_GRAM> prevWordIdArray;
+    const WordIdArrayView prevWordIds = prevWordsInfo->getPrevWordIds(this, &prevWordIdArray,
+            false /* tryLowerCaseSerch */);
+    if (prevWordIds.firstOrDefault(NOT_A_WORD_ID) == NOT_A_WORD_ID) {
         return false;
     }
     const int wordPos = getTerminalPtNodePosFromWordId(getWordId(wordCodePoints,
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/bigram/bigram_list_read_write_utils.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/bigram/bigram_list_read_write_utils.cpp
index f7fd5c0..1b2f857 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/bigram/bigram_list_read_write_utils.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/bigram/bigram_list_read_write_utils.cpp
@@ -39,32 +39,31 @@
         BigramListReadWriteUtils::MASK_ATTRIBUTE_PROBABILITY = 0x0F;
 
 /* static */ bool BigramListReadWriteUtils::getBigramEntryPropertiesAndAdvancePosition(
-        const uint8_t *const bigramsBuf, const int bufSize, BigramFlags *const outBigramFlags,
+        const ReadOnlyByteArrayView buffer, BigramFlags *const outBigramFlags,
         int *const outTargetPtNodePos, int *const bigramEntryPos) {
-    if (bufSize <= *bigramEntryPos) {
-        AKLOGE("Read invalid pos in getBigramEntryPropertiesAndAdvancePosition(). bufSize: %d, "
-                "bigramEntryPos: %d.", bufSize, *bigramEntryPos);
+    if (static_cast<int>(buffer.size()) <= *bigramEntryPos) {
+        AKLOGE("Read invalid pos in getBigramEntryPropertiesAndAdvancePosition(). bufSize: %zd, "
+                "bigramEntryPos: %d.", buffer.size(), *bigramEntryPos);
         return false;
     }
-    const BigramFlags bigramFlags = ByteArrayUtils::readUint8AndAdvancePosition(bigramsBuf,
+    const BigramFlags bigramFlags = ByteArrayUtils::readUint8AndAdvancePosition(buffer.data(),
             bigramEntryPos);
     if (outBigramFlags) {
         *outBigramFlags = bigramFlags;
     }
-    const int targetPos = getBigramAddressAndAdvancePosition(bigramsBuf, bigramFlags,
-            bigramEntryPos);
+    const int targetPos = getBigramAddressAndAdvancePosition(buffer, bigramFlags, bigramEntryPos);
     if (outTargetPtNodePos) {
         *outTargetPtNodePos = targetPos;
     }
     return true;
 }
 
-/* static */ bool BigramListReadWriteUtils::skipExistingBigrams(const uint8_t *const bigramsBuf,
-        const int bufSize, int *const bigramListPos) {
+/* static */ bool BigramListReadWriteUtils::skipExistingBigrams(const ReadOnlyByteArrayView buffer,
+        int *const bigramListPos) {
     BigramFlags flags;
     do {
-        if (!getBigramEntryPropertiesAndAdvancePosition(bigramsBuf, bufSize, &flags,
-                0 /* outTargetPtNodePos */, bigramListPos)) {
+        if (!getBigramEntryPropertiesAndAdvancePosition(buffer, &flags, 0 /* outTargetPtNodePos */,
+                bigramListPos)) {
             return false;
         }
     } while(hasNext(flags));
@@ -72,18 +71,18 @@
 }
 
 /* static */ int BigramListReadWriteUtils::getBigramAddressAndAdvancePosition(
-        const uint8_t *const bigramsBuf, const BigramFlags flags, int *const pos) {
+        const ReadOnlyByteArrayView buffer, const BigramFlags flags, int *const pos) {
     int offset = 0;
     const int origin = *pos;
     switch (MASK_ATTRIBUTE_ADDRESS_TYPE & flags) {
         case FLAG_ATTRIBUTE_ADDRESS_TYPE_ONEBYTE:
-            offset = ByteArrayUtils::readUint8AndAdvancePosition(bigramsBuf, pos);
+            offset = ByteArrayUtils::readUint8AndAdvancePosition(buffer.data(), pos);
             break;
         case FLAG_ATTRIBUTE_ADDRESS_TYPE_TWOBYTES:
-            offset = ByteArrayUtils::readUint16AndAdvancePosition(bigramsBuf, pos);
+            offset = ByteArrayUtils::readUint16AndAdvancePosition(buffer.data(), pos);
             break;
         case FLAG_ATTRIBUTE_ADDRESS_TYPE_THREEBYTES:
-            offset = ByteArrayUtils::readUint24AndAdvancePosition(bigramsBuf, pos);
+            offset = ByteArrayUtils::readUint24AndAdvancePosition(buffer.data(), pos);
             break;
     }
     if (isOffsetNegative(flags)) {
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/bigram/bigram_list_read_write_utils.h b/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/bigram/bigram_list_read_write_utils.h
index 10f93fb..a0f7d5e 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/bigram/bigram_list_read_write_utils.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/bigram/bigram_list_read_write_utils.h
@@ -21,6 +21,7 @@
 #include <cstdlib>
 
 #include "defines.h"
+#include "utils/byte_array_view.h"
 
 namespace latinime {
 
@@ -30,8 +31,8 @@
 public:
    typedef uint8_t BigramFlags;
 
-   static bool getBigramEntryPropertiesAndAdvancePosition(const uint8_t *const bigramsBuf,
-           const int bufSize, BigramFlags *const outBigramFlags, int *const outTargetPtNodePos,
+   static bool getBigramEntryPropertiesAndAdvancePosition(const ReadOnlyByteArrayView buffer,
+           BigramFlags *const outBigramFlags, int *const outTargetPtNodePos,
            int *const bigramEntryPos);
 
    static AK_FORCE_INLINE int getProbabilityFromFlags(const BigramFlags flags) {
@@ -43,8 +44,7 @@
    }
 
    // Bigrams reading methods
-   static bool skipExistingBigrams(const uint8_t *const bigramsBuf, const int bufSize,
-           int *const bigramListPos);
+   static bool skipExistingBigrams(const ReadOnlyByteArrayView buffer, int *const bigramListPos);
 
 private:
    DISALLOW_IMPLICIT_CONSTRUCTORS(BigramListReadWriteUtils);
@@ -61,7 +61,7 @@
        return (flags & FLAG_ATTRIBUTE_OFFSET_NEGATIVE) != 0;
    }
 
-   static int getBigramAddressAndAdvancePosition(const uint8_t *const bigramsBuf,
+   static int getBigramAddressAndAdvancePosition(const ReadOnlyByteArrayView buffer,
            const BigramFlags flags, int *const pos);
 };
 } // namespace latinime
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/dynamic_pt_reading_helper.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/dynamic_pt_reading_helper.cpp
index 086d98b..40782a4 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/dynamic_pt_reading_helper.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/dynamic_pt_reading_helper.cpp
@@ -218,9 +218,9 @@
 }
 
 int DynamicPtReadingHelper::getTerminalPtNodePositionOfWord(const int *const inWord,
-        const int length, const bool forceLowerCaseSearch) {
+        const size_t length, const bool forceLowerCaseSearch) {
     int searchCodePoints[length];
-    for (int i = 0; i < length; ++i) {
+    for (size_t i = 0; i < length; ++i) {
         searchCodePoints[i] = forceLowerCaseSearch ? CharUtils::toLowerCase(inWord[i]) : inWord[i];
     }
     while (!isEnd()) {
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/dynamic_pt_reading_helper.h b/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/dynamic_pt_reading_helper.h
index b726258..9a7abc9 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/dynamic_pt_reading_helper.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/dynamic_pt_reading_helper.h
@@ -138,12 +138,12 @@
     }
 
     // Return code point count exclude the last read node's code points.
-    AK_FORCE_INLINE int getPrevTotalCodePointCount() const {
+    AK_FORCE_INLINE size_t getPrevTotalCodePointCount() const {
         return mReadingState.mTotalCodePointCountSinceInitialization;
     }
 
     // Return code point count include the last read node's code points.
-    AK_FORCE_INLINE int getTotalCodePointCount(const PtNodeParams &ptNodeParams) const {
+    AK_FORCE_INLINE size_t getTotalCodePointCount(const PtNodeParams &ptNodeParams) const {
         return mReadingState.mTotalCodePointCountSinceInitialization
                 + ptNodeParams.getCodePointCount();
     }
@@ -214,7 +214,7 @@
     int getCodePointsAndProbabilityAndReturnCodePointCount(const int maxCodePointCount,
             int *const outCodePoints, int *const outUnigramProbability);
 
-    int getTerminalPtNodePositionOfWord(const int *const inWord, const int length,
+    int getTerminalPtNodePositionOfWord(const int *const inWord, const size_t length,
             const bool forceLowerCaseSearch);
 
  private:
@@ -234,7 +234,7 @@
         int mPos;
         // Remaining node count in the current array.
         int mRemainingPtNodeCountInThisArray;
-        int mTotalCodePointCountSinceInitialization;
+        size_t mTotalCodePointCountSinceInitialization;
         // Counter of PtNodes used to avoid infinite loops caused by broken or malicious links.
         int mTotalPtNodeIndexInThisArrayChain;
         // Counter of PtNode arrays used to avoid infinite loops caused by cyclic links of empty
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/dynamic_pt_updating_helper.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/dynamic_pt_updating_helper.cpp
index 3c62e2e..3b58d7d 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/dynamic_pt_updating_helper.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/dynamic_pt_updating_helper.cpp
@@ -28,17 +28,16 @@
 
 const int DynamicPtUpdatingHelper::CHILDREN_POSITION_FIELD_SIZE = 3;
 
-bool DynamicPtUpdatingHelper::addUnigramWord(
-        DynamicPtReadingHelper *const readingHelper,
-        const int *const wordCodePoints, const int codePointCount,
-        const UnigramProperty *const unigramProperty, bool *const outAddedNewUnigram) {
+bool DynamicPtUpdatingHelper::addUnigramWord(DynamicPtReadingHelper *const readingHelper,
+        const CodePointArrayView wordCodePoints, const UnigramProperty *const unigramProperty,
+        bool *const outAddedNewUnigram) {
     int parentPos = NOT_A_DICT_POS;
     while (!readingHelper->isEnd()) {
         const PtNodeParams ptNodeParams(readingHelper->getPtNodeParams());
         if (!ptNodeParams.isValid()) {
             break;
         }
-        const int matchedCodePointCount = readingHelper->getPrevTotalCodePointCount();
+        const size_t matchedCodePointCount = readingHelper->getPrevTotalCodePointCount();
         if (!readingHelper->isMatchedCodePoint(ptNodeParams, 0 /* index */,
                 wordCodePoints[matchedCodePointCount])) {
             // The first code point is different from target code point. Skip this node and read
@@ -47,26 +46,25 @@
             continue;
         }
         // Check following merged node code points.
-        const int nodeCodePointCount = ptNodeParams.getCodePointCount();
-        for (int j = 1; j < nodeCodePointCount; ++j) {
-            const int nextIndex = matchedCodePointCount + j;
-            if (nextIndex >= codePointCount || !readingHelper->isMatchedCodePoint(ptNodeParams, j,
-                    wordCodePoints[matchedCodePointCount + j])) {
+        const size_t nodeCodePointCount = ptNodeParams.getCodePointArrayView().size();
+        for (size_t j = 1; j < nodeCodePointCount; ++j) {
+            const size_t nextIndex = matchedCodePointCount + j;
+            if (nextIndex >= wordCodePoints.size()
+                    || !readingHelper->isMatchedCodePoint(ptNodeParams, j,
+                            wordCodePoints[matchedCodePointCount + j])) {
                 *outAddedNewUnigram = true;
                 return reallocatePtNodeAndAddNewPtNodes(&ptNodeParams, j, unigramProperty,
-                        wordCodePoints + matchedCodePointCount,
-                        codePointCount - matchedCodePointCount);
+                        wordCodePoints.skip(matchedCodePointCount));
             }
         }
         // All characters are matched.
-        if (codePointCount == readingHelper->getTotalCodePointCount(ptNodeParams)) {
+        if (wordCodePoints.size() == readingHelper->getTotalCodePointCount(ptNodeParams)) {
             return setPtNodeProbability(&ptNodeParams, unigramProperty, outAddedNewUnigram);
         }
         if (!ptNodeParams.hasChildren()) {
             *outAddedNewUnigram = true;
             return createChildrenPtNodeArrayAndAChildPtNode(&ptNodeParams, unigramProperty,
-                    wordCodePoints + readingHelper->getTotalCodePointCount(ptNodeParams),
-                    codePointCount - readingHelper->getTotalCodePointCount(ptNodeParams));
+                    wordCodePoints.skip(readingHelper->getTotalCodePointCount(ptNodeParams)));
         }
         // Advance to the children nodes.
         parentPos = ptNodeParams.getHeadPos();
@@ -79,9 +77,8 @@
     int pos = readingHelper->getPosOfLastForwardLinkField();
     *outAddedNewUnigram = true;
     return createAndInsertNodeIntoPtNodeArray(parentPos,
-            wordCodePoints + readingHelper->getPrevTotalCodePointCount(),
-            codePointCount - readingHelper->getPrevTotalCodePointCount(),
-            unigramProperty, &pos);
+            wordCodePoints.skip(readingHelper->getPrevTotalCodePointCount()), unigramProperty,
+            &pos);
 }
 
 bool DynamicPtUpdatingHelper::addNgramEntry(const PtNodePosArrayView prevWordsPtNodePos,
@@ -120,23 +117,21 @@
 }
 
 bool DynamicPtUpdatingHelper::addShortcutTarget(const int wordPos,
-        const int *const targetCodePoints, const int targetCodePointCount,
-        const int shortcutProbability) {
+        const CodePointArrayView targetCodePoints, const int shortcutProbability) {
     const PtNodeParams ptNodeParams(mPtNodeReader->fetchPtNodeParamsInBufferFromPtNodePos(wordPos));
-    return mPtNodeWriter->addShortcutTarget(&ptNodeParams, targetCodePoints, targetCodePointCount,
-            shortcutProbability);
+    return mPtNodeWriter->addShortcutTarget(&ptNodeParams, targetCodePoints.data(),
+            targetCodePoints.size(), shortcutProbability);
 }
 
 bool DynamicPtUpdatingHelper::createAndInsertNodeIntoPtNodeArray(const int parentPos,
-        const int *const nodeCodePoints, const int nodeCodePointCount,
-        const UnigramProperty *const unigramProperty, int *const forwardLinkFieldPos) {
+        const CodePointArrayView ptNodeCodePoints, const UnigramProperty *const unigramProperty,
+        int *const forwardLinkFieldPos) {
     const int newPtNodeArrayPos = mBuffer->getTailPosition();
     if (!DynamicPtWritingUtils::writeForwardLinkPositionAndAdvancePosition(mBuffer,
             newPtNodeArrayPos, forwardLinkFieldPos)) {
         return false;
     }
-    return createNewPtNodeArrayWithAChildPtNode(parentPos, nodeCodePoints, nodeCodePointCount,
-            unigramProperty);
+    return createNewPtNodeArrayWithAChildPtNode(parentPos, ptNodeCodePoints, unigramProperty);
 }
 
 bool DynamicPtUpdatingHelper::setPtNodeProbability(const PtNodeParams *const originalPtNodeParams,
@@ -153,8 +148,7 @@
         const PtNodeParams ptNodeParamsToWrite(getUpdatedPtNodeParams(originalPtNodeParams,
                 unigramProperty->isNotAWord(), unigramProperty->isBlacklisted(),
                 true /* isTerminal */, originalPtNodeParams->getParentPos(),
-                originalPtNodeParams->getCodePointCount(), originalPtNodeParams->getCodePoints(),
-                unigramProperty->getProbability()));
+                originalPtNodeParams->getCodePointArrayView(), unigramProperty->getProbability()));
         if (!mPtNodeWriter->writeNewTerminalPtNodeAndAdvancePosition(&ptNodeParamsToWrite,
                 unigramProperty, &writingPos)) {
             return false;
@@ -168,17 +162,17 @@
 
 bool DynamicPtUpdatingHelper::createChildrenPtNodeArrayAndAChildPtNode(
         const PtNodeParams *const parentPtNodeParams, const UnigramProperty *const unigramProperty,
-        const int *const codePoints, const int codePointCount) {
+        const CodePointArrayView codePoints) {
     const int newPtNodeArrayPos = mBuffer->getTailPosition();
     if (!mPtNodeWriter->updateChildrenPosition(parentPtNodeParams, newPtNodeArrayPos)) {
         return false;
     }
     return createNewPtNodeArrayWithAChildPtNode(parentPtNodeParams->getHeadPos(), codePoints,
-            codePointCount, unigramProperty);
+            unigramProperty);
 }
 
 bool DynamicPtUpdatingHelper::createNewPtNodeArrayWithAChildPtNode(
-        const int parentPtNodePos, const int *const nodeCodePoints, const int nodeCodePointCount,
+        const int parentPtNodePos, const CodePointArrayView ptNodeCodePoints,
         const UnigramProperty *const unigramProperty) {
     int writingPos = mBuffer->getTailPosition();
     if (!DynamicPtWritingUtils::writePtNodeArraySizeAndAdvancePosition(mBuffer,
@@ -187,8 +181,7 @@
     }
     const PtNodeParams ptNodeParamsToWrite(getPtNodeParamsForNewPtNode(
             unigramProperty->isNotAWord(), unigramProperty->isBlacklisted(), true /* isTerminal */,
-            parentPtNodePos, nodeCodePointCount, nodeCodePoints,
-            unigramProperty->getProbability()));
+            parentPtNodePos, ptNodeCodePoints, unigramProperty->getProbability()));
     if (!mPtNodeWriter->writeNewTerminalPtNodeAndAdvancePosition(&ptNodeParamsToWrite,
             unigramProperty, &writingPos)) {
         return false;
@@ -202,9 +195,9 @@
 
 // Returns whether the dictionary updating was succeeded or not.
 bool DynamicPtUpdatingHelper::reallocatePtNodeAndAddNewPtNodes(
-        const PtNodeParams *const reallocatingPtNodeParams, const int overlappingCodePointCount,
-        const UnigramProperty *const unigramProperty, const int *const newNodeCodePoints,
-        const int newNodeCodePointCount) {
+        const PtNodeParams *const reallocatingPtNodeParams, const size_t overlappingCodePointCount,
+        const UnigramProperty *const unigramProperty,
+        const CodePointArrayView newPtNodeCodePoints) {
     // When addsExtraChild is true, split the reallocating PtNode and add new child.
     // Reallocating PtNode: abcde, newNode: abcxy.
     // abc (1st, not terminal) __ de (2nd)
@@ -212,16 +205,18 @@
     // Otherwise, this method makes 1st part terminal and write information in unigramProperty.
     // Reallocating PtNode: abcde, newNode: abc.
     // abc (1st, terminal) __ de (2nd)
-    const bool addsExtraChild = newNodeCodePointCount > overlappingCodePointCount;
+    const bool addsExtraChild = newPtNodeCodePoints.size() > overlappingCodePointCount;
     const int firstPartOfReallocatedPtNodePos = mBuffer->getTailPosition();
     int writingPos = firstPartOfReallocatedPtNodePos;
     // Write the 1st part of the reallocating node. The children position will be updated later
     // with actual children position.
+    const CodePointArrayView firstPtNodeCodePoints =
+            reallocatingPtNodeParams->getCodePointArrayView().limit(overlappingCodePointCount);
     if (addsExtraChild) {
         const PtNodeParams ptNodeParamsToWrite(getPtNodeParamsForNewPtNode(
                 false /* isNotAWord */, false /* isBlacklisted */, false /* isTerminal */,
-                reallocatingPtNodeParams->getParentPos(), overlappingCodePointCount,
-                reallocatingPtNodeParams->getCodePoints(), NOT_A_PROBABILITY));
+                reallocatingPtNodeParams->getParentPos(), firstPtNodeCodePoints,
+                NOT_A_PROBABILITY));
         if (!mPtNodeWriter->writePtNodeAndAdvancePosition(&ptNodeParamsToWrite, &writingPos)) {
             return false;
         }
@@ -229,8 +224,7 @@
         const PtNodeParams ptNodeParamsToWrite(getPtNodeParamsForNewPtNode(
                 unigramProperty->isNotAWord(), unigramProperty->isBlacklisted(),
                 true /* isTerminal */, reallocatingPtNodeParams->getParentPos(),
-                overlappingCodePointCount, reallocatingPtNodeParams->getCodePoints(),
-                unigramProperty->getProbability()));
+                firstPtNodeCodePoints, unigramProperty->getProbability()));
         if (!mPtNodeWriter->writeNewTerminalPtNodeAndAdvancePosition(&ptNodeParamsToWrite,
                 unigramProperty, &writingPos)) {
             return false;
@@ -248,8 +242,7 @@
     const PtNodeParams childPartPtNodeParams(getUpdatedPtNodeParams(reallocatingPtNodeParams,
             reallocatingPtNodeParams->isNotAWord(), reallocatingPtNodeParams->isBlacklisted(),
             reallocatingPtNodeParams->isTerminal(), firstPartOfReallocatedPtNodePos,
-            reallocatingPtNodeParams->getCodePointCount() - overlappingCodePointCount,
-            reallocatingPtNodeParams->getCodePoints() + overlappingCodePointCount,
+            reallocatingPtNodeParams->getCodePointArrayView().skip(overlappingCodePointCount),
             reallocatingPtNodeParams->getProbability()));
     if (!mPtNodeWriter->writePtNodeAndAdvancePosition(&childPartPtNodeParams, &writingPos)) {
         return false;
@@ -258,8 +251,8 @@
         const PtNodeParams extraChildPtNodeParams(getPtNodeParamsForNewPtNode(
                 unigramProperty->isNotAWord(), unigramProperty->isBlacklisted(),
                 true /* isTerminal */, firstPartOfReallocatedPtNodePos,
-                newNodeCodePointCount - overlappingCodePointCount,
-                newNodeCodePoints + overlappingCodePointCount, unigramProperty->getProbability()));
+                newPtNodeCodePoints.skip(overlappingCodePointCount),
+                unigramProperty->getProbability()));
         if (!mPtNodeWriter->writeNewTerminalPtNodeAndAdvancePosition(&extraChildPtNodeParams,
                 unigramProperty, &writingPos)) {
             return false;
@@ -282,26 +275,24 @@
 }
 
 const PtNodeParams DynamicPtUpdatingHelper::getUpdatedPtNodeParams(
-        const PtNodeParams *const originalPtNodeParams,
-        const bool isNotAWord, const bool isBlacklisted, const bool isTerminal, const int parentPos,
-        const int codePointCount, const int *const codePoints, const int probability) const {
+        const PtNodeParams *const originalPtNodeParams, const bool isNotAWord,
+        const bool isBlacklisted, const bool isTerminal, const int parentPos,
+        const CodePointArrayView codePoints, const int probability) const {
     const PatriciaTrieReadingUtils::NodeFlags flags = PatriciaTrieReadingUtils::createAndGetFlags(
             isBlacklisted, isNotAWord, isTerminal, false /* hasShortcutTargets */,
-            false /* hasBigrams */, codePointCount > 1 /* hasMultipleChars */,
+            false /* hasBigrams */, codePoints.size() > 1u /* hasMultipleChars */,
             CHILDREN_POSITION_FIELD_SIZE);
-    return PtNodeParams(originalPtNodeParams, flags, parentPos, codePointCount, codePoints,
-            probability);
+    return PtNodeParams(originalPtNodeParams, flags, parentPos, codePoints, probability);
 }
 
-const PtNodeParams DynamicPtUpdatingHelper::getPtNodeParamsForNewPtNode(
-        const bool isNotAWord, const bool isBlacklisted, const bool isTerminal,
-        const int parentPos, const int codePointCount, const int *const codePoints,
-        const int probability) const {
+const PtNodeParams DynamicPtUpdatingHelper::getPtNodeParamsForNewPtNode(const bool isNotAWord,
+        const bool isBlacklisted, const bool isTerminal, const int parentPos,
+        const CodePointArrayView codePoints, const int probability) const {
     const PatriciaTrieReadingUtils::NodeFlags flags = PatriciaTrieReadingUtils::createAndGetFlags(
             isBlacklisted, isNotAWord, isTerminal, false /* hasShortcutTargets */,
-            false /* hasBigrams */, codePointCount > 1 /* hasMultipleChars */,
+            false /* hasBigrams */, codePoints.size() > 1u /* hasMultipleChars */,
             CHILDREN_POSITION_FIELD_SIZE);
-    return PtNodeParams(flags, parentPos, codePointCount, codePoints, probability);
+    return PtNodeParams(flags, parentPos, codePoints, probability);
 }
 
 } // namespace latinime
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/dynamic_pt_updating_helper.h b/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/dynamic_pt_updating_helper.h
index 97c05c1..710047e 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/dynamic_pt_updating_helper.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/dynamic_pt_updating_helper.h
@@ -40,19 +40,21 @@
 
     // Add a word to the dictionary. If the word already exists, update the probability.
     bool addUnigramWord(DynamicPtReadingHelper *const readingHelper,
-            const int *const wordCodePoints, const int codePointCount,
-            const UnigramProperty *const unigramProperty, bool *const outAddedNewUnigram);
+            const CodePointArrayView wordCodePoints, const UnigramProperty *const unigramProperty,
+            bool *const outAddedNewUnigram);
 
+    // TODO: Remove after stopping supporting v402.
     // Add an n-gram entry.
     bool addNgramEntry(const PtNodePosArrayView prevWordsPtNodePos, const int wordPos,
             const BigramProperty *const bigramProperty, bool *const outAddedNewEntry);
 
+    // TODO: Remove after stopping supporting v402.
     // Remove an n-gram entry.
     bool removeNgramEntry(const PtNodePosArrayView prevWordsPtNodePos, const int wordPos);
 
     // Add a shortcut target.
-    bool addShortcutTarget(const int wordPos, const int *const targetCodePoints,
-            const int targetCodePointCount, const int shortcutProbability);
+    bool addShortcutTarget(const int wordPos, const CodePointArrayView targetCodePoints,
+            const int shortcutProbability);
 
  private:
     DISALLOW_IMPLICIT_CONSTRUCTORS(DynamicPtUpdatingHelper);
@@ -63,33 +65,32 @@
     const PtNodeReader *const mPtNodeReader;
     PtNodeWriter *const mPtNodeWriter;
 
-    bool createAndInsertNodeIntoPtNodeArray(const int parentPos, const int *const nodeCodePoints,
-            const int nodeCodePointCount, const UnigramProperty *const unigramProperty,
+    bool createAndInsertNodeIntoPtNodeArray(const int parentPos,
+            const CodePointArrayView ptNodeCodePoints, const UnigramProperty *const unigramProperty,
             int *const forwardLinkFieldPos);
 
     bool setPtNodeProbability(const PtNodeParams *const originalPtNodeParams,
             const UnigramProperty *const unigramProperty, bool *const outAddedNewUnigram);
 
     bool createChildrenPtNodeArrayAndAChildPtNode(const PtNodeParams *const parentPtNodeParams,
-            const UnigramProperty *const unigramProperty, const int *const codePoints,
-            const int codePointCount);
+            const UnigramProperty *const unigramProperty,
+            const CodePointArrayView remainingCodePoints);
 
-    bool createNewPtNodeArrayWithAChildPtNode(const int parentPos, const int *const nodeCodePoints,
-            const int nodeCodePointCount, const UnigramProperty *const unigramProperty);
+    bool createNewPtNodeArrayWithAChildPtNode(const int parentPos,
+            const CodePointArrayView ptNodeCodePoints,
+            const UnigramProperty *const unigramProperty);
 
-    bool reallocatePtNodeAndAddNewPtNodes(
-            const PtNodeParams *const reallocatingPtNodeParams, const int overlappingCodePointCount,
-            const UnigramProperty *const unigramProperty, const int *const newNodeCodePoints,
-            const int newNodeCodePointCount);
+    bool reallocatePtNodeAndAddNewPtNodes(const PtNodeParams *const reallocatingPtNodeParams,
+            const size_t overlappingCodePointCount, const UnigramProperty *const unigramProperty,
+            const CodePointArrayView newPtNodeCodePoints);
 
     const PtNodeParams getUpdatedPtNodeParams(const PtNodeParams *const originalPtNodeParams,
             const bool isNotAWord, const bool isBlacklisted, const bool isTerminal,
-            const int parentPos, const int codePointCount,
-            const int *const codePoints, const int probability) const;
+            const int parentPos, const CodePointArrayView codePoints, const int probability) const;
 
     const PtNodeParams getPtNodeParamsForNewPtNode(const bool isNotAWord, const bool isBlacklisted,
-            const bool isTerminal, const int parentPos,
-            const int codePointCount, const int *const codePoints, const int probability) const;
+            const bool isTerminal, const int parentPos, const CodePointArrayView codePoints,
+            const int probability) const;
 };
 } // namespace latinime
 #endif /* LATINIME_DYNAMIC_PATRICIA_TRIE_UPDATING_HELPER_H */
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/pt_node_params.h b/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/pt_node_params.h
index c12fed3..3ff1829 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/pt_node_params.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/pt_node_params.h
@@ -89,9 +89,9 @@
     // Construct new params by updating existing PtNode params.
     PtNodeParams(const PtNodeParams *const ptNodeParams,
             const PatriciaTrieReadingUtils::NodeFlags flags, const int parentPos,
-            const int codePointCount, const int *const codePoints, const int probability)
+            const CodePointArrayView codePoints, const int probability)
             : mHeadPos(ptNodeParams->getHeadPos()), mFlags(flags), mHasMovedFlag(true),
-              mParentPos(parentPos), mCodePointCount(codePointCount), mCodePoints(),
+              mParentPos(parentPos), mCodePointCount(codePoints.size()), mCodePoints(),
               mTerminalIdFieldPos(ptNodeParams->getTerminalIdFieldPos()),
               mTerminalId(ptNodeParams->getTerminalId()),
               mProbabilityFieldPos(ptNodeParams->getProbabilityFieldPos()),
@@ -102,20 +102,20 @@
               mShortcutPos(ptNodeParams->getShortcutPos()),
               mBigramPos(ptNodeParams->getBigramsPos()),
               mSiblingPos(ptNodeParams->getSiblingNodePos()) {
-        memcpy(mCodePoints, codePoints, sizeof(int) * mCodePointCount);
+        memcpy(mCodePoints, codePoints.data(), sizeof(int) * mCodePointCount);
     }
 
     PtNodeParams(const PatriciaTrieReadingUtils::NodeFlags flags, const int parentPos,
-            const int codePointCount, const int *const codePoints, const int probability)
+            const CodePointArrayView codePoints, const int probability)
             : mHeadPos(NOT_A_DICT_POS), mFlags(flags), mHasMovedFlag(true), mParentPos(parentPos),
-              mCodePointCount(codePointCount), mCodePoints(),
+              mCodePointCount(codePoints.size()), mCodePoints(),
               mTerminalIdFieldPos(NOT_A_DICT_POS),
               mTerminalId(Ver4DictConstants::NOT_A_TERMINAL_ID),
               mProbabilityFieldPos(NOT_A_DICT_POS), mProbability(probability),
               mChildrenPosFieldPos(NOT_A_DICT_POS), mChildrenPos(NOT_A_DICT_POS),
               mBigramLinkedNodePos(NOT_A_DICT_POS), mShortcutPos(NOT_A_DICT_POS),
               mBigramPos(NOT_A_DICT_POS), mSiblingPos(NOT_A_DICT_POS) {
-        memcpy(mCodePoints, codePoints, sizeof(int) * mCodePointCount);
+        memcpy(mCodePoints, codePoints.data(), sizeof(int) * mCodePointCount);
     }
 
     AK_FORCE_INLINE bool isValid() const {
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/shortcut/shortcut_list_reading_utils.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/shortcut/shortcut_list_reading_utils.cpp
index 91c7694..7cb7dff 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/shortcut/shortcut_list_reading_utils.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/shortcut/shortcut_list_reading_utils.cpp
@@ -31,21 +31,21 @@
 const int ShortcutListReadingUtils::WHITELIST_SHORTCUT_PROBABILITY = 15;
 
 /* static */ ShortcutListReadingUtils::ShortcutFlags
-        ShortcutListReadingUtils::getFlagsAndForwardPointer(const uint8_t *const dictRoot,
+        ShortcutListReadingUtils::getFlagsAndForwardPointer(const ReadOnlyByteArrayView buffer,
                 int *const pos) {
-    return ByteArrayUtils::readUint8AndAdvancePosition(dictRoot, pos);
+    return ByteArrayUtils::readUint8AndAdvancePosition(buffer.data(), pos);
 }
 
 /* static */ int ShortcutListReadingUtils::getShortcutListSizeAndForwardPointer(
-        const uint8_t *const dictRoot, int *const pos) {
+        const ReadOnlyByteArrayView buffer, int *const pos) {
     // readUint16andAdvancePosition() returns an offset *including* the uint16 field itself.
-    return ByteArrayUtils::readUint16AndAdvancePosition(dictRoot, pos)
+    return ByteArrayUtils::readUint16AndAdvancePosition(buffer.data(), pos)
             - SHORTCUT_LIST_SIZE_FIELD_SIZE;
 }
 
-/* static */ int ShortcutListReadingUtils::readShortcutTarget(
-        const uint8_t *const dictRoot, const int maxLength, int *const outWord, int *const pos) {
-    return ByteArrayUtils::readStringAndAdvancePosition(dictRoot, maxLength, outWord, pos);
+/* static */ int ShortcutListReadingUtils::readShortcutTarget(const ReadOnlyByteArrayView buffer,
+        const int maxLength, int *const outWord, int *const pos) {
+    return ByteArrayUtils::readStringAndAdvancePosition(buffer.data(), maxLength, outWord, pos);
 }
 
 } // namespace latinime
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/shortcut/shortcut_list_reading_utils.h b/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/shortcut/shortcut_list_reading_utils.h
index d065bf7..71cb8cc 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/shortcut/shortcut_list_reading_utils.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/shortcut/shortcut_list_reading_utils.h
@@ -20,6 +20,7 @@
 #include <cstdint>
 
 #include "defines.h"
+#include "utils/byte_array_view.h"
 
 namespace latinime {
 
@@ -27,7 +28,8 @@
  public:
     typedef uint8_t ShortcutFlags;
 
-    static ShortcutFlags getFlagsAndForwardPointer(const uint8_t *const dictRoot, int *const pos);
+    static ShortcutFlags getFlagsAndForwardPointer(const ReadOnlyByteArrayView buffer,
+            int *const pos);
 
     static AK_FORCE_INLINE int getProbabilityFromFlags(const ShortcutFlags flags) {
         return flags & MASK_ATTRIBUTE_PROBABILITY;
@@ -39,14 +41,15 @@
 
     // This method returns the size of the shortcut list region excluding the shortcut list size
     // field at the beginning.
-    static int getShortcutListSizeAndForwardPointer(const uint8_t *const dictRoot, int *const pos);
+    static int getShortcutListSizeAndForwardPointer(const ReadOnlyByteArrayView buffer,
+            int *const pos);
 
     static AK_FORCE_INLINE int getShortcutListSizeFieldSize() {
         return SHORTCUT_LIST_SIZE_FIELD_SIZE;
     }
 
-    static AK_FORCE_INLINE void skipShortcuts(const uint8_t *const dictRoot, int *const pos) {
-        const int shortcutListSize = getShortcutListSizeAndForwardPointer(dictRoot, pos);
+    static AK_FORCE_INLINE void skipShortcuts(const ReadOnlyByteArrayView buffer, int *const pos) {
+        const int shortcutListSize = getShortcutListSizeAndForwardPointer(buffer, pos);
         *pos += shortcutListSize;
     }
 
@@ -54,7 +57,7 @@
         return getProbabilityFromFlags(flags) == WHITELIST_SHORTCUT_PROBABILITY;
     }
 
-    static int readShortcutTarget(const uint8_t *const dictRoot, const int maxLength,
+    static int readShortcutTarget(const ReadOnlyByteArrayView buffer, const int maxLength,
             int *const outWord, int *const pos);
 
  private:
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v2/bigram/bigram_list_policy.h b/native/jni/src/suggest/policyimpl/dictionary/structure/v2/bigram/bigram_list_policy.h
index 73e291e..e260843 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/v2/bigram/bigram_list_policy.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v2/bigram/bigram_list_policy.h
@@ -22,22 +22,22 @@
 #include "defines.h"
 #include "suggest/core/policy/dictionary_bigrams_structure_policy.h"
 #include "suggest/policyimpl/dictionary/structure/pt_common/bigram/bigram_list_read_write_utils.h"
+#include "utils/byte_array_view.h"
 
 namespace latinime {
 
 class BigramListPolicy : public DictionaryBigramsStructurePolicy {
  public:
-    BigramListPolicy(const uint8_t *const bigramsBuf, const int bufSize)
-            : mBigramsBuf(bigramsBuf), mBufSize(bufSize) {}
+    BigramListPolicy(const ReadOnlyByteArrayView buffer) : mBuffer(buffer) {}
 
     ~BigramListPolicy() {}
 
     void getNextBigram(int *const outBigramPos, int *const outProbability, bool *const outHasNext,
             int *const pos) const {
         BigramListReadWriteUtils::BigramFlags flags;
-        if (!BigramListReadWriteUtils::getBigramEntryPropertiesAndAdvancePosition(mBigramsBuf,
-                mBufSize, &flags, outBigramPos, pos)) {
-            AKLOGE("Cannot read bigram entry. mBufSize: %d, pos: %d. ", mBufSize, *pos);
+        if (!BigramListReadWriteUtils::getBigramEntryPropertiesAndAdvancePosition(mBuffer, &flags,
+                outBigramPos, pos)) {
+            AKLOGE("Cannot read bigram entry. bufSize: %zd, pos: %d. ", mBuffer.size(), *pos);
             *outProbability = NOT_A_PROBABILITY;
             *outHasNext = false;
             return;
@@ -47,14 +47,13 @@
     }
 
     bool skipAllBigrams(int *const pos) const {
-        return BigramListReadWriteUtils::skipExistingBigrams(mBigramsBuf, mBufSize, pos);
+        return BigramListReadWriteUtils::skipExistingBigrams(mBuffer, pos);
     }
 
  private:
     DISALLOW_IMPLICIT_CONSTRUCTORS(BigramListPolicy);
 
-    const uint8_t *const mBigramsBuf;
-    const int mBufSize;
+    const ReadOnlyByteArrayView mBuffer;
 };
 } // namespace latinime
 #endif // LATINIME_BIGRAM_LIST_POLICY_H
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v2/patricia_trie_policy.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/v2/patricia_trie_policy.cpp
index aae61af..64b767d 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/v2/patricia_trie_policy.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v2/patricia_trie_policy.cpp
@@ -37,19 +37,19 @@
         return;
     }
     int nextPos = dicNode->getChildrenPtNodeArrayPos();
-    if (nextPos < 0 || nextPos >= mDictBufferSize) {
-        AKLOGE("Children PtNode array position is invalid. pos: %d, dict size: %d",
-                nextPos, mDictBufferSize);
+    if (!isValidPos(nextPos)) {
+        AKLOGE("Children PtNode array position is invalid. pos: %d, dict size: %zd",
+                nextPos, mBuffer.size());
         mIsCorrupted = true;
         ASSERT(false);
         return;
     }
     const int childCount = PatriciaTrieReadingUtils::getPtNodeArraySizeAndAdvancePosition(
-            mDictRoot, &nextPos);
+            mBuffer.data(), &nextPos);
     for (int i = 0; i < childCount; i++) {
-        if (nextPos < 0 || nextPos >= mDictBufferSize) {
-            AKLOGE("Child PtNode position is invalid. pos: %d, dict size: %d, childCount: %d / %d",
-                    nextPos, mDictBufferSize, i, childCount);
+        if (!isValidPos(nextPos)) {
+            AKLOGE("Child PtNode position is invalid. pos: %d, dict size: %zd, childCount: %d / %d",
+                    nextPos, mBuffer.size(), i, childCount);
             mIsCorrupted = true;
             ASSERT(false);
             return;
@@ -91,56 +91,57 @@
         int lastCandidatePtNodePos = 0;
         // Let's loop through PtNodes in this PtNode array searching for either the terminal
         // or one of its ascendants.
-        if (pos < 0 || pos >= mDictBufferSize) {
-            AKLOGE("PtNode array position is invalid. pos: %d, dict size: %d",
-                    pos, mDictBufferSize);
+        if (!isValidPos(pos)) {
+            AKLOGE("PtNode array position is invalid. pos: %d, dict size: %zd",
+                    pos, mBuffer.size());
             mIsCorrupted = true;
             ASSERT(false);
             *outUnigramProbability = NOT_A_PROBABILITY;
             return 0;
         }
         for (int ptNodeCount = PatriciaTrieReadingUtils::getPtNodeArraySizeAndAdvancePosition(
-                mDictRoot, &pos); ptNodeCount > 0; --ptNodeCount) {
+                mBuffer.data(), &pos); ptNodeCount > 0; --ptNodeCount) {
             const int startPos = pos;
-            if (pos < 0 || pos >= mDictBufferSize) {
-                AKLOGE("PtNode position is invalid. pos: %d, dict size: %d", pos, mDictBufferSize);
+            if (!isValidPos(pos)) {
+                AKLOGE("PtNode position is invalid. pos: %d, dict size: %zd", pos, mBuffer.size());
                 mIsCorrupted = true;
                 ASSERT(false);
                 *outUnigramProbability = NOT_A_PROBABILITY;
                 return 0;
             }
             const PatriciaTrieReadingUtils::NodeFlags flags =
-                    PatriciaTrieReadingUtils::getFlagsAndAdvancePosition(mDictRoot, &pos);
+                    PatriciaTrieReadingUtils::getFlagsAndAdvancePosition(mBuffer.data(), &pos);
             const int character = PatriciaTrieReadingUtils::getCodePointAndAdvancePosition(
-                    mDictRoot, &pos);
+                    mBuffer.data(), &pos);
             if (ptNodePos == startPos) {
                 // We found the position. Copy the rest of the code points in the buffer and return
                 // the length.
                 outCodePoints[wordPos] = character;
                 if (PatriciaTrieReadingUtils::hasMultipleChars(flags)) {
                     int nextChar = PatriciaTrieReadingUtils::getCodePointAndAdvancePosition(
-                            mDictRoot, &pos);
+                            mBuffer.data(), &pos);
                     // We count code points in order to avoid infinite loops if the file is broken
                     // or if there is some other bug
                     int charCount = maxCodePointCount;
                     while (NOT_A_CODE_POINT != nextChar && --charCount > 0) {
                         outCodePoints[++wordPos] = nextChar;
                         nextChar = PatriciaTrieReadingUtils::getCodePointAndAdvancePosition(
-                                mDictRoot, &pos);
+                                mBuffer.data(), &pos);
                     }
                 }
                 *outUnigramProbability =
-                        PatriciaTrieReadingUtils::readProbabilityAndAdvancePosition(mDictRoot,
+                        PatriciaTrieReadingUtils::readProbabilityAndAdvancePosition(mBuffer.data(),
                                 &pos);
                 return ++wordPos;
             }
             // We need to skip past this PtNode, so skip any remaining code points after the
             // first and possibly the probability.
             if (PatriciaTrieReadingUtils::hasMultipleChars(flags)) {
-                PatriciaTrieReadingUtils::skipCharacters(mDictRoot, flags, MAX_WORD_LENGTH, &pos);
+                PatriciaTrieReadingUtils::skipCharacters(mBuffer.data(), flags, MAX_WORD_LENGTH,
+                        &pos);
             }
             if (PatriciaTrieReadingUtils::isTerminal(flags)) {
-                PatriciaTrieReadingUtils::readProbabilityAndAdvancePosition(mDictRoot, &pos);
+                PatriciaTrieReadingUtils::readProbabilityAndAdvancePosition(mBuffer.data(), &pos);
             }
             // The fact that this PtNode has children is very important. Since we already know
             // that this PtNode does not match, if it has no children we know it is irrelevant
@@ -155,7 +156,8 @@
                 int currentPos = pos;
                 // Here comes the tricky part. First, read the children position.
                 const int childrenPos = PatriciaTrieReadingUtils
-                        ::readChildrenPositionAndAdvancePosition(mDictRoot, flags, &currentPos);
+                        ::readChildrenPositionAndAdvancePosition(mBuffer.data(), flags,
+                                &currentPos);
                 if (childrenPos > ptNodePos) {
                     // If the children pos is greater than the position, it means the previous
                     // PtNode, which position is stored in lastCandidatePtNodePos, was the right
@@ -185,30 +187,30 @@
                 if (0 != lastCandidatePtNodePos) {
                     const PatriciaTrieReadingUtils::NodeFlags lastFlags =
                             PatriciaTrieReadingUtils::getFlagsAndAdvancePosition(
-                                    mDictRoot, &lastCandidatePtNodePos);
+                                    mBuffer.data(), &lastCandidatePtNodePos);
                     const int lastChar = PatriciaTrieReadingUtils::getCodePointAndAdvancePosition(
-                            mDictRoot, &lastCandidatePtNodePos);
+                            mBuffer.data(), &lastCandidatePtNodePos);
                     // We copy all the characters in this PtNode to the buffer
                     outCodePoints[wordPos] = lastChar;
                     if (PatriciaTrieReadingUtils::hasMultipleChars(lastFlags)) {
                         int nextChar = PatriciaTrieReadingUtils::getCodePointAndAdvancePosition(
-                                mDictRoot, &lastCandidatePtNodePos);
+                                mBuffer.data(), &lastCandidatePtNodePos);
                         int charCount = maxCodePointCount;
                         while (-1 != nextChar && --charCount > 0) {
                             outCodePoints[++wordPos] = nextChar;
                             nextChar = PatriciaTrieReadingUtils::getCodePointAndAdvancePosition(
-                                    mDictRoot, &lastCandidatePtNodePos);
+                                    mBuffer.data(), &lastCandidatePtNodePos);
                         }
                     }
                     ++wordPos;
                     // Now we only need to branch to the children address. Skip the probability if
                     // it's there, read pos, and break to resume the search at pos.
                     if (PatriciaTrieReadingUtils::isTerminal(lastFlags)) {
-                        PatriciaTrieReadingUtils::readProbabilityAndAdvancePosition(mDictRoot,
+                        PatriciaTrieReadingUtils::readProbabilityAndAdvancePosition(mBuffer.data(),
                                 &lastCandidatePtNodePos);
                     }
                     pos = PatriciaTrieReadingUtils::readChildrenPositionAndAdvancePosition(
-                            mDictRoot, lastFlags, &lastCandidatePtNodePos);
+                            mBuffer.data(), lastFlags, &lastCandidatePtNodePos);
                     break;
                 } else {
                     // Here is a little tricky part: we come here if we found out that all children
@@ -220,14 +222,14 @@
                     // ready to start the next one.
                     if (PatriciaTrieReadingUtils::hasChildrenInFlags(flags)) {
                         PatriciaTrieReadingUtils::readChildrenPositionAndAdvancePosition(
-                                mDictRoot, flags, &pos);
+                                mBuffer.data(), flags, &pos);
                     }
                     if (PatriciaTrieReadingUtils::hasShortcutTargets(flags)) {
                         mShortcutListPolicy.skipAllShortcuts(&pos);
                     }
                     if (PatriciaTrieReadingUtils::hasBigrams(flags)) {
                         if (!mBigramListPolicy.skipAllBigrams(&pos)) {
-                            AKLOGE("Cannot skip bigrams. BufSize: %d, pos: %d.", mDictBufferSize,
+                            AKLOGE("Cannot skip bigrams. BufSize: %zd, pos: %d.", mBuffer.size(),
                                     pos);
                             mIsCorrupted = true;
                             ASSERT(false);
@@ -244,14 +246,14 @@
                 // our pos is after the end of this PtNode, at the start of the next one.
                 if (PatriciaTrieReadingUtils::hasChildrenInFlags(flags)) {
                     PatriciaTrieReadingUtils::readChildrenPositionAndAdvancePosition(
-                            mDictRoot, flags, &pos);
+                            mBuffer.data(), flags, &pos);
                 }
                 if (PatriciaTrieReadingUtils::hasShortcutTargets(flags)) {
                     mShortcutListPolicy.skipAllShortcuts(&pos);
                 }
                 if (PatriciaTrieReadingUtils::hasBigrams(flags)) {
                     if (!mBigramListPolicy.skipAllBigrams(&pos)) {
-                        AKLOGE("Cannot skip bigrams. BufSize: %d, pos: %d.", mDictBufferSize, pos);
+                        AKLOGE("Cannot skip bigrams. BufSize: %zd, pos: %d.", mBuffer.size(), pos);
                         mIsCorrupted = true;
                         ASSERT(false);
                         *outUnigramProbability = NOT_A_PROBABILITY;
@@ -402,7 +404,7 @@
     int shortcutPos = NOT_A_DICT_POS;
     int bigramPos = NOT_A_DICT_POS;
     int siblingPos = NOT_A_DICT_POS;
-    PatriciaTrieReadingUtils::readPtNodeInfo(mDictRoot, ptNodePos, &mShortcutListPolicy,
+    PatriciaTrieReadingUtils::readPtNodeInfo(mBuffer.data(), ptNodePos, &mShortcutListPolicy,
             &mBigramListPolicy, &flags, &mergedNodeCodePointCount, mergedNodeCodePoints,
             &probability, &childrenPos, &shortcutPos, &bigramPos, &siblingPos);
     // Skip PtNodes don't start with Unicode code point because they represent non-word information.
@@ -452,14 +454,14 @@
     int shortcutPos = getShortcutPositionOfPtNode(ptNodePos);
     if (shortcutPos != NOT_A_DICT_POS) {
         int shortcutTargetCodePoints[MAX_WORD_LENGTH];
-        ShortcutListReadingUtils::getShortcutListSizeAndForwardPointer(mDictRoot, &shortcutPos);
+        ShortcutListReadingUtils::getShortcutListSizeAndForwardPointer(mBuffer, &shortcutPos);
         bool hasNext = true;
         while (hasNext) {
             const ShortcutListReadingUtils::ShortcutFlags shortcutFlags =
-                    ShortcutListReadingUtils::getFlagsAndForwardPointer(mDictRoot, &shortcutPos);
+                    ShortcutListReadingUtils::getFlagsAndForwardPointer(mBuffer, &shortcutPos);
             hasNext = ShortcutListReadingUtils::hasNext(shortcutFlags);
             const int shortcutTargetLength = ShortcutListReadingUtils::readShortcutTarget(
-                    mDictRoot, MAX_WORD_LENGTH, shortcutTargetCodePoints, &shortcutPos);
+                    mBuffer, MAX_WORD_LENGTH, shortcutTargetCodePoints, &shortcutPos);
             const std::vector<int> shortcutTarget(shortcutTargetCodePoints,
                     shortcutTargetCodePoints + shortcutTargetLength);
             const int shortcutProbability =
@@ -512,4 +514,9 @@
 int PatriciaTriePolicy::getTerminalPtNodePosFromWordId(const int wordId) const {
     return wordId == NOT_A_WORD_ID ? NOT_A_DICT_POS : wordId;
 }
+
+bool PatriciaTriePolicy::isValidPos(const int pos) const {
+    return pos >= 0 && pos < static_cast<int>(mBuffer.size());
+}
+
 } // namespace latinime
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v2/patricia_trie_policy.h b/native/jni/src/suggest/policyimpl/dictionary/structure/v2/patricia_trie_policy.h
index fc65de5..70e8d84 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/v2/patricia_trie_policy.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v2/patricia_trie_policy.h
@@ -44,14 +44,11 @@
             : mMmappedBuffer(std::move(mmappedBuffer)),
               mHeaderPolicy(mMmappedBuffer->getReadOnlyByteArrayView().data(),
                       FormatUtils::VERSION_2),
-              mDictRoot(mMmappedBuffer->getReadOnlyByteArrayView().data()
-                      + mHeaderPolicy.getSize()),
-              mDictBufferSize(mMmappedBuffer->getReadOnlyByteArrayView().size()
-                      - mHeaderPolicy.getSize()),
-              mBigramListPolicy(mDictRoot, mDictBufferSize), mShortcutListPolicy(mDictRoot),
-              mPtNodeReader(mDictRoot, mDictBufferSize, &mBigramListPolicy, &mShortcutListPolicy),
-              mPtNodeArrayReader(mDictRoot, mDictBufferSize),
-              mTerminalPtNodePositionsForIteratingWords(), mIsCorrupted(false) {}
+              mBuffer(mMmappedBuffer->getReadOnlyByteArrayView().skip(mHeaderPolicy.getSize())),
+              mBigramListPolicy(mBuffer), mShortcutListPolicy(mBuffer),
+              mPtNodeReader(mBuffer, &mBigramListPolicy, &mShortcutListPolicy),
+              mPtNodeArrayReader(mBuffer), mTerminalPtNodePositionsForIteratingWords(),
+              mIsCorrupted(false) {}
 
     AK_FORCE_INLINE int getRootPosition() const {
         return 0;
@@ -149,8 +146,7 @@
 
     const MmappedBuffer::MmappedBufferPtr mMmappedBuffer;
     const HeaderPolicy mHeaderPolicy;
-    const uint8_t *const mDictRoot;
-    const int mDictBufferSize;
+    const ReadOnlyByteArrayView mBuffer;
     const BigramListPolicy mBigramListPolicy;
     const ShortcutListPolicy mShortcutListPolicy;
     const Ver2ParticiaTrieNodeReader mPtNodeReader;
@@ -166,6 +162,7 @@
     int getTerminalPtNodePosFromWordId(const int wordId) const;
     const WordAttributes getWordAttributes(const int probability,
             const PtNodeParams &ptNodeParams) const;
+    bool isValidPos(const int pos) const;
 };
 } // namespace latinime
 #endif // LATINIME_PATRICIA_TRIE_POLICY_H
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v2/shortcut/shortcut_list_policy.h b/native/jni/src/suggest/policyimpl/dictionary/structure/v2/shortcut/shortcut_list_policy.h
index 8e16ccc..5319dd2 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/v2/shortcut/shortcut_list_policy.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v2/shortcut/shortcut_list_policy.h
@@ -22,13 +22,13 @@
 #include "defines.h"
 #include "suggest/core/policy/dictionary_shortcuts_structure_policy.h"
 #include "suggest/policyimpl/dictionary/structure/pt_common/shortcut/shortcut_list_reading_utils.h"
+#include "utils/byte_array_view.h"
 
 namespace latinime {
 
 class ShortcutListPolicy : public DictionaryShortcutsStructurePolicy {
  public:
-    explicit ShortcutListPolicy(const uint8_t *const shortcutBuf)
-            : mShortcutsBuf(shortcutBuf) {}
+    explicit ShortcutListPolicy(const ReadOnlyByteArrayView buffer) : mBuffer(buffer) {}
 
     ~ShortcutListPolicy() {}
 
@@ -37,7 +37,7 @@
             return NOT_A_DICT_POS;
         }
         int listPos = pos;
-        ShortcutListReadingUtils::getShortcutListSizeAndForwardPointer(mShortcutsBuf, &listPos);
+        ShortcutListReadingUtils::getShortcutListSizeAndForwardPointer(mBuffer, &listPos);
         return listPos;
     }
 
@@ -45,7 +45,7 @@
             int *const outCodePointCount, bool *const outIsWhitelist, bool *const outHasNext,
             int *const pos) const {
         const ShortcutListReadingUtils::ShortcutFlags flags =
-                ShortcutListReadingUtils::getFlagsAndForwardPointer(mShortcutsBuf, pos);
+                ShortcutListReadingUtils::getFlagsAndForwardPointer(mBuffer, pos);
         if (outHasNext) {
             *outHasNext = ShortcutListReadingUtils::hasNext(flags);
         }
@@ -54,20 +54,20 @@
         }
         if (outCodePoint) {
             *outCodePointCount = ShortcutListReadingUtils::readShortcutTarget(
-                        mShortcutsBuf, maxCodePointCount, outCodePoint, pos);
+                    mBuffer, maxCodePointCount, outCodePoint, pos);
         }
     }
 
     void skipAllShortcuts(int *const pos) const {
         const int shortcutListSize = ShortcutListReadingUtils
-                ::getShortcutListSizeAndForwardPointer(mShortcutsBuf, pos);
+                ::getShortcutListSizeAndForwardPointer(mBuffer, pos);
         *pos += shortcutListSize;
     }
 
  private:
     DISALLOW_IMPLICIT_CONSTRUCTORS(ShortcutListPolicy);
 
-    const uint8_t *const mShortcutsBuf;
+    const ReadOnlyByteArrayView mBuffer;
 };
 } // namespace latinime
 #endif // LATINIME_SHORTCUT_LIST_POLICY_H
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v2/ver2_patricia_trie_node_reader.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/v2/ver2_patricia_trie_node_reader.cpp
index c1e9387..74cdf79 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/v2/ver2_patricia_trie_node_reader.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v2/ver2_patricia_trie_node_reader.cpp
@@ -22,10 +22,10 @@
 
 const PtNodeParams Ver2ParticiaTrieNodeReader::fetchPtNodeParamsInBufferFromPtNodePos(
         const int ptNodePos) const {
-    if (ptNodePos < 0 || ptNodePos >= mDictSize) {
+    if (ptNodePos < 0 || ptNodePos >= static_cast<int>(mBuffer.size())) {
         // Reading invalid position because of bug or broken dictionary.
-        AKLOGE("Fetching PtNode info from invalid dictionary position: %d, dictionary size: %d",
-                ptNodePos, mDictSize);
+        AKLOGE("Fetching PtNode info from invalid dictionary position: %d, dictionary size: %zd",
+                ptNodePos, mBuffer.size());
         ASSERT(false);
         return PtNodeParams();
     }
@@ -37,7 +37,7 @@
     int shortcutPos = NOT_A_DICT_POS;
     int bigramPos = NOT_A_DICT_POS;
     int siblingPos = NOT_A_DICT_POS;
-    PatriciaTrieReadingUtils::readPtNodeInfo(mDictBuffer, ptNodePos, mShortuctPolicy,
+    PatriciaTrieReadingUtils::readPtNodeInfo(mBuffer.data(), ptNodePos, mShortuctPolicy,
             mBigramPolicy, &flags, &mergedNodeCodePointCount, mergedNodeCodePoints, &probability,
             &childrenPos, &shortcutPos, &bigramPos, &siblingPos);
     if (mergedNodeCodePointCount <= 0) {
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v2/ver2_patricia_trie_node_reader.h b/native/jni/src/suggest/policyimpl/dictionary/structure/v2/ver2_patricia_trie_node_reader.h
index f0725b6..0f6769d 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/v2/ver2_patricia_trie_node_reader.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v2/ver2_patricia_trie_node_reader.h
@@ -22,6 +22,7 @@
 #include "defines.h"
 #include "suggest/policyimpl/dictionary/structure/pt_common/pt_node_params.h"
 #include "suggest/policyimpl/dictionary/structure/pt_common/pt_node_reader.h"
+#include "utils/byte_array_view.h"
 
 namespace latinime {
 
@@ -30,19 +31,17 @@
 
 class Ver2ParticiaTrieNodeReader : public PtNodeReader {
  public:
-    Ver2ParticiaTrieNodeReader(const uint8_t *const dictBuffer, const int dictSize,
+    Ver2ParticiaTrieNodeReader(const ReadOnlyByteArrayView buffer,
             const DictionaryBigramsStructurePolicy *const bigramPolicy,
             const DictionaryShortcutsStructurePolicy *const shortcutPolicy)
-            : mDictBuffer(dictBuffer), mDictSize(dictSize), mBigramPolicy(bigramPolicy),
-              mShortuctPolicy(shortcutPolicy) {}
+            : mBuffer(buffer), mBigramPolicy(bigramPolicy), mShortuctPolicy(shortcutPolicy) {}
 
     virtual const PtNodeParams fetchPtNodeParamsInBufferFromPtNodePos(const int ptNodePos) const;
 
  private:
     DISALLOW_IMPLICIT_CONSTRUCTORS(Ver2ParticiaTrieNodeReader);
 
-    const uint8_t *const mDictBuffer;
-    const int mDictSize;
+    const ReadOnlyByteArrayView mBuffer;
     const DictionaryBigramsStructurePolicy *const mBigramPolicy;
     const DictionaryShortcutsStructurePolicy *const mShortuctPolicy;
 };
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v2/ver2_pt_node_array_reader.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/v2/ver2_pt_node_array_reader.cpp
index b46617d..72ad1eb 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/v2/ver2_pt_node_array_reader.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v2/ver2_pt_node_array_reader.cpp
@@ -22,16 +22,16 @@
 
 bool Ver2PtNodeArrayReader::readPtNodeArrayInfoAndReturnIfValid(const int ptNodeArrayPos,
         int *const outPtNodeCount, int *const outFirstPtNodePos) const {
-    if (ptNodeArrayPos < 0 || ptNodeArrayPos >= mDictSize) {
+    if (ptNodeArrayPos < 0 || ptNodeArrayPos >= static_cast<int>(mBuffer.size())) {
         // Reading invalid position because of a bug or a broken dictionary.
-        AKLOGE("Reading PtNode array info from invalid dictionary position: %d, dict size: %d",
-                ptNodeArrayPos, mDictSize);
+        AKLOGE("Reading PtNode array info from invalid dictionary position: %d, dict size: %zd",
+                ptNodeArrayPos, mBuffer.size());
         ASSERT(false);
         return false;
     }
     int readingPos = ptNodeArrayPos;
     const int ptNodeCountInArray = PatriciaTrieReadingUtils::getPtNodeArraySizeAndAdvancePosition(
-            mDictBuffer, &readingPos);
+            mBuffer.data(), &readingPos);
     *outPtNodeCount = ptNodeCountInArray;
     *outFirstPtNodePos = readingPos;
     return true;
@@ -39,10 +39,10 @@
 
 bool Ver2PtNodeArrayReader::readForwardLinkAndReturnIfValid(const int forwordLinkPos,
         int *const outNextPtNodeArrayPos) const {
-    if (forwordLinkPos < 0 || forwordLinkPos >= mDictSize) {
+    if (forwordLinkPos < 0 || forwordLinkPos >=  static_cast<int>(mBuffer.size())) {
         // Reading invalid position because of bug or broken dictionary.
-        AKLOGE("Reading forward link from invalid dictionary position: %d, dict size: %d",
-                forwordLinkPos, mDictSize);
+        AKLOGE("Reading forward link from invalid dictionary position: %d, dict size: %zd",
+                forwordLinkPos, mBuffer.size());
         ASSERT(false);
         return false;
     }
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v2/ver2_pt_node_array_reader.h b/native/jni/src/suggest/policyimpl/dictionary/structure/v2/ver2_pt_node_array_reader.h
index 5482721..548f36b 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/v2/ver2_pt_node_array_reader.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v2/ver2_pt_node_array_reader.h
@@ -21,13 +21,13 @@
 
 #include "defines.h"
 #include "suggest/policyimpl/dictionary/structure/pt_common/pt_node_array_reader.h"
+#include "utils/byte_array_view.h"
 
 namespace latinime {
 
 class Ver2PtNodeArrayReader : public PtNodeArrayReader {
  public:
-    Ver2PtNodeArrayReader(const uint8_t *const dictBuffer, const int dictSize)
-            : mDictBuffer(dictBuffer), mDictSize(dictSize) {};
+    Ver2PtNodeArrayReader(const ReadOnlyByteArrayView buffer) : mBuffer(buffer) {};
 
     virtual bool readPtNodeArrayInfoAndReturnIfValid(const int ptNodeArrayPos,
             int *const outPtNodeCount, int *const outFirstPtNodePos) const;
@@ -37,8 +37,7 @@
  private:
     DISALLOW_COPY_AND_ASSIGN(Ver2PtNodeArrayReader);
 
-    const uint8_t *const mDictBuffer;
-    const int mDictSize;
+    const ReadOnlyByteArrayView mBuffer;
 };
 } // namespace latinime
 #endif /* LATINIME_VER2_PT_NODE_ARRAY_READER_H */
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/content/language_model_dict_content.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/content/language_model_dict_content.cpp
index f54bb15..35f0f76 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/content/language_model_dict_content.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/content/language_model_dict_content.cpp
@@ -39,7 +39,7 @@
 }
 
 int LanguageModelDictContent::getWordProbability(const WordIdArrayView prevWordIds,
-        const int wordId) const {
+        const int wordId, const HeaderPolicy *const headerPolicy) const {
     int bitmapEntryIndices[MAX_PREV_WORD_COUNT_FOR_N_GRAM + 1];
     bitmapEntryIndices[0] = mTrieMap.getRootBitmapEntryIndex();
     int maxLevel = 0;
@@ -58,14 +58,15 @@
         if (!result.mIsValid) {
             continue;
         }
-        const int probability =
-                ProbabilityEntry::decode(result.mValue, mHasHistoricalInfo).getProbability();
+        const ProbabilityEntry probabilityEntry =
+                ProbabilityEntry::decode(result.mValue, mHasHistoricalInfo);
         if (mHasHistoricalInfo) {
-            return std::min(
-                    probability + ForgettingCurveUtils::getProbabilityBiasForNgram(i + 1 /* n */),
-                    MAX_PROBABILITY);
+            const int probability = ForgettingCurveUtils::decodeProbability(
+                    probabilityEntry.getHistoricalInfo(), headerPolicy)
+                            + ForgettingCurveUtils::getProbabilityBiasForNgram(i + 1 /* n */);
+            return std::min(probability, MAX_PROBABILITY);
         } else {
-            return probability;
+            return probabilityEntry.getProbability();
         }
     }
     // Cannot find the word.
@@ -166,7 +167,15 @@
     if (lastBitmapEntryIndex == TrieMap::INVALID_INDEX) {
         return TrieMap::INVALID_INDEX;
     }
-    return mTrieMap.getNextLevelBitmapEntryIndex(prevWordIds[prevWordIds.size() - 1],
+    const int oldestPrevWordId = prevWordIds.lastOrDefault(NOT_A_WORD_ID);
+    const TrieMap::Result result = mTrieMap.get(oldestPrevWordId, lastBitmapEntryIndex);
+    if (!result.mIsValid) {
+        if (!mTrieMap.put(oldestPrevWordId,
+                ProbabilityEntry().encode(mHasHistoricalInfo), lastBitmapEntryIndex)) {
+            return TrieMap::INVALID_INDEX;
+        }
+    }
+    return mTrieMap.getNextLevelBitmapEntryIndex(prevWordIds.lastOrDefault(NOT_A_WORD_ID),
             lastBitmapEntryIndex);
 }
 
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/content/language_model_dict_content.h b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/content/language_model_dict_content.h
index 4e0b470..a793af4 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/content/language_model_dict_content.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/content/language_model_dict_content.h
@@ -128,7 +128,8 @@
             const LanguageModelDictContent *const originalContent,
             int *const outNgramCount);
 
-    int getWordProbability(const WordIdArrayView prevWordIds, const int wordId) const;
+    int getWordProbability(const WordIdArrayView prevWordIds, const int wordId,
+            const HeaderPolicy *const headerPolicy) const;
 
     ProbabilityEntry getProbabilityEntry(const int wordId) const {
         return getNgramProbabilityEntry(WordIdArrayView(), wordId);
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/content/probability_entry.h b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/content/probability_entry.h
index 3dfaba7..f1bf12c 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/content/probability_entry.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/content/probability_entry.h
@@ -36,7 +36,8 @@
 
     // Dummy entry
     ProbabilityEntry()
-            : mFlags(0), mProbability(NOT_A_PROBABILITY), mHistoricalInfo() {}
+            : mFlags(Ver4DictConstants::FLAG_NOT_A_VALID_ENTRY), mProbability(NOT_A_PROBABILITY),
+              mHistoricalInfo() {}
 
     // Entry without historical information
     ProbabilityEntry(const int flags, const int probability)
@@ -61,7 +62,7 @@
                       bigramProperty->getCount()) {}
 
     bool isValid() const {
-        return (mProbability != NOT_A_PROBABILITY) || hasHistoricalInfo();
+        return (mFlags & Ver4DictConstants::FLAG_NOT_A_VALID_ENTRY) == 0;
     }
 
     bool hasHistoricalInfo() const {
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_dict_constants.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_dict_constants.cpp
index 9acf2d4..39822b9 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_dict_constants.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_dict_constants.cpp
@@ -53,6 +53,7 @@
 const int Ver4DictConstants::WORD_COUNT_FIELD_SIZE = 1;
 
 const uint8_t Ver4DictConstants::FLAG_REPRESENTS_BEGINNING_OF_SENTENCE = 0x1;
+const uint8_t Ver4DictConstants::FLAG_NOT_A_VALID_ENTRY = 0x2;
 
 const int Ver4DictConstants::SHORTCUT_ADDRESS_TABLE_BLOCK_SIZE = 64;
 const int Ver4DictConstants::SHORTCUT_ADDRESS_TABLE_DATA_SIZE = 4;
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_dict_constants.h b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_dict_constants.h
index 9703531..dfcdd4d 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_dict_constants.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_dict_constants.h
@@ -51,6 +51,7 @@
     static const int WORD_COUNT_FIELD_SIZE;
     // Flags in probability entry.
     static const uint8_t FLAG_REPRESENTS_BEGINNING_OF_SENTENCE;
+    static const uint8_t FLAG_NOT_A_VALID_ENTRY;
 
     static const int SHORTCUT_ADDRESS_TABLE_BLOCK_SIZE;
     static const int SHORTCUT_ADDRESS_TABLE_DATA_SIZE;
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_node_writer.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_node_writer.cpp
index 9ca7124..75ec169 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_node_writer.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_node_writer.cpp
@@ -211,19 +211,17 @@
 
 bool Ver4PatriciaTrieNodeWriter::addNgramEntry(const WordIdArrayView prevWordIds, const int wordId,
         const BigramProperty *const bigramProperty, bool *const outAddedNewBigram) {
-    // TODO: Support n-gram.
     LanguageModelDictContent *const languageModelDictContent =
             mBuffers->getMutableLanguageModelDictContent();
     const ProbabilityEntry probabilityEntry =
-            languageModelDictContent->getNgramProbabilityEntry(
-                    prevWordIds.limit(1 /* maxSize */), wordId);
+            languageModelDictContent->getNgramProbabilityEntry(prevWordIds, wordId);
     const ProbabilityEntry probabilityEntryOfBigramProperty(bigramProperty);
     const ProbabilityEntry updatedProbabilityEntry = createUpdatedEntryFrom(
             &probabilityEntry, &probabilityEntryOfBigramProperty);
     if (!languageModelDictContent->setNgramProbabilityEntry(
-            prevWordIds.limit(1 /* maxSize */), wordId, &updatedProbabilityEntry)) {
-        AKLOGE("Cannot add new ngram entry. prevWordId: %d, wordId: %d",
-                prevWordIds[0], wordId);
+            prevWordIds, wordId, &updatedProbabilityEntry)) {
+        AKLOGE("Cannot add new ngram entry. prevWordId[0]: %d, prevWordId.size(): %zd, wordId: %d",
+                prevWordIds[0], prevWordIds.size(), wordId);
         return false;
     }
     if (!probabilityEntry.isValid() && outAddedNewBigram) {
@@ -234,11 +232,9 @@
 
 bool Ver4PatriciaTrieNodeWriter::removeNgramEntry(const WordIdArrayView prevWordIds,
         const int wordId) {
-    // TODO: Support n-gram.
     LanguageModelDictContent *const languageModelDictContent =
             mBuffers->getMutableLanguageModelDictContent();
-    return languageModelDictContent->removeNgramProbabilityEntry(prevWordIds.limit(1 /* maxSize */),
-            wordId);
+    return languageModelDictContent->removeNgramProbabilityEntry(prevWordIds, wordId);
 }
 
 // TODO: Remove when we stop supporting v402 format.
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_policy.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_policy.cpp
index 5b1907f..8d41356 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_policy.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_policy.cpp
@@ -120,15 +120,15 @@
     const int ptNodePos =
             mBuffers->getTerminalPositionLookupTable()->getTerminalPtNodePosition(wordId);
     const PtNodeParams ptNodeParams = mNodeReader.fetchPtNodeParamsInBufferFromPtNodePos(ptNodePos);
-    // TODO: Support n-gram.
-    return WordAttributes(mBuffers->getLanguageModelDictContent()->getWordProbability(
-            prevWordIds.limit(1 /* maxSize */), wordId), ptNodeParams.isBlacklisted(),
-            ptNodeParams.isNotAWord(), ptNodeParams.getProbability() == 0);
+    const int probability = mBuffers->getLanguageModelDictContent()->getWordProbability(
+            prevWordIds, wordId, mHeaderPolicy);
+    return WordAttributes(probability, ptNodeParams.isBlacklisted(), ptNodeParams.isNotAWord(),
+            probability == 0);
 }
 
 int Ver4PatriciaTriePolicy::getProbabilityOfWord(const WordIdArrayView prevWordIds,
         const int wordId) const {
-    if (wordId == NOT_A_WORD_ID) {
+    if (wordId == NOT_A_WORD_ID || prevWordIds.contains(NOT_A_WORD_ID)) {
         return NOT_A_PROBABILITY;
     }
     const int ptNodePos =
@@ -137,10 +137,8 @@
     if (ptNodeParams.isDeleted() || ptNodeParams.isBlacklisted() || ptNodeParams.isNotAWord()) {
         return NOT_A_PROBABILITY;
     }
-    // TODO: Support n-gram.
     const ProbabilityEntry probabilityEntry =
-            mBuffers->getLanguageModelDictContent()->getNgramProbabilityEntry(
-                    prevWordIds.limit(1 /* maxSize */), wordId);
+            mBuffers->getLanguageModelDictContent()->getNgramProbabilityEntry(prevWordIds, wordId);
     if (!probabilityEntry.isValid()) {
         return NOT_A_PROBABILITY;
     }
@@ -163,16 +161,18 @@
     if (prevWordIds.empty()) {
         return;
     }
-    // TODO: Support n-gram.
     const auto languageModelDictContent = mBuffers->getLanguageModelDictContent();
-    for (const auto entry : languageModelDictContent->getProbabilityEntries(
-            prevWordIds.limit(1 /* maxSize */))) {
-        const ProbabilityEntry &probabilityEntry = entry.getProbabilityEntry();
-        const int probability = probabilityEntry.hasHistoricalInfo() ?
-                ForgettingCurveUtils::decodeProbability(
-                        probabilityEntry.getHistoricalInfo(), mHeaderPolicy) :
-                probabilityEntry.getProbability();
-        listener->onVisitEntry(probability, entry.getWordId());
+    for (size_t i = 1; i <= prevWordIds.size(); ++i) {
+        for (const auto entry : languageModelDictContent->getProbabilityEntries(
+                prevWordIds.limit(i))) {
+            const ProbabilityEntry &probabilityEntry = entry.getProbabilityEntry();
+            const int probability = probabilityEntry.hasHistoricalInfo() ?
+                    ForgettingCurveUtils::decodeProbability(
+                            probabilityEntry.getHistoricalInfo(), mHeaderPolicy)
+                            + ForgettingCurveUtils::getProbabilityBiasForNgram(i + 1 /* n */) :
+                    probabilityEntry.getProbability();
+            listener->onVisitEntry(probability, entry.getWordId());
+        }
     }
 }
 
@@ -227,8 +227,8 @@
         return false;
     }
     const CodePointArrayView codePointArrayView(codePointsToAdd, codePointCountToAdd);
-    if (mUpdatingHelper.addUnigramWord(&readingHelper, codePointArrayView.data(),
-            codePointArrayView.size(), unigramProperty, &addedNewUnigram)) {
+    if (mUpdatingHelper.addUnigramWord(&readingHelper, codePointArrayView, unigramProperty,
+            &addedNewUnigram)) {
         if (addedNewUnigram && !unigramProperty->representsBeginningOfSentence()) {
             mUnigramCount++;
         }
@@ -243,8 +243,8 @@
                     mBuffers->getTerminalPositionLookupTable()->getTerminalPtNodePosition(wordId);
             for (const auto &shortcut : unigramProperty->getShortcuts()) {
                 if (!mUpdatingHelper.addShortcutTarget(wordPos,
-                        shortcut.getTargetCodePoints()->data(),
-                        shortcut.getTargetCodePoints()->size(), shortcut.getProbability())) {
+                        CodePointArrayView(*shortcut.getTargetCodePoints()),
+                        shortcut.getProbability())) {
                     AKLOGE("Cannot add new shortcut target. PtNodePos: %d, length: %zd, "
                             "probability: %d", wordPos, shortcut.getTargetCodePoints()->size(),
                             shortcut.getProbability());
@@ -303,26 +303,31 @@
                 "length: %zd", bigramProperty->getTargetCodePoints()->size());
         return false;
     }
-    WordIdArray<MAX_PREV_WORD_COUNT_FOR_N_GRAM> prevWordIds;
-    prevWordsInfo->getPrevWordIds(this, prevWordIds.data(), false /* tryLowerCaseSearch */);
-    // TODO: Support N-gram.
-    if (prevWordIds[0] == NOT_A_WORD_ID) {
-        if (prevWordsInfo->isNthPrevWordBeginningOfSentence(1 /* n */)) {
-            const std::vector<UnigramProperty::ShortcutProperty> shortcuts;
-            const UnigramProperty beginningOfSentenceUnigramProperty(
-                    true /* representsBeginningOfSentence */, true /* isNotAWord */,
-                    false /* isBlacklisted */, MAX_PROBABILITY /* probability */,
-                    NOT_A_TIMESTAMP /* timestamp */, 0 /* level */, 0 /* count */, &shortcuts);
-            if (!addUnigramEntry(prevWordsInfo->getNthPrevWordCodePoints(1 /* n */),
-                    &beginningOfSentenceUnigramProperty)) {
-                AKLOGE("Cannot add unigram entry for the beginning-of-sentence.");
-                return false;
-            }
-            // Refresh word ids.
-            prevWordsInfo->getPrevWordIds(this, prevWordIds.data(), false /* tryLowerCaseSearch */);
-        } else {
+    WordIdArray<MAX_PREV_WORD_COUNT_FOR_N_GRAM> prevWordIdArray;
+    const WordIdArrayView prevWordIds = prevWordsInfo->getPrevWordIds(this, &prevWordIdArray,
+            false /* tryLowerCaseSearch */);
+    if (prevWordIds.empty()) {
+        return false;
+    }
+    for (size_t i = 0; i < prevWordIds.size(); ++i) {
+        if (prevWordIds[i] != NOT_A_WORD_ID) {
+            continue;
+        }
+        if (!prevWordsInfo->isNthPrevWordBeginningOfSentence(i + 1 /* n */)) {
             return false;
         }
+        const std::vector<UnigramProperty::ShortcutProperty> shortcuts;
+        const UnigramProperty beginningOfSentenceUnigramProperty(
+                true /* representsBeginningOfSentence */, true /* isNotAWord */,
+                false /* isBlacklisted */, MAX_PROBABILITY /* probability */,
+                NOT_A_TIMESTAMP /* timestamp */, 0 /* level */, 0 /* count */, &shortcuts);
+        if (!addUnigramEntry(prevWordsInfo->getNthPrevWordCodePoints(1 /* n */),
+                &beginningOfSentenceUnigramProperty)) {
+            AKLOGE("Cannot add unigram entry for the beginning-of-sentence.");
+            return false;
+        }
+        // Refresh word ids.
+        prevWordsInfo->getPrevWordIds(this, &prevWordIdArray, false /* tryLowerCaseSearch */);
     }
     const int wordId = getWordId(CodePointArrayView(*bigramProperty->getTargetCodePoints()),
             false /* forceLowerCaseSearch */);
@@ -330,15 +335,7 @@
         return false;
     }
     bool addedNewEntry = false;
-    WordIdArray<MAX_PREV_WORD_COUNT_FOR_N_GRAM> prevWordsPtNodePos;
-    for (size_t i = 0; i < prevWordsPtNodePos.size(); ++i) {
-        prevWordsPtNodePos[i] = mBuffers->getTerminalPositionLookupTable()
-                ->getTerminalPtNodePosition(prevWordIds[i]);
-    }
-    const int wordPtNodePos = mBuffers->getTerminalPositionLookupTable()
-            ->getTerminalPtNodePosition(wordId);
-    if (mUpdatingHelper.addNgramEntry(WordIdArrayView::fromArray(prevWordsPtNodePos),
-            wordPtNodePos, bigramProperty, &addedNewEntry)) {
+    if (mNodeWriter.addNgramEntry(prevWordIds, wordId, bigramProperty, &addedNewEntry)) {
         if (addedNewEntry) {
             mBigramCount++;
         }
@@ -367,25 +364,17 @@
         AKLOGE("word is too long to remove n-gram entry form the dictionary. length: %zd",
                 wordCodePoints.size());
     }
-    WordIdArray<MAX_PREV_WORD_COUNT_FOR_N_GRAM> prevWordIds;
-    prevWordsInfo->getPrevWordIds(this, prevWordIds.data(), false /* tryLowerCaseSerch */);
-    // TODO: Support N-gram.
-    if (prevWordIds[0] == NOT_A_WORD_ID) {
+    WordIdArray<MAX_PREV_WORD_COUNT_FOR_N_GRAM> prevWordIdArray;
+    const WordIdArrayView prevWordIds = prevWordsInfo->getPrevWordIds(this, &prevWordIdArray,
+            false /* tryLowerCaseSerch */);
+    if (prevWordIds.empty() || prevWordIds.contains(NOT_A_WORD_ID)) {
         return false;
     }
     const int wordId = getWordId(wordCodePoints, false /* forceLowerCaseSearch */);
     if (wordId == NOT_A_WORD_ID) {
         return false;
     }
-    std::array<int, MAX_PREV_WORD_COUNT_FOR_N_GRAM> prevWordsPtNodePos;
-    for (size_t i = 0; i < prevWordsPtNodePos.size(); ++i) {
-        prevWordsPtNodePos[i] = mBuffers->getTerminalPositionLookupTable()
-                ->getTerminalPtNodePosition(prevWordIds[i]);
-    }
-    const int wordPtNodePos = mBuffers->getTerminalPositionLookupTable()
-            ->getTerminalPtNodePosition(wordId);
-    if (mUpdatingHelper.removeNgramEntry(WordIdArrayView::fromArray(prevWordsPtNodePos),
-            wordPtNodePos)) {
+    if (mNodeWriter.removeNgramEntry(prevWordIds, wordId)) {
         mBigramCount--;
         return true;
     } else {
diff --git a/native/jni/src/utils/byte_array_view.h b/native/jni/src/utils/byte_array_view.h
index 10d7ae2..2b778af 100644
--- a/native/jni/src/utils/byte_array_view.h
+++ b/native/jni/src/utils/byte_array_view.h
@@ -42,6 +42,13 @@
         return mPtr;
     }
 
+    AK_FORCE_INLINE const ReadOnlyByteArrayView skip(const size_t n) const {
+        if (mSize <= n) {
+            return ReadOnlyByteArrayView();
+        }
+        return ReadOnlyByteArrayView(mPtr + n, mSize - n);
+    }
+
  private:
     DISALLOW_ASSIGNMENT_OPERATOR(ReadOnlyByteArrayView);
 
diff --git a/native/jni/src/utils/int_array_view.h b/native/jni/src/utils/int_array_view.h
index caa13d9..f3a8589 100644
--- a/native/jni/src/utils/int_array_view.h
+++ b/native/jni/src/utils/int_array_view.h
@@ -17,6 +17,7 @@
 #ifndef LATINIME_INT_ARRAY_VIEW_H
 #define LATINIME_INT_ARRAY_VIEW_H
 
+#include <algorithm>
 #include <array>
 #include <cstdint>
 #include <cstring>
@@ -92,12 +93,16 @@
         return mPtr + mSize;
     }
 
+    AK_FORCE_INLINE bool contains(const int value) const {
+        return std::find(begin(), end(), value) != end();
+    }
+
     // Returns the view whose size is smaller than or equal to the given count.
-    const IntArrayView limit(const size_t maxSize) const {
+    AK_FORCE_INLINE const IntArrayView limit(const size_t maxSize) const {
         return IntArrayView(mPtr, std::min(maxSize, mSize));
     }
 
-    const IntArrayView skip(const size_t n) const {
+    AK_FORCE_INLINE const IntArrayView skip(const size_t n) const {
         if (mSize <= n) {
             return IntArrayView();
         }
@@ -110,6 +115,20 @@
         memmove(buffer->data() + offset, mPtr, sizeof(int) * mSize);
     }
 
+    AK_FORCE_INLINE int firstOrDefault(const int defaultValue) const {
+        if (empty()) {
+            return defaultValue;
+        }
+        return mPtr[0];
+    }
+
+    AK_FORCE_INLINE int lastOrDefault(const int defaultValue) const {
+        if (empty()) {
+            return defaultValue;
+        }
+        return mPtr[mSize - 1];
+    }
+
  private:
     DISALLOW_ASSIGNMENT_OPERATOR(IntArrayView);
 
diff --git a/native/jni/tests/suggest/policyimpl/dictionary/structure/v4/content/language_model_dict_content_test.cpp b/native/jni/tests/suggest/policyimpl/dictionary/structure/v4/content/language_model_dict_content_test.cpp
index 7608b45..06f82df 100644
--- a/native/jni/tests/suggest/policyimpl/dictionary/structure/v4/content/language_model_dict_content_test.cpp
+++ b/native/jni/tests/suggest/policyimpl/dictionary/structure/v4/content/language_model_dict_content_test.cpp
@@ -29,7 +29,7 @@
 TEST(LanguageModelDictContentTest, TestUnigramProbability) {
     LanguageModelDictContent languageModelDictContent(false /* useHistoricalInfo */);
 
-    const int flag = 0xFF;
+    const int flag = 0xF0;
     const int probability = 10;
     const int wordId = 100;
     const ProbabilityEntry probabilityEntry(flag, probability);
@@ -107,13 +107,15 @@
     languageModelDictContent.setProbabilityEntry(prevWordIds[0], &probabilityEntry);
     languageModelDictContent.setNgramProbabilityEntry(prevWordIds.limit(1), wordId,
             &bigramProbabilityEntry);
-    EXPECT_EQ(bigramProbability, languageModelDictContent.getWordProbability(prevWordIds, wordId));
+    EXPECT_EQ(bigramProbability, languageModelDictContent.getWordProbability(prevWordIds, wordId,
+            nullptr /* headerPolicy */));
     const ProbabilityEntry trigramProbabilityEntry(flag, trigramProbability);
     languageModelDictContent.setNgramProbabilityEntry(prevWordIds.limit(1),
             prevWordIds[1], &probabilityEntry);
     languageModelDictContent.setNgramProbabilityEntry(prevWordIds.limit(2), wordId,
             &trigramProbabilityEntry);
-    EXPECT_EQ(trigramProbability, languageModelDictContent.getWordProbability(prevWordIds, wordId));
+    EXPECT_EQ(trigramProbability, languageModelDictContent.getWordProbability(prevWordIds, wordId,
+            nullptr /* headerPolicy */));
 }
 
 }  // namespace
diff --git a/native/jni/tests/utils/int_array_view_test.cpp b/native/jni/tests/utils/int_array_view_test.cpp
index 3bc294c..487bd04 100644
--- a/native/jni/tests/utils/int_array_view_test.cpp
+++ b/native/jni/tests/utils/int_array_view_test.cpp
@@ -58,6 +58,19 @@
     EXPECT_EQ(object, intArrayView[0]);
 }
 
+TEST(IntArrayViewTest, TestContains) {
+    EXPECT_FALSE(IntArrayView().contains(0));
+    EXPECT_FALSE(IntArrayView().contains(1));
+
+    const std::vector<int> intVector = {3, 2, 1, 0, -1, -2};
+    IntArrayView intArrayView(intVector);
+    EXPECT_TRUE(intArrayView.contains(0));
+    EXPECT_TRUE(intArrayView.contains(3));
+    EXPECT_TRUE(intArrayView.contains(-2));
+    EXPECT_FALSE(intArrayView.contains(-3));
+    EXPECT_FALSE(intArrayView.limit(0).contains(3));
+}
+
 TEST(IntArrayViewTest, TestLimit) {
     const std::vector<int> intVector = {3, 2, 1, 0, -1, -2};
     IntArrayView intArrayView(intVector);
@@ -111,5 +124,25 @@
     EXPECT_EQ(70, buffer[6]);
 }
 
+TEST(IntArrayViewTest, TestFirstOrDefault) {
+    const std::vector<int> intVector = {3, 2, 1, 0, -1, -2};
+    IntArrayView intArrayView(intVector);
+
+    EXPECT_EQ(3, intArrayView.firstOrDefault(10));
+    EXPECT_EQ(10, intArrayView.limit(0).firstOrDefault(10));
+    EXPECT_EQ(-10, intArrayView.limit(0).firstOrDefault(-10));
+    EXPECT_EQ(10, intArrayView.skip(6).firstOrDefault(10));
+}
+
+TEST(IntArrayViewTest, TestLastOrDefault) {
+    const std::vector<int> intVector = {3, 2, 1, 0, -1, -2};
+    IntArrayView intArrayView(intVector);
+
+    EXPECT_EQ(-2, intArrayView.lastOrDefault(10));
+    EXPECT_EQ(10, intArrayView.limit(0).lastOrDefault(10));
+    EXPECT_EQ(-10, intArrayView.limit(0).lastOrDefault(-10));
+    EXPECT_EQ(10, intArrayView.skip(6).lastOrDefault(10));
+}
+
 }  // namespace
 }  // namespace latinime
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
index 4ca846b..8a628cd 100644
--- a/tests/AndroidManifest.xml
+++ b/tests/AndroidManifest.xml
@@ -17,7 +17,7 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.android.inputmethod.latin.tests">
 
-    <uses-sdk android:minSdkVersion="14" android:targetSdkVersion="19" />
+    <uses-sdk android:minSdkVersion="14" android:targetSdkVersion="21" />
 
     <uses-permission android:name="android.permission.READ_CONTACTS" />
 
diff --git a/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetTestsBase.java b/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetTestsBase.java
index 5708657..b64ab8c 100644
--- a/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetTestsBase.java
+++ b/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetTestsBase.java
@@ -51,7 +51,7 @@
     protected void setUp() throws Exception {
         super.setUp();
         final KeyboardTheme keyboardTheme = KeyboardTheme.searchKeyboardThemeById(
-                getKeyboardThemeForTests());
+                getKeyboardThemeForTests(), KeyboardTheme.KEYBOARD_THEMES);
         setContext(new ContextThemeWrapper(getContext(), keyboardTheme.mStyleId));
         KeyboardLayoutSet.onKeyboardThemeChanged();
 
diff --git a/tests/src/com/android/inputmethod/keyboard/KeyboardThemeTests.java b/tests/src/com/android/inputmethod/keyboard/KeyboardThemeTests.java
index c20954f..34cf407 100644
--- a/tests/src/com/android/inputmethod/keyboard/KeyboardThemeTests.java
+++ b/tests/src/com/android/inputmethod/keyboard/KeyboardThemeTests.java
@@ -28,6 +28,8 @@
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
 
+import java.util.Arrays;
+
 @SmallTest
 public class KeyboardThemeTests extends AndroidTestCase {
     private SharedPreferences mPrefs;
@@ -77,7 +79,9 @@
     }
 
     private void assertKeyboardTheme(final int sdkVersion, final int expectedThemeId) {
-        assertEquals(expectedThemeId, KeyboardTheme.getKeyboardTheme(mPrefs, sdkVersion).mThemeId);
+        final KeyboardTheme actualTheme = KeyboardTheme.getKeyboardTheme(
+                mPrefs, sdkVersion, KeyboardTheme.KEYBOARD_THEMES);
+        assertEquals(expectedThemeId, actualTheme.mThemeId);
     }
 
     /*
@@ -139,8 +143,8 @@
         final String oldPrefKey = KeyboardTheme.KLP_KEYBOARD_THEME_KEY;
         setKeyboardThemePreference(oldPrefKey, previousThemeId);
 
-        final KeyboardTheme defaultTheme =
-                KeyboardTheme.getDefaultKeyboardTheme(mPrefs, sdkVersion);
+        final KeyboardTheme defaultTheme = KeyboardTheme.getDefaultKeyboardTheme(
+                mPrefs, sdkVersion, KeyboardTheme.KEYBOARD_THEMES);
 
         assertNotNull(defaultTheme);
         assertEquals(expectedThemeId, defaultTheme.mThemeId);
@@ -194,7 +198,8 @@
         // Clean up new keyboard theme preference to simulate "upgrade to LXX keyboard".
         setKeyboardThemePreference(KeyboardTheme.LXX_KEYBOARD_THEME_KEY, THEME_ID_NULL);
 
-        final KeyboardTheme theme = KeyboardTheme.getKeyboardTheme(mPrefs, sdkVersion);
+        final KeyboardTheme theme = KeyboardTheme.getKeyboardTheme(
+                mPrefs, sdkVersion, KeyboardTheme.KEYBOARD_THEMES);
 
         assertNotNull(theme);
         assertEquals(expectedThemeId, theme.mThemeId);
@@ -341,4 +346,60 @@
         assertUpgradePlatformFromTo(
                 oldSdkVersion, newSdkVersion, THEME_ID_ILLEGAL, THEME_ID_LXX_LIGHT);
     }
+
+    /*
+     * Test for missing selected theme.
+     */
+    private static KeyboardTheme[] LIMITED_THEMES = {
+        KeyboardTheme.searchKeyboardThemeById(THEME_ID_ICS, KeyboardTheme.KEYBOARD_THEMES),
+        KeyboardTheme.searchKeyboardThemeById(THEME_ID_KLP, KeyboardTheme.KEYBOARD_THEMES)
+    };
+    static {
+        Arrays.sort(LIMITED_THEMES);
+    }
+
+    public void testMissingSelectedThemeIcs() {
+        // Clean up preferences.
+        setKeyboardThemePreference(KeyboardTheme.KLP_KEYBOARD_THEME_KEY, THEME_ID_NULL);
+        setKeyboardThemePreference(KeyboardTheme.LXX_KEYBOARD_THEME_KEY, THEME_ID_NULL);
+
+        final int sdkVersion = VERSION_CODES.ICE_CREAM_SANDWICH;
+        final String oldPrefKey = KeyboardTheme.getPreferenceKey(sdkVersion);
+        setKeyboardThemePreference(oldPrefKey, THEME_ID_LXX_LIGHT);
+
+        final KeyboardTheme actualTheme = KeyboardTheme.getKeyboardTheme(
+                mPrefs, sdkVersion, LIMITED_THEMES);
+        // LXX_LIGHT is missing, fall-back to KLP.
+        assertEquals(THEME_ID_KLP, actualTheme.mThemeId);
+    }
+
+    public void testMissingSelectedThemeKlp() {
+        // Clean up preferences.
+        setKeyboardThemePreference(KeyboardTheme.KLP_KEYBOARD_THEME_KEY, THEME_ID_NULL);
+        setKeyboardThemePreference(KeyboardTheme.LXX_KEYBOARD_THEME_KEY, THEME_ID_NULL);
+
+        final int sdkVersion = VERSION_CODES.KITKAT;
+        final String oldPrefKey = KeyboardTheme.getPreferenceKey(sdkVersion);
+        setKeyboardThemePreference(oldPrefKey, THEME_ID_LXX_LIGHT);
+
+        final KeyboardTheme actualTheme = KeyboardTheme.getKeyboardTheme(
+                mPrefs, sdkVersion, LIMITED_THEMES);
+        // LXX_LIGHT is missing, fall-back to KLP.
+        assertEquals(THEME_ID_KLP, actualTheme.mThemeId);
+    }
+
+    public void testMissingSelectedThemeLxx() {
+        // Clean up preferences.
+        setKeyboardThemePreference(KeyboardTheme.KLP_KEYBOARD_THEME_KEY, THEME_ID_NULL);
+        setKeyboardThemePreference(KeyboardTheme.LXX_KEYBOARD_THEME_KEY, THEME_ID_NULL);
+
+        final int sdkVersion = VERSION_CODES_LXX;
+        final String oldPrefKey = KeyboardTheme.getPreferenceKey(sdkVersion);
+        setKeyboardThemePreference(oldPrefKey, THEME_ID_LXX_DARK);
+
+        final KeyboardTheme actualTheme = KeyboardTheme.getKeyboardTheme(
+                mPrefs, sdkVersion, LIMITED_THEMES);
+        // LXX_DARK is missing, fall-back to KLP.
+        assertEquals(THEME_ID_KLP, actualTheme.mThemeId);
+    }
 }
diff --git a/tests/src/com/android/inputmethod/latin/InputTestsBase.java b/tests/src/com/android/inputmethod/latin/InputTestsBase.java
index d7a649a..6860bea 100644
--- a/tests/src/com/android/inputmethod/latin/InputTestsBase.java
+++ b/tests/src/com/android/inputmethod/latin/InputTestsBase.java
@@ -183,6 +183,9 @@
                 | InputType.TYPE_TEXT_FLAG_MULTI_LINE;
         mEditText.setInputType(inputType);
         mEditText.setEnabled(true);
+        if (null == Looper.myLooper()) {
+            Looper.prepare();
+        }
         setupService();
         mLatinIME = getService();
         setDebugMode(true);
diff --git a/tests/src/com/android/inputmethod/latin/utils/DistracterFilterTest.java b/tests/src/com/android/inputmethod/latin/utils/DistracterFilterTest.java
index 5fbd36a..6ed9120 100644
--- a/tests/src/com/android/inputmethod/latin/utils/DistracterFilterTest.java
+++ b/tests/src/com/android/inputmethod/latin/utils/DistracterFilterTest.java
@@ -57,7 +57,7 @@
         mDistracterFilter.close();
     }
 
-    public void testIsDistractorToWordsInDictionaries() {
+    public void testIsDistracterToWordsInDictionaries() {
         final PrevWordsInfo EMPTY_PREV_WORDS_INFO = PrevWordsInfo.EMPTY_PREV_WORDS_INFO;
 
         final Locale localeEnUs = new Locale("en", "US");