Merge "[AC5] Introduce the setting for phrase gesture."
diff --git a/java/res/values-hi/strings.xml b/java/res/values-hi/strings.xml
index 01bba5c..dcbbcd9 100644
--- a/java/res/values-hi/strings.xml
+++ b/java/res/values-hi/strings.xml
@@ -31,7 +31,7 @@
     <string name="correction_category" msgid="2236750915056607613">"पाठ सुधार"</string>
     <string name="gesture_typing_category" msgid="497263612130532630">"जेस्चर लिखना"</string>
     <string name="misc_category" msgid="6894192814868233453">"अन्य विकल्प"</string>
-    <string name="advanced_settings" msgid="362895144495591463">"उन्नत सेटिंग"</string>
+    <string name="advanced_settings" msgid="362895144495591463">"अतिरिक्त सेटिंग"</string>
     <string name="advanced_settings_summary" msgid="4487980456152830271">"विशेषज्ञों के लिए विकल्‍प"</string>
     <string name="include_other_imes_in_language_switch_list" msgid="4533689960308565519">"अन्‍य इनपुट पद्धतियों पर जाएं"</string>
     <string name="include_other_imes_in_language_switch_list_summary" msgid="840637129103317635">"भाषा स्‍विच कुंजी में अन्‍य इनपुट पद्धतियां भी शामिल हैं"</string>
diff --git a/java/res/values-land/dimens.xml b/java/res/values-land/dimens.xml
index c954e60..42a746b 100644
--- a/java/res/values-land/dimens.xml
+++ b/java/res/values-land/dimens.xml
@@ -75,5 +75,9 @@
     <dimen name="gesture_floating_preview_vertical_padding">15dp</dimen>
 
     <!-- Emoji keyboard -->
-    <fraction name="emoji_keyboard_key_width">8.3333%p</fraction>
+    <fraction name="emoji_keyboard_key_width">10%p</fraction>
+    <fraction name="emoji_keyboard_row_height">50%p</fraction>
+    <fraction name="emoji_keyboard_key_letter_size">60%p</fraction>
+    <integer name="emoji_keyboard_max_key_count">20</integer>
+
 </resources>
diff --git a/java/res/values-lo/donottranslate.xml b/java/res/values-lo/donottranslate.xml
new file mode 100644
index 0000000..a9893fe
--- /dev/null
+++ b/java/res/values-lo/donottranslate.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2013, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Whether this language uses spaces between words -->
+    <bool name="current_language_has_spaces">false</bool>
+</resources>
diff --git a/java/res/values-pt-rPT/strings.xml b/java/res/values-pt-rPT/strings.xml
index bad0bd6..cd89a77 100644
--- a/java/res/values-pt-rPT/strings.xml
+++ b/java/res/values-pt-rPT/strings.xml
@@ -134,7 +134,7 @@
     <string name="select_language" msgid="3693815588777926848">"Idiomas de introdução"</string>
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"Toque novamente para guardar"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Dicionário disponível"</string>
-    <string name="prefs_enable_log" msgid="6620424505072963557">"Activar comentários do utilizador"</string>
+    <string name="prefs_enable_log" msgid="6620424505072963557">"Ativar comentários do utilizador"</string>
     <string name="prefs_description_log" msgid="7525225584555429211">"Envie automaticamente estatísticas de utilização e relatórios de falhas e ajude-nos a melhorar este editor do método de introdução."</string>
     <string name="keyboard_layout" msgid="8451164783510487501">"Tema do teclado"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"Inglês (RU)"</string>
diff --git a/java/res/values-th/donottranslate.xml b/java/res/values-th/donottranslate.xml
index aeeebed..a9893fe 100644
--- a/java/res/values-th/donottranslate.xml
+++ b/java/res/values-th/donottranslate.xml
@@ -18,6 +18,6 @@
 */
 -->
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- Whether this language uses spaces -->
+    <!-- Whether this language uses spaces between words -->
     <bool name="current_language_has_spaces">false</bool>
 </resources>
diff --git a/java/res/values/dimens.xml b/java/res/values/dimens.xml
index 7de93e6..88e327f 100644
--- a/java/res/values/dimens.xml
+++ b/java/res/values/dimens.xml
@@ -117,6 +117,9 @@
 
     <!-- Emoji keyboard -->
     <fraction name="emoji_keyboard_key_width">14.2857%p</fraction>
+    <fraction name="emoji_keyboard_row_height">33%p</fraction>
+    <fraction name="emoji_keyboard_key_letter_size">90%p</fraction>
+    <integer name="emoji_keyboard_max_key_count">21</integer>
 
     <!-- Inset used in Accessibility mode to avoid accidental key presses when a finger slides off the screen. -->
     <dimen name="accessibility_edge_slop">8dp</dimen>
@@ -124,4 +127,5 @@
     <integer name="user_dictionary_max_word_length" translatable="false">48</integer>
 
     <dimen name="language_on_spacebar_horizontal_margin">1dp</dimen>
+
 </resources>
diff --git a/java/res/values/donottranslate.xml b/java/res/values/donottranslate.xml
index 52ebe16..82c5ce4 100644
--- a/java/res/values/donottranslate.xml
+++ b/java/res/values/donottranslate.xml
@@ -31,7 +31,7 @@
     <string name="symbols_word_separators">"&#x0009;&#x0020;\n"()[]{}*&amp;&lt;&gt;+=|.,;:!?/_\"</string>
     <!-- Word connectors -->
     <string name="symbols_word_connectors">\'-</string>
-    <!-- Whether this language uses spaces -->
+    <!-- Whether this language uses spaces between words -->
     <bool name="current_language_has_spaces">true</bool>
 
     <!--  Always show the suggestion strip -->
diff --git a/java/res/xml-sw600dp/rows_lao.xml b/java/res/xml-sw600dp/rows_lao.xml
new file mode 100644
index 0000000..cfe8db9
--- /dev/null
+++ b/java/res/xml-sw600dp/rows_lao.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2013, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <include
+        latin:keyboardLayout="@xml/key_styles_common" />
+    <Row
+        latin:keyWidth="7.5%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_lao1" />
+        <Key
+            latin:keyStyle="deleteKeyStyle"
+            latin:keyWidth="fillRight" />
+        </Row>
+    <Row
+        latin:keyWidth="7.5%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_lao2" />
+    </Row>
+    <Row
+        latin:keyWidth="7.5%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_lao3" />
+        <Key
+            latin:keyStyle="enterKeyStyle"
+            latin:keyWidth="fillRight" />
+    </Row>
+    <Row
+        latin:keyWidth="7.5%p"
+    >
+        <Key
+            latin:keyStyle="shiftKeyStyle"
+            latin:keyWidth="10.0%p" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_lao4" />
+        <include
+            latin:keyboardLayout="@xml/keys_exclamation_question" />
+    </Row>
+    <include
+        latin:keyboardLayout="@xml/row_qwerty4" />
+</merge>
diff --git a/java/res/xml/kbd_emoji_recents.xml b/java/res/xml/kbd_emoji_recents.xml
index f56b79a..73926ec 100644
--- a/java/res/xml/kbd_emoji_recents.xml
+++ b/java/res/xml/kbd_emoji_recents.xml
@@ -21,8 +21,9 @@
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
     latin:keyWidth="@fraction/emoji_keyboard_key_width"
-    latin:keyLetterSize="90%p"
+    latin:keyLetterSize="@fraction/emoji_keyboard_key_letter_size"
     latin:keyLabelSize="60%p"
+    latin:rowHeight="@fraction/emoji_keyboard_row_height"
 >
     <GridRows
         latin:codesArray="@array/emoji_recents"
diff --git a/java/res/xml/kbd_lao.xml b/java/res/xml/kbd_lao.xml
new file mode 100644
index 0000000..2bba330
--- /dev/null
+++ b/java/res/xml/kbd_lao.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2013, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<Keyboard
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+    latin:rowHeight="20%p"
+    latin:verticalGap="@fraction/key_bottom_gap_5row"
+    latin:keyLetterSize="@fraction/key_letter_ratio_5row"
+    latin:keyShiftedLetterHintRatio="@fraction/key_uppercase_letter_ratio_5row"
+    latin:touchPositionCorrectionData="@array/touch_position_correction_data_default"
+>
+    <include
+        latin:keyboardLayout="@xml/rows_lao" />
+</Keyboard>
diff --git a/java/res/xml/key_space_5kw.xml b/java/res/xml/key_space_5kw.xml
index 02ee42f..b6d38fb 100644
--- a/java/res/xml/key_space_5kw.xml
+++ b/java/res/xml/key_space_5kw.xml
@@ -23,7 +23,7 @@
 >
     <switch>
         <case
-            latin:languageCode="fa"
+            latin:languageCode="fa|ne"
             latin:languageSwitchKeyEnabled="true"
         >
             <Key
@@ -35,7 +35,7 @@
                 latin:keyStyle="zwnjKeyStyle" />
         </case>
         <case
-            latin:languageCode="fa"
+            latin:languageCode="fa|ne"
             latin:languageSwitchKeyEnabled="false"
         >
             <Key
diff --git a/java/res/xml/key_styles_currency.xml b/java/res/xml/key_styles_currency.xml
index 60333ee..b7677a2 100644
--- a/java/res/xml/key_styles_currency.xml
+++ b/java/res/xml/key_styles_currency.xml
@@ -95,14 +95,16 @@
         <!-- fa: Persian (Rial and Afgahni)
              hi: Hindi (Indian Rupee)
              iw: Hebrew (New Sheqel)
+             lo: Lao (Kip)
              mn: Mongolian (Tugrik)
+             ne: Nepali (Nepalese Rupee)
              th: Thai (Baht)
              uk: Ukrainian (Hryvnia)
              vi: Vietnamese (Dong)  -->
         <!-- TODO: The currency sign of Turkish Lira was created in 2012 and assigned U+20BA for
              its unicode, although there is no font glyph for it as of November 2012. -->
         <case
-            latin:languageCode="fa|hi|iw|mn|th|uk|vi"
+            latin:languageCode="fa|hi|iw|lo|mn|ne|th|uk|vi"
         >
             <!-- U+00A3: "£" POUND SIGN
                  U+20AC: "€" EURO SIGN
diff --git a/java/res/xml/keyboard_layout_set_lao.xml b/java/res/xml/keyboard_layout_set_lao.xml
new file mode 100644
index 0000000..2ffde45
--- /dev/null
+++ b/java/res/xml/keyboard_layout_set_lao.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2013, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<KeyboardLayoutSet
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin">
+    <Element
+        latin:elementName="alphabet"
+        latin:elementKeyboard="@xml/kbd_lao"
+        latin:enableProximityCharsCorrection="true" />
+    <Element
+        latin:elementName="alphabetAutomaticShifted"
+        latin:elementKeyboard="@xml/kbd_lao"
+        latin:enableProximityCharsCorrection="true" />
+    <!-- On these shifted alphabet layouts the proximity characters correction should be disabled
+         because the letters on these layouts aren't the ones in different case of the above
+         unshifted layouts. -->
+    <Element
+        latin:elementName="alphabetManualShifted"
+        latin:elementKeyboard="@xml/kbd_lao" />
+    <Element
+        latin:elementName="alphabetShiftLocked"
+        latin:elementKeyboard="@xml/kbd_lao" />
+    <Element
+        latin:elementName="alphabetShiftLockShifted"
+        latin:elementKeyboard="@xml/kbd_lao" />
+    <Element
+        latin:elementName="symbols"
+        latin:elementKeyboard="@xml/kbd_symbols" />
+    <Element
+        latin:elementName="symbolsShifted"
+        latin:elementKeyboard="@xml/kbd_symbols_shift" />
+    <Element
+        latin:elementName="phone"
+        latin:elementKeyboard="@xml/kbd_phone" />
+    <Element
+        latin:elementName="phoneSymbols"
+        latin:elementKeyboard="@xml/kbd_phone_symbols" />
+    <Element
+        latin:elementName="number"
+        latin:elementKeyboard="@xml/kbd_number" />
+</KeyboardLayoutSet>
diff --git a/java/res/xml/keyboard_layout_set_nepali_romanized.xml b/java/res/xml/keyboard_layout_set_nepali_romanized.xml
index 82f36cf..fbbc6a5 100644
--- a/java/res/xml/keyboard_layout_set_nepali_romanized.xml
+++ b/java/res/xml/keyboard_layout_set_nepali_romanized.xml
@@ -44,6 +44,9 @@
         latin:elementName="symbols"
         latin:elementKeyboard="@xml/kbd_symbols" />
     <Element
+        latin:elementName="symbolsShifted"
+        latin:elementKeyboard="@xml/kbd_symbols_shift" />
+    <Element
         latin:elementName="phone"
         latin:elementKeyboard="@xml/kbd_phone" />
     <Element
diff --git a/java/res/xml/keyboard_layout_set_nepali_traditional.xml b/java/res/xml/keyboard_layout_set_nepali_traditional.xml
index 2a6dc8e..4a3b601 100644
--- a/java/res/xml/keyboard_layout_set_nepali_traditional.xml
+++ b/java/res/xml/keyboard_layout_set_nepali_traditional.xml
@@ -44,6 +44,9 @@
         latin:elementName="symbols"
         latin:elementKeyboard="@xml/kbd_symbols" />
     <Element
+        latin:elementName="symbolsShifted"
+        latin:elementKeyboard="@xml/kbd_symbols_shift" />
+    <Element
         latin:elementName="phone"
         latin:elementKeyboard="@xml/kbd_phone" />
     <Element
diff --git a/java/res/xml/method.xml b/java/res/xml/method.xml
index c3d68c6..6014646 100644
--- a/java/res/xml/method.xml
+++ b/java/res/xml/method.xml
@@ -55,6 +55,7 @@
     ka: Georgian/georgian
     (kk: Kazakh/east_slavic) # disabled temporarily. waiting for strnig resources.
     ky: Kyrgyz/east_slavic
+    lo: Lao/lao
     lt: Lithuanian/qwerty
     lv: Latvian/qwerty
     mk: Macedonian/south_slavic
@@ -332,6 +333,13 @@
     />
     <subtype android:icon="@drawable/ic_subtype_keyboard"
             android:label="@string/subtype_generic"
+            android:subtypeId="0x8315772c"
+            android:imeSubtypeLocale="lo"
+            android:imeSubtypeMode="keyboard"
+            android:imeSubtypeExtraValue="KeyboardLayoutSet=lao"
+    />
+    <subtype android:icon="@drawable/ic_subtype_keyboard"
+            android:label="@string/subtype_generic"
             android:subtypeId="0x8321bb43"
             android:imeSubtypeLocale="lt"
             android:imeSubtypeMode="keyboard"
diff --git a/java/res/xml/rowkeys_lao1.xml b/java/res/xml/rowkeys_lao1.xml
new file mode 100644
index 0000000..fa1ad97
--- /dev/null
+++ b/java/res/xml/rowkeys_lao1.xml
@@ -0,0 +1,164 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2013, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <switch>
+        <case
+            latin:keyboardLayoutSetElement="alphabetManualShifted|alphabetShiftLocked|alphabetShiftLockShifted"
+        >
+            <!-- U+0ED1: "໑" LAO DIGIT ONE -->
+            <Key
+                latin:keyLabel="&#x0ED1;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0ED2: "໒" LAO DIGIT TWO -->
+            <Key
+                latin:keyLabel="&#x0ED2;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0ED3: "໓" LAO DIGIT THREE -->
+            <Key
+                latin:keyLabel="&#x0ED3;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0ED4: "໔" LAO DIGIT FOUR -->
+            <Key
+                latin:keyLabel="&#x0ED4;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0ECC: "໌" LAO CANCELLATION MARK -->
+            <Key
+                latin:keyLabel="&#x0ECC;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0EBC: "ຼ" LAO SEMIVOWEL SIGN LO -->
+            <Key
+                latin:keyLabel="&#x0EBC;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0ED5: "໕" LAO DIGIT FIVE -->
+            <Key
+                latin:keyLabel="&#x0ED5;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0ED6: "໖" LAO DIGIT SIX -->
+            <Key
+                latin:keyLabel="&#x0ED6;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0ED7: "໗" LAO DIGIT SEVEN -->
+            <Key
+                latin:keyLabel="&#x0ED7;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0ED8: "໘" LAO DIGIT EIGHT -->
+            <Key
+                latin:keyLabel="&#x0ED8;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0ED9: "໙" LAO DIGIT NINE -->
+            <Key
+                latin:keyLabel="&#x0ED9;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0ECD/U+0EC8: "ໍ່" LAO NIGGAHITA/LAO TONE MAI EK -->
+            <Key
+                latin:keyLabel="&#x0ECD;&#x0EC8;"
+                latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
+        </case>
+        <default>
+            <!-- U+0EA2: "ຢ" LAO LETTER YO
+                 U+0ED1: "໑" LAO DIGIT ONE -->
+            <Key
+                latin:keyLabel="&#x0EA2;"
+                latin:keyHintLabel="1"
+                latin:additionalMoreKeys="1"
+                latin:moreKeys="&#x0ED1;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0E9F: "ຟ" LAO LETTER FO SUNG
+                 U+0ED2: "໒" LAO DIGIT TWO -->
+            <Key
+                latin:keyLabel="&#x0E9F;"
+                latin:keyHintLabel="2"
+                latin:additionalMoreKeys="2"
+                latin:moreKeys="&#x0ED2;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0EC2: "ໂ" LAO VOWEL SIGN O
+                 U+0ED3: "໓" LAO DIGIT THREE -->
+            <Key
+                latin:keyLabel="&#x0EC2;"
+                latin:keyHintLabel="3"
+                latin:additionalMoreKeys="3"
+                latin:moreKeys="&#x0ED3;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0E96: "ຖ" LAO LETTER THO SUNG
+                 U+0ED4: "໔" LAO DIGIT FOUR -->
+            <Key
+                latin:keyLabel="&#x0E96;"
+                latin:keyHintLabel="4"
+                latin:additionalMoreKeys="4"
+                latin:moreKeys="&#x0ED4;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0EB8: "ຸ" LAO VOWEL SIGN U -->
+            <Key
+                latin:keyLabel="&#x0EB8;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0EB9: "ູ" LAO VOWEL SIGN UU -->
+            <Key
+                latin:keyLabel="&#x0EB9;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0E84: "ຄ" LAO LETTER KHO TAM
+                 U+0ED5: "໕" LAO DIGIT FIVE -->
+            <Key
+                latin:keyLabel="&#x0E84;"
+                latin:keyHintLabel="5"
+                latin:additionalMoreKeys="5"
+                latin:moreKeys="&#x0ED5;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0E95: "ຕ" LAO LETTER TO
+                 U+0ED6: "໖" LAO DIGIT SIX -->
+            <Key
+                latin:keyLabel="&#x0E95;"
+                latin:keyHintLabel="6"
+                latin:additionalMoreKeys="6"
+                latin:moreKeys="&#x0ED6;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0E88: "ຈ" LAO LETTER CO
+                 U+0ED7: "໗" LAO DIGIT SEVEN -->
+            <Key
+                latin:keyLabel="&#x0E88;"
+                latin:keyHintLabel="7"
+                latin:additionalMoreKeys="7"
+                latin:moreKeys="&#x0ED7;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0E82: "ຂ" LAO LETTER KHO SUNG
+                 U+0ED8: "໘" LAO DIGIT EIGHT -->
+            <Key
+                latin:keyLabel="&#x0E82;"
+                latin:keyHintLabel="8"
+                latin:additionalMoreKeys="8"
+                latin:moreKeys="&#x0ED8;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0E8A: "ຊ" LAO LETTER SO TAM
+                 U+0ED9: "໙" LAO DIGIT NINE -->
+            <Key
+                latin:keyLabel="&#x0E8A;"
+                latin:keyHintLabel="9"
+                latin:additionalMoreKeys="9"
+                latin:moreKeys="&#x0ED9;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0ECD: "ໍ" LAO NIGGAHITA -->
+            <Key
+                latin:keyLabel="&#x0ECD;"
+                latin:keyLabelFlags="fontNormal" />
+        </default>
+    </switch>
+</merge>
diff --git a/java/res/xml/rowkeys_lao2.xml b/java/res/xml/rowkeys_lao2.xml
new file mode 100644
index 0000000..fca58ac
--- /dev/null
+++ b/java/res/xml/rowkeys_lao2.xml
@@ -0,0 +1,127 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2013, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <switch>
+        <case
+            latin:keyboardLayoutSetElement="alphabetManualShifted|alphabetShiftLocked|alphabetShiftLockShifted"
+        >
+            <!-- U+0EBB/U+0EC9: "" LAO VOWEL SIGN MAI KON/LAO TONE MAI THO -->
+            <Key
+                latin:keyLabel="&#x0EBB;&#x0EC9;"
+                latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
+            <!-- U+0ED0: "໐" LAO DIGIT ZERO -->
+            <Key
+                latin:keyLabel="&#x0ED0;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0EB3/U+0EC9: "ຳ້" LAO VOWEL SIGN AM/LAO TONE MAI THO -->
+            <Key
+                latin:keyLabel="&#x0EB3;&#x0EC9;"
+                latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
+            <Key
+                latin:keyLabel="_" />
+            <Key
+                latin:keyLabel="+" />
+            <!-- U+0EB4/U+0EC9: "ິ້" LAO VOWEL SIGN I/LAO TONE MAI THO -->
+            <Key
+                latin:keyLabel="&#x0EB4;&#x0EC9;"
+                latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
+            <!-- U+0EB5/U+0EC9: "ີ້" LAO VOWEL SIGN II/LAO TONE MAI THO -->
+            <Key
+                latin:keyLabel="&#x0EB5;&#x0EC9;"
+                latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
+            <!-- U+0EA3: "ຣ" LAO LETTER LO LING -->
+            <Key
+                latin:keyLabel="&#x0EA3;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0EDC: "ໜ" LAO HO NO -->
+            <Key
+                latin:keyLabel="&#x0EDC;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0EBD: "ຽ" LAO SEMIVOWEL SIGN NYO -->
+            <Key
+                latin:keyLabel="&#x0EBD;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0EAB/U+0EBC: "" LAO LETTER HO SUNG/LAO SEMIVOWEL SIGN LO -->
+            <Key
+                latin:keyLabel="&#x0EAB;&#x0EBC;"
+                latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
+            <!-- U+201D: "”" RIGHT DOUBLE QUOTATION MARK -->
+            <Key
+                latin:keyLabel="&#x201D;" />
+        </case>
+        <default>
+            <!-- U+0EBB: "ົ" LAO VOWEL SIGN MAI KON -->
+            <Key
+                latin:keyLabel="&#x0EBB;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0EC4: "ໄ" LAO VOWEL SIGN AI
+                 U+0ED0: "໐" LAO DIGIT ZERO -->
+            <Key
+                latin:keyLabel="&#x0EC4;"
+                latin:keyHintLabel="0"
+                latin:additionalMoreKeys="0"
+                latin:moreKeys="&#x0ED0;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0EB3: "ຳ" LAO VOWEL SIGN AM -->
+            <Key
+                latin:keyLabel="&#x0EB3;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0E9E: "ພ" LAO LETTER PHO TAM -->
+            <Key
+                latin:keyLabel="&#x0E9E;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0EB0: "ະ" LAO VOWEL SIGN A -->
+            <Key
+                latin:keyLabel="&#x0EB0;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0EB4: "ິ" LAO VOWEL SIGN I -->
+            <Key
+                latin:keyLabel="&#x0EB4;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0EB5: "ີ" LAO VOWEL SIGN II -->
+            <Key
+                latin:keyLabel="&#x0EB5;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0EAE: "ຮ" LAO LETTER HO TAM -->
+            <Key
+                latin:keyLabel="&#x0EAE;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0E99: "ນ" LAO LETTER NO -->
+            <Key
+                latin:keyLabel="&#x0E99;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0E8D: "ຍ" LAO LETTER NYO -->
+            <Key
+                latin:keyLabel="&#x0E8D;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0E9A: "ບ" LAO LETTER BO -->
+            <Key
+                latin:keyLabel="&#x0E9A;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0EA5: "ລ" LAO LETTER LO LOOT -->
+            <Key
+                latin:keyLabel="&#x0EA5;"
+                latin:keyLabelFlags="fontNormal" />
+        </default>
+    </switch>
+</merge>
diff --git a/java/res/xml/rowkeys_lao3.xml b/java/res/xml/rowkeys_lao3.xml
new file mode 100644
index 0000000..2a6c2d1
--- /dev/null
+++ b/java/res/xml/rowkeys_lao3.xml
@@ -0,0 +1,110 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2013, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <switch>
+        <case
+            latin:keyboardLayoutSetElement="alphabetManualShifted|alphabetShiftLocked|alphabetShiftLockShifted"
+        >
+            <!-- U+0EB1/U+0EC9: "ັ້" LAO VOWEL SIGN MAI KAN/LAO TONE MAI THO -->
+            <Key
+                latin:keyLabel="&#x0EB1;&#x0EC9;"
+                latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
+            <Key
+                latin:keyLabel=";" />
+            <Key
+                latin:keyLabel="." />
+            <Key
+                latin:keyLabel="," />
+            <Key
+                latin:keyLabel=":" />
+            <!-- U+0ECA: "໊" LAO TONE MAI TI -->
+            <Key
+                latin:keyLabel="&#x0ECA;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0ECB: "໋" LAO TONE MAI CATAWA -->
+            <Key
+                latin:keyLabel="&#x0ECB;"
+                latin:keyLabelFlags="fontNormal" />
+            <Key
+                latin:keyLabel="!" />
+            <Key
+                latin:keyLabel="\?" />
+            <Key
+                latin:keyLabel="%" />
+            <Key
+                latin:keyLabel="=" />
+            <!-- U+201C: "“" LEFT DOUBLE QUOTATION MARK -->
+            <Key
+                latin:keyLabel="&#x201C;" />
+        </case>
+        <default>
+            <!-- U+0EB1: "ັ" LAO VOWEL SIGN MAI KAN -->
+            <Key
+                latin:keyLabel="&#x0EB1;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0EAB: "ຫ" LAO LETTER HO SUNG -->
+            <Key
+                latin:keyLabel="&#x0EAB;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0E81: "ກ" LAO LETTER KO -->
+            <Key
+                latin:keyLabel="&#x0E81;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0E94: "ດ" LAO LETTER DO -->
+            <Key
+                latin:keyLabel="&#x0E94;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0EC0: "ເ" LAO VOWEL SIGN E -->
+            <Key
+                latin:keyLabel="&#x0EC0;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0EC9: "້" LAO TONE MAI THO -->
+            <Key
+                latin:keyLabel="&#x0EC9;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0EC8: "່" LAO TONE MAI EK -->
+            <Key
+                latin:keyLabel="&#x0EC8;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0EB2: "າ" LAO VOWEL SIGN AA -->
+            <Key
+                latin:keyLabel="&#x0EB2;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0EAA: "ສ" LAO LETTER SO SUNG -->
+            <Key
+                latin:keyLabel="&#x0EAA;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0EA7: "ວ" LAO LETTER WO -->
+            <Key
+                latin:keyLabel="&#x0EA7;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0E87: "ງ" LAO LETTER NGO -->
+            <Key
+                latin:keyLabel="&#x0E87;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+201C: "“" LEFT DOUBLE QUOTATION MARK -->
+            <Key
+                latin:keyLabel="&#x201C;" />
+        </default>
+    </switch>
+</merge>
diff --git a/java/res/xml/rowkeys_lao4.xml b/java/res/xml/rowkeys_lao4.xml
new file mode 100644
index 0000000..fae9cc9
--- /dev/null
+++ b/java/res/xml/rowkeys_lao4.xml
@@ -0,0 +1,103 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2013, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <switch>
+        <case
+            latin:keyboardLayoutSetElement="alphabetManualShifted|alphabetShiftLocked|alphabetShiftLockShifted"
+        >
+            <!-- U+20AD: "₭" KIP SIGN -->
+            <Key
+                latin:keyLabel="&#x20AD;" />
+            <Key
+                latin:keyLabel="(" />
+            <!-- U+0EAF: "ຯ" LAO ELLIPSIS -->
+            <Key
+                latin:keyLabel="&#x0EAF;"
+                latin:keyLabelFlags="fontNormal" />
+            <Key
+                latin:keyLabel="\@" />
+            <!-- U+0EB6/U+0EC9: "ຶ້" LAO VOWEL SIGN Y/LAO TONE MAI THO -->
+            <Key
+                latin:keyLabel="&#x0EB6;&#x0EC9;"
+                latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
+            <!-- U+0EB7/U+0EC9: "ື້" LAO VOWEL SIGN YY/LAO TONE MAI THO -->
+            <Key
+                latin:keyLabel="&#x0EB7;&#x0EC9;"
+                latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
+            <!-- U+0EC6: "ໆ" LAO KO LA -->
+            <Key
+                latin:keyLabel="&#x0EC6;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0EDD: "ໝ" LAO HO MO -->
+            <Key
+                latin:keyLabel="&#x0EDD;"
+                latin:keyLabelFlags="fontNormal" />
+            <Key
+                latin:keyLabel="$" />
+            <Key
+                latin:keyLabel=")" />
+        </case>
+        <default>
+            <!-- U+0E9C: "ຜ" LAO LETTER PHO SUNG -->
+            <Key
+                latin:keyLabel="&#x0E9C;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0E9B: "ປ" LAO LETTER PO -->
+            <Key
+                latin:keyLabel="&#x0E9B;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0EC1: "ແ" LAO VOWEL SIGN EI -->
+            <Key
+                latin:keyLabel="&#x0EC1;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0EAD: "ອ" LAO LETTER O -->
+            <Key
+                latin:keyLabel="&#x0EAD;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0EB6: "ຶ" LAO VOWEL SIGN Y -->
+            <Key
+                latin:keyLabel="&#x0EB6;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0EB7: "ື" LAO VOWEL SIGN YY -->
+            <Key
+                latin:keyLabel="&#x0EB7;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0E97: "ທ" LAO LETTER THO TAM -->
+            <Key
+                latin:keyLabel="&#x0E97;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0EA1: "ມ" LAO LETTER MO -->
+            <Key
+                latin:keyLabel="&#x0EA1;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0EC3: "ໃ" LAO VOWEL SIGN AY -->
+            <Key
+                latin:keyLabel="&#x0EC3;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0E9D: "ຝ" LAO LETTER FO TAM -->
+            <Key
+                latin:keyLabel="&#x0E9D;"
+                latin:keyLabelFlags="fontNormal" />
+        </default>
+    </switch>
+</merge>
diff --git a/java/res/xml/rowkeys_nepali_romanized3.xml b/java/res/xml/rowkeys_nepali_romanized3.xml
index 5660596..166d028 100644
--- a/java/res/xml/rowkeys_nepali_romanized3.xml
+++ b/java/res/xml/rowkeys_nepali_romanized3.xml
@@ -48,7 +48,7 @@
                 latin:keyLabelFlags="fontNormal" />
             <!-- U+0923: "ण" DEVANAGARI LETTER NNA -->
             <Key
-                latin:keyLabel="&#x0936;"
+                latin:keyLabel="&#x0923;"
                 latin:keyLabelFlags="fontNormal" />
             <!-- Because the font rendering system prior to API version 16 can't automatically
                  render dotted circle for incomplete combining letter of some scripts, different
diff --git a/java/res/xml/rows_lao.xml b/java/res/xml/rows_lao.xml
new file mode 100644
index 0000000..321f411
--- /dev/null
+++ b/java/res/xml/rows_lao.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2013, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <include
+        latin:keyboardLayout="@xml/key_styles_common" />
+    <Row
+        latin:keyWidth="8.3333%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_lao1" />
+    </Row>
+    <Row
+        latin:keyWidth="8.3333%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_lao2" />
+    </Row>
+    <Row
+        latin:keyWidth="8.3333%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_lao3" />
+    </Row>
+    <Row
+        latin:keyWidth="8.3333%p"
+    >
+        <Key
+            latin:keyStyle="shiftKeyStyle" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_lao4" />
+        <Key
+            latin:keyStyle="deleteKeyStyle" />
+    </Row>
+    <include
+        latin:keyboardLayout="@xml/row_qwerty4" />
+</merge>
diff --git a/java/src/com/android/inputmethod/keyboard/EmojiKeyboardView.java b/java/src/com/android/inputmethod/keyboard/EmojiKeyboardView.java
index 9996a6d..d28b508 100644
--- a/java/src/com/android/inputmethod/keyboard/EmojiKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/EmojiKeyboardView.java
@@ -19,14 +19,18 @@
 import static com.android.inputmethod.latin.Constants.NOT_A_COORDINATE;
 
 import android.content.Context;
+import android.content.SharedPreferences;
 import android.content.res.ColorStateList;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
+import android.graphics.Rect;
 import android.os.Build;
+import android.preference.PreferenceManager;
 import android.support.v4.view.PagerAdapter;
 import android.support.v4.view.ViewPager;
 import android.util.AttributeSet;
 import android.util.Log;
+import android.util.Pair;
 import android.util.SparseArray;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -43,11 +47,15 @@
 import com.android.inputmethod.latin.Constants;
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.SubtypeSwitcher;
+import com.android.inputmethod.latin.settings.Settings;
 import com.android.inputmethod.latin.utils.CollectionUtils;
 import com.android.inputmethod.latin.utils.ResourceUtils;
 
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
 import java.util.HashMap;
+import java.util.concurrent.ConcurrentHashMap;
 
 /**
  * View class to implement Emoji keyboards.
@@ -75,16 +83,25 @@
 
     private KeyboardActionListener mKeyboardActionListener = KeyboardActionListener.EMPTY_LISTENER;
 
+    private static final int CATEGORY_ID_UNSPECIFIED = -1;
+    public static final int CATEGORY_ID_RECENTS = 0;
+    public static final int CATEGORY_ID_PEOPLE = 1;
+    public static final int CATEGORY_ID_OBJECTS = 2;
+    public static final int CATEGORY_ID_NATURE = 3;
+    public static final int CATEGORY_ID_PLACES = 4;
+    public static final int CATEGORY_ID_SYMBOLS = 5;
+    public static final int CATEGORY_ID_EMOTICONS = 6;
+
+    private static class CategoryProperties {
+        public int mCategoryId;
+        public int mPageCount;
+        public CategoryProperties(final int categoryId, final int pageCount) {
+            mCategoryId = categoryId;
+            mPageCount = pageCount;
+        }
+    }
+
     private static class EmojiCategory {
-        private int mCurrentCategory = CATEGORY_UNSPECIFIED;
-        private static final int CATEGORY_UNSPECIFIED = -1;
-        private static final int CATEGORY_RECENTS = 0;
-        private static final int CATEGORY_PEOPLE = 1;
-        private static final int CATEGORY_OBJECTS = 2;
-        private static final int CATEGORY_NATURE = 3;
-        private static final int CATEGORY_PLACES = 4;
-        private static final int CATEGORY_SYMBOLS = 5;
-        private static final int CATEGORY_EMOTICONS = 6;
         private static final String[] sCategoryName = {
                 "recents",
                 "people",
@@ -110,87 +127,231 @@
                 KeyboardId.ELEMENT_EMOJI_CATEGORY3,
                 KeyboardId.ELEMENT_EMOJI_CATEGORY4,
                 KeyboardId.ELEMENT_EMOJI_CATEGORY5,
-                KeyboardId.ELEMENT_EMOJI_CATEGORY6, };
+                KeyboardId.ELEMENT_EMOJI_CATEGORY6 };
+        private final SharedPreferences mPrefs;
+        private final int mMaxPageKeyCount;
+        private final KeyboardLayoutSet mLayoutSet;
         private final HashMap<String, Integer> mCategoryNameToIdMap = CollectionUtils.newHashMap();
-        private final ArrayList<Integer> mShownCategories = new ArrayList<Integer>();
+        private final ArrayList<CategoryProperties> mShownCategories =
+                CollectionUtils.newArrayList();
+        private final ConcurrentHashMap<Long, DynamicGridKeyboard>
+                mCategoryKeyboardMap = new ConcurrentHashMap<Long, DynamicGridKeyboard>();
 
-        public EmojiCategory() {
+        private int mCurrentCategoryId = CATEGORY_ID_UNSPECIFIED;
+        private int mCurrentCategoryPageId = 0;
+
+        public EmojiCategory(final SharedPreferences prefs, final Resources res,
+                final KeyboardLayoutSet layoutSet) {
+            mPrefs = prefs;
+            mMaxPageKeyCount = res.getInteger(R.integer.emoji_keyboard_max_key_count);
+            mLayoutSet = layoutSet;
             for (int i = 0; i < sCategoryName.length; ++i) {
                 mCategoryNameToIdMap.put(sCategoryName[i], i);
             }
-            mShownCategories.add(CATEGORY_RECENTS);
+            addShownCategoryId(CATEGORY_ID_RECENTS);
             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
-                mShownCategories.add(CATEGORY_PEOPLE);
-                mShownCategories.add(CATEGORY_OBJECTS);
-                mShownCategories.add(CATEGORY_NATURE);
-                mShownCategories.add(CATEGORY_PLACES);
-                // TODO: Restore last saved category
-                mCurrentCategory = CATEGORY_PEOPLE;
+                addShownCategoryId(CATEGORY_ID_PEOPLE);
+                addShownCategoryId(CATEGORY_ID_OBJECTS);
+                addShownCategoryId(CATEGORY_ID_NATURE);
+                addShownCategoryId(CATEGORY_ID_PLACES);
+                mCurrentCategoryId = CATEGORY_ID_PEOPLE;
             } else {
-                // TODO: Restore last saved category
-                mCurrentCategory = CATEGORY_SYMBOLS;
+                mCurrentCategoryId = CATEGORY_ID_SYMBOLS;
             }
-            mShownCategories.add(CATEGORY_SYMBOLS);
-            mShownCategories.add(CATEGORY_EMOTICONS);
+            addShownCategoryId(CATEGORY_ID_SYMBOLS);
+            addShownCategoryId(CATEGORY_ID_EMOTICONS);
+            getKeyboard(CATEGORY_ID_RECENTS, 0 /* cagetoryPageId */)
+                    .loadRecentKeys(mCategoryKeyboardMap.values());
         }
 
-        public String getCategoryName(int category) {
-            return sCategoryName[category];
+        private void addShownCategoryId(int categoryId) {
+            // Load a keyboard of categoryId
+            getKeyboard(categoryId, 0 /* cagetoryPageId */);
+            final CategoryProperties properties =
+                    new CategoryProperties(categoryId, getCategoryPageCount(categoryId));
+            mShownCategories.add(properties);
+        }
+
+        public String getCategoryName(int categoryId, int categoryPageId) {
+            return sCategoryName[categoryId] + "-" + categoryPageId;
         }
 
         public int getCategoryId(String name) {
-            return mCategoryNameToIdMap.get(name);
+            final String[] strings = name.split("-");
+            return mCategoryNameToIdMap.get(strings[0]);
         }
 
-        public int getCategoryIcon(int category) {
-            return sCategoryIcon[category];
+        public int getCategoryIcon(int categoryId) {
+            return sCategoryIcon[categoryId];
         }
 
-        public String getCategoryLabel(int category) {
-            return sCategoryLabel[category];
+        public String getCategoryLabel(int categoryId) {
+            return sCategoryLabel[categoryId];
         }
 
-        public ArrayList<Integer> getShownCategories() {
+        public ArrayList<CategoryProperties> getShownCategories() {
             return mShownCategories;
         }
 
-        public int getCurrentCategory() {
-            // TODO: Record current category.
-            return mCurrentCategory;
+        public int getCurrentCategoryId() {
+            return mCurrentCategoryId;
         }
 
-        public void setCurrentCategory(int category) {
-            mCurrentCategory = category;
+        public void setCurrentCategoryId(int categoryId) {
+            mCurrentCategoryId = categoryId;
+        }
+
+        public void setCurrentCategoryPageId(int id) {
+            mCurrentCategoryPageId = id;
+        }
+
+        public void saveLastTypedCategoryPage() {
+            Settings.writeEmojiCategoryLastTypedId(
+                    mPrefs, mCurrentCategoryId, mCurrentCategoryPageId);
         }
 
         public boolean isInRecentTab() {
-            return mCurrentCategory == CATEGORY_RECENTS;
+            return mCurrentCategoryId == CATEGORY_ID_RECENTS;
         }
 
-        public int getTabIdFromCategory(int category) {
+        public int getTabIdFromCategoryId(int categoryId) {
             for (int i = 0; i < mShownCategories.size(); ++i) {
-                if (mShownCategories.get(i) == category) {
+                if (mShownCategories.get(i).mCategoryId == categoryId) {
                     return i;
                 }
             }
-            Log.w(TAG, "category not found: " + category);
+            Log.w(TAG, "categoryId not found: " + categoryId);
+            return 0;
+        }
+
+        // Returns the view pager's page position for the categoryId
+        public int getPageIdFromCategoryId(int categoryId) {
+            final int lastSavedCategoryPageId =
+                    Settings.readEmojiCategoryLastTypedId(mPrefs, categoryId);
+            int sum = 0;
+            for (int i = 0; i < mShownCategories.size(); ++i) {
+                final CategoryProperties props = mShownCategories.get(i);
+                if (props.mCategoryId == categoryId) {
+                    return sum + lastSavedCategoryPageId;
+                }
+                sum += props.mPageCount;
+            }
+            Log.w(TAG, "categoryId not found: " + categoryId);
             return 0;
         }
 
         public int getRecentTabId() {
-            return getTabIdFromCategory(CATEGORY_RECENTS);
+            return getTabIdFromCategoryId(CATEGORY_ID_RECENTS);
         }
 
-        public int getCategoryFromTabId(int tabId) {
-            return mShownCategories.get(tabId);
+        private int getCategoryPageCount(int categoryId) {
+            final Keyboard keyboard = mLayoutSet.getKeyboard(sCategoryElementId[categoryId]);
+            return (keyboard.getKeys().length - 1) / mMaxPageKeyCount + 1;
         }
 
-        public int getElementIdFromTabId(int tabId) {
-            return sCategoryElementId[getCategoryFromTabId(tabId)];
+        // Returns a pair of the category id and the category page id from the view pager's page
+        // position. The category page id is numbered in each category. And the view page position
+        // is the position of the current shown page in the view pager which contains all pages of
+        // all categories.
+        public Pair<Integer, Integer> getCategoryIdAndPageIdFromPagePosition(int position) {
+            int sum = 0;
+            for (CategoryProperties properties : mShownCategories) {
+                final int temp = sum;
+                sum += properties.mPageCount;
+                if (sum > position) {
+                    return new Pair<Integer, Integer>(properties.mCategoryId, position - temp);
+                }
+            }
+            return null;
+        }
+
+        // Returns a keyboard from the view pager's page position.
+        public DynamicGridKeyboard getKeyboardFromPagePosition(int position) {
+            final Pair<Integer, Integer> categoryAndId =
+                    getCategoryIdAndPageIdFromPagePosition(position);
+            if (categoryAndId != null) {
+                return getKeyboard(categoryAndId.first, categoryAndId.second);
+            }
+            return null;
+        }
+
+        public DynamicGridKeyboard getKeyboard(int categoryId, int id) {
+            synchronized(mCategoryKeyboardMap) {
+                final long key = (((long) categoryId) << Constants.MAX_INT_BIT_COUNT) | id;
+                final DynamicGridKeyboard kbd;
+                if (!mCategoryKeyboardMap.containsKey(key)) {
+                    if (categoryId != CATEGORY_ID_RECENTS) {
+                        final Keyboard keyboard =
+                                mLayoutSet.getKeyboard(sCategoryElementId[categoryId]);
+                        final Key[][] sortedKeys = sortKeys(keyboard.getKeys(), mMaxPageKeyCount);
+                        for (int i = 0; i < sortedKeys.length; ++i) {
+                            final DynamicGridKeyboard tempKbd = new DynamicGridKeyboard(mPrefs,
+                                    mLayoutSet.getKeyboard(KeyboardId.ELEMENT_EMOJI_RECENTS),
+                                    mMaxPageKeyCount, categoryId, i /* categoryPageId */);
+                            for (Key emojiKey : sortedKeys[i]) {
+                                if (emojiKey == null) {
+                                    break;
+                                }
+                                tempKbd.addKeyLast(emojiKey);
+                            }
+                            mCategoryKeyboardMap.put((((long) categoryId)
+                                    << Constants.MAX_INT_BIT_COUNT) | i, tempKbd);
+                        }
+                        kbd = mCategoryKeyboardMap.get(key);
+                    } else {
+                        kbd = new DynamicGridKeyboard(mPrefs,
+                                mLayoutSet.getKeyboard(KeyboardId.ELEMENT_EMOJI_RECENTS),
+                                mMaxPageKeyCount, categoryId, 0 /* categoryPageId */);
+                        mCategoryKeyboardMap.put(key, kbd);
+                    }
+                } else {
+                    kbd = mCategoryKeyboardMap.get(key);
+                }
+                return kbd;
+            }
+        }
+
+        public int getTotalPageCountOfAllCategories() {
+            int sum = 0;
+            for (CategoryProperties properties : mShownCategories) {
+                sum += properties.mPageCount;
+            }
+            return sum;
+        }
+
+        private Key[][] sortKeys(Key[] inKeys, int maxPageCount) {
+            Key[] keys = Arrays.copyOf(inKeys, inKeys.length);
+            Arrays.sort(keys, 0, keys.length, new Comparator<Key>() {
+                @Override
+                public int compare(Key lhs, Key rhs) {
+                    final Rect lHitBox = lhs.getHitBox();
+                    final Rect rHitBox = rhs.getHitBox();
+                    if (lHitBox.top < rHitBox.top) {
+                        return -1;
+                    } else if (lHitBox.top > rHitBox.top) {
+                        return 1;
+                    }
+                    if (lHitBox.left < rHitBox.left) {
+                        return -1;
+                    } else if (lHitBox.left > rHitBox.left) {
+                        return 1;
+                    }
+                    if (lhs.getCode() == rhs.getCode()) {
+                        return 0;
+                    }
+                    return lhs.getCode() < rhs.getCode() ? -1 : 1;
+                }
+            });
+            final int pageCount = (keys.length - 1) / maxPageCount + 1;
+            final Key[][] retval = new Key[pageCount][maxPageCount];
+            for (int i = 0; i < keys.length; ++i) {
+                retval[i / maxPageCount][i % maxPageCount] = keys[i];
+            }
+            return retval;
         }
     }
 
-    private final EmojiCategory mEmojiCategory = new EmojiCategory();
+    private final EmojiCategory mEmojiCategory;
 
     public EmojiKeyboardView(final Context context, final AttributeSet attrs) {
         this(context, attrs, R.attr.emojiKeyboardViewStyle);
@@ -213,13 +374,14 @@
         final KeyboardLayoutSet.Builder builder = new KeyboardLayoutSet.Builder(
                 context, null /* editorInfo */);
         final Resources res = context.getResources();
+        final EmojiLayoutParams emojiLp = new EmojiLayoutParams(res);
         builder.setSubtype(SubtypeSwitcher.getInstance().getEmojiSubtype());
         builder.setKeyboardGeometry(ResourceUtils.getDefaultKeyboardWidth(res),
-                (int)ResourceUtils.getDefaultKeyboardHeight(res)
-                        + res.getDimensionPixelSize(R.dimen.suggestions_strip_height));
+                emojiLp.mEmojiKeyboardHeight);
         builder.setOptions(false, false, false /* lanuageSwitchKeyEnabled */);
         mLayoutSet = builder.build();
-        // TODO: Save/restore recent keys from/to preferences.
+        mEmojiCategory = new EmojiCategory(PreferenceManager.getDefaultSharedPreferences(context),
+                context.getResources(), builder.build());
     }
 
     @Override
@@ -235,20 +397,20 @@
         setMeasuredDimension(width, height);
     }
 
-    private void addTab(final TabHost host, final int category) {
-        final String tabId = mEmojiCategory.getCategoryName(category);
+    private void addTab(final TabHost host, final int categoryId) {
+        final String tabId = mEmojiCategory.getCategoryName(categoryId, 0 /* categoryPageId */);
         final TabHost.TabSpec tspec = host.newTabSpec(tabId);
         tspec.setContent(R.id.emoji_keyboard_dummy);
-        if (mEmojiCategory.getCategoryIcon(category) != 0) {
+        if (mEmojiCategory.getCategoryIcon(categoryId) != 0) {
             final ImageView iconView = (ImageView)LayoutInflater.from(getContext()).inflate(
                     R.layout.emoji_keyboard_tab_icon, null);
-            iconView.setImageResource(mEmojiCategory.getCategoryIcon(category));
+            iconView.setImageResource(mEmojiCategory.getCategoryIcon(categoryId));
             tspec.setIndicator(iconView);
         }
-        if (mEmojiCategory.getCategoryLabel(category) != null) {
+        if (mEmojiCategory.getCategoryLabel(categoryId) != null) {
             final TextView textView = (TextView)LayoutInflater.from(getContext()).inflate(
                     R.layout.emoji_keyboard_tab_label, null);
-            textView.setText(mEmojiCategory.getCategoryLabel(category));
+            textView.setText(mEmojiCategory.getCategoryLabel(categoryId));
             textView.setTextColor(mTabLabelColor);
             tspec.setIndicator(textView);
         }
@@ -259,8 +421,8 @@
     protected void onFinishInflate() {
         mTabHost = (TabHost)findViewById(R.id.emoji_category_tabhost);
         mTabHost.setup();
-        for (final int i : mEmojiCategory.getShownCategories()) {
-            addTab(mTabHost, i);
+        for (final CategoryProperties properties : mEmojiCategory.getShownCategories()) {
+            addTab(mTabHost, properties.mCategoryId);
         }
         mTabHost.setOnTabChangedListener(this);
         mTabHost.getTabWidget().setStripEnabled(true);
@@ -275,7 +437,7 @@
         final EmojiLayoutParams emojiLp = new EmojiLayoutParams(res);
         emojiLp.setPagerProps(mEmojiPager);
 
-        setCurrentCategory(mEmojiCategory.getCurrentCategory(), true /* force */);
+        setCurrentCategoryId(mEmojiCategory.getCurrentCategoryId(), true /* force */);
 
         final LinearLayout actionBar = (LinearLayout)findViewById(R.id.emoji_action_bar);
         emojiLp.setActionBarProps(actionBar);
@@ -302,14 +464,17 @@
 
     @Override
     public void onTabChanged(final String tabId) {
-        final int category = mEmojiCategory.getCategoryId(tabId);
-        setCurrentCategory(category, false /* force */);
+        final int categoryId = mEmojiCategory.getCategoryId(tabId);
+        setCurrentCategoryId(categoryId, false /* force */);
     }
 
 
     @Override
     public void onPageSelected(final int position) {
-        setCurrentCategory(mEmojiCategory.getCategoryFromTabId(position), false /* force */);
+        final Pair<Integer, Integer> newPos =
+                mEmojiCategory.getCategoryIdAndPageIdFromPagePosition(position);
+        setCurrentCategoryId(newPos.first /* categoryId */, false /* force */);
+        mEmojiCategory.setCurrentCategoryPageId(newPos.second /* categoryPageId */);
     }
 
     @Override
@@ -341,6 +506,7 @@
     @Override
     public void onKeyClick(final Key key) {
         mEmojiKeyboardAdapter.addRecentKey(key);
+        mEmojiCategory.saveLastTypedCategoryPage();
         final int code = key.getCode();
         if (code == Constants.CODE_OUTPUT_TEXT) {
             mKeyboardActionListener.onTextInput(key.getOutputText());
@@ -357,25 +523,25 @@
         mKeyboardActionListener = listener;
     }
 
-    private void setCurrentCategory(final int category, final boolean force) {
-        if (mEmojiCategory.getCurrentCategory() == category && !force) {
+    private void setCurrentCategoryId(final int categoryId, final boolean force) {
+        if (mEmojiCategory.getCurrentCategoryId() == categoryId && !force) {
             return;
         }
 
-        mEmojiCategory.setCurrentCategory(category);
-        final int newTabId = mEmojiCategory.getTabIdFromCategory(category);
-        if (force || mEmojiPager.getCurrentItem() != newTabId) {
-            mEmojiPager.setCurrentItem(newTabId, true /* smoothScroll */);
+        mEmojiCategory.setCurrentCategoryId(categoryId);
+        final int newTabId = mEmojiCategory.getTabIdFromCategoryId(categoryId);
+        final int newCategoryPageId = mEmojiCategory.getPageIdFromCategoryId(categoryId);
+        if (force || mEmojiCategory.getCategoryIdAndPageIdFromPagePosition(
+                mEmojiPager.getCurrentItem()).first != categoryId) {
+            mEmojiPager.setCurrentItem(newCategoryPageId, true /* smoothScroll */);
         }
         if (force || mTabHost.getCurrentTab() != newTabId) {
             mTabHost.setCurrentTab(newTabId);
         }
-        // TODO: Record current category
     }
 
     private static class EmojiKeyboardAdapter extends PagerAdapter {
         private final ScrollKeyboardView.OnKeyClickListener mListener;
-        private final KeyboardLayoutSet mLayoutSet;
         private final DynamicGridKeyboard mRecentsKeyboard;
         private final SparseArray<ScrollKeyboardView> mActiveKeyboardView =
                 CollectionUtils.newSparseArray();
@@ -387,16 +553,14 @@
                 final ScrollKeyboardView.OnKeyClickListener listener) {
             mEmojiCategory = emojiCategory;
             mListener = listener;
-            mLayoutSet = layoutSet;
-            mRecentsKeyboard = new DynamicGridKeyboard(
-                    layoutSet.getKeyboard(KeyboardId.ELEMENT_EMOJI_RECENTS));
+            mRecentsKeyboard = mEmojiCategory.getKeyboard(CATEGORY_ID_RECENTS, 0);
         }
 
         public void addRecentKey(final Key key) {
             if (mEmojiCategory.isInRecentTab()) {
                 return;
             }
-            mRecentsKeyboard.addRecentKey(key);
+            mRecentsKeyboard.addKeyFirst(key);
             final KeyboardView recentKeyboardView =
                     mActiveKeyboardView.get(mEmojiCategory.getRecentTabId());
             if (recentKeyboardView != null) {
@@ -406,7 +570,7 @@
 
         @Override
         public int getCount() {
-            return mEmojiCategory.getShownCategories().size();
+            return mEmojiCategory.getTotalPageCountOfAllCategories();
         }
 
         @Override
@@ -424,9 +588,8 @@
 
         @Override
         public Object instantiateItem(final ViewGroup container, final int position) {
-            final int elementId = mEmojiCategory.getElementIdFromTabId(position);
-            final Keyboard keyboard = (elementId == KeyboardId.ELEMENT_EMOJI_RECENTS)
-                    ? mRecentsKeyboard : mLayoutSet.getKeyboard(elementId);
+            final Keyboard keyboard =
+                    mEmojiCategory.getKeyboardFromPagePosition(position);
             final LayoutInflater inflater = LayoutInflater.from(container.getContext());
             final View view = inflater.inflate(
                     R.layout.emoji_keyboard_page, container, false /* attachToRoot */);
diff --git a/java/src/com/android/inputmethod/keyboard/EmojiLayoutParams.java b/java/src/com/android/inputmethod/keyboard/EmojiLayoutParams.java
index 6486fc9..5570d59 100644
--- a/java/src/com/android/inputmethod/keyboard/EmojiLayoutParams.java
+++ b/java/src/com/android/inputmethod/keyboard/EmojiLayoutParams.java
@@ -27,6 +27,8 @@
 public class EmojiLayoutParams {
     private static final int DEFAULT_KEYBOARD_ROWS = 4;
 
+    public final int mEmojiPagerHeight;
+    private final int mEmojiPagerBottomMargin;
     public final int mEmojiKeyboardHeight;
     public final int mEmojiActionBarHeight;
     public final int mKeyVerticalGap;
@@ -49,13 +51,15 @@
                 + mKeyVerticalGap;
         mEmojiActionBarHeight = ((int) baseheight) / DEFAULT_KEYBOARD_ROWS
                 - (mKeyVerticalGap - mBottomPadding) / 2;
-        mEmojiKeyboardHeight = defaultKeyboardHeight - mEmojiActionBarHeight;
+        mEmojiPagerHeight = defaultKeyboardHeight - mEmojiActionBarHeight;
+        mEmojiPagerBottomMargin = mKeyVerticalGap / 2;
+        mEmojiKeyboardHeight = mEmojiPagerHeight - mEmojiPagerBottomMargin - 1;
     }
 
     public void setPagerProps(ViewPager vp) {
         final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) vp.getLayoutParams();
-        lp.height = mEmojiKeyboardHeight - mKeyVerticalGap / 2;
-        lp.bottomMargin = mKeyVerticalGap / 2;
+        lp.height = mEmojiPagerHeight - mEmojiPagerBottomMargin;
+        lp.bottomMargin = mEmojiPagerBottomMargin;
         vp.setLayoutParams(lp);
     }
 
diff --git a/java/src/com/android/inputmethod/keyboard/internal/DynamicGridKeyboard.java b/java/src/com/android/inputmethod/keyboard/internal/DynamicGridKeyboard.java
index a226891..f203eb7 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/DynamicGridKeyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/DynamicGridKeyboard.java
@@ -16,33 +16,41 @@
 
 package com.android.inputmethod.keyboard.internal;
 
+import android.content.SharedPreferences;
 import android.text.TextUtils;
 
+import com.android.inputmethod.keyboard.EmojiKeyboardView;
 import com.android.inputmethod.keyboard.Key;
 import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.latin.Constants;
+import com.android.inputmethod.latin.settings.Settings;
 import com.android.inputmethod.latin.utils.CollectionUtils;
 
 import java.util.ArrayDeque;
-import java.util.Random;
+import java.util.Collection;
 
 /**
  * This is a Keyboard class where you can add keys dynamically shown in a grid layout
  */
-// TODO: Save/restore recent keys from/to preferences.
 public class DynamicGridKeyboard extends Keyboard {
     private static final int TEMPLATE_KEY_CODE_0 = 0x30;
     private static final int TEMPLATE_KEY_CODE_1 = 0x31;
+    // Recent codes are saved as an integer array, so we use comma as a separater.
+    private static final String RECENT_KEY_SEPARATOR = Constants.STRING_COMMA;
 
+    private final SharedPreferences mPrefs;
     private final int mLeftPadding;
     private final int mHorizontalStep;
     private final int mVerticalStep;
     private final int mColumnsNum;
-    private final int mMaxRecentKeyCount;
-    private final ArrayDeque<RecentKey> mRecentKeys = CollectionUtils.newArrayDeque();
+    private final int mMaxKeyCount;
+    private final boolean mIsRecents;
+    private final ArrayDeque<GridKey> mGridKeys = CollectionUtils.newArrayDeque();
 
-    private Key[] mCachedRecentKeys;
+    private Key[] mCachedGridKeys;
 
-    public DynamicGridKeyboard(final Keyboard templateKeyboard) {
+    public DynamicGridKeyboard(final SharedPreferences prefs, final Keyboard templateKeyboard,
+            final int maxKeyCount, final int categoryId, final int categoryPageId) {
         super(templateKeyboard);
         final Key key0 = getTemplateKey(TEMPLATE_KEY_CODE_0);
         final Key key1 = getTemplateKey(TEMPLATE_KEY_CODE_1);
@@ -50,8 +58,9 @@
         mHorizontalStep = Math.abs(key1.getX() - key0.getX());
         mVerticalStep = key0.getHeight() + mVerticalGap;
         mColumnsNum = mBaseWidth / mHorizontalStep;
-        final int rowsNum = mBaseHeight / mVerticalStep;
-        mMaxRecentKeyCount = mColumnsNum * rowsNum;
+        mMaxKeyCount = maxKeyCount;
+        mIsRecents = categoryId == EmojiKeyboardView.CATEGORY_ID_RECENTS;
+        mPrefs = prefs;
     }
 
     private Key getTemplateKey(final int code) {
@@ -63,32 +72,67 @@
         throw new RuntimeException("Can't find template key: code=" + code);
     }
 
-    private final Random random = new Random();
+    public void addKeyFirst(final Key usedKey) {
+        addKey(usedKey, true);
+        if (mIsRecents) {
+            saveRecentKeys();
+        }
+    }
 
-    public void addRecentKey(final Key usedKey) {
-        synchronized (mRecentKeys) {
-            mCachedRecentKeys = null;
-            final RecentKey key = (usedKey instanceof RecentKey)
-                    ? (RecentKey)usedKey : new RecentKey(usedKey);
-            while (mRecentKeys.remove(key)) {
+    public void addKeyLast(final Key usedKey) {
+        addKey(usedKey, false);
+    }
+
+    private void addKey(final Key usedKey, final boolean addFirst) {
+        synchronized (mGridKeys) {
+            mCachedGridKeys = null;
+            final GridKey key = new GridKey(usedKey);
+            while (mGridKeys.remove(key)) {
                 // Remove duplicate keys.
             }
-            mRecentKeys.addFirst(key);
-            while (mRecentKeys.size() > mMaxRecentKeyCount) {
-                mRecentKeys.removeLast();
+            if (addFirst) {
+                mGridKeys.addFirst(key);
+            } else {
+                mGridKeys.addLast(key);
+            }
+            while (mGridKeys.size() > mMaxKeyCount) {
+                mGridKeys.removeLast();
             }
             int index = 0;
-            for (final RecentKey recentKey : mRecentKeys) {
+            for (final GridKey gridKey : mGridKeys) {
                 final int keyX = getKeyX(index);
                 final int keyY = getKeyY(index);
-                final int x = keyX+random.nextInt(recentKey.getWidth());
-                final int y = keyY+random.nextInt(recentKey.getHeight());
-                recentKey.updateCorrdinates(keyX, keyY);
+                gridKey.updateCorrdinates(keyX, keyY);
                 index++;
             }
         }
     }
 
+    private void saveRecentKeys() {
+        final StringBuilder sb = new StringBuilder();
+        for (final Key key : mGridKeys) {
+            sb.append(key.getCode()).append(RECENT_KEY_SEPARATOR);
+        }
+        Settings.writeEmojiRecentKeys(mPrefs, sb.toString());
+    }
+
+    public void loadRecentKeys(Collection<DynamicGridKeyboard> keyboards) {
+        final String str = Settings.readEmojiRecentKeys(mPrefs);
+        for (String s : str.split(RECENT_KEY_SEPARATOR)) {
+            if (TextUtils.isEmpty(s)) {
+                continue;
+            }
+            final int code = Integer.valueOf(s);
+            for (DynamicGridKeyboard kbd : keyboards) {
+                final Key key = kbd.getKey(code);
+                if (key != null) {
+                    addKeyLast(key);
+                    break;
+                }
+            }
+        }
+    }
+
     private int getKeyX(final int index) {
         final int column = index % mColumnsNum;
         return column * mHorizontalStep + mLeftPadding;
@@ -101,26 +145,26 @@
 
     @Override
     public Key[] getKeys() {
-        synchronized (mRecentKeys) {
-            if (mCachedRecentKeys != null) {
-                return mCachedRecentKeys;
+        synchronized (mGridKeys) {
+            if (mCachedGridKeys != null) {
+                return mCachedGridKeys;
             }
-            mCachedRecentKeys = mRecentKeys.toArray(new Key[mRecentKeys.size()]);
-            return mCachedRecentKeys;
+            mCachedGridKeys = mGridKeys.toArray(new Key[mGridKeys.size()]);
+            return mCachedGridKeys;
         }
     }
 
     @Override
     public Key[] getNearestKeys(final int x, final int y) {
-        // TODO: Calculate the nearest key index in mRecentKeys from x and y.
+        // TODO: Calculate the nearest key index in mGridKeys from x and y.
         return getKeys();
     }
 
-    static final class RecentKey extends Key {
+    static final class GridKey extends Key {
         private int mCurrentX;
         private int mCurrentY;
 
-        public RecentKey(final Key originalKey) {
+        public GridKey(final Key originalKey) {
             super(originalKey);
         }
 
@@ -151,7 +195,7 @@
 
         @Override
         public String toString() {
-            return "RecentKey: " + super.toString();
+            return "GridKey: " + super.toString();
         }
     }
 }
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java
index 7008b06..a72595f 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java
@@ -383,7 +383,7 @@
         // Label for "switch to more symbol" modifier key.  Must be short to fit on key!
         /* 124 */ "= \\ <",
         // Label for "switch to more symbol" modifier key on tablets.  Must be short to fit on key!
-        /* 125 */ "~ [ {",
+        /* 125 */ "~ [ <",
         // Label for "Tab" key.  Must be short to fit on key!
         /* 126 */ "Tab",
         // Label for "switch to phone numeric" key.  Must be short to fit on key!
@@ -2056,6 +2056,25 @@
         /* 45 */ "\u0410\u0411\u0412",
     };
 
+    /* Language lo: Lao */
+    private static final String[] LANGUAGE_lo = {
+        /* 0~ */
+        null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+        null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+        null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+        /* ~44 */
+        // Label for "switch to alphabetic" key.
+        // U+0E81: "ກ" LAO LETTER KO
+        // U+0E82: "ຂ" LAO LETTER KHO SUNG
+        // U+0E84: "ຄ" LAO LETTER KHO TAM
+        /* 45 */ "\u0E81\u0E82\u0E84",
+        /* 46~ */
+        null, null, null, null, null,
+        /* ~50 */
+        // U+20AD: "₭" KIP SIGN
+        /* 51 */ "\u20AD",
+    };
+
     /* Language lt: Lithuanian */
     private static final String[] LANGUAGE_lt = {
         // U+0105: "ą" LATIN SMALL LETTER A WITH OGONEK
@@ -2347,6 +2366,63 @@
         /* 47 */ "!text/double_9qm_rqm",
     };
 
+    /* Language ne: Nepali */
+    private static final String[] LANGUAGE_ne = {
+        /* 0~ */
+        null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+        null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+        null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+        /* ~44 */
+        // Label for "switch to alphabetic" key.
+        // U+0915: "क" DEVANAGARI LETTER KA
+        // U+0916: "ख" DEVANAGARI LETTER KHA
+        // U+0917: "ग" DEVANAGARI LETTER GA
+        /* 45 */ "\u0915\u0916\u0917",
+        /* 46~ */
+        null, null, null, null, null,
+        /* ~50 */
+        // U+0930/U+0941/U+002E "रु." NEPALESE RUPEE SIGN
+        /* 51 */ "\u0930\u0941.",
+        /* 52~ */
+        null, null, null, null, null, null, null, null, null, null, null,
+        /* ~62 */
+        // U+0967: "१" DEVANAGARI DIGIT ONE
+        /* 63 */ "\u0967",
+        // U+0968: "२" DEVANAGARI DIGIT TWO
+        /* 64 */ "\u0968",
+        // U+0969: "३" DEVANAGARI DIGIT THREE
+        /* 65 */ "\u0969",
+        // U+096A: "४" DEVANAGARI DIGIT FOUR
+        /* 66 */ "\u096A",
+        // U+096B: "५" DEVANAGARI DIGIT FIVE
+        /* 67 */ "\u096B",
+        // U+096C: "६" DEVANAGARI DIGIT SIX
+        /* 68 */ "\u096C",
+        // U+096D: "७" DEVANAGARI DIGIT SEVEN
+        /* 69 */ "\u096D",
+        // U+096E: "८" DEVANAGARI DIGIT EIGHT
+        /* 70 */ "\u096E",
+        // U+096F: "९" DEVANAGARI DIGIT NINE
+        /* 71 */ "\u096F",
+        // U+0966: "०" DEVANAGARI DIGIT ZERO
+        /* 72 */ "\u0966",
+        // Label for "switch to symbols" key.
+        /* 73 */ "?\u0967\u0968\u0969",
+        // Label for "switch to symbols with microphone" key. This string shouldn't include the "mic"
+        // part because it'll be appended by the code.
+        /* 74 */ "\u0967\u0968\u0969",
+        /* 75 */ "1",
+        /* 76 */ "2",
+        /* 77 */ "3",
+        /* 78 */ "4",
+        /* 79 */ "5",
+        /* 80 */ "6",
+        /* 81 */ "7",
+        /* 82 */ "8",
+        /* 83 */ "9",
+        /* 84 */ "0",
+    };
+
     /* Language nl: Dutch */
     private static final String[] LANGUAGE_nl = {
         // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE
@@ -3332,11 +3408,13 @@
         "ka", LANGUAGE_ka, /* Georgian */
         "kk", LANGUAGE_kk, /* Kazakh */
         "ky", LANGUAGE_ky, /* Kirghiz */
+        "lo", LANGUAGE_lo, /* Lao */
         "lt", LANGUAGE_lt, /* Lithuanian */
         "lv", LANGUAGE_lv, /* Latvian */
         "mk", LANGUAGE_mk, /* Macedonian */
         "mn", LANGUAGE_mn, /* Mongolian */
         "nb", LANGUAGE_nb, /* Norwegian Bokmål */
+        "ne", LANGUAGE_ne, /* Nepali */
         "nl", LANGUAGE_nl, /* Dutch */
         "pl", LANGUAGE_pl, /* Polish */
         "pt", LANGUAGE_pt, /* Portuguese */
diff --git a/java/src/com/android/inputmethod/latin/Constants.java b/java/src/com/android/inputmethod/latin/Constants.java
index 8aec03f..029ba02 100644
--- a/java/src/com/android/inputmethod/latin/Constants.java
+++ b/java/src/com/android/inputmethod/latin/Constants.java
@@ -220,7 +220,11 @@
         }
     }
 
+    public static final int MAX_INT_BIT_COUNT = 32;
+    public static final String STRING_COMMA = ",";
+
     private Constants() {
         // This utility class is not publicly instantiable.
     }
+
 }
diff --git a/java/src/com/android/inputmethod/latin/settings/Settings.java b/java/src/com/android/inputmethod/latin/settings/Settings.java
index 686c8d5..1a0fecc 100644
--- a/java/src/com/android/inputmethod/latin/settings/Settings.java
+++ b/java/src/com/android/inputmethod/latin/settings/Settings.java
@@ -98,6 +98,10 @@
     public static final String PREF_SEND_FEEDBACK = "send_feedback";
     public static final String PREF_ABOUT_KEYBOARD = "about_keyboard";
 
+    // Emoji
+    public static final String PREF_EMOJI_RECENT_KEYS = "emoji_recent_keys";
+    public static final String PREF_EMOJI_CATEGORY_LAST_TYPED_ID = "emoji_category_last_typed_id";
+
     private Resources mRes;
     private SharedPreferences mPrefs;
     private SettingsValues mSettingsValues;
@@ -370,4 +374,24 @@
         final String tokenStr = mPrefs.getString(PREF_LAST_USED_PERSONALIZATION_TOKEN, null);
         return StringUtils.hexStringToByteArray(tokenStr);
     }
+
+    public static void writeEmojiRecentKeys(final SharedPreferences prefs, String str) {
+        prefs.edit().putString(PREF_EMOJI_RECENT_KEYS, str).apply();
+    }
+
+    public static String readEmojiRecentKeys(final SharedPreferences prefs) {
+        return prefs.getString(PREF_EMOJI_RECENT_KEYS, "");
+    }
+
+    public static void writeEmojiCategoryLastTypedId(
+            final SharedPreferences prefs, final int category, final int id) {
+        final String key = PREF_EMOJI_CATEGORY_LAST_TYPED_ID + category;
+        prefs.edit().putInt(key, id).apply();
+    }
+
+    public static int readEmojiCategoryLastTypedId(
+            final SharedPreferences prefs, final int category) {
+        final String key = PREF_EMOJI_CATEGORY_LAST_TYPED_ID + category;
+        return prefs.getInt(key, 0);
+    }
 }
diff --git a/native/jni/src/defines.h b/native/jni/src/defines.h
index 4605890..89dfa39 100644
--- a/native/jni/src/defines.h
+++ b/native/jni/src/defines.h
@@ -292,7 +292,6 @@
 // of the binary dictionary where a {key,value} string pair scheme is used.
 #define LARGEST_INT_DIGIT_COUNT 11
 
-#define NOT_A_VALID_WORD_POS (-99)
 #define NOT_A_CODE_POINT (-1)
 #define NOT_A_DISTANCE (-1)
 #define NOT_A_COORDINATE (-1)
diff --git a/native/jni/src/suggest/core/dicnode/dic_node.h b/native/jni/src/suggest/core/dicnode/dic_node.h
index cdd9f59..3770153 100644
--- a/native/jni/src/suggest/core/dicnode/dic_node.h
+++ b/native/jni/src/suggest/core/dicnode/dic_node.h
@@ -112,7 +112,7 @@
         mIsUsed = true;
         mIsCachedForNextSuggestion = false;
         mDicNodeProperties.init(
-                NOT_A_VALID_WORD_POS /* pos */, rootGroupPos, NOT_A_CODE_POINT /* nodeCodePoint */,
+                NOT_A_DICT_POS /* pos */, rootGroupPos, NOT_A_CODE_POINT /* nodeCodePoint */,
                 NOT_A_PROBABILITY /* probability */, false /* isTerminal */,
                 true /* hasChildren */, false /* isBlacklistedOrNotAWord */, 0 /* depth */,
                 0 /* terminalDepth */);
@@ -125,7 +125,7 @@
         mIsUsed = true;
         mIsCachedForNextSuggestion = dicNode->mIsCachedForNextSuggestion;
         mDicNodeProperties.init(
-                NOT_A_VALID_WORD_POS /* pos */, rootGroupPos, NOT_A_CODE_POINT /* nodeCodePoint */,
+                NOT_A_DICT_POS /* pos */, rootGroupPos, NOT_A_CODE_POINT /* nodeCodePoint */,
                 NOT_A_PROBABILITY /* probability */, false /* isTerminal */,
                 true /* hasChildren */, false /* isBlacklistedOrNotAWord */,  0 /* depth */,
                 0 /* terminalDepth */);
@@ -234,7 +234,7 @@
     }
 
     bool isFirstWord() const {
-        return mDicNodeState.mDicNodeStatePrevWord.getPrevWordNodePos() == NOT_A_VALID_WORD_POS;
+        return mDicNodeState.mDicNodeStatePrevWord.getPrevWordNodePos() == NOT_A_DICT_POS;
     }
 
     bool isCompletion(const int inputSize) const {
diff --git a/native/jni/src/suggest/core/dicnode/dic_node_utils.cpp b/native/jni/src/suggest/core/dicnode/dic_node_utils.cpp
index e815919..ec65114 100644
--- a/native/jni/src/suggest/core/dicnode/dic_node_utils.cpp
+++ b/native/jni/src/suggest/core/dicnode/dic_node_utils.cpp
@@ -89,7 +89,7 @@
     const int unigramProbability = node->getProbability();
     const int wordPos = node->getPos();
     const int prevWordPos = node->getPrevWordPos();
-    if (NOT_A_VALID_WORD_POS == wordPos || NOT_A_VALID_WORD_POS == prevWordPos) {
+    if (NOT_A_DICT_POS == wordPos || NOT_A_DICT_POS == prevWordPos) {
         // Note: Normally wordPos comes from the dictionary and should never equal
         // NOT_A_VALID_WORD_POS.
         return dictionaryStructurePolicy->getProbability(unigramProbability,
diff --git a/native/jni/src/suggest/core/dicnode/internal/dic_node_state_prevword.h b/native/jni/src/suggest/core/dicnode/internal/dic_node_state_prevword.h
index 9bc9687..b7af970 100644
--- a/native/jni/src/suggest/core/dicnode/internal/dic_node_state_prevword.h
+++ b/native/jni/src/suggest/core/dicnode/internal/dic_node_state_prevword.h
@@ -29,7 +29,7 @@
  public:
     AK_FORCE_INLINE DicNodeStatePrevWord()
             : mPrevWordCount(0), mPrevWordLength(0), mPrevWordStart(0), mPrevWordProbability(0),
-              mPrevWordNodePos(NOT_A_VALID_WORD_POS) {
+              mPrevWordNodePos(NOT_A_DICT_POS) {
         memset(mPrevWord, 0, sizeof(mPrevWord));
         memset(mPrevSpacePositions, 0, sizeof(mPrevSpacePositions));
     }
@@ -41,7 +41,7 @@
         mPrevWordCount = 0;
         mPrevWordStart = 0;
         mPrevWordProbability = -1;
-        mPrevWordNodePos = NOT_A_VALID_WORD_POS;
+        mPrevWordNodePos = NOT_A_DICT_POS;
         memset(mPrevSpacePositions, 0, sizeof(mPrevSpacePositions));
     }
 
diff --git a/native/jni/src/suggest/core/dictionary/bigram_dictionary.cpp b/native/jni/src/suggest/core/dictionary/bigram_dictionary.cpp
index cf1cd88..425b076 100644
--- a/native/jni/src/suggest/core/dictionary/bigram_dictionary.cpp
+++ b/native/jni/src/suggest/core/dictionary/bigram_dictionary.cpp
@@ -116,7 +116,7 @@
             mDictionaryStructurePolicy->getBigramsStructurePolicy(), pos);
     while (bigramsIt.hasNext()) {
         bigramsIt.next();
-        if (bigramsIt.getBigramPos() == NOT_A_VALID_WORD_POS) {
+        if (bigramsIt.getBigramPos() == NOT_A_DICT_POS) {
             continue;
         }
         const int codePointCount = mDictionaryStructurePolicy->
@@ -146,7 +146,7 @@
     if (0 >= prevWordLength) return NOT_A_DICT_POS;
     int pos = mDictionaryStructurePolicy->getTerminalNodePositionOfWord(prevWord, prevWordLength,
             forceLowerCaseSearch);
-    if (NOT_A_VALID_WORD_POS == pos) return NOT_A_DICT_POS;
+    if (NOT_A_DICT_POS == pos) return NOT_A_DICT_POS;
     return mDictionaryStructurePolicy->getBigramsPositionOfNode(pos);
 }
 
@@ -157,7 +157,7 @@
     if (NOT_A_DICT_POS == pos) return false;
     int nextWordPos = mDictionaryStructurePolicy->getTerminalNodePositionOfWord(word1, length1,
             false /* forceLowerCaseSearch */);
-    if (NOT_A_VALID_WORD_POS == nextWordPos) return false;
+    if (NOT_A_DICT_POS == nextWordPos) return false;
 
     BinaryDictionaryBigramsIterator bigramsIt(
             mDictionaryStructurePolicy->getBigramsStructurePolicy(), pos);
diff --git a/native/jni/src/suggest/core/dictionary/dictionary.cpp b/native/jni/src/suggest/core/dictionary/dictionary.cpp
index 02ece63..29fe7ab 100644
--- a/native/jni/src/suggest/core/dictionary/dictionary.cpp
+++ b/native/jni/src/suggest/core/dictionary/dictionary.cpp
@@ -87,7 +87,7 @@
 int Dictionary::getProbability(const int *word, int length) const {
     int pos = getDictionaryStructurePolicy()->getTerminalNodePositionOfWord(word, length,
             false /* forceLowerCaseSearch */);
-    if (NOT_A_VALID_WORD_POS == pos) {
+    if (NOT_A_DICT_POS == pos) {
         return NOT_A_PROBABILITY;
     }
     return getDictionaryStructurePolicy()->getUnigramProbabilityOfPtNode(pos);
diff --git a/native/jni/src/suggest/core/dictionary/multi_bigram_map.h b/native/jni/src/suggest/core/dictionary/multi_bigram_map.h
index 9efe5f6..aecf413 100644
--- a/native/jni/src/suggest/core/dictionary/multi_bigram_map.h
+++ b/native/jni/src/suggest/core/dictionary/multi_bigram_map.h
@@ -73,7 +73,7 @@
                     bigramsListPos);
             while (bigramsIt.hasNext()) {
                 bigramsIt.next();
-                if (bigramsIt.getBigramPos() == NOT_A_VALID_WORD_POS) {
+                if (bigramsIt.getBigramPos() == NOT_A_DICT_POS) {
                     continue;
                 }
                 mBigramMap[bigramsIt.getBigramPos()] = bigramsIt.getProbability();
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 2c22592..50f2bbd 100644
--- a/native/jni/src/suggest/core/session/dic_traverse_session.cpp
+++ b/native/jni/src/suggest/core/session/dic_traverse_session.cpp
@@ -35,13 +35,13 @@
             ->getMultiWordCostMultiplier();
     mSuggestOptions = suggestOptions;
     if (!prevWord) {
-        mPrevWordPos = NOT_A_VALID_WORD_POS;
+        mPrevWordPos = NOT_A_DICT_POS;
         return;
     }
     // TODO: merge following similar calls to getTerminalPosition into one case-insensitive call.
     mPrevWordPos = getDictionaryStructurePolicy()->getTerminalNodePositionOfWord(
             prevWord, prevWordLength, false /* forceLowerCaseSearch */);
-    if (mPrevWordPos == NOT_A_VALID_WORD_POS) {
+    if (mPrevWordPos == NOT_A_DICT_POS) {
         // Check bigrams for lower-cased previous word if original was not found. Useful for
         // auto-capitalized words like "The [current_word]".
         mPrevWordPos = getDictionaryStructurePolicy()->getTerminalNodePositionOfWord(
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 fe88935..e2ef5fc 100644
--- a/native/jni/src/suggest/core/session/dic_traverse_session.h
+++ b/native/jni/src/suggest/core/session/dic_traverse_session.h
@@ -59,7 +59,7 @@
     }
 
     AK_FORCE_INLINE DicTraverseSession(JNIEnv *env, jstring localeStr, bool usesLargeCache)
-            : mPrevWordPos(NOT_A_VALID_WORD_POS), mProximityInfo(0),
+            : mPrevWordPos(NOT_A_DICT_POS), mProximityInfo(0),
               mDictionary(0), mSuggestOptions(0), mDicNodesCache(usesLargeCache),
               mMultiBigramMap(), mInputSize(0), mPartiallyCommited(false), mMaxPointerCount(1),
               mMultiWordCostMultiplier(1.0f) {
diff --git a/native/jni/src/suggest/policyimpl/dictionary/bigram/bigram_list_read_write_utils.cpp b/native/jni/src/suggest/policyimpl/dictionary/bigram/bigram_list_read_write_utils.cpp
index d575474..09e832f 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/bigram/bigram_list_read_write_utils.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/bigram/bigram_list_read_write_utils.cpp
@@ -38,6 +38,22 @@
         BigramListReadWriteUtils::MASK_ATTRIBUTE_PROBABILITY = 0x0F;
 const int BigramListReadWriteUtils::ATTRIBUTE_ADDRESS_SHIFT = 4;
 
+/* static */ BigramListReadWriteUtils::BigramFlags
+        BigramListReadWriteUtils::getFlagsAndForwardPointer(const uint8_t *const bigramsBuf,
+                int *const pos) {
+    return ByteArrayUtils::readUint8AndAdvancePosition(bigramsBuf, pos);
+}
+
+/* static */ void BigramListReadWriteUtils::skipExistingBigrams(const uint8_t *const bigramsBuf,
+        int *const pos) {
+    BigramFlags flags = getFlagsAndForwardPointer(bigramsBuf, pos);
+    while (hasNext(flags)) {
+        *pos += attributeAddressSize(flags);
+        flags = getFlagsAndForwardPointer(bigramsBuf, pos);
+    }
+    *pos += attributeAddressSize(flags);
+}
+
 /* static */ int BigramListReadWriteUtils::getBigramAddressAndForwardPointer(
         const uint8_t *const bigramsBuf, const BigramFlags flags, int *const pos) {
     int offset = 0;
@@ -54,7 +70,7 @@
             break;
     }
     if (offset == 0) {
-        return NOT_A_VALID_WORD_POS;
+        return NOT_A_DICT_POS;
     }
     if (isOffsetNegative(flags)) {
         return origin - offset;
diff --git a/native/jni/src/suggest/policyimpl/dictionary/bigram/bigram_list_read_write_utils.h b/native/jni/src/suggest/policyimpl/dictionary/bigram/bigram_list_read_write_utils.h
index ee2b722..9a93074 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/bigram/bigram_list_read_write_utils.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/bigram/bigram_list_read_write_utils.h
@@ -21,7 +21,6 @@
 #include <stdint.h>
 
 #include "defines.h"
-#include "suggest/policyimpl/dictionary/utils/byte_array_utils.h"
 
 namespace latinime {
 
@@ -29,10 +28,7 @@
 public:
    typedef uint8_t BigramFlags;
 
-   static AK_FORCE_INLINE BigramFlags getFlagsAndForwardPointer(
-           const uint8_t *const bigramsBuf, int *const pos) {
-       return ByteArrayUtils::readUint8AndAdvancePosition(bigramsBuf, pos);
-   }
+   static BigramFlags getFlagsAndForwardPointer(const uint8_t *const bigramsBuf, int *const pos);
 
    static AK_FORCE_INLINE int getProbabilityFromFlags(const BigramFlags flags) {
        return flags & MASK_ATTRIBUTE_PROBABILITY;
@@ -43,15 +39,7 @@
    }
 
    // Bigrams reading methods
-   static AK_FORCE_INLINE void skipExistingBigrams(const uint8_t *const bigramsBuf,
-           int *const pos) {
-       BigramFlags flags = getFlagsAndForwardPointer(bigramsBuf, pos);
-       while (hasNext(flags)) {
-           *pos += attributeAddressSize(flags);
-           flags = getFlagsAndForwardPointer(bigramsBuf, pos);
-       }
-       *pos += attributeAddressSize(flags);
-   }
+   static void skipExistingBigrams(const uint8_t *const bigramsBuf, int *const pos);
 
    static int getBigramAddressAndForwardPointer(const uint8_t *const bigramsBuf,
            const BigramFlags flags, int *const pos);
@@ -79,7 +67,7 @@
            const int entryPos, const int targetPos, const int probability, const bool hasNext,
            BigramFlags *const outBigramFlags, uint32_t *const outOffset,
            int *const outOffsetFieldSize) {
-       if (targetPos == NOT_A_VALID_WORD_POS) {
+       if (targetPos == NOT_A_DICT_POS) {
            return false;
        }
        BigramFlags flags = probability & MASK_ATTRIBUTE_PROBABILITY;
diff --git a/native/jni/src/suggest/policyimpl/dictionary/bigram/dynamic_bigram_list_policy.cpp b/native/jni/src/suggest/policyimpl/dictionary/bigram/dynamic_bigram_list_policy.cpp
index 4ee1381..ca3b64d 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/bigram/dynamic_bigram_list_policy.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/bigram/dynamic_bigram_list_policy.cpp
@@ -31,7 +31,7 @@
             BigramListReadWriteUtils::getFlagsAndForwardPointer(buffer, pos);
     int originalBigramPos = BigramListReadWriteUtils::getBigramAddressAndForwardPointer(
             buffer, flags, pos);
-    if (usesAdditionalBuffer && originalBigramPos != NOT_A_VALID_WORD_POS) {
+    if (usesAdditionalBuffer && originalBigramPos != NOT_A_DICT_POS) {
         originalBigramPos += mBuffer->getOriginalBufferSize();
     }
     *outBigramPos = followBigramLinkAndGetCurrentBigramPtNodePos(originalBigramPos);
@@ -66,7 +66,7 @@
         flags = BigramListReadWriteUtils::getFlagsAndForwardPointer(buffer, fromPos);
         int originalBigramPos = BigramListReadWriteUtils::getBigramAddressAndForwardPointer(
                 buffer, flags, fromPos);
-        if (originalBigramPos == NOT_A_VALID_WORD_POS) {
+        if (originalBigramPos == NOT_A_DICT_POS) {
             // skip invalid bigram entry.
             continue;
         }
@@ -172,7 +172,7 @@
         }
         int originalBigramPos = BigramListReadWriteUtils::getBigramAddressAndForwardPointer(
                 buffer, flags, &pos);
-        if (usesAdditionalBuffer && originalBigramPos != NOT_A_VALID_WORD_POS) {
+        if (usesAdditionalBuffer && originalBigramPos != NOT_A_DICT_POS) {
             originalBigramPos += mBuffer->getOriginalBufferSize();
         }
         const int bigramPos = followBigramLinkAndGetCurrentBigramPtNodePos(originalBigramPos);
@@ -192,8 +192,8 @@
 
 int DynamicBigramListPolicy::followBigramLinkAndGetCurrentBigramPtNodePos(
         const int originalBigramPos) const {
-    if (originalBigramPos == NOT_A_VALID_WORD_POS) {
-        return NOT_A_VALID_WORD_POS;
+    if (originalBigramPos == NOT_A_DICT_POS) {
+        return NOT_A_DICT_POS;
     }
     int currentPos = originalBigramPos;
     DynamicPatriciaTrieNodeReader nodeReader(mBuffer, this /* bigramsPolicy */, mShortcutPolicy);
@@ -206,7 +206,7 @@
         if (bigramLinkCount > BIGRAM_LINK_COUNT_LIMIT) {
             AKLOGI("Bigram link is invalid. start position: %d", bigramPos);
             ASSERT(false);
-            return NOT_A_VALID_WORD_POS;
+            return NOT_A_DICT_POS;
         }
     }
     return currentPos;
diff --git a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_node_reader.cpp b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_node_reader.cpp
index 56ef60a..e455080 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_node_reader.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_node_reader.cpp
@@ -25,6 +25,13 @@
 
 void DynamicPatriciaTrieNodeReader::fetchNodeInfoFromBufferAndProcessMovedNode(const int nodePos,
         const int maxCodePointCount, int *const outCodePoints) {
+    if (nodePos < 0 || nodePos >= mBuffer->getTailPosition()) {
+        AKLOGE("Fetching PtNode info form invalid dictionary position: %d, dictionary size: %d",
+                nodePos, mBuffer->getTailPosition());
+        ASSERT(false);
+        invalidatePtNodeInfo();
+        return;
+    }
     const bool usesAdditionalBuffer = mBuffer->isInAdditionalBuffer(nodePos);
     const uint8_t *const dictBuf = mBuffer->getBuffer(usesAdditionalBuffer);
     int pos = nodePos;
@@ -62,7 +69,7 @@
     if (usesAdditionalBuffer && mChildrenPos != NOT_A_DICT_POS) {
         mChildrenPos += mBuffer->getOriginalBufferSize();
     }
-    if (mSiblingPos == NOT_A_VALID_WORD_POS && DynamicPatriciaTrieReadingUtils::isMoved(mFlags)) {
+    if (mSiblingPos == NOT_A_DICT_POS && DynamicPatriciaTrieReadingUtils::isMoved(mFlags)) {
         mBigramLinkedNodePos = mChildrenPos;
     } else {
         mBigramLinkedNodePos = NOT_A_DICT_POS;
@@ -83,7 +90,7 @@
         mBigramPos = NOT_A_DICT_POS;
     }
     // Update siblingPos if needed.
-    if (mSiblingPos == NOT_A_VALID_WORD_POS) {
+    if (mSiblingPos == NOT_A_DICT_POS) {
         // Sibling position is the tail position of current node.
         mSiblingPos = pos;
     }
@@ -94,4 +101,19 @@
     }
 }
 
+void DynamicPatriciaTrieNodeReader::invalidatePtNodeInfo() {
+    mHeadPos = NOT_A_DICT_POS;
+    mFlags = 0;
+    mParentPos = NOT_A_DICT_POS;
+    mCodePointCount = 0;
+    mProbabilityFieldPos = NOT_A_DICT_POS;
+    mProbability = NOT_A_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;
+}
+
 }
diff --git a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_node_reader.h b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_node_reader.h
index 89d38a5..6ef5f58 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_node_reader.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_node_reader.h
@@ -39,12 +39,12 @@
             const DictionaryBigramsStructurePolicy *const bigramsPolicy,
             const DictionaryShortcutsStructurePolicy *const shortcutsPolicy)
             : mBuffer(buffer), mBigramsPolicy(bigramsPolicy),
-              mShortcutsPolicy(shortcutsPolicy), mHeadPos(NOT_A_VALID_WORD_POS), mFlags(0),
+              mShortcutsPolicy(shortcutsPolicy), mHeadPos(NOT_A_DICT_POS), mFlags(0),
               mParentPos(NOT_A_DICT_POS), mCodePointCount(0), mProbabilityFieldPos(NOT_A_DICT_POS),
               mProbability(NOT_A_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_VALID_WORD_POS) {}
+              mSiblingPos(NOT_A_DICT_POS) {}
 
     ~DynamicPatriciaTrieNodeReader() {}
 
@@ -56,7 +56,7 @@
 
     AK_FORCE_INLINE void fetchNodeInfoFromBufferAndGetNodeCodePoints(const int nodePos,
             const int maxCodePointCount, int *const outCodePoints) {
-        mSiblingPos = NOT_A_VALID_WORD_POS;
+        mSiblingPos = NOT_A_DICT_POS;
         mBigramLinkedNodePos = NOT_A_DICT_POS;
         fetchNodeInfoFromBufferAndProcessMovedNode(nodePos, maxCodePointCount, outCodePoints);
     }
@@ -156,6 +156,8 @@
 
     void fetchNodeInfoFromBufferAndProcessMovedNode(const int nodePos, const int maxCodePointCount,
             int *const outCodePoints);
+
+    void invalidatePtNodeInfo();
 };
 } // namespace latinime
 #endif /* LATINIME_DYNAMIC_PATRICIA_TRIE_NODE_READER_H */
diff --git a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_policy.cpp b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_policy.cpp
index ece1781..cccc090 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_policy.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_policy.cpp
@@ -116,7 +116,7 @@
             if (!readingHelper.isMatchedCodePoint(
                     j, searchCodePoints[matchedCodePointCount + j])) {
                 // Different code point is found. The given word is not included in the dictionary.
-                return NOT_A_VALID_WORD_POS;
+                return NOT_A_DICT_POS;
             }
         }
         // All characters are matched.
@@ -125,14 +125,14 @@
             return nodeReader->getHeadPos();
         }
         if (!nodeReader->hasChildren()) {
-            return NOT_A_VALID_WORD_POS;
+            return NOT_A_DICT_POS;
         }
         // Advance to the children nodes.
         readingHelper.readChildNode();
     }
     // If we already traversed the tree further than the word is long, there means
     // there was no match (or we would have found it).
-    return NOT_A_VALID_WORD_POS;
+    return NOT_A_DICT_POS;
 }
 
 int DynamicPatriciaTriePolicy::getProbability(const int unigramProbability,
@@ -149,7 +149,7 @@
 }
 
 int DynamicPatriciaTriePolicy::getUnigramProbabilityOfPtNode(const int nodePos) const {
-    if (nodePos == NOT_A_VALID_WORD_POS) {
+    if (nodePos == NOT_A_DICT_POS) {
         return NOT_A_PROBABILITY;
     }
     DynamicPatriciaTrieNodeReader nodeReader(&mBufferWithExtendableBuffer,
@@ -162,7 +162,7 @@
 }
 
 int DynamicPatriciaTriePolicy::getShortcutPositionOfNode(const int nodePos) const {
-    if (nodePos == NOT_A_VALID_WORD_POS) {
+    if (nodePos == NOT_A_DICT_POS) {
         return NOT_A_DICT_POS;
     }
     DynamicPatriciaTrieNodeReader nodeReader(&mBufferWithExtendableBuffer,
@@ -175,7 +175,7 @@
 }
 
 int DynamicPatriciaTriePolicy::getBigramsPositionOfNode(const int nodePos) const {
-    if (nodePos == NOT_A_VALID_WORD_POS) {
+    if (nodePos == NOT_A_DICT_POS) {
         return NOT_A_DICT_POS;
     }
     DynamicPatriciaTrieNodeReader nodeReader(&mBufferWithExtendableBuffer,
@@ -209,12 +209,12 @@
     }
     const int word0Pos = getTerminalNodePositionOfWord(word0, length0,
             false /* forceLowerCaseSearch */);
-    if (word0Pos == NOT_A_VALID_WORD_POS) {
+    if (word0Pos == NOT_A_DICT_POS) {
         return false;
     }
     const int word1Pos = getTerminalNodePositionOfWord(word1, length1,
             false /* forceLowerCaseSearch */);
-    if (word1Pos == NOT_A_VALID_WORD_POS) {
+    if (word1Pos == NOT_A_DICT_POS) {
         return false;
     }
     DynamicPatriciaTrieWritingHelper writingHelper(&mBufferWithExtendableBuffer,
@@ -230,12 +230,12 @@
     }
     const int word0Pos = getTerminalNodePositionOfWord(word0, length0,
             false /* forceLowerCaseSearch */);
-    if (word0Pos == NOT_A_VALID_WORD_POS) {
+    if (word0Pos == NOT_A_DICT_POS) {
         return false;
     }
     const int word1Pos = getTerminalNodePositionOfWord(word1, length1,
             false /* forceLowerCaseSearch */);
-    if (word1Pos == NOT_A_VALID_WORD_POS) {
+    if (word1Pos == NOT_A_DICT_POS) {
         return false;
     }
     DynamicPatriciaTrieWritingHelper writingHelper(&mBufferWithExtendableBuffer,
diff --git a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_helper.h b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_helper.h
index db1c392..120fd76 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_helper.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_helper.h
@@ -72,8 +72,7 @@
 
     // Initialize reading state with the head position of a node.
     AK_FORCE_INLINE void initWithNodePos(const int nodePos) {
-        // TODO: Consolidate NOT_A_VALID_WORD_POS and NOT_A_DICT_POS
-        if (nodePos == NOT_A_VALID_WORD_POS || nodePos == NOT_A_DICT_POS) {
+        if (nodePos == NOT_A_DICT_POS) {
             mPos = NOT_A_DICT_POS;
         } else {
             mIsError = false;
diff --git a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_utils.cpp b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_utils.cpp
index c7e89ff..8428c0b 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_utils.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_utils.cpp
@@ -28,6 +28,17 @@
 const DptReadingUtils::NodeFlags DptReadingUtils::FLAG_IS_MOVED = 0x40;
 const DptReadingUtils::NodeFlags DptReadingUtils::FLAG_IS_DELETED = 0x80;
 
+/* static */ int DptReadingUtils::getForwardLinkPosition(const uint8_t *const buffer,
+        const int pos) {
+    int linkAddressPos = pos;
+    return ByteArrayUtils::readSint24AndAdvancePosition(buffer, &linkAddressPos);
+}
+
+/* static */ int DptReadingUtils::getParentPosAndAdvancePosition(const uint8_t *const buffer,
+        int *const pos) {
+    return ByteArrayUtils::readSint24AndAdvancePosition(buffer, pos);
+}
+
 /* static */ int DptReadingUtils::readChildrenPositionAndAdvancePosition(
         const uint8_t *const buffer, int *const pos) {
     const int base = *pos;
diff --git a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_utils.h b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_utils.h
index 5a2ad9c..db5f9b1 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_utils.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_utils.h
@@ -20,7 +20,6 @@
 #include <stdint.h>
 
 #include "defines.h"
-#include "suggest/policyimpl/dictionary/utils/byte_array_utils.h"
 
 namespace latinime {
 
@@ -28,19 +27,13 @@
  public:
     typedef uint8_t NodeFlags;
 
-    static AK_FORCE_INLINE int getForwardLinkPosition(const uint8_t *const buffer, const int pos) {
-        int linkAddressPos = pos;
-        return ByteArrayUtils::readSint24AndAdvancePosition(buffer, &linkAddressPos);
-    }
+    static int getForwardLinkPosition(const uint8_t *const buffer, const int pos);
 
     static AK_FORCE_INLINE bool isValidForwardLinkPosition(const int forwardLinkAddress) {
         return forwardLinkAddress != 0;
     }
 
-    static AK_FORCE_INLINE int getParentPosAndAdvancePosition(const uint8_t *const buffer,
-            int *const pos) {
-        return ByteArrayUtils::readSint24AndAdvancePosition(buffer, pos);
-    }
+    static int getParentPosAndAdvancePosition(const uint8_t *const buffer, int *const pos);
 
     static int readChildrenPositionAndAdvancePosition(const uint8_t *const buffer, int *const pos);
 
diff --git a/native/jni/src/suggest/policyimpl/dictionary/patricia_trie_policy.cpp b/native/jni/src/suggest/policyimpl/dictionary/patricia_trie_policy.cpp
index d5a83a9..e6cff43 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/patricia_trie_policy.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/patricia_trie_policy.cpp
@@ -219,7 +219,7 @@
 }
 
 // This function gets the position of the terminal node of the exact matching word in the
-// dictionary. If no match is found, it returns NOT_A_VALID_WORD_POS.
+// dictionary. If no match is found, it returns NOT_A_DICT_POS.
 int PatriciaTriePolicy::getTerminalNodePositionOfWord(const int *const inWord,
         const int length, const bool forceLowerCaseSearch) const {
     int pos = getRootPosition();
@@ -228,7 +228,7 @@
     while (true) {
         // If we already traversed the tree further than the word is long, there means
         // there was no match (or we would have found it).
-        if (wordPos >= length) return NOT_A_VALID_WORD_POS;
+        if (wordPos >= length) return NOT_A_DICT_POS;
         int ptNodeCount = PatriciaTrieReadingUtils::getPtNodeArraySizeAndAdvancePosition(mDictRoot,
                 &pos);
         const int wChar = forceLowerCaseSearch
@@ -236,7 +236,7 @@
         while (true) {
             // If there are no more PtNodes in this array, it means we could not
             // find a matching character for this depth, therefore there is no match.
-            if (0 >= ptNodeCount) return NOT_A_VALID_WORD_POS;
+            if (0 >= ptNodeCount) return NOT_A_DICT_POS;
             const int ptNodePos = pos;
             const PatriciaTrieReadingUtils::NodeFlags flags =
                     PatriciaTrieReadingUtils::getFlagsAndAdvancePosition(mDictRoot, &pos);
@@ -245,7 +245,7 @@
             if (character == wChar) {
                 // This is the correct PtNode. Only one PtNode may start with the same char within
                 // a PtNode array, so either we found our match in this array, or there is
-                // no match and we can return NOT_A_VALID_WORD_POS. So we will check all the
+                // no match and we can return NOT_A_DICT_POS. So we will check all the
                 // characters in this PtNode indeed does match.
                 if (PatriciaTrieReadingUtils::hasMultipleChars(flags)) {
                     character = PatriciaTrieReadingUtils::getCodePointAndAdvancePosition(mDictRoot,
@@ -256,8 +256,8 @@
                         // character that does not match, as explained above, it means the word is
                         // not in the dictionary (by virtue of this PtNode being the only one to
                         // match the word on the first character, but not matching the whole word).
-                        if (wordPos >= length) return NOT_A_VALID_WORD_POS;
-                        if (inWord[wordPos] != character) return NOT_A_VALID_WORD_POS;
+                        if (wordPos >= length) return NOT_A_DICT_POS;
+                        if (inWord[wordPos] != character) return NOT_A_DICT_POS;
                         character = PatriciaTrieReadingUtils::getCodePointAndAdvancePosition(
                                 mDictRoot, &pos);
                     }
@@ -274,7 +274,7 @@
                     PatriciaTrieReadingUtils::readProbabilityAndAdvancePosition(mDictRoot, &pos);
                 }
                 if (!PatriciaTrieReadingUtils::hasChildrenInFlags(flags)) {
-                    return NOT_A_VALID_WORD_POS;
+                    return NOT_A_DICT_POS;
                 }
                 // We have children and we are still shorter than the word we are searching for, so
                 // we need to traverse children. Put the pointer on the children position, and
@@ -320,7 +320,7 @@
 }
 
 int PatriciaTriePolicy::getUnigramProbabilityOfPtNode(const int nodePos) const {
-    if (nodePos == NOT_A_VALID_WORD_POS) {
+    if (nodePos == NOT_A_DICT_POS) {
         return NOT_A_PROBABILITY;
     }
     int pos = nodePos;
@@ -342,7 +342,7 @@
 }
 
 int PatriciaTriePolicy::getShortcutPositionOfNode(const int nodePos) const {
-    if (nodePos == NOT_A_VALID_WORD_POS) {
+    if (nodePos == NOT_A_DICT_POS) {
         return NOT_A_DICT_POS;
     }
     int pos = nodePos;
@@ -362,7 +362,7 @@
 }
 
 int PatriciaTriePolicy::getBigramsPositionOfNode(const int nodePos) const {
-    if (nodePos == NOT_A_VALID_WORD_POS) {
+    if (nodePos == NOT_A_DICT_POS) {
         return NOT_A_DICT_POS;
     }
     int pos = nodePos;
diff --git a/native/jni/src/suggest/policyimpl/dictionary/patricia_trie_reading_utils.cpp b/native/jni/src/suggest/policyimpl/dictionary/patricia_trie_reading_utils.cpp
index 576a158..1316b42 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/patricia_trie_reading_utils.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/patricia_trie_reading_utils.cpp
@@ -42,6 +42,63 @@
 // Flag for blacklist
 const PtReadingUtils::NodeFlags PtReadingUtils::FLAG_IS_BLACKLISTED = 0x01;
 
+/* static */ int PtReadingUtils::getPtNodeArraySizeAndAdvancePosition(
+        const uint8_t *const buffer, int *const pos) {
+    const uint8_t firstByte = ByteArrayUtils::readUint8AndAdvancePosition(buffer, pos);
+    if (firstByte < 0x80) {
+        return firstByte;
+    } else {
+        return ((firstByte & 0x7F) << 8) ^ ByteArrayUtils::readUint8AndAdvancePosition(
+                buffer, pos);
+    }
+}
+
+/* static */ PtReadingUtils::NodeFlags PtReadingUtils::getFlagsAndAdvancePosition(
+        const uint8_t *const buffer, int *const pos) {
+    return ByteArrayUtils::readUint8AndAdvancePosition(buffer, pos);
+}
+
+/* static */ int PtReadingUtils::getCodePointAndAdvancePosition(const uint8_t *const buffer,
+        int *const pos) {
+    return ByteArrayUtils::readCodePointAndAdvancePosition(buffer, pos);
+}
+
+// Returns the number of read characters.
+/* static */ int PtReadingUtils::getCharsAndAdvancePosition(const uint8_t *const buffer,
+        const NodeFlags flags, const int maxLength, int *const outBuffer, int *const pos) {
+    int length = 0;
+    if (hasMultipleChars(flags)) {
+        length = ByteArrayUtils::readStringAndAdvancePosition(buffer, maxLength, outBuffer,
+                pos);
+    } else {
+        if (maxLength > 0) {
+            outBuffer[0] = getCodePointAndAdvancePosition(buffer, pos);
+            length = 1;
+        }
+    }
+    return length;
+}
+
+// Returns the number of skipped characters.
+/* static */ int PtReadingUtils::skipCharacters(const uint8_t *const buffer, const NodeFlags flags,
+        const int maxLength, int *const pos) {
+    if (hasMultipleChars(flags)) {
+        return ByteArrayUtils::advancePositionToBehindString(buffer, maxLength, pos);
+    } else {
+        if (maxLength > 0) {
+            getCodePointAndAdvancePosition(buffer, pos);
+            return 1;
+        } else {
+            return 0;
+        }
+    }
+}
+
+/* static */ int PtReadingUtils::readProbabilityAndAdvancePosition(const uint8_t *const buffer,
+        int *const pos) {
+    return ByteArrayUtils::readUint8AndAdvancePosition(buffer, pos);
+}
+
 /* static */ int PtReadingUtils::readChildrenPositionAndAdvancePosition(
         const uint8_t *const buffer, const NodeFlags flags, int *const pos) {
     const int base = *pos;
diff --git a/native/jni/src/suggest/policyimpl/dictionary/patricia_trie_reading_utils.h b/native/jni/src/suggest/policyimpl/dictionary/patricia_trie_reading_utils.h
index 2b0646d..8420ee9 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/patricia_trie_reading_utils.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/patricia_trie_reading_utils.h
@@ -20,7 +20,6 @@
 #include <stdint.h>
 
 #include "defines.h"
-#include "suggest/policyimpl/dictionary/utils/byte_array_utils.h"
 
 namespace latinime {
 
@@ -28,62 +27,21 @@
  public:
     typedef uint8_t NodeFlags;
 
-    static AK_FORCE_INLINE int getPtNodeArraySizeAndAdvancePosition(
-            const uint8_t *const buffer, int *const pos) {
-        const uint8_t firstByte = ByteArrayUtils::readUint8AndAdvancePosition(buffer, pos);
-        if (firstByte < 0x80) {
-            return firstByte;
-        } else {
-            return ((firstByte & 0x7F) << 8) ^ ByteArrayUtils::readUint8AndAdvancePosition(
-                    buffer, pos);
-        }
-    }
+    static int getPtNodeArraySizeAndAdvancePosition(const uint8_t *const buffer, int *const pos);
 
-    static AK_FORCE_INLINE NodeFlags getFlagsAndAdvancePosition(const uint8_t *const buffer,
-            int *const pos) {
-        return ByteArrayUtils::readUint8AndAdvancePosition(buffer, pos);
-    }
+    static NodeFlags getFlagsAndAdvancePosition(const uint8_t *const buffer, int *const pos);
 
-    static AK_FORCE_INLINE int getCodePointAndAdvancePosition(const uint8_t *const buffer,
-            int *const pos) {
-        return ByteArrayUtils::readCodePointAndAdvancePosition(buffer, pos);
-    }
+    static int getCodePointAndAdvancePosition(const uint8_t *const buffer, int *const pos);
 
     // Returns the number of read characters.
-    static AK_FORCE_INLINE int getCharsAndAdvancePosition(const uint8_t *const buffer,
-            const NodeFlags flags, const int maxLength, int *const outBuffer, int *const pos) {
-        int length = 0;
-        if (hasMultipleChars(flags)) {
-            length = ByteArrayUtils::readStringAndAdvancePosition(buffer, maxLength, outBuffer,
-                    pos);
-        } else {
-            if (maxLength > 0) {
-                outBuffer[0] = getCodePointAndAdvancePosition(buffer, pos);
-                length = 1;
-            }
-        }
-        return length;
-    }
+    static int getCharsAndAdvancePosition(const uint8_t *const buffer, const NodeFlags flags,
+            const int maxLength, int *const outBuffer, int *const pos);
 
     // Returns the number of skipped characters.
-    static AK_FORCE_INLINE int skipCharacters(const uint8_t *const buffer, const NodeFlags flags,
-            const int maxLength, int *const pos) {
-        if (hasMultipleChars(flags)) {
-            return ByteArrayUtils::advancePositionToBehindString(buffer, maxLength, pos);
-        } else {
-            if (maxLength > 0) {
-                getCodePointAndAdvancePosition(buffer, pos);
-                return 1;
-            } else {
-                return 0;
-            }
-        }
-    }
+    static int skipCharacters(const uint8_t *const buffer, const NodeFlags flags,
+            const int maxLength, int *const pos);
 
-    static AK_FORCE_INLINE int readProbabilityAndAdvancePosition(const uint8_t *const buffer,
-            int *const pos) {
-        return ByteArrayUtils::readUint8AndAdvancePosition(buffer, pos);
-    }
+    static int readProbabilityAndAdvancePosition(const uint8_t *const buffer, int *const pos);
 
     static int readChildrenPositionAndAdvancePosition(const uint8_t *const buffer,
             const NodeFlags flags, int *const pos);
diff --git a/native/jni/src/suggest/policyimpl/dictionary/shortcut/shortcut_list_reading_utils.cpp b/native/jni/src/suggest/policyimpl/dictionary/shortcut/shortcut_list_reading_utils.cpp
index e70bb50..847dcde 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/shortcut/shortcut_list_reading_utils.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/shortcut/shortcut_list_reading_utils.cpp
@@ -16,6 +16,8 @@
 
 #include "suggest/policyimpl/dictionary/shortcut/shortcut_list_reading_utils.h"
 
+#include "suggest/policyimpl/dictionary/utils/byte_array_utils.h"
+
 namespace latinime {
 
 // Flag for presence of more attributes
@@ -28,4 +30,22 @@
 // The numeric value of the shortcut probability that means 'whitelist'.
 const int ShortcutListReadingUtils::WHITELIST_SHORTCUT_PROBABILITY = 15;
 
+/* static */ ShortcutListReadingUtils::ShortcutFlags
+        ShortcutListReadingUtils::getFlagsAndForwardPointer(const uint8_t *const dictRoot,
+                int *const pos) {
+    return ByteArrayUtils::readUint8AndAdvancePosition(dictRoot, pos);
+}
+
+/* static */ int ShortcutListReadingUtils::getShortcutListSizeAndForwardPointer(
+        const uint8_t *const dictRoot, int *const pos) {
+    // readUint16andAdvancePosition() returns an offset *including* the uint16 field itself.
+    return ByteArrayUtils::readUint16AndAdvancePosition(dictRoot, 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);
+}
+
 } // namespace latinime
diff --git a/native/jni/src/suggest/policyimpl/dictionary/shortcut/shortcut_list_reading_utils.h b/native/jni/src/suggest/policyimpl/dictionary/shortcut/shortcut_list_reading_utils.h
index 5f4f240..a83ed5a 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/shortcut/shortcut_list_reading_utils.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/shortcut/shortcut_list_reading_utils.h
@@ -20,7 +20,6 @@
 #include <stdint.h>
 
 #include "defines.h"
-#include "suggest/policyimpl/dictionary/utils/byte_array_utils.h"
 
 namespace latinime {
 
@@ -28,10 +27,7 @@
  public:
     typedef uint8_t ShortcutFlags;
 
-    static AK_FORCE_INLINE ShortcutFlags getFlagsAndForwardPointer(
-            const uint8_t *const dictRoot, int *const pos) {
-        return ByteArrayUtils::readUint8AndAdvancePosition(dictRoot, pos);
-    }
+    static ShortcutFlags getFlagsAndForwardPointer(const uint8_t *const dictRoot, int *const pos);
 
     static AK_FORCE_INLINE int getProbabilityFromFlags(const ShortcutFlags flags) {
         return flags & MASK_ATTRIBUTE_PROBABILITY;
@@ -43,12 +39,7 @@
 
     // This method returns the size of the shortcut list region excluding the shortcut list size
     // field at the beginning.
-    static AK_FORCE_INLINE int getShortcutListSizeAndForwardPointer(
-            const uint8_t *const dictRoot, int *const pos) {
-        // readUint16andAdvancePosition() returns an offset *including* the uint16 field itself.
-        return ByteArrayUtils::readUint16AndAdvancePosition(dictRoot, pos)
-                - SHORTCUT_LIST_SIZE_FIELD_SIZE;
-    }
+    static int getShortcutListSizeAndForwardPointer(const uint8_t *const dictRoot, int *const pos);
 
     static AK_FORCE_INLINE int getShortcutListSizeFieldSize() {
         return SHORTCUT_LIST_SIZE_FIELD_SIZE;
@@ -63,11 +54,8 @@
         return getProbabilityFromFlags(flags) == WHITELIST_SHORTCUT_PROBABILITY;
     }
 
-    static AK_FORCE_INLINE int readShortcutTarget(
-            const uint8_t *const dictRoot, const int maxLength,  int *const outWord,
-            int *const pos) {
-        return ByteArrayUtils::readStringAndAdvancePosition(dictRoot, maxLength, outWord, pos);
-    }
+    static int readShortcutTarget(const uint8_t *const dictRoot, const int maxLength,
+            int *const outWord, int *const pos);
 
  private:
     DISALLOW_IMPLICIT_CONSTRUCTORS(ShortcutListReadingUtils);
diff --git a/tools/make-keyboard-text/res/values-lo/donottranslate-more-keys.xml b/tools/make-keyboard-text/res/values-lo/donottranslate-more-keys.xml
new file mode 100644
index 0000000..1d8ffa8
--- /dev/null
+++ b/tools/make-keyboard-text/res/values-lo/donottranslate-more-keys.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2013, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Label for "switch to alphabetic" key.
+         U+0E81: "ກ" LAO LETTER KO
+         U+0E82: "ຂ" LAO LETTER KHO SUNG
+         U+0E84: "ຄ" LAO LETTER KHO TAM -->
+    <string name="label_to_alpha_key">&#x0E81;&#x0E82;&#x0E84;</string>
+    <!-- U+20AD: "₭" KIP SIGN -->
+    <string name="keylabel_for_currency">&#x20AD;</string>
+</resources>
diff --git a/tools/make-keyboard-text/res/values-ne/donottranslate-more-keys.xml b/tools/make-keyboard-text/res/values-ne/donottranslate-more-keys.xml
new file mode 100644
index 0000000..9205e53
--- /dev/null
+++ b/tools/make-keyboard-text/res/values-ne/donottranslate-more-keys.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2013, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Label for "switch to alphabetic" key.
+         U+0915: "क" DEVANAGARI LETTER KA
+         U+0916: "ख" DEVANAGARI LETTER KHA
+         U+0917: "ग" DEVANAGARI LETTER GA -->
+    <string name="label_to_alpha_key">&#x0915;&#x0916;&#x0917;</string>
+    <!-- U+0967: "१" DEVANAGARI DIGIT ONE -->
+    <string name="keylabel_for_symbols_1">&#x0967;</string>
+    <!-- U+0968: "२" DEVANAGARI DIGIT TWO -->
+    <string name="keylabel_for_symbols_2">&#x0968;</string>
+    <!-- U+0969: "३" DEVANAGARI DIGIT THREE -->
+    <string name="keylabel_for_symbols_3">&#x0969;</string>
+    <!-- U+096A: "४" DEVANAGARI DIGIT FOUR -->
+    <string name="keylabel_for_symbols_4">&#x096A;</string>
+    <!-- U+096B: "५" DEVANAGARI DIGIT FIVE -->
+    <string name="keylabel_for_symbols_5">&#x096B;</string>
+    <!-- U+096C: "६" DEVANAGARI DIGIT SIX -->
+    <string name="keylabel_for_symbols_6">&#x096C;</string>
+    <!-- U+096D: "७" DEVANAGARI DIGIT SEVEN -->
+    <string name="keylabel_for_symbols_7">&#x096D;</string>
+    <!-- U+096E: "८" DEVANAGARI DIGIT EIGHT -->
+    <string name="keylabel_for_symbols_8">&#x096E;</string>
+    <!-- U+096F: "९" DEVANAGARI DIGIT NINE -->
+    <string name="keylabel_for_symbols_9">&#x096F;</string>
+    <!-- U+0966: "०" DEVANAGARI DIGIT ZERO -->
+    <string name="keylabel_for_symbols_0">&#x0966;</string>
+    <!-- Label for "switch to symbols" key. -->
+    <string name="label_to_symbol_key">\?&#x0967;&#x0968;&#x0969;</string>
+    <!-- Label for "switch to symbols with microphone" key. This string shouldn't include the "mic"
+         part because it'll be appended by the code. -->
+    <string name="label_to_symbol_with_microphone_key">&#x0967;&#x0968;&#x0969;</string>
+    <string name="additional_more_keys_for_symbols_1">1</string>
+    <string name="additional_more_keys_for_symbols_2">2</string>
+    <string name="additional_more_keys_for_symbols_3">3</string>
+    <string name="additional_more_keys_for_symbols_4">4</string>
+    <string name="additional_more_keys_for_symbols_5">5</string>
+    <string name="additional_more_keys_for_symbols_6">6</string>
+    <string name="additional_more_keys_for_symbols_7">7</string>
+    <string name="additional_more_keys_for_symbols_8">8</string>
+    <string name="additional_more_keys_for_symbols_9">9</string>
+    <string name="additional_more_keys_for_symbols_0">0</string>
+    <!-- U+0930/U+0941/U+002E "रु." NEPALESE RUPEE SIGN -->
+    <string name="keylabel_for_currency">&#x0930;&#x0941;&#x002E;</string>
+</resources>
diff --git a/tools/make-keyboard-text/res/values/donottranslate-more-keys.xml b/tools/make-keyboard-text/res/values/donottranslate-more-keys.xml
index a4c2f12..cc09f7f 100644
--- a/tools/make-keyboard-text/res/values/donottranslate-more-keys.xml
+++ b/tools/make-keyboard-text/res/values/donottranslate-more-keys.xml
@@ -190,7 +190,7 @@
     <!-- Label for "switch to more symbol" modifier key.  Must be short to fit on key! -->
     <string name="label_to_more_symbol_key">= \\ &lt;</string>
     <!-- Label for "switch to more symbol" modifier key on tablets.  Must be short to fit on key! -->
-    <string name="label_to_more_symbol_for_tablet_key">~ [ {</string>
+    <string name="label_to_more_symbol_for_tablet_key">~ [ &lt;</string>
     <!-- Label for "Tab" key.  Must be short to fit on key! -->
     <string name="label_tab_key">Tab</string>
     <!-- Label for "switch to phone numeric" key.  Must be short to fit on key! -->