Merge "Import translations. DO NOT MERGE"
diff --git a/java/res/layout/emoji_keyboard_view.xml b/java/res/layout/emoji_keyboard_view.xml
index 5fee419..36909a1 100644
--- a/java/res/layout/emoji_keyboard_view.xml
+++ b/java/res/layout/emoji_keyboard_view.xml
@@ -72,6 +72,11 @@
         android:id="@+id/emoji_keyboard_pager"
         android:layout_width="match_parent"
         android:layout_height="wrap_content" />
+    <com.android.inputmethod.keyboard.EmojiCategoryPageIndicatorView
+        android:id="@+id/emoji_category_page_id_view"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:background="@color/emoji_category_page_id_view_background" />
     <LinearLayout
         android:id="@+id/emoji_action_bar"
         android:orientation="horizontal"
diff --git a/java/res/values-km/donottranslate.xml b/java/res/values-km/donottranslate.xml
new file mode 100644
index 0000000..a9893fe
--- /dev/null
+++ b/java/res/values-km/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/colors.xml b/java/res/values/colors.xml
index ea762f9..a37f282 100644
--- a/java/res/values/colors.xml
+++ b/java/res/values/colors.xml
@@ -55,4 +55,6 @@
     <color name="setup_text_action">@android:color/holo_blue_light</color>
     <color name="setup_step_background">@android:color/background_light</color>
     <color name="setup_welcome_video_margin_color">#FFCCCCCC</color>
+    <color name="emoji_category_page_id_view_background">#FF000000</color>
+    <color name="emoji_category_page_id_view_foreground">#80FFFFFF</color>
 </resources>
diff --git a/java/res/values/dimens.xml b/java/res/values/dimens.xml
index 88e327f..4e3b2f5 100644
--- a/java/res/values/dimens.xml
+++ b/java/res/values/dimens.xml
@@ -113,13 +113,14 @@
     <dimen name="gesture_floating_preview_text_offset">73dp</dimen>
     <dimen name="gesture_floating_preview_horizontal_padding">24dp</dimen>
     <dimen name="gesture_floating_preview_vertical_padding">16dp</dimen>
-    <dimen name="gesture_floating_preview_round_radius">3dp</dimen>
+    <dimen name="gesture_floating_preview_round_radius">2dp</dimen>
 
     <!-- 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>
+    <dimen name="emoji_category_page_id_height">3dp</dimen>
 
     <!-- Inset used in Accessibility mode to avoid accidental key presses when a finger slides off the screen. -->
     <dimen name="accessibility_edge_slop">8dp</dimen>
diff --git a/java/res/xml-sw600dp/row_pcqwerty5.xml b/java/res/xml-sw600dp/row_pcqwerty5.xml
index a79d2a8..b854f10 100644
--- a/java/res/xml-sw600dp/row_pcqwerty5.xml
+++ b/java/res/xml-sw600dp/row_pcqwerty5.xml
@@ -53,7 +53,7 @@
                     latin:keyXPos="-9.0%p"
                     latin:keyWidth="9.0%p"
                     latin:backgroundType="functional"
-                    latin:keyboardLayout="@xml/key_symbols_period" />
+                    latin:keyboardLayout="@xml/key_f2" />
             </default>
         </switch>
     </Row>
diff --git a/java/res/xml-sw600dp/rows_khmer.xml b/java/res/xml-sw600dp/rows_khmer.xml
new file mode 100644
index 0000000..2824a5c
--- /dev/null
+++ b/java/res/xml-sw600dp/rows_khmer.xml
@@ -0,0 +1,72 @@
+<?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_khmer1" />
+        <Key
+            latin:keyStyle="deleteKeyStyle"
+            latin:keyWidth="fillRight" />
+        </Row>
+    <Row
+        latin:keyWidth="7.5%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_khmer2" />
+    </Row>
+    <Row
+        latin:keyWidth="7.5%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_khmer3" />
+        <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_khmer4" />
+        <switch>
+            <case
+                latin:keyboardLayoutSetElement="alphabetManualShifted|alphabetShiftLocked|alphabetShiftLockShifted"
+            >
+                <Spacer />
+            </case>
+            <default>
+                <include
+                    latin:keyboardLayout="@xml/keys_exclamation_question" />
+            </default>
+        </switch>
+    </Row>
+    <include
+        latin:keyboardLayout="@xml/row_qwerty4" />
+</merge>
diff --git a/java/res/xml-sw600dp/rows_lao.xml b/java/res/xml-sw600dp/rows_lao.xml
index cfe8db9..446d9bd 100644
--- a/java/res/xml-sw600dp/rows_lao.xml
+++ b/java/res/xml-sw600dp/rows_lao.xml
@@ -55,8 +55,17 @@
             latin:keyWidth="10.0%p" />
         <include
             latin:keyboardLayout="@xml/rowkeys_lao4" />
-        <include
-            latin:keyboardLayout="@xml/keys_exclamation_question" />
+        <switch>
+            <case
+                latin:keyboardLayoutSetElement="alphabetManualShifted|alphabetShiftLocked|alphabetShiftLockShifted"
+            >
+                <Spacer />
+            </case>
+            <default>
+                <include
+                    latin:keyboardLayout="@xml/keys_exclamation_question" />
+            </default>
+        </switch>
     </Row>
     <include
         latin:keyboardLayout="@xml/row_qwerty4" />
diff --git a/java/res/xml-sw600dp/rows_thai.xml b/java/res/xml-sw600dp/rows_thai.xml
index cfcaf68..7738c7f 100644
--- a/java/res/xml-sw600dp/rows_thai.xml
+++ b/java/res/xml-sw600dp/rows_thai.xml
@@ -59,8 +59,17 @@
             latin:keyWidth="10.0%p" />
         <include
             latin:keyboardLayout="@xml/rowkeys_thai4" />
-        <include
-            latin:keyboardLayout="@xml/keys_exclamation_question" />
+        <switch>
+            <case
+                latin:keyboardLayoutSetElement="alphabetManualShifted|alphabetShiftLocked|alphabetShiftLockShifted"
+            >
+                <Spacer />
+            </case>
+            <default>
+                <include
+                    latin:keyboardLayout="@xml/keys_exclamation_question" />
+            </default>
+        </switch>
     </Row>
     <include
         latin:keyboardLayout="@xml/row_qwerty4" />
diff --git a/java/res/xml/kbd_khmer.xml b/java/res/xml/kbd_khmer.xml
new file mode 100644
index 0000000..7a2337a
--- /dev/null
+++ b/java/res/xml/kbd_khmer.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_khmer" />
+</Keyboard>
diff --git a/java/res/xml/key_styles_common.xml b/java/res/xml/key_styles_common.xml
index 6b3dc9a..67ed962 100644
--- a/java/res/xml/key_styles_common.xml
+++ b/java/res/xml/key_styles_common.xml
@@ -184,4 +184,11 @@
         latin:keyLabelFlags="hasPopupHint"
         latin:moreKeys="!text/more_keys_for_punctuation"
         latin:backgroundType="functional" />
+    <key-style
+        latin:styleName="comKeyStyle"
+        latin:keyLabel="!text/keylabel_for_popular_domain"
+        latin:keyLabelFlags="autoXScale|fontNormal|hasPopupHint|preserveCase"
+        latin:keyOutputText="!text/keylabel_for_popular_domain"
+        latin:moreKeys="!text/more_keys_for_popular_domain"
+        latin:backgroundType="functional" />
 </merge>
diff --git a/java/res/xml/keyboard_layout_set_khmer.xml b/java/res/xml/keyboard_layout_set_khmer.xml
new file mode 100644
index 0000000..181f98b
--- /dev/null
+++ b/java/res/xml/keyboard_layout_set_khmer.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_khmer"
+        latin:enableProximityCharsCorrection="true" />
+    <Element
+        latin:elementName="alphabetAutomaticShifted"
+        latin:elementKeyboard="@xml/kbd_khmer"
+        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_khmer" />
+    <Element
+        latin:elementName="alphabetShiftLocked"
+        latin:elementKeyboard="@xml/kbd_khmer" />
+    <Element
+        latin:elementName="alphabetShiftLockShifted"
+        latin:elementKeyboard="@xml/kbd_khmer" />
+    <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/method.xml b/java/res/xml/method.xml
index d7424c0..945fbd5 100644
--- a/java/res/xml/method.xml
+++ b/java/res/xml/method.xml
@@ -54,6 +54,7 @@
     iw: Hebrew/hebrew        # "he" is official language code of Hebrew.
     ka: Georgian/georgian
     (kk: Kazakh/east_slavic) # disabled temporarily. waiting for strnig resources.
+    km: Khmer/khmer
     ky: Kyrgyz/east_slavic
     lo: Lao/lao
     lt: Lithuanian/qwerty
@@ -62,8 +63,8 @@
     mn: Mongolian/mongolian
     ms: Malay/qwerty
     nb: Norwegian Bokmål/nordic
-    ne: Nepali Romanized/nepali_romanized
-    ne: Nepali Traditional/nepali_traditional
+    ne: Nepali Romanized/nepali_romanized  # disabled temporarily
+    ne: Nepali Traditional/nepali_traditional  # disabled temporarily
     nl: Dutch/qwerty
     nl_BE: Dutch Belgium/azerty
     pl: Polish/qwerty
@@ -326,6 +327,14 @@
     -->
     <subtype android:icon="@drawable/ic_subtype_keyboard"
             android:label="@string/subtype_generic"
+            android:subtypeId="0x1365683a"
+            android:imeSubtypeLocale="km"
+            android:imeSubtypeMode="keyboard"
+            android:imeSubtypeExtraValue="KeyboardLayoutSet=khmer,EmojiCapable"
+    />
+            <!-- android:subtypeId="Need this for km" -->
+    <subtype android:icon="@drawable/ic_subtype_keyboard"
+            android:label="@string/subtype_generic"
             android:subtypeId="0x2e391c04"
             android:imeSubtypeLocale="ky"
             android:imeSubtypeMode="keyboard"
@@ -380,6 +389,7 @@
             android:imeSubtypeMode="keyboard"
             android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection,EmojiCapable"
     />
+    <!--
     <subtype android:icon="@drawable/ic_subtype_keyboard"
             android:label="@string/subtype_generic"
             android:subtypeId="0xd80a4cee"
@@ -394,6 +404,7 @@
             android:imeSubtypeMode="keyboard"
             android:imeSubtypeExtraValue="KeyboardLayoutSet=nepali_traditional,EmojiCapable"
     />
+    -->
     <subtype android:icon="@drawable/ic_subtype_keyboard"
             android:label="@string/subtype_generic"
             android:subtypeId="0x3f9fd91e"
diff --git a/java/res/xml/row_pcqwerty5.xml b/java/res/xml/row_pcqwerty5.xml
index 0e61805..4ec908b 100644
--- a/java/res/xml/row_pcqwerty5.xml
+++ b/java/res/xml/row_pcqwerty5.xml
@@ -51,13 +51,13 @@
                     latin:keyWidth="11.538%p" />
                 <Key
                     latin:keyStyle="spaceKeyStyle"
-                    latin:keyWidth="42.310%p" />
+                    latin:keyWidth="38.464%p" />
                 </case>
             <!-- languageSwitchKeyEnabled="false" -->
             <default>
                 <Key
                     latin:keyStyle="spaceKeyStyle"
-                    latin:keyWidth="53.848%p" />
+                    latin:keyWidth="50.002%p" />
             </default>
         </switch>
         <Key
@@ -71,9 +71,9 @@
             </case>
             <!-- keyboardLayoutSetElement="alphabetManualShifted|alphabetShiftLocked|alphabetShiftLockShifted" -->
             <default>
-                <include
-                    latin:keyboardLayout="@xml/key_symbols_period"
-                    latin:backgroundType="functional" />
+                <Key
+                    latin:keyStyle="emojiKeyStyle"
+                    latin:keyWidth="fillRight" />
             </default>
         </switch>
     </Row>
diff --git a/java/res/xml/rowkeys_khmer1.xml b/java/res/xml/rowkeys_khmer1.xml
new file mode 100644
index 0000000..174ac75
--- /dev/null
+++ b/java/res/xml/rowkeys_khmer1.xml
@@ -0,0 +1,191 @@
+<?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+200D: ZERO WIDTH JOINER -->
+            <Key
+                latin:keyLabel="!"
+                latin:moreKeys="!icon/zwj_key|&#x200D;" />
+            <!-- U+17D7: "ៗ" KHMER SIGN LEK TOO
+                 U+200C: ZERO WIDTH NON-JOINER -->
+            <Key
+                latin:keyLabel="&#x17D7;"
+                latin:moreKeys="!icon/zwnj_key|&#x200C;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+17D1: "៑" KHMER SIGN VIRIAM -->
+            <Key
+                latin:keyLabel="&quot;"
+                latin:keyHintLabel="&#x17D1;"
+                latin:moreKeys="&#x17D1;" />
+            <!-- U+17DB: "៛" KHMER CURRENCY SYMBOL RIEL
+                 U+20AC: "€" EURO SIGN -->
+            <Key
+                latin:keyLabel="&#x17DB;"
+                latin:keyHintLabel="$"
+                latin:moreKeys="$,&#x20AC;" />
+            <!-- U+17D6: "៖" KHMER SIGN CAMNUC PII KUUH -->
+            <Key
+                latin:keyLabel="%"
+                latin:keyHintLabel="&#x17D6;"
+                latin:moreKeys="&#x17D6;" />
+            <!-- U+17CD: "៍" KHMER SIGN TOANDAKHIAT
+                 U+17D9: "៙" KHMER SIGN PHNAEK MUAN -->
+            <Key
+                latin:keyLabel="&#x17CD;"
+                latin:keyHintLabel="&#x17D9;"
+                latin:moreKeys="&#x17D9;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+17D0: "័" KHMER SIGN SAMYOK SANNYA
+                 U+17DA: "៚" KHMER SIGN KOOMUUT -->
+            <Key
+                latin:keyLabel="&#x17D0;"
+                latin:keyHintLabel="&#x17DA;"
+                latin:moreKeys="&#x17DA;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+17CF: "៏" KHMER SIGN AHSDA -->
+            <Key
+                latin:keyLabel="&#x17CF;"
+                latin:keyHintLabel="*"
+                latin:moreKeys="*"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK -->
+            <Key
+                latin:keyLabel="("
+                latin:keyHintLabel="{"
+                latin:moreKeys="{,&#x00AB;" />
+            <!-- U+00BB: "»" RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK -->
+            <Key
+                latin:keyLabel=")"
+                latin:keyHintLabel="}"
+                latin:moreKeys="},&#x00BB;" />
+            <!-- U+17CC: "៌" KHMER SIGN ROBAT
+                 U+00D7: "×" MULTIPLICATION SIGN -->
+            <Key
+                latin:keyLabel="&#x17CC;"
+                latin:keyHintLabel="&#x00D7;"
+                latin:moreKeys="&#x00D7;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+17CE: "៎" KHMER SIGN KAKABAT -->
+            <Key
+                latin:keyLabel="&#x17CE;"
+                latin:keyLabelFlags="fontNormal" />
+        </case>
+        <default>
+            <!-- U+17E1: "១" KHMER DIGIT ONE
+                 U+17F1: "៱" KHMER SYMBOL LEK ATTAK MUOY -->
+            <Key
+                latin:keyLabel="&#x17E1;"
+                latin:keyHintLabel="1"
+                latin:additionalMoreKeys="1"
+                latin:moreKeys="&#x17F1;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+17E2: "២" KHMER DIGIT TWO
+                 U+17F2: "៲" KHMER SYMBOL LEK ATTAK PII -->
+            <Key
+                latin:keyLabel="&#x17E2;"
+                latin:keyHintLabel="2"
+                latin:additionalMoreKeys="2"
+                latin:moreKeys="&#x17F2;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+17E3: "៣" KHMER DIGIT THREE
+                 U+17F3: "៳" KHMER SYMBOL LEK ATTAK BEI -->
+            <Key
+                latin:keyLabel="&#x17E3;"
+                latin:keyHintLabel="3"
+                latin:additionalMoreKeys="3"
+                latin:moreKeys="&#x17F3;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+17E4: "៤" KHMER DIGIT FOUR
+                 U+17F4: "៴" KHMER SYMBOL LEK ATTAK BUON -->
+            <Key
+                latin:keyLabel="&#x17E4;"
+                latin:keyHintLabel="4"
+                latin:additionalMoreKeys="4"
+                latin:moreKeys="&#x17F4;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+17E5: "៥" KHMER DIGIT FIVE
+                 U+17F5: "៵" KHMER SYMBOL LEK ATTAK PRAM -->
+            <Key
+                latin:keyLabel="&#x17E5;"
+                latin:keyHintLabel="5"
+                latin:additionalMoreKeys="5"
+                latin:moreKeys="&#x17F5;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+17E6: "៦" KHMER DIGIT SIX
+                 U+17F6: "៶" KHMER SYMBOL LEK ATTAK PRAM-MUOY -->
+            <Key
+                latin:keyLabel="&#x17E6;"
+                latin:keyHintLabel="6"
+                latin:additionalMoreKeys="6"
+                latin:moreKeys="&#x17F6;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+17E7: "៧" KHMER DIGIT SEVEN
+                 U+17F7: "៷" KHMER SYMBOL LEK ATTAK PRAM-PII -->
+            <Key
+                latin:keyLabel="&#x17E7;"
+                latin:keyHintLabel="7"
+                latin:additionalMoreKeys="7"
+                latin:moreKeys="&#x17F7;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+17E8: "៨" KHMER DIGIT EIGHT
+                 U+17F8: "៸" KHMER SYMBOL LEK ATTAK PRAM-BEI -->
+            <Key
+                latin:keyLabel="&#x17E8;"
+                latin:keyHintLabel="8"
+                latin:additionalMoreKeys="8"
+                latin:moreKeys="&#x17F8;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+17E9: "៩" KHMER DIGIT NINE
+                 U+17F9: "៹" KHMER SYMBOL LEK ATTAK PRAM-BUON -->
+            <Key
+                latin:keyLabel="&#x17E9;"
+                latin:keyHintLabel="9"
+                latin:additionalMoreKeys="9"
+                latin:moreKeys="&#x17F9;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+17E0: "០" KHMER DIGIT ZERO
+                 U+17F0: "៰" KHMER SYMBOL LEK ATTAK SON -->
+            <Key
+                latin:keyLabel="&#x17E0;"
+                latin:keyHintLabel="0"
+                latin:additionalMoreKeys="0"
+                latin:moreKeys="&#x17F0;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+17A5: "ឥ" KHMER INDEPENDENT VOWEL QI
+                 U+17A6: "ឦ" KHMER INDEPENDENT VOWEL QII -->
+            <Key
+                latin:keyLabel="&#x17A5;"
+                latin:moreKeys=".,&#x17A6;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+17B2: "ឲ" KHMER INDEPENDENT VOWEL QOO TYPE TWO
+                 U+17B1: "ឱ" KHMER INDEPENDENT VOWEL QOO TYPE ONE -->
+            <Key
+                latin:keyLabel="&#x17B2;"
+                latin:moreKeys="\\,,&#x17B1;"
+                latin:keyLabelFlags="fontNormal" />
+        </default>
+    </switch>
+</merge>
diff --git a/java/res/xml/rowkeys_khmer2.xml b/java/res/xml/rowkeys_khmer2.xml
new file mode 100644
index 0000000..cba2d3b
--- /dev/null
+++ b/java/res/xml/rowkeys_khmer2.xml
@@ -0,0 +1,144 @@
+<?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+1788: "ឈ" KHMER LETTER CHO
+                 U+17DC: "ៜ" KHMER SIGN AVAKRAHASANYA -->
+            <Key
+                latin:keyLabel="&#x1788;"
+                latin:keyHintLabel="&#x17DC;"
+                latin:moreKeys="&#x17DC;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+17BA: "ឺ" KHMER VOWEL SIGN YY
+                 U+17DD: "៝" KHMER SIGN ATTHACAN -->
+            <Key
+                latin:keyLabel="&#x17BA;"
+                latin:keyHintLabel="&#x17DD;"
+                latin:moreKeys="&#x17DD;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+17C2: "ែ" KHMER VOWEL SIGN AE -->
+            <Key
+                latin:keyLabel="&#x17C2;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+17AC: "ឬ" KHMER INDEPENDENT VOWEL RYY
+                 U+17AB: "ឫ" KHMER INDEPENDENT VOWEL RY -->
+            <Key
+                latin:keyLabel="&#x17AC;"
+                latin:keyHintLabel="&#x17AB;"
+                latin:moreKeys="&#x17AB;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+1791: "ទ" KHMER LETTER TO -->
+            <Key
+                latin:keyLabel="&#x1791;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+17BD: "ួ" KHMER VOWEL SIGN UA -->
+            <Key
+                latin:keyLabel="&#x17BD;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+17BC: "ូ" KHMER VOWEL SIGN UU -->
+            <Key
+                latin:keyLabel="&#x17BC;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+17B8: "ី" KHMER VOWEL SIGN II -->
+            <Key
+                latin:keyLabel="&#x17B8;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+17C5: "ៅ" KHMER VOWEL SIGN AU -->
+            <Key
+                latin:keyLabel="&#x17C5;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+1797: "ភ" KHMER LETTER PHO -->
+            <Key
+                latin:keyLabel="&#x1797;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+17BF: "ឿ" KHMER VOWEL SIGN YA -->
+            <Key
+                latin:keyLabel="&#x17BF;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+17B0: "ឰ" KHMER INDEPENDENT VOWEL QAI -->
+            <Key
+                latin:keyLabel="&#x17B0;"
+                latin:keyLabelFlags="fontNormal" />
+        </case>
+        <default>
+            <!-- U+1786: "ឆ" KHMER LETTER CHA -->
+            <Key
+                latin:keyLabel="&#x1786;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+17B9: "ឹ" KHMER VOWEL SIGN Y -->
+            <Key
+                latin:keyLabel="&#x17B9;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+17C1: "េ" KHMER VOWEL SIGN E -->
+            <Key
+                latin:keyLabel="&#x17C1;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+179A: "រ" KHMER LETTER RO -->
+            <Key
+                latin:keyLabel="&#x179A;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+178F: "ត" KHMER LETTER TA -->
+            <Key
+                latin:keyLabel="&#x178F;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+1799: "យ" KHMER LETTER YO -->
+            <Key
+                latin:keyLabel="&#x1799;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+17BB: "ុ" KHMER VOWEL SIGN U -->
+            <Key
+                latin:keyLabel="&#x17BB;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+17B7: "ិ" KHMER VOWEL SIGN I -->
+            <Key
+                latin:keyLabel="&#x17B7;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+17C4: "ោ" KHMER VOWEL SIGN OO -->
+            <Key
+                latin:keyLabel="&#x17C4;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+1795: "ផ" KHMER LETTER PHA -->
+            <Key
+                latin:keyLabel="&#x1795;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+17C0: "ៀ" KHMER VOWEL SIGN IE -->
+            <Key
+                latin:keyLabel="&#x17C0;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+17AA: "ឪ" KHMER INDEPENDENT VOWEL QUUV
+                 U+17A7: "ឧ" KHMER INDEPENDENT VOWEL QU
+                 U+17B1: "ឱ" KHMER INDEPENDENT VOWEL QOO TYPE ONE
+                 U+17B3: "ឳ" KHMER INDEPENDENT VOWEL QAU
+                 U+17A9: "ឩ" KHMER INDEPENDENT VOWEL QUU
+                 U+17A8: "ឨ" KHMER INDEPENDENT VOWEL QUK -->
+            <Key
+                latin:keyLabel="&#x17AA;"
+                latin:keyHintLabel="&#x17A7;"
+                latin:moreKeys="&#x17A7;,&#x17B1;,&#x17B3;,&#x17A9;,&#x17A8;"
+                latin:keyLabelFlags="fontNormal" />
+        </default>
+    </switch>
+</merge>
diff --git a/java/res/xml/rowkeys_khmer3.xml b/java/res/xml/rowkeys_khmer3.xml
new file mode 100644
index 0000000..5d55b9c
--- /dev/null
+++ b/java/res/xml/rowkeys_khmer3.xml
@@ -0,0 +1,138 @@
+<?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+17B6/U+17C6: "ាំ" KHMER VOWEL SIGN AA/KHMER SIGN NIKAHIT -->
+            <Key
+                latin:keyLabel="&#x17B6;&#x17C6;"
+                latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
+            <!-- U+17C3: "ៃ" KHMER VOWEL SIGN AI -->
+            <Key
+                latin:keyLabel="&#x17C3;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+178C: "ឌ" KHMER LETTER DO -->
+            <Key
+                latin:keyLabel="&#x178C;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+1792: "ធ" KHMER LETTER THO -->
+            <Key
+                latin:keyLabel="&#x178C;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+17A2: "អ" KHMER LETTER QA -->
+            <Key
+                latin:keyLabel="&#x17A2;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+17C7: "ះ" KHMER SIGN REAHMUK
+                 U+17C8: "ៈ" KHMER SIGN YUUKALEAPINTU;-->
+            <Key
+                latin:keyLabel="&#x17C7;"
+                latin:keyHintLabel="&#x17C8;"
+                latin:moreKeys="&#x17C8;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+1789: "ញ" KHMER LETTER NYO -->
+            <Key
+                latin:keyLabel="&#x1789;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+1782: "គ" KHMER LETTER KO
+                 U+179D: "ឝ" KHMER LETTER SHA -->
+            <Key
+                latin:keyLabel="&#x1782;"
+                latin:keyHintLabel="&#x179D;"
+                latin:moreKeys="&#x179D;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+17A1: "ឡ" KHMER LETTER LA -->
+            <Key
+                latin:keyLabel="&#x17A1;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+17C4/U+17C7: "ោះ" KHMER VOWEL SIGN OO/KHMER SIGN REAHMUK -->
+            <Key
+                latin:keyLabel="&#x17C4;&#x17C7;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+17C9: "៉" KHMER SIGN MUUSIKATOAN -->
+            <Key
+                latin:keyLabel="&#x17C9;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+17AF: "ឯ" KHMER INDEPENDENT VOWEL QE -->
+            <Key
+                latin:keyLabel="&#x17AF;"
+                latin:keyLabelFlags="fontNormal" />
+        </case>
+        <default>
+            <!-- U+17B6: "ា" KHMER VOWEL SIGN AA -->
+            <Key
+                latin:keyLabel="&#x17B6;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+179F: "ស" KHMER LETTER SA -->
+            <Key
+                latin:keyLabel="&#x179F;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+178A: "ដ" KHMER LETTER DA -->
+            <Key
+                latin:keyLabel="&#x178A;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+1790: "ថ" KHMER LETTER THA -->
+            <Key
+                latin:keyLabel="&#x1790;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+1784: "ង" KHMER LETTER NGO -->
+            <Key
+                latin:keyLabel="&#x1784;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+17A0: "ហ" KHMER LETTER HA -->
+            <Key
+                latin:keyLabel="&#x17A0;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+17D2: "្" KHMER SIGN COENG -->
+            <Key
+                latin:keyLabel="&#x17D2;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+1780: "ក" KHMER LETTER KA -->
+            <Key
+                latin:keyLabel="&#x1780;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+179B: "ល" KHMER LETTER LO -->
+            <Key
+                latin:keyLabel="&#x179B;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+17BE: "ើ" KHMER VOWEL SIGN OE -->
+            <Key
+                latin:keyLabel="&#x17BE;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+17CB: "់" KHMER SIGN BANTOC -->
+            <Key
+                latin:keyLabel="&#x17CB;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+17AE: "ឮ" KHMER INDEPENDENT VOWEL LYY
+                 U+17AD: "ឭ" KHMER INDEPENDENT VOWEL LY
+                 U+17B0: "ឰ" KHMER INDEPENDENT VOWEL QAI -->
+            <Key
+                latin:keyLabel="&#x17AE;"
+                latin:keyHintLabel="&#x17AD;"
+                latin:moreKeys="&#x17AD;,&#x17B0;"
+                latin:keyLabelFlags="fontNormal" />
+        </default>
+    </switch>
+</merge>
diff --git a/java/res/xml/rowkeys_khmer4.xml b/java/res/xml/rowkeys_khmer4.xml
new file mode 100644
index 0000000..fe6c591
--- /dev/null
+++ b/java/res/xml/rowkeys_khmer4.xml
@@ -0,0 +1,113 @@
+<?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+178D: "ឍ" KHMER LETTER TTHO -->
+            <Key
+                latin:keyLabel="&#x178D;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+1783: "ឃ" KHMER LETTER KHO -->
+            <Key
+                latin:keyLabel="&#x1783;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+1787: "ជ" KHMER LETTER CO -->
+            <Key
+                latin:keyLabel="&#x1787;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+17C1/U+17C7: "េះ" KHMER VOWEL SIGN E/KHMER SIGN REAHMUK -->
+            <Key
+                latin:keyLabel="&#x17C1;&#x17C7;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+1796: "ព" KHMER LETTER PO
+                 U+179E: "ឞ" KHMER LETTER SSO -->
+            <Key
+                latin:keyLabel="&#x1796;"
+                latin:keyHintLabel="&#x179E;"
+                latin:moreKeys="&#x179E;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+178E: "ណ" KHMER LETTER NNO -->
+            <Key
+                latin:keyLabel="&#x178E;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+17C6: "ំ" KHMER SIGN NIKAHIT -->
+            <Key
+                latin:keyLabel="&#x17C6;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+17BB/U+17C7: "ុះ" KHMER VOWEL SIGN U/KHMER SIGN REAHMUK -->
+            <Key
+                latin:keyLabel="&#x17BB;&#x17C7;"
+                latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
+            <!-- U+17D5: "៕" KHMER SIGN BARIYOOSAN -->
+            <Key
+                latin:keyLabel="&#x17D5;"
+                latin:keyLabelFlags="fontNormal" />
+            <Key
+                latin:keyLabel="\?" />
+        </case>
+        <default>
+            <!-- U+178B: "ឋ" KHMER LETTER TTHA -->
+            <Key
+                latin:keyLabel="&#x178B;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+1781: "ខ" KHMER LETTER KHA -->
+            <Key
+                latin:keyLabel="&#x1781;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+1785: "ច" KHMER LETTER CA -->
+            <Key
+                latin:keyLabel="&#x1785;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+179C: "វ" KHMER LETTER VO -->
+            <Key
+                latin:keyLabel="&#x179C;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+1794: "ប" KHMER LETTER BA -->
+            <Key
+                latin:keyLabel="&#x1794;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+1793: "ន" KHMER LETTER NO -->
+            <Key
+                latin:keyLabel="&#x1793;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+1798: "ម" KHMER LETTER MO -->
+            <Key
+                latin:keyLabel="&#x1798;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+17BB/U+17C6: "ុំ" KHMER VOWEL SIGN U/KHMER SIGN NIKAHIT -->
+            <Key
+                latin:keyLabel="&#x17BB;&#x17C6;"
+                latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
+            <!-- U+17D4: "។" KHMER SIGN KHAN -->
+            <Key
+                latin:keyLabel="&#x17D4;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+17CA: "៊" KHMER SIGN TRIISAP -->
+            <Key
+                latin:keyLabel="&#x17CA;"
+                latin:keyLabelFlags="fontNormal" />
+        </default>
+    </switch>
+</merge>
diff --git a/java/res/xml/rows_khmer.xml b/java/res/xml/rows_khmer.xml
new file mode 100644
index 0000000..e399387
--- /dev/null
+++ b/java/res/xml/rows_khmer.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_khmer1" />
+    </Row>
+    <Row
+        latin:keyWidth="8.3333%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_khmer2" />
+    </Row>
+    <Row
+        latin:keyWidth="8.3333%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_khmer3" />
+    </Row>
+    <Row
+        latin:keyWidth="8.3333%p"
+    >
+        <Key
+            latin:keyStyle="shiftKeyStyle" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_khmer4" />
+        <Key
+            latin:keyStyle="deleteKeyStyle" />
+    </Row>
+    <include
+        latin:keyboardLayout="@xml/row_qwerty4" />
+</merge>
diff --git a/java/src/com/android/inputmethod/keyboard/EmojiCategoryPageIndicatorView.java b/java/src/com/android/inputmethod/keyboard/EmojiCategoryPageIndicatorView.java
new file mode 100644
index 0000000..fed134e
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/EmojiCategoryPageIndicatorView.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.keyboard;
+
+import com.android.inputmethod.latin.R;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.util.AttributeSet;
+import android.widget.LinearLayout;
+
+public class EmojiCategoryPageIndicatorView extends LinearLayout {
+    private static final float BOTTOM_MARGIN_RATIO = 0.66f;
+    private final Paint mPaint = new Paint();
+    private int mCategoryPageSize = 0;
+    private int mCurrentCategoryPageId = 0;
+    private float mOffset = 0.0f;
+
+    public EmojiCategoryPageIndicatorView(Context context) {
+        this(context, null /* attrs */);
+    }
+
+    public EmojiCategoryPageIndicatorView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        mPaint.setColor(context.getResources().getColor(
+                R.color.emoji_category_page_id_view_foreground));
+    }
+
+    public void setCategoryPageId(int size, int id, float offset) {
+        mCategoryPageSize = size;
+        mCurrentCategoryPageId = id;
+        mOffset = offset;
+        invalidate();
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        if (mCategoryPageSize == 0) {
+            // If the category is not set yet, just clear and return.
+            canvas.drawColor(0);
+            return;
+        }
+        final float height = getHeight();
+        final float width = getWidth();
+        final float unitWidth = width / mCategoryPageSize;
+        final float left = unitWidth * mCurrentCategoryPageId + mOffset * unitWidth;
+        final float top = 0.0f;
+        final float right = left + unitWidth;
+        final float bottom = height * BOTTOM_MARGIN_RATIO;
+        canvas.drawRect(left, top, right, bottom, mPaint);
+    }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/EmojiKeyboardView.java b/java/src/com/android/inputmethod/keyboard/EmojiKeyboardView.java
index 546fa81..db65de2 100644
--- a/java/src/com/android/inputmethod/keyboard/EmojiKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/EmojiKeyboardView.java
@@ -80,6 +80,7 @@
 
     private TabHost mTabHost;
     private ViewPager mEmojiPager;
+    private EmojiCategoryPageIndicatorView mEmojiCategoryPageIndicatorView;
 
     private KeyboardActionListener mKeyboardActionListener = KeyboardActionListener.EMPTY_LISTENER;
 
@@ -197,6 +198,14 @@
             return mCurrentCategoryId;
         }
 
+        public int getCurrentCategoryPageSize() {
+            return getCategoryPageSize(mCurrentCategoryId);
+        }
+
+        public int getCategoryPageSize(int categoryId) {
+            return mShownCategories.get(categoryId).mPageCount;
+        }
+
         public void setCurrentCategoryId(int categoryId) {
             mCurrentCategoryId = categoryId;
         }
@@ -205,6 +214,10 @@
             mCurrentCategoryPageId = id;
         }
 
+        public int getCurrentCategoryPageId() {
+            return mCurrentCategoryPageId;
+        }
+
         public void saveLastTypedCategoryPage() {
             Settings.writeEmojiCategoryLastTypedId(
                     mPrefs, mCurrentCategoryId, mCurrentCategoryPageId);
@@ -435,12 +448,16 @@
         mEmojiPager.setOffscreenPageLimit(0);
         final Resources res = getResources();
         final EmojiLayoutParams emojiLp = new EmojiLayoutParams(res);
-        emojiLp.setPagerProps(mEmojiPager);
+        emojiLp.setPagerProperties(mEmojiPager);
+
+        mEmojiCategoryPageIndicatorView =
+                (EmojiCategoryPageIndicatorView)findViewById(R.id.emoji_category_page_id_view);
+        emojiLp.setCategoryPageIdViewProperties(mEmojiCategoryPageIndicatorView);
 
         setCurrentCategoryId(mEmojiCategory.getCurrentCategoryId(), true /* force */);
 
         final LinearLayout actionBar = (LinearLayout)findViewById(R.id.emoji_action_bar);
-        emojiLp.setActionBarProps(actionBar);
+        emojiLp.setActionBarProperties(actionBar);
 
         // TODO: Implement auto repeat, using View.OnTouchListener?
         final ImageView deleteKey = (ImageView)findViewById(R.id.emoji_keyboard_delete);
@@ -455,7 +472,7 @@
         spaceKey.setBackgroundResource(mKeyBackgroundId);
         spaceKey.setTag(Constants.CODE_SPACE);
         spaceKey.setOnClickListener(this);
-        emojiLp.setKeyProps(spaceKey);
+        emojiLp.setKeyProperties(spaceKey);
         final ImageView sendKey = (ImageView)findViewById(R.id.emoji_keyboard_send);
         sendKey.setBackgroundResource(mEmojiFunctionalKeyBackgroundId);
         sendKey.setTag(Constants.CODE_ENTER);
@@ -466,6 +483,7 @@
     public void onTabChanged(final String tabId) {
         final int categoryId = mEmojiCategory.getCategoryId(tabId);
         setCurrentCategoryId(categoryId, false /* force */);
+        updateEmojiCategoryPageIdView();
     }
 
 
@@ -475,6 +493,7 @@
                 mEmojiCategory.getCategoryIdAndPageIdFromPagePosition(position);
         setCurrentCategoryId(newPos.first /* categoryId */, false /* force */);
         mEmojiCategory.setCurrentCategoryPageId(newPos.second /* categoryPageId */);
+        updateEmojiCategoryPageIdView();
     }
 
     @Override
@@ -485,7 +504,23 @@
     @Override
     public void onPageScrolled(final int position, final float positionOffset,
             final int positionOffsetPixels) {
-        // Ignore this message. Only want the actual page selected.
+        final Pair<Integer, Integer> newPos =
+                mEmojiCategory.getCategoryIdAndPageIdFromPagePosition(position);
+        final int newCategoryId = newPos.first;
+        final int newCategorySize = mEmojiCategory.getCategoryPageSize(newCategoryId);
+        final int currentCategoryId = mEmojiCategory.getCurrentCategoryId();
+        final int currentCategoryPageId = mEmojiCategory.getCurrentCategoryPageId();
+        final int currentCategorySize = mEmojiCategory.getCurrentCategoryPageSize();
+        if (newCategoryId == currentCategoryId) {
+            mEmojiCategoryPageIndicatorView.setCategoryPageId(
+                    newCategorySize, newPos.second, positionOffset);
+        } else if (newCategoryId > currentCategoryId) {
+            mEmojiCategoryPageIndicatorView.setCategoryPageId(
+                    currentCategorySize, currentCategoryPageId, positionOffset);
+        } else if (newCategoryId < currentCategoryId) {
+            mEmojiCategoryPageIndicatorView.setCategoryPageId(
+                    currentCategorySize, currentCategoryPageId, positionOffset - 1);
+        }
     }
 
     @Override
@@ -523,6 +558,15 @@
         mKeyboardActionListener = listener;
     }
 
+    private void updateEmojiCategoryPageIdView() {
+        if (mEmojiCategoryPageIndicatorView == null) {
+            return;
+        }
+        mEmojiCategoryPageIndicatorView.setCategoryPageId(
+                mEmojiCategory.getCurrentCategoryPageSize(),
+                mEmojiCategory.getCurrentCategoryPageId(), 0.0f /* offset */);
+    }
+
     private void setCurrentCategoryId(final int categoryId, final boolean force) {
         if (mEmojiCategory.getCurrentCategoryId() == categoryId && !force) {
             return;
diff --git a/java/src/com/android/inputmethod/keyboard/EmojiLayoutParams.java b/java/src/com/android/inputmethod/keyboard/EmojiLayoutParams.java
index 5570d59..267fad5 100644
--- a/java/src/com/android/inputmethod/keyboard/EmojiLayoutParams.java
+++ b/java/src/com/android/inputmethod/keyboard/EmojiLayoutParams.java
@@ -30,6 +30,7 @@
     public final int mEmojiPagerHeight;
     private final int mEmojiPagerBottomMargin;
     public final int mEmojiKeyboardHeight;
+    private final int mEmojiCategoryPageIdViewHeight;
     public final int mEmojiActionBarHeight;
     public final int mKeyVerticalGap;
     private final int mKeyHorizontalGap;
@@ -47,23 +48,32 @@
                 (int) defaultKeyboardHeight, (int) defaultKeyboardHeight);
         mKeyHorizontalGap = (int) (res.getFraction(R.fraction.key_horizontal_gap_ics,
                 defaultKeyboardWidth, defaultKeyboardWidth));
+        mEmojiCategoryPageIdViewHeight =
+                (int) (res.getDimension(R.dimen.emoji_category_page_id_height));
         final int baseheight = defaultKeyboardHeight - mBottomPadding - mTopPadding
                 + mKeyVerticalGap;
         mEmojiActionBarHeight = ((int) baseheight) / DEFAULT_KEYBOARD_ROWS
                 - (mKeyVerticalGap - mBottomPadding) / 2;
-        mEmojiPagerHeight = defaultKeyboardHeight - mEmojiActionBarHeight;
+        mEmojiPagerHeight = defaultKeyboardHeight - mEmojiActionBarHeight
+                - mEmojiCategoryPageIdViewHeight;
         mEmojiPagerBottomMargin = mKeyVerticalGap / 2;
         mEmojiKeyboardHeight = mEmojiPagerHeight - mEmojiPagerBottomMargin - 1;
     }
 
-    public void setPagerProps(ViewPager vp) {
+    public void setPagerProperties(ViewPager vp) {
         final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) vp.getLayoutParams();
-        lp.height = mEmojiPagerHeight - mEmojiPagerBottomMargin;
+        lp.height = mEmojiKeyboardHeight;
         lp.bottomMargin = mEmojiPagerBottomMargin;
         vp.setLayoutParams(lp);
     }
 
-    public void setActionBarProps(LinearLayout ll) {
+    public void setCategoryPageIdViewProperties(LinearLayout ll) {
+        final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) ll.getLayoutParams();
+        lp.height = mEmojiCategoryPageIdViewHeight;
+        ll.setLayoutParams(lp);
+    }
+
+    public void setActionBarProperties(LinearLayout ll) {
         final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) ll.getLayoutParams();
         lp.height = mEmojiActionBarHeight;
         lp.topMargin = 0;
@@ -71,7 +81,7 @@
         ll.setLayoutParams(lp);
     }
 
-    public void setKeyProps(ImageView ib) {
+    public void setKeyProperties(ImageView ib) {
         final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) ib.getLayoutParams();
         lp.leftMargin = mKeyHorizontalGap / 2;
         lp.rightMargin = mKeyHorizontalGap / 2;
diff --git a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
index cd127c7..a031669 100644
--- a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
+++ b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
@@ -245,8 +245,8 @@
         final int threshold = (int) (defaultWidth * SEARCH_DISTANCE);
         final int thresholdSquared = threshold * threshold;
         // Round-up so we don't have any pixels outside the grid
-        final int fullGridWidth = mGridWidth * mCellWidth;
-        final int fullGridHeight = mGridHeight * mCellHeight;
+        final int lastPixelXCoordinate = mGridWidth * mCellWidth - 1;
+        final int lastPixelYCoordinate = mGridHeight * mCellHeight - 1;
 
         // For large layouts, 'neighborsFlatBuffer' is about 80k of memory: gridSize is usually 512,
         // keycount is about 40 and a pointer to a Key is 4 bytes. This contains, for each cell,
@@ -329,22 +329,20 @@
             final int yMiddleOfTopCell = topPixelWithinThreshold - yDeltaToGrid + halfCellHeight;
             final int yStart = Math.max(halfCellHeight,
                     yMiddleOfTopCell + (yDeltaToGrid <= halfCellHeight ? 0 : mCellHeight));
-            final int yEnd = Math.min(fullGridHeight, keyY + key.getHeight() + threshold);
+            final int yEnd = Math.min(lastPixelYCoordinate, keyY + key.getHeight() + threshold);
 
             final int leftPixelWithinThreshold = keyX - threshold;
             final int xDeltaToGrid = leftPixelWithinThreshold % mCellWidth;
             final int xMiddleOfLeftCell = leftPixelWithinThreshold - xDeltaToGrid + halfCellWidth;
             final int xStart = Math.max(halfCellWidth,
                     xMiddleOfLeftCell + (xDeltaToGrid <= halfCellWidth ? 0 : mCellWidth));
-            final int xEnd = Math.min(fullGridWidth, keyX + key.getWidth() + threshold);
+            final int xEnd = Math.min(lastPixelXCoordinate, keyX + key.getWidth() + threshold);
 
             int baseIndexOfCurrentRow = (yStart / mCellHeight) * mGridWidth + (xStart / mCellWidth);
             for (int centerY = yStart; centerY <= yEnd; centerY += mCellHeight) {
                 int index = baseIndexOfCurrentRow;
                 for (int centerX = xStart; centerX <= xEnd; centerX += mCellWidth) {
-                    // TODO: Remove "index < neighborCountPerCell.length" below.
-                    if (index < neighborCountPerCell.length
-                            && key.squaredDistanceToEdge(centerX, centerY) < thresholdSquared) {
+                    if (key.squaredDistanceToEdge(centerX, centerY) < thresholdSquared) {
                         neighborsFlatBuffer[index * keyCount + neighborCountPerCell[index]] = key;
                         ++neighborCountPerCell[index];
                     }
diff --git a/java/src/com/android/inputmethod/keyboard/internal/DynamicGridKeyboard.java b/java/src/com/android/inputmethod/keyboard/internal/DynamicGridKeyboard.java
index 2976e23..0dd71e2 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/DynamicGridKeyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/DynamicGridKeyboard.java
@@ -193,7 +193,7 @@
         public void updateCorrdinates(final int x, final int y) {
             mCurrentX = x;
             mCurrentY = y;
-            getHitBox().offsetTo(x, y);
+            getHitBox().set(x, y, x + getWidth(), y + getHeight());
         }
 
         @Override
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java
index a72595f..2af2f69 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java
@@ -2015,6 +2015,25 @@
         /* 45 */ "\u0410\u0411\u0412",
     };
 
+    /* Language km: Khmer */
+    private static final String[] LANGUAGE_km = {
+        /* 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+1780: "ក" KHMER LETTER KA
+        // U+1781: "ខ" KHMER LETTER KHA
+        // U+1782: "គ" KHMER LETTER KO
+        /* 45 */ "\u1780\u1781\u1782",
+        /* 46~ */
+        null, null, null, null,
+        /* ~49 */
+        // U+17DB: "៛" KHMER CURRENCY SYMBOL RIEL
+        /* 50 */ "\u17DB,\u00A2,\u00A3,\u20AC,\u00A5,\u20B1",
+    };
+
     /* Language ky: Kirghiz */
     private static final String[] LANGUAGE_ky = {
         /* 0~ */
@@ -3407,6 +3426,7 @@
         "iw", LANGUAGE_iw, /* Hebrew */
         "ka", LANGUAGE_ka, /* Georgian */
         "kk", LANGUAGE_kk, /* Kazakh */
+        "km", LANGUAGE_km, /* Khmer */
         "ky", LANGUAGE_ky, /* Kirghiz */
         "lo", LANGUAGE_lo, /* Lao */
         "lt", LANGUAGE_lt, /* Lithuanian */
diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
index 2a90764..fcd7ede 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
@@ -311,9 +311,10 @@
     }
 
     @Override
-    public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
+    public ArrayList<SuggestedWordInfo> getSuggestionsWithSessionId(final WordComposer composer,
             final String prevWord, final ProximityInfo proximityInfo,
-            final boolean blockOffensiveWords, final int[] additionalFeaturesOptions) {
+            final boolean blockOffensiveWords, final int[] additionalFeaturesOptions,
+            final int sessionId) {
         reloadDictionaryIfRequired();
         final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList();
         final AsyncResultHolder<ArrayList<SuggestedWordInfo>> holder =
@@ -321,14 +322,16 @@
         getExecutor(mFilename).executePrioritized(new Runnable() {
             @Override
             public void run() {
-                final ArrayList<SuggestedWordInfo> inMemDictSuggestion =
-                        mDictionaryWriter.getSuggestions(composer, prevWord, proximityInfo,
-                                blockOffensiveWords, additionalFeaturesOptions);
+                final ArrayList<SuggestedWordInfo> inMemDictSuggestion = composer.isBatchMode() ?
+                        null : mDictionaryWriter.getSuggestionsWithSessionId(composer, prevWord,
+                                proximityInfo, blockOffensiveWords, additionalFeaturesOptions,
+                                sessionId);
                 // TODO: Remove checking mIsUpdatable and use native suggestion.
                 if (mBinaryDictionary != null && !mIsUpdatable) {
                     final ArrayList<SuggestedWordInfo> binarySuggestion =
-                            mBinaryDictionary.getSuggestions(composer, prevWord, proximityInfo,
-                                    blockOffensiveWords, additionalFeaturesOptions);
+                            mBinaryDictionary.getSuggestionsWithSessionId(composer, prevWord,
+                                    proximityInfo, blockOffensiveWords, additionalFeaturesOptions,
+                                    sessionId);
                     if (inMemDictSuggestion == null) {
                         holder.set(binarySuggestion);
                     } else if (binarySuggestion == null) {
@@ -342,11 +345,18 @@
                 }
             }
         });
-
         return holder.get(null, TIMEOUT_FOR_READ_OPS_IN_MILLISECONDS);
     }
 
     @Override
+    public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
+            final String prevWord, final ProximityInfo proximityInfo,
+            final boolean blockOffensiveWords, final int[] additionalFeaturesOptions) {
+        return getSuggestionsWithSessionId(composer, prevWord, proximityInfo, blockOffensiveWords,
+                additionalFeaturesOptions, 0 /* sessionId */);
+    }
+
+    @Override
     public boolean isValidWord(final String word) {
         reloadDictionaryIfRequired();
         return isValidWordInner(word);
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 9f779eb..dead530 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -46,6 +46,7 @@
 import android.text.TextUtils;
 import android.text.style.SuggestionSpan;
 import android.util.Log;
+import android.util.Pair;
 import android.util.PrintWriterPrinter;
 import android.util.Printer;
 import android.view.KeyCharacterMap;
@@ -236,6 +237,8 @@
         private static final int ARG1_NOT_GESTURE_INPUT = 0;
         private static final int ARG1_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT = 1;
         private static final int ARG1_SHOW_GESTURE_FLOATING_PREVIEW_TEXT = 2;
+        private static final int ARG2_WITHOUT_TYPED_WORD = 0;
+        private static final int ARG2_WITH_TYPED_WORD = 1;
 
         private int mDelayUpdateSuggestions;
         private int mDelayUpdateShiftState;
@@ -269,7 +272,13 @@
                 break;
             case MSG_SHOW_GESTURE_PREVIEW_AND_SUGGESTION_STRIP:
                 if (msg.arg1 == ARG1_NOT_GESTURE_INPUT) {
-                    latinIme.showSuggestionStrip((SuggestedWords) msg.obj);
+                    if (msg.arg2 == ARG2_WITH_TYPED_WORD) {
+                        final Pair<SuggestedWords, String> p =
+                                (Pair<SuggestedWords, String>) msg.obj;
+                        latinIme.showSuggestionStripWithTypedWord(p.first, p.second);
+                    } else {
+                        latinIme.showSuggestionStrip((SuggestedWords) msg.obj);
+                    }
                 } else {
                     latinIme.showGesturePreviewAndSuggestionStrip((SuggestedWords) msg.obj,
                             msg.arg1 == ARG1_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT);
@@ -331,14 +340,23 @@
             final int arg1 = dismissGestureFloatingPreviewText
                     ? ARG1_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT
                     : ARG1_SHOW_GESTURE_FLOATING_PREVIEW_TEXT;
-            obtainMessage(MSG_SHOW_GESTURE_PREVIEW_AND_SUGGESTION_STRIP, arg1, 0, suggestedWords)
-                    .sendToTarget();
+            obtainMessage(MSG_SHOW_GESTURE_PREVIEW_AND_SUGGESTION_STRIP, arg1,
+                    ARG2_WITHOUT_TYPED_WORD, suggestedWords).sendToTarget();
         }
 
         public void showSuggestionStrip(final SuggestedWords suggestedWords) {
             removeMessages(MSG_SHOW_GESTURE_PREVIEW_AND_SUGGESTION_STRIP);
             obtainMessage(MSG_SHOW_GESTURE_PREVIEW_AND_SUGGESTION_STRIP,
-                    ARG1_NOT_GESTURE_INPUT, 0, suggestedWords).sendToTarget();
+                    ARG1_NOT_GESTURE_INPUT, ARG2_WITHOUT_TYPED_WORD, suggestedWords).sendToTarget();
+        }
+
+        // TODO: Remove this method.
+        public void showSuggestionStripWithTypedWord(final SuggestedWords suggestedWords,
+                final String typedWord) {
+            removeMessages(MSG_SHOW_GESTURE_PREVIEW_AND_SUGGESTION_STRIP);
+            obtainMessage(MSG_SHOW_GESTURE_PREVIEW_AND_SUGGESTION_STRIP, ARG1_NOT_GESTURE_INPUT,
+                    ARG2_WITH_TYPED_WORD,
+                    new Pair<SuggestedWords, String>(suggestedWords, typedWord)).sendToTarget();
         }
 
         public void onEndBatchInput(final SuggestedWords suggestedWords) {
@@ -2468,27 +2486,39 @@
                 false /* isPrediction */);
     }
 
-    private void setAutoCorrection(final SuggestedWords suggestedWords) {
+    private void setAutoCorrection(final SuggestedWords suggestedWords, final String typedWord) {
         if (suggestedWords.isEmpty()) return;
         final String autoCorrection;
         if (suggestedWords.mWillAutoCorrect) {
             autoCorrection = suggestedWords.getWord(SuggestedWords.INDEX_OF_AUTO_CORRECTION);
         } else {
-            autoCorrection = suggestedWords.getWord(SuggestedWords.INDEX_OF_TYPED_WORD);
+            // We can't use suggestedWords.getWord(SuggestedWords.INDEX_OF_TYPED_WORD)
+            // because it may differ from mWordComposer.mTypedWord.
+            autoCorrection = typedWord;
         }
         mWordComposer.setAutoCorrection(autoCorrection);
     }
 
+    private void showSuggestionStripWithTypedWord(final SuggestedWords suggestedWords,
+            final String typedWord) {
+      if (suggestedWords.isEmpty()) {
+          clearSuggestionStrip();
+          return;
+      }
+      setAutoCorrection(suggestedWords, typedWord);
+      final boolean isAutoCorrection = suggestedWords.willAutoCorrect();
+      setSuggestedWords(suggestedWords, isAutoCorrection);
+      setAutoCorrectionIndicator(isAutoCorrection);
+      setSuggestionStripShown(isSuggestionsStripVisible());
+    }
+
     private void showSuggestionStrip(final SuggestedWords suggestedWords) {
         if (suggestedWords.isEmpty()) {
             clearSuggestionStrip();
             return;
         }
-        setAutoCorrection(suggestedWords);
-        final boolean isAutoCorrection = suggestedWords.willAutoCorrect();
-        setSuggestedWords(suggestedWords, isAutoCorrection);
-        setAutoCorrectionIndicator(isAutoCorrection);
-        setSuggestionStripShown(isSuggestionsStripVisible());
+        showSuggestionStripWithTypedWord(suggestedWords,
+            suggestedWords.getWord(SuggestedWords.INDEX_OF_TYPED_WORD));
     }
 
     private void commitCurrentAutoCorrection(final String separator) {
@@ -2766,7 +2796,10 @@
                         // Since there is only one word, willAutoCorrect is false.
                         suggestedWords = suggestedWordsIncludingTypedWord;
                     }
-                    unsetIsAutoCorrectionIndicatorOnAndCallShowSuggestionStrip(suggestedWords);
+                    // We need to pass typedWord because mWordComposer.mTypedWord may differ from
+                    // typedWord.
+                    unsetIsAutoCorrectionIndicatorOnAndCallShowSuggestionStrip(suggestedWords,
+                        typedWord);
                 }});
         } else {
             // We found suggestion spans in the word. We'll create the SuggestedWords out of
@@ -2775,12 +2808,13 @@
                     true /* typedWordValid */, false /* willAutoCorrect */,
                     false /* isPunctuationSuggestions */, false /* isObsoleteSuggestions */,
                     false /* isPrediction */);
-            unsetIsAutoCorrectionIndicatorOnAndCallShowSuggestionStrip(suggestedWords);
+            // We need to pass typedWord because mWordComposer.mTypedWord may differ from typedWord.
+            unsetIsAutoCorrectionIndicatorOnAndCallShowSuggestionStrip(suggestedWords, typedWord);
         }
     }
 
     public void unsetIsAutoCorrectionIndicatorOnAndCallShowSuggestionStrip(
-            final SuggestedWords suggestedWords) {
+            final SuggestedWords suggestedWords, final String typedWord) {
         // Note that it's very important here that suggestedWords.mWillAutoCorrect is false.
         // We never want to auto-correct on a resumed suggestion. Please refer to the three places
         // above in restartSuggestionsOnWordTouchedByCursor() where suggestedWords is affected.
@@ -2788,7 +2822,7 @@
         // the text to adapt it.
         // TODO: remove mIsAutoCorrectionIndicatorOn (see comment on definition)
         mIsAutoCorrectionIndicatorOn = false;
-        mHandler.showSuggestionStrip(suggestedWords);
+        mHandler.showSuggestionStripWithTypedWord(suggestedWords, typedWord);
     }
 
     /**
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index 1684d47..6c18c94 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -361,12 +361,6 @@
 
         // At second character typed, search the unigrams (scores being affected by bigrams)
         for (final String key : mDictionaries.keySet()) {
-            // Skip User history dictionary for lookup
-            // TODO: The user history dictionary should just override getSuggestionsWithSessionId
-            // to make sure it doesn't return anything and we should remove this test
-            if (key.equals(Dictionary.TYPE_USER_HISTORY)) {
-                continue;
-            }
             final Dictionary dictionary = mDictionaries.get(key);
             suggestionsSet.addAll(dictionary.getSuggestionsWithSessionId(wordComposer,
                     prevWordForBigram, proximityInfo, blockOffensiveWords,
diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderUtils.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderUtils.java
index 5b319ad..665c7a2 100644
--- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderUtils.java
+++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderUtils.java
@@ -498,7 +498,7 @@
 
             // reach the end of the array.
             if (options.mSupportsDynamicUpdate) {
-                final boolean hasValidForwardLink = dictDecoder.readForwardLinkAndAdvancePosition();
+                final boolean hasValidForwardLink = dictDecoder.readAndFollowForwardLink();
                 if (!hasValidForwardLink) break;
             }
         } while (options.mSupportsDynamicUpdate && dictDecoder.hasNextPtNodeArray());
@@ -550,7 +550,7 @@
      * @return the created (or merged) dictionary.
      */
     @UsedForTesting
-    /* package */ static FusionDictionary readDictionaryBinary(final Ver3DictDecoder dictDecoder,
+    /* package */ static FusionDictionary readDictionaryBinary(final DictDecoder dictDecoder,
             final FusionDictionary dict) throws IOException, UnsupportedFormatException {
         // Read header
         final FileHeader fileHeader = dictDecoder.readHeader();
diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictEncoderUtils.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictEncoderUtils.java
index 70931f8..4dba8e5 100644
--- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictEncoderUtils.java
+++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictEncoderUtils.java
@@ -368,9 +368,9 @@
             if (null != ptNode.mBigrams) {
                 for (WeightedString bigram : ptNode.mBigrams) {
                     final int offset = getOffsetToTargetPtNodeDuringUpdate(ptNodeArray,
-                            nodeSize + size + FormatSpec.PTNODE_FLAGS_SIZE,
+                            nodeSize + size + FormatSpec.PTNODE_ATTRIBUTE_FLAGS_SIZE,
                             FusionDictionary.findWordInTree(dict.mRootNodeArray, bigram.mWord));
-                    nodeSize += getByteSize(offset) + FormatSpec.PTNODE_FLAGS_SIZE;
+                    nodeSize += getByteSize(offset) + FormatSpec.PTNODE_ATTRIBUTE_FLAGS_SIZE;
                 }
             }
             ptNode.mCachedSize = nodeSize;
diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java
index 2c5e93e..a282f59 100644
--- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java
+++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java
@@ -114,7 +114,7 @@
             if (p.mPosition == p.mNumOfPtNode) {
                 if (formatOptions.mSupportsDynamicUpdate) {
                     final boolean hasValidForwardLinkAddress =
-                            dictDecoder.readForwardLinkAndAdvancePosition();
+                            dictDecoder.readAndFollowForwardLink();
                     if (hasValidForwardLinkAddress && dictDecoder.hasNextPtNodeArray()) {
                         // The node array has a forward link.
                         p.mNumOfPtNode = Position.NOT_READ_PTNODE_COUNT;
@@ -233,7 +233,7 @@
                 }
 
                 final boolean hasValidForwardLinkAddress =
-                        dictDecoder.readForwardLinkAndAdvancePosition();
+                        dictDecoder.readAndFollowForwardLink();
                 if (!hasValidForwardLinkAddress || !dictDecoder.hasNextPtNodeArray()) {
                     return FormatSpec.NOT_VALID_WORD;
                 }
diff --git a/java/src/com/android/inputmethod/latin/makedict/DictDecoder.java b/java/src/com/android/inputmethod/latin/makedict/DictDecoder.java
index 40e8524..3796a46 100644
--- a/java/src/com/android/inputmethod/latin/makedict/DictDecoder.java
+++ b/java/src/com/android/inputmethod/latin/makedict/DictDecoder.java
@@ -17,9 +17,11 @@
 package com.android.inputmethod.latin.makedict;
 
 import com.android.inputmethod.annotations.UsedForTesting;
+import com.android.inputmethod.latin.makedict.BinaryDictDecoderUtils.CharEncoding;
 import com.android.inputmethod.latin.makedict.BinaryDictDecoderUtils.DictBuffer;
 import com.android.inputmethod.latin.makedict.FormatSpec.FileHeader;
 import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions;
+import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
 import com.android.inputmethod.latin.utils.ByteArrayDictBuffer;
 
 import java.io.File;
@@ -30,13 +32,50 @@
 import java.nio.ByteBuffer;
 import java.nio.channels.FileChannel;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.TreeMap;
 
 /**
- * An interface of binary dictionary decoder.
+ * The base class of binary dictionary decoders.
  */
-public interface DictDecoder {
-    public FileHeader readHeader() throws IOException, UnsupportedFormatException;
+public abstract class DictDecoder {
+
+    protected FileHeader readHeader(final DictBuffer dictBuffer)
+            throws IOException, UnsupportedFormatException {
+        if (dictBuffer == null) {
+            openDictBuffer();
+        }
+
+        final int version = HeaderReader.readVersion(dictBuffer);
+        if (version < FormatSpec.MINIMUM_SUPPORTED_VERSION
+                || version > FormatSpec.MAXIMUM_SUPPORTED_VERSION) {
+            throw new UnsupportedFormatException("Unsupported version : " + version);
+        }
+        // TODO: Remove this field.
+        final int optionsFlags = HeaderReader.readOptionFlags(dictBuffer);
+
+        final int headerSize = HeaderReader.readHeaderSize(dictBuffer);
+
+        if (headerSize < 0) {
+            throw new UnsupportedFormatException("header size can't be negative.");
+        }
+
+        final HashMap<String, String> attributes = HeaderReader.readAttributes(dictBuffer,
+                headerSize);
+
+        final FileHeader header = new FileHeader(headerSize,
+                new FusionDictionary.DictionaryOptions(attributes,
+                        0 != (optionsFlags & FormatSpec.GERMAN_UMLAUT_PROCESSING_FLAG),
+                        0 != (optionsFlags & FormatSpec.FRENCH_LIGATURE_PROCESSING_FLAG)),
+                        new FormatOptions(version,
+                                0 != (optionsFlags & FormatSpec.SUPPORTS_DYNAMIC_UPDATE)));
+        return header;
+    }
+
+    /**
+     * Reads and returns the file header.
+     */
+    public abstract FileHeader readHeader() throws IOException, UnsupportedFormatException;
 
     /**
      * Reads PtNode from nodeAddress.
@@ -44,7 +83,7 @@
      * @param formatOptions the format options.
      * @return PtNodeInfo.
      */
-    public PtNodeInfo readPtNode(final int ptNodePos, final FormatOptions formatOptions);
+    public abstract PtNodeInfo readPtNode(final int ptNodePos, final FormatOptions formatOptions);
 
     /**
      * Reads a buffer and returns the memory representation of the dictionary.
@@ -59,9 +98,9 @@
      * @return the created (or merged) dictionary.
      */
     @UsedForTesting
-    public FusionDictionary readDictionaryBinary(final FusionDictionary dict,
+    public abstract FusionDictionary readDictionaryBinary(final FusionDictionary dict,
             final boolean deleteDictIfBroken)
-            throws FileNotFoundException, IOException, UnsupportedFormatException;
+                    throws FileNotFoundException, IOException, UnsupportedFormatException;
 
     /**
      * Gets the address of the last PtNode of the exact matching word in the dictionary.
@@ -74,7 +113,12 @@
      */
     @UsedForTesting
     public int getTerminalPosition(final String word)
-            throws IOException, UnsupportedFormatException;
+            throws IOException, UnsupportedFormatException {
+        if (!isDictBufferOpen()) {
+            openDictBuffer();
+        }
+        return BinaryDictIOUtils.getTerminalPosition(this, word);
+    }
 
     /**
      * Reads unigrams and bigrams from the binary file.
@@ -86,50 +130,56 @@
      * @throws IOException if the file can't be read.
      * @throws UnsupportedFormatException if the format of the file is not recognized.
      */
+    @UsedForTesting
     public void readUnigramsAndBigramsBinary(final TreeMap<Integer, String> words,
             final TreeMap<Integer, Integer> frequencies,
             final TreeMap<Integer, ArrayList<PendingAttribute>> bigrams)
-            throws IOException, UnsupportedFormatException;
+            throws IOException, UnsupportedFormatException {
+        if (!isDictBufferOpen()) {
+            openDictBuffer();
+        }
+        BinaryDictIOUtils.readUnigramsAndBigramsBinary(this, words, frequencies, bigrams);
+    }
 
     /**
      * Sets the position of the buffer to the given value.
      *
      * @param newPos the new position
      */
-    public void setPosition(final int newPos);
+    public abstract void setPosition(final int newPos);
 
     /**
      * Gets the position of the buffer.
      *
      * @return the position
      */
-    public int getPosition();
+    public abstract int getPosition();
 
     /**
      * Reads and returns the PtNode count out of a buffer and forwards the pointer.
      */
-    public int readPtNodeCount();
+    public abstract int readPtNodeCount();
 
     /**
      * Reads the forward link and advances the position.
      *
-     * @return if this method advances the position then true else false.
+     * @return true if this method moves the file pointer, false otherwise.
      */
-    public boolean readForwardLinkAndAdvancePosition();
-    public boolean hasNextPtNodeArray();
+    public abstract boolean readAndFollowForwardLink();
+    public abstract boolean hasNextPtNodeArray();
 
     /**
      * Opens the dictionary file and makes DictBuffer.
      */
     @UsedForTesting
-    public void openDictBuffer() throws FileNotFoundException, IOException;
+    public abstract void openDictBuffer() throws FileNotFoundException, IOException;
     @UsedForTesting
-    public boolean isOpenedDictBuffer();
+    public abstract boolean isDictBufferOpen();
 
-    // Flags for DictionaryBufferFactory.
+    // Constants for DictionaryBufferFactory.
     public static final int USE_READONLY_BYTEBUFFER = 0x01000000;
     public static final int USE_BYTEARRAY = 0x02000000;
-    public static final int USE_WRITABLE_BYTEBUFFER = 0x04000000;
+    public static final int USE_WRITABLE_BYTEBUFFER = 0x03000000;
     public static final int MASK_DICTBUFFER = 0x0F000000;
 
     public interface DictionaryBufferFactory {
@@ -221,4 +271,124 @@
             return null;
         }
     }
+
+    /**
+     * A utility class for reading a file header.
+     */
+    protected static class HeaderReader {
+        protected static int readVersion(final DictBuffer dictBuffer)
+                throws IOException, UnsupportedFormatException {
+            return BinaryDictDecoderUtils.checkFormatVersion(dictBuffer);
+        }
+
+        protected static int readOptionFlags(final DictBuffer dictBuffer) {
+            return dictBuffer.readUnsignedShort();
+        }
+
+        protected static int readHeaderSize(final DictBuffer dictBuffer) {
+            return dictBuffer.readInt();
+        }
+
+        protected static HashMap<String, String> readAttributes(final DictBuffer dictBuffer,
+                final int headerSize) {
+            final HashMap<String, String> attributes = new HashMap<String, String>();
+            while (dictBuffer.position() < headerSize) {
+                // We can avoid an infinite loop here since dictBuffer.position() is always
+                // increased by calling CharEncoding.readString.
+                final String key = CharEncoding.readString(dictBuffer);
+                final String value = CharEncoding.readString(dictBuffer);
+                attributes.put(key, value);
+            }
+            dictBuffer.position(headerSize);
+            return attributes;
+        }
+    }
+
+    /**
+     * A utility class for reading a PtNode.
+     */
+    protected static class PtNodeReader {
+        protected static int readPtNodeOptionFlags(final DictBuffer dictBuffer) {
+            return dictBuffer.readUnsignedByte();
+        }
+
+        protected static int readParentAddress(final DictBuffer dictBuffer,
+                final FormatOptions formatOptions) {
+            if (BinaryDictIOUtils.supportsDynamicUpdate(formatOptions)) {
+                return BinaryDictDecoderUtils.readSInt24(dictBuffer);
+            } else {
+                return FormatSpec.NO_PARENT_ADDRESS;
+            }
+        }
+
+        protected static int readChildrenAddress(final DictBuffer dictBuffer, final int optionFlags,
+                final FormatOptions formatOptions) {
+            if (BinaryDictIOUtils.supportsDynamicUpdate(formatOptions)) {
+                final int address = BinaryDictDecoderUtils.readSInt24(dictBuffer);
+                if (address == 0) return FormatSpec.NO_CHILDREN_ADDRESS;
+                return address;
+            } else {
+                switch (optionFlags & FormatSpec.MASK_CHILDREN_ADDRESS_TYPE) {
+                    case FormatSpec.FLAG_CHILDREN_ADDRESS_TYPE_ONEBYTE:
+                        return dictBuffer.readUnsignedByte();
+                    case FormatSpec.FLAG_CHILDREN_ADDRESS_TYPE_TWOBYTES:
+                        return dictBuffer.readUnsignedShort();
+                    case FormatSpec.FLAG_CHILDREN_ADDRESS_TYPE_THREEBYTES:
+                        return dictBuffer.readUnsignedInt24();
+                    case FormatSpec.FLAG_CHILDREN_ADDRESS_TYPE_NOADDRESS:
+                    default:
+                        return FormatSpec.NO_CHILDREN_ADDRESS;
+                }
+            }
+        }
+
+        // Reads shortcuts and returns the read length.
+        protected static int readShortcut(final DictBuffer dictBuffer,
+                final ArrayList<WeightedString> shortcutTargets) {
+            final int pointerBefore = dictBuffer.position();
+            dictBuffer.readUnsignedShort(); // skip the size
+            while (true) {
+                final int targetFlags = dictBuffer.readUnsignedByte();
+                final String word = CharEncoding.readString(dictBuffer);
+                shortcutTargets.add(new WeightedString(word,
+                        targetFlags & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_FREQUENCY));
+                if (0 == (targetFlags & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_HAS_NEXT)) break;
+            }
+            return dictBuffer.position() - pointerBefore;
+        }
+
+        protected static int readBigramAddresses(final DictBuffer dictBuffer,
+                final ArrayList<PendingAttribute> bigrams, final int baseAddress) {
+            int readLength = 0;
+            int bigramCount = 0;
+            while (bigramCount++ < FormatSpec.MAX_BIGRAMS_IN_A_PTNODE) {
+                final int bigramFlags = dictBuffer.readUnsignedByte();
+                ++readLength;
+                final int sign = 0 == (bigramFlags & FormatSpec.FLAG_BIGRAM_ATTR_OFFSET_NEGATIVE)
+                        ? 1 : -1;
+                int bigramAddress = baseAddress + readLength;
+                switch (bigramFlags & FormatSpec.MASK_BIGRAM_ATTR_ADDRESS_TYPE) {
+                    case FormatSpec.FLAG_BIGRAM_ATTR_ADDRESS_TYPE_ONEBYTE:
+                        bigramAddress += sign * dictBuffer.readUnsignedByte();
+                        readLength += 1;
+                        break;
+                    case FormatSpec.FLAG_BIGRAM_ATTR_ADDRESS_TYPE_TWOBYTES:
+                        bigramAddress += sign * dictBuffer.readUnsignedShort();
+                        readLength += 2;
+                        break;
+                    case FormatSpec.FLAG_BIGRAM_ATTR_ADDRESS_TYPE_THREEBYTES:
+                        bigramAddress += sign * dictBuffer.readUnsignedInt24();
+                        readLength += 3;
+                        break;
+                    default:
+                        throw new RuntimeException("Has bigrams with no address");
+                }
+                bigrams.add(new PendingAttribute(
+                        bigramFlags & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_FREQUENCY,
+                        bigramAddress));
+                if (0 == (bigramFlags & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_HAS_NEXT)) break;
+            }
+            return readLength;
+        }
+    }
 }
diff --git a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
index 96ccd8e..51b89a0 100644
--- a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
+++ b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
@@ -360,18 +360,26 @@
      * Returns new dictionary decoder.
      *
      * @param dictFile the dictionary file.
-     * @param bufferType the flag indicating buffer type which is used by the dictionary decoder.
+     * @param bufferType The type of buffer, as one of USE_* in DictDecoder.
      * @return new dictionary decoder if the dictionary file exists, otherwise null.
      */
     public static DictDecoder getDictDecoder(final File dictFile, final int bufferType) {
-        if (!dictFile.isFile()) return null;
-        return new Ver3DictDecoder(dictFile, bufferType);
+        if (dictFile.isDirectory()) {
+            return new Ver4DictDecoder(dictFile, bufferType);
+        } else if (dictFile.isFile()) {
+            return new Ver3DictDecoder(dictFile, bufferType);
+        }
+        return null;
     }
 
     public static DictDecoder getDictDecoder(final File dictFile,
             final DictionaryBufferFactory factory) {
-        if (!dictFile.isFile()) return null;
-        return new Ver3DictDecoder(dictFile, factory);
+        if (dictFile.isDirectory()) {
+            return new Ver4DictDecoder(dictFile, factory);
+        } else if (dictFile.isFile()) {
+            return new Ver3DictDecoder(dictFile, factory);
+        }
+        return null;
     }
 
     public static DictDecoder getDictDecoder(final File dictFile) {
diff --git a/java/src/com/android/inputmethod/latin/makedict/Ver3DictDecoder.java b/java/src/com/android/inputmethod/latin/makedict/Ver3DictDecoder.java
index 1a90a4b..848277c 100644
--- a/java/src/com/android/inputmethod/latin/makedict/Ver3DictDecoder.java
+++ b/java/src/com/android/inputmethod/latin/makedict/Ver3DictDecoder.java
@@ -32,14 +32,12 @@
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.HashMap;
-import java.util.TreeMap;
 
 /**
  * An implementation of DictDecoder for version 3 binary dictionary.
  */
 @UsedForTesting
-public class Ver3DictDecoder implements DictDecoder {
+public class Ver3DictDecoder extends DictDecoder {
     private static final String TAG = Ver3DictDecoder.class.getSimpleName();
 
     static {
@@ -49,124 +47,10 @@
     // TODO: implement something sensical instead of just a phony method
     private static native int doNothing();
 
-    private final static class HeaderReader {
-        protected static int readVersion(final DictBuffer dictBuffer)
-                throws IOException, UnsupportedFormatException {
-            return BinaryDictDecoderUtils.checkFormatVersion(dictBuffer);
-        }
-
-        protected static int readOptionFlags(final DictBuffer dictBuffer) {
-            return dictBuffer.readUnsignedShort();
-        }
-
-        protected static int readHeaderSize(final DictBuffer dictBuffer) {
-            return dictBuffer.readInt();
-        }
-
-        protected static HashMap<String, String> readAttributes(final DictBuffer dictBuffer,
-                final int headerSize) {
-            final HashMap<String, String> attributes = new HashMap<String, String>();
-            while (dictBuffer.position() < headerSize) {
-                // We can avoid an infinite loop here since dictBuffer.position() is always
-                // increased by calling CharEncoding.readString.
-                final String key = CharEncoding.readString(dictBuffer);
-                final String value = CharEncoding.readString(dictBuffer);
-                attributes.put(key, value);
-            }
-            dictBuffer.position(headerSize);
-            return attributes;
-        }
-    }
-
-    private final static class PtNodeReader {
-        protected static int readPtNodeOptionFlags(final DictBuffer dictBuffer) {
+    protected static class PtNodeReader extends DictDecoder.PtNodeReader {
+        private static int readFrequency(final DictBuffer dictBuffer) {
             return dictBuffer.readUnsignedByte();
         }
-
-        protected static int readParentAddress(final DictBuffer dictBuffer,
-                final FormatOptions formatOptions) {
-            if (BinaryDictIOUtils.supportsDynamicUpdate(formatOptions)) {
-                return BinaryDictDecoderUtils.readSInt24(dictBuffer);
-            } else {
-                return FormatSpec.NO_PARENT_ADDRESS;
-            }
-        }
-
-        protected static int readFrequency(final DictBuffer dictBuffer) {
-            return dictBuffer.readUnsignedByte();
-        }
-
-        protected static int readChildrenAddress(final DictBuffer dictBuffer, final int optionFlags,
-                final FormatOptions formatOptions) {
-            if (BinaryDictIOUtils.supportsDynamicUpdate(formatOptions)) {
-                final int address = BinaryDictDecoderUtils.readSInt24(dictBuffer);
-                if (address == 0) return FormatSpec.NO_CHILDREN_ADDRESS;
-                return address;
-            } else {
-                switch (optionFlags & FormatSpec.MASK_CHILDREN_ADDRESS_TYPE) {
-                    case FormatSpec.FLAG_CHILDREN_ADDRESS_TYPE_ONEBYTE:
-                        return dictBuffer.readUnsignedByte();
-                    case FormatSpec.FLAG_CHILDREN_ADDRESS_TYPE_TWOBYTES:
-                        return dictBuffer.readUnsignedShort();
-                    case FormatSpec.FLAG_CHILDREN_ADDRESS_TYPE_THREEBYTES:
-                        return dictBuffer.readUnsignedInt24();
-                    case FormatSpec.FLAG_CHILDREN_ADDRESS_TYPE_NOADDRESS:
-                    default:
-                        return FormatSpec.NO_CHILDREN_ADDRESS;
-                }
-            }
-        }
-
-        // Reads shortcuts and returns the read length.
-        protected static int readShortcut(final DictBuffer dictBuffer,
-                final ArrayList<WeightedString> shortcutTargets) {
-            final int pointerBefore = dictBuffer.position();
-            dictBuffer.readUnsignedShort(); // skip the size
-            while (true) {
-                final int targetFlags = dictBuffer.readUnsignedByte();
-                final String word = CharEncoding.readString(dictBuffer);
-                shortcutTargets.add(new WeightedString(word,
-                        targetFlags & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_FREQUENCY));
-                if (0 == (targetFlags & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_HAS_NEXT)) break;
-            }
-            return dictBuffer.position() - pointerBefore;
-        }
-
-        protected static int readBigrams(final DictBuffer dictBuffer,
-                final ArrayList<PendingAttribute> bigrams, final int baseAddress) {
-            int readLength = 0;
-            int bigramCount = 0;
-            while (bigramCount++ < FormatSpec.MAX_BIGRAMS_IN_A_PTNODE) {
-                final int bigramFlags = dictBuffer.readUnsignedByte();
-                ++readLength;
-                final int sign = 0 == (bigramFlags & FormatSpec.FLAG_BIGRAM_ATTR_OFFSET_NEGATIVE)
-                        ? 1 : -1;
-                int bigramAddress = baseAddress + readLength;
-                switch (bigramFlags & FormatSpec.MASK_BIGRAM_ATTR_ADDRESS_TYPE) {
-                    case FormatSpec.FLAG_BIGRAM_ATTR_ADDRESS_TYPE_ONEBYTE:
-                        bigramAddress += sign * dictBuffer.readUnsignedByte();
-                        readLength += 1;
-                        break;
-                    case FormatSpec.FLAG_BIGRAM_ATTR_ADDRESS_TYPE_TWOBYTES:
-                        bigramAddress += sign * dictBuffer.readUnsignedShort();
-                        readLength += 2;
-                        break;
-                    case FormatSpec.FLAG_BIGRAM_ATTR_ADDRESS_TYPE_THREEBYTES:
-                        final int offset = (dictBuffer.readUnsignedByte() << 16)
-                                + dictBuffer.readUnsignedShort();
-                        bigramAddress += sign * offset;
-                        readLength += 3;
-                        break;
-                    default:
-                        throw new RuntimeException("Has bigrams with no address");
-                }
-                bigrams.add(new PendingAttribute(
-                        bigramFlags & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_FREQUENCY,
-                        bigramAddress));
-                if (0 == (bigramFlags & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_HAS_NEXT)) break;
-            }
-            return readLength;
-        }
     }
 
     private final File mDictionaryBinaryFile;
@@ -199,7 +83,7 @@
     }
 
     @Override
-    public boolean isOpenedDictBuffer() {
+    public boolean isDictBufferOpen() {
         return mDictBuffer != null;
     }
 
@@ -218,25 +102,11 @@
         if (mDictBuffer == null) {
             openDictBuffer();
         }
-
-        final int version = HeaderReader.readVersion(mDictBuffer);
-        final int optionsFlags = HeaderReader.readOptionFlags(mDictBuffer);
-
-        final int headerSize = HeaderReader.readHeaderSize(mDictBuffer);
-
-        if (headerSize < 0) {
-            throw new UnsupportedFormatException("header size can't be negative.");
+        final FileHeader header = super.readHeader(mDictBuffer);
+        final int version = header.mFormatOptions.mVersion;
+        if (!(version >= 2 && version <= 3)) {
+          throw new UnsupportedFormatException("File header has a wrong version : " + version);
         }
-
-        final HashMap<String, String> attributes = HeaderReader.readAttributes(mDictBuffer,
-                headerSize);
-
-        final FileHeader header = new FileHeader(headerSize,
-                new FusionDictionary.DictionaryOptions(attributes,
-                        0 != (optionsFlags & FormatSpec.GERMAN_UMLAUT_PROCESSING_FLAG),
-                        0 != (optionsFlags & FormatSpec.FRENCH_LIGATURE_PROCESSING_FLAG)),
-                new FormatOptions(version,
-                        0 != (optionsFlags & FormatSpec.SUPPORTS_DYNAMIC_UPDATE)));
         return header;
     }
 
@@ -246,11 +116,11 @@
     public PtNodeInfo readPtNode(final int ptNodePos, final FormatOptions options) {
         int addressPointer = ptNodePos;
         final int flags = PtNodeReader.readPtNodeOptionFlags(mDictBuffer);
-        ++addressPointer;
+        addressPointer += FormatSpec.PTNODE_FLAGS_SIZE;
 
         final int parentAddress = PtNodeReader.readParentAddress(mDictBuffer, options);
         if (BinaryDictIOUtils.supportsDynamicUpdate(options)) {
-            addressPointer += 3;
+            addressPointer += FormatSpec.PARENT_ADDRESS_SIZE;
         }
 
         final int characters[];
@@ -258,7 +128,7 @@
             int index = 0;
             int character = CharEncoding.readChar(mDictBuffer);
             addressPointer += CharEncoding.getCharSize(character);
-            while (-1 != character) {
+            while (FormatSpec.INVALID_CHARACTER != character) {
                 // FusionDictionary is making sure that the length of the word is smaller than
                 // MAX_WORD_LENGTH.
                 // So we'll never write past the end of mCharacterBuffer.
@@ -274,8 +144,8 @@
         }
         final int frequency;
         if (0 != (FormatSpec.FLAG_IS_TERMINAL & flags)) {
-            ++addressPointer;
             frequency = PtNodeReader.readFrequency(mDictBuffer);
+            addressPointer += FormatSpec.PTNODE_FREQUENCY_SIZE;
         } else {
             frequency = PtNode.NOT_A_TERMINAL;
         }
@@ -296,7 +166,8 @@
         final ArrayList<PendingAttribute> bigrams;
         if (0 != (flags & FormatSpec.FLAG_HAS_BIGRAMS)) {
             bigrams = new ArrayList<PendingAttribute>();
-            addressPointer += PtNodeReader.readBigrams(mDictBuffer, bigrams, addressPointer);
+            addressPointer += PtNodeReader.readBigramAddresses(mDictBuffer, bigrams, 
+                    addressPointer);
             if (bigrams.size() >= FormatSpec.MAX_BIGRAMS_IN_A_PTNODE) {
                 MakedictLog.d("too many bigrams in a PtNode.");
             }
@@ -332,25 +203,6 @@
     }
 
     @Override
-    public int getTerminalPosition(String word) throws IOException, UnsupportedFormatException {
-        if (mDictBuffer == null) {
-            openDictBuffer();
-        }
-        return BinaryDictIOUtils.getTerminalPosition(this, word);
-    }
-
-    @Override
-    public void readUnigramsAndBigramsBinary(final TreeMap<Integer, String> words,
-            final TreeMap<Integer, Integer> frequencies,
-            final TreeMap<Integer, ArrayList<PendingAttribute>> bigrams)
-            throws IOException, UnsupportedFormatException {
-        if (mDictBuffer == null) {
-            openDictBuffer();
-        }
-        BinaryDictIOUtils.readUnigramsAndBigramsBinary(this, words, frequencies, bigrams);
-    }
-
-    @Override
     public void setPosition(int newPos) {
         mDictBuffer.position(newPos);
     }
@@ -366,7 +218,7 @@
     }
 
     @Override
-    public boolean readForwardLinkAndAdvancePosition() {
+    public boolean readAndFollowForwardLink() {
         final int nextAddress = mDictBuffer.readUnsignedInt24();
         if (nextAddress >= 0 && nextAddress < mDictBuffer.limit()) {
             mDictBuffer.position(nextAddress);
diff --git a/java/src/com/android/inputmethod/latin/makedict/Ver3DictEncoder.java b/java/src/com/android/inputmethod/latin/makedict/Ver3DictEncoder.java
index 222a0f4..76f0f40 100644
--- a/java/src/com/android/inputmethod/latin/makedict/Ver3DictEncoder.java
+++ b/java/src/com/android/inputmethod/latin/makedict/Ver3DictEncoder.java
@@ -167,7 +167,7 @@
         }
     }
 
-    public void writeChildrenPosition(final PtNode ptNode, final FormatOptions formatOptions) {
+    private void writeChildrenPosition(final PtNode ptNode, final FormatOptions formatOptions) {
         final int childrenPos = BinaryDictEncoderUtils.getChildrenPosition(ptNode, formatOptions);
         if (formatOptions.mSupportsDynamicUpdate) {
             mPosition += BinaryDictEncoderUtils.writeSignedChildrenPosition(mBuffer, mPosition,
diff --git a/java/src/com/android/inputmethod/latin/makedict/Ver4DictDecoder.java b/java/src/com/android/inputmethod/latin/makedict/Ver4DictDecoder.java
new file mode 100644
index 0000000..36c5a27
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/makedict/Ver4DictDecoder.java
@@ -0,0 +1,259 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.makedict;
+
+import com.android.inputmethod.annotations.UsedForTesting;
+import com.android.inputmethod.latin.makedict.BinaryDictDecoderUtils.CharEncoding;
+import com.android.inputmethod.latin.makedict.BinaryDictDecoderUtils.DictBuffer;
+import com.android.inputmethod.latin.makedict.FormatSpec.FileHeader;
+import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions;
+import com.android.inputmethod.latin.makedict.FusionDictionary.PtNode;
+import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
+
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * An implementation of binary dictionary decoder for version 4 binary dictionary.
+ */
+@UsedForTesting
+public class Ver4DictDecoder extends DictDecoder {
+    private static final String TAG = Ver4DictDecoder.class.getSimpleName();
+
+    private static final int FILETYPE_TRIE = 1;
+    private static final int FILETYPE_FREQUENCY = 2;
+
+    private final File mDictDirectory;
+    private final DictionaryBufferFactory mBufferFactory;
+    private DictBuffer mDictBuffer;
+    private DictBuffer mFrequencyBuffer;
+
+    @UsedForTesting
+    /* package */ Ver4DictDecoder(final File dictDirectory, final int factoryFlag) {
+        mDictDirectory = dictDirectory;
+        mDictBuffer = mFrequencyBuffer = null;
+
+        if ((factoryFlag & MASK_DICTBUFFER) == USE_READONLY_BYTEBUFFER) {
+            mBufferFactory = new DictionaryBufferFromReadOnlyByteBufferFactory();
+        } else if ((factoryFlag  & MASK_DICTBUFFER) == USE_BYTEARRAY) {
+            mBufferFactory = new DictionaryBufferFromByteArrayFactory();
+        } else if ((factoryFlag & MASK_DICTBUFFER) == USE_WRITABLE_BYTEBUFFER) {
+            mBufferFactory = new DictionaryBufferFromWritableByteBufferFactory();
+        } else {
+            mBufferFactory = new DictionaryBufferFromReadOnlyByteBufferFactory();
+        }
+    }
+
+    @UsedForTesting
+    /* package */ Ver4DictDecoder(final File dictDirectory, final DictionaryBufferFactory factory) {
+        mDictDirectory = dictDirectory;
+        mBufferFactory = factory;
+        mDictBuffer = mFrequencyBuffer = null;
+    }
+
+    private File getFile(final int fileType) {
+        if (fileType == FILETYPE_TRIE) {
+            return new File(mDictDirectory,
+                    mDictDirectory.getName() + FormatSpec.TRIE_FILE_EXTENSION);
+        } else if (fileType == FILETYPE_FREQUENCY) {
+            return new File(mDictDirectory,
+                    mDictDirectory.getName() + FormatSpec.FREQ_FILE_EXTENSION);
+        } else {
+            throw new RuntimeException("Unsupported kind of file : " + fileType);
+        }
+    }
+
+    @Override
+    public void openDictBuffer() throws FileNotFoundException, IOException {
+        final String filename = mDictDirectory.getName();
+        mDictBuffer = mBufferFactory.getDictionaryBuffer(getFile(FILETYPE_TRIE));
+        mFrequencyBuffer = mBufferFactory.getDictionaryBuffer(getFile(FILETYPE_FREQUENCY));
+    }
+
+    @Override
+    public boolean isDictBufferOpen() {
+        return mDictBuffer != null;
+    }
+
+    /* package */ DictBuffer getDictBuffer() {
+        return mDictBuffer;
+    }
+
+    @Override
+    public FileHeader readHeader() throws IOException, UnsupportedFormatException {
+        if (mDictBuffer == null) {
+            openDictBuffer();
+        }
+        final FileHeader header = super.readHeader(mDictBuffer);
+        final int version = header.mFormatOptions.mVersion;
+        if (version != 4) {
+            throw new UnsupportedFormatException("File header has a wrong version : " + version);
+        }
+        return header;
+    }
+
+    protected static class PtNodeReader extends DictDecoder.PtNodeReader {
+        protected static int readFrequency(final DictBuffer frequencyBuffer, final int terminalId) {
+            frequencyBuffer.position(terminalId * FormatSpec.FREQUENCY_AND_FLAGS_SIZE + 1);
+            return frequencyBuffer.readUnsignedByte();
+        }
+
+        protected static int readTerminalId(final DictBuffer dictBuffer) {
+            return dictBuffer.readInt();
+        }
+    }
+
+    // TODO: Make this buffer thread safe.
+    // TODO: Support words longer than FormatSpec.MAX_WORD_LENGTH.
+    private final int[] mCharacterBuffer = new int[FormatSpec.MAX_WORD_LENGTH];
+    @Override
+    public PtNodeInfo readPtNode(int ptNodePos, FormatOptions options) {
+        int addressPointer = ptNodePos;
+        final int flags = PtNodeReader.readPtNodeOptionFlags(mDictBuffer);
+        addressPointer += FormatSpec.PTNODE_FLAGS_SIZE;
+
+        final int parentAddress = PtNodeReader.readParentAddress(mDictBuffer, options);
+        if (BinaryDictIOUtils.supportsDynamicUpdate(options)) {
+            addressPointer += FormatSpec.PARENT_ADDRESS_SIZE;
+        }
+
+        final int characters[];
+        if (0 != (flags & FormatSpec.FLAG_HAS_MULTIPLE_CHARS)) {
+            int index = 0;
+            int character = CharEncoding.readChar(mDictBuffer);
+            addressPointer += CharEncoding.getCharSize(character);
+            while (FormatSpec.INVALID_CHARACTER != character
+                    && index < FormatSpec.MAX_WORD_LENGTH) {
+                mCharacterBuffer[index++] = character;
+                character = CharEncoding.readChar(mDictBuffer);
+                addressPointer += CharEncoding.getCharSize(character);
+            }
+            characters = Arrays.copyOfRange(mCharacterBuffer, 0, index);
+        } else {
+            final int character = CharEncoding.readChar(mDictBuffer);
+            addressPointer += CharEncoding.getCharSize(character);
+            characters = new int[] { character };
+        }
+        final int terminalId;
+        if (0 != (FormatSpec.FLAG_IS_TERMINAL & flags)) {
+            terminalId = PtNodeReader.readTerminalId(mDictBuffer);
+            addressPointer += FormatSpec.PTNODE_TERMINAL_ID_SIZE;
+        } else {
+            terminalId = PtNode.NOT_A_TERMINAL;
+        }
+
+        final int frequency;
+        if (0 != (FormatSpec.FLAG_IS_TERMINAL & flags)) {
+            frequency = PtNodeReader.readFrequency(mFrequencyBuffer, terminalId);
+        } else {
+            frequency = PtNode.NOT_A_TERMINAL;
+        }
+        int childrenAddress = PtNodeReader.readChildrenAddress(mDictBuffer, flags, options);
+        if (childrenAddress != FormatSpec.NO_CHILDREN_ADDRESS) {
+            childrenAddress += addressPointer;
+        }
+        addressPointer += BinaryDictIOUtils.getChildrenAddressSize(flags, options);
+        final ArrayList<WeightedString> shortcutTargets;
+        if (0 != (flags & FormatSpec.FLAG_HAS_SHORTCUT_TARGETS)) {
+            // readShortcut will add shortcuts to shortcutTargets.
+            shortcutTargets = new ArrayList<WeightedString>();
+            addressPointer += PtNodeReader.readShortcut(mDictBuffer, shortcutTargets);
+        } else {
+            shortcutTargets = null;
+        }
+
+        final ArrayList<PendingAttribute> bigrams;
+        if (0 != (flags & FormatSpec.FLAG_HAS_BIGRAMS)) {
+            bigrams = new ArrayList<PendingAttribute>();
+            addressPointer += PtNodeReader.readBigramAddresses(mDictBuffer, bigrams,
+                    addressPointer);
+            if (bigrams.size() >= FormatSpec.MAX_BIGRAMS_IN_A_PTNODE) {
+                MakedictLog.d("too many bigrams in a node.");
+            }
+        } else {
+            bigrams = null;
+        }
+        return new PtNodeInfo(ptNodePos, addressPointer, flags, characters, frequency,
+                parentAddress, childrenAddress, shortcutTargets, bigrams);
+    }
+
+    private void deleteDictFiles() {
+        final File[] files = mDictDirectory.listFiles();
+        for (int i = 0; i < files.length; ++i) {
+            files[i].delete();
+        }
+    }
+
+    @Override
+    public FusionDictionary readDictionaryBinary(final FusionDictionary dict,
+            final boolean deleteDictIfBroken)
+            throws FileNotFoundException, IOException, UnsupportedFormatException {
+        if (mDictBuffer == null) {
+            openDictBuffer();
+        }
+        try {
+            return BinaryDictDecoderUtils.readDictionaryBinary(this, dict);
+        } catch (IOException e) {
+            Log.e(TAG, "The dictionary " + mDictDirectory.getName() + " is broken.", e);
+            if (deleteDictIfBroken) {
+                deleteDictFiles();
+            }
+            throw e;
+        } catch (UnsupportedFormatException e) {
+            Log.e(TAG, "The dictionary " + mDictDirectory.getName() + " is broken.", e);
+            if (deleteDictIfBroken) {
+                deleteDictFiles();
+            }
+            throw e;
+        }
+    }
+
+    @Override
+    public void setPosition(int newPos) {
+        mDictBuffer.position(newPos);
+    }
+
+    @Override
+    public int getPosition() {
+        return mDictBuffer.position();
+    }
+
+    @Override
+    public int readPtNodeCount() {
+        return BinaryDictDecoderUtils.readPtNodeCount(mDictBuffer);
+    }
+
+    @Override
+    public boolean readAndFollowForwardLink() {
+        final int nextAddress = mDictBuffer.readUnsignedInt24();
+        if (nextAddress >= 0 && nextAddress < mDictBuffer.limit()) {
+            mDictBuffer.position(nextAddress);
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public boolean hasNextPtNodeArray() {
+        return mDictBuffer.position() != FormatSpec.NO_FORWARD_LINK_ADDRESS;
+    }
+}
diff --git a/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 884bcd7..9092a80 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
@@ -98,6 +98,13 @@
            flags |= FLAG_ATTRIBUTE_ADDRESS_TYPE_ONEBYTE;
            *outOffsetFieldSize = 1;
        }
+
+       // Currently, all newly written bigram position fields are 3 bytes to simplify dictionary
+       // writing.
+       // TODO: Remove following 2 lines and optimize memory space.
+       flags = (flags & (~MASK_ATTRIBUTE_ADDRESS_TYPE)) | FLAG_ATTRIBUTE_ADDRESS_TYPE_THREEBYTES;
+       *outOffsetFieldSize = 3;
+
        *outBigramFlags = flags;
        *outOffset = absOffest;
        return true;
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 4c44d22..bd58b99 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
@@ -54,8 +54,8 @@
     }
 }
 
-bool DynamicBigramListPolicy::copyAllBigrams(int *const fromPos, int *const toPos,
-        int *outBigramsCount) {
+bool DynamicBigramListPolicy::copyAllBigrams(BufferWithExtendableBuffer *const bufferToWrite,
+        int *const fromPos, int *const toPos, int *const outBigramsCount) const {
     const bool usesAdditionalBuffer = mBuffer->isInAdditionalBuffer(*fromPos);
     if (usesAdditionalBuffer) {
         *fromPos -= mBuffer->getOriginalBufferSize();
@@ -86,10 +86,10 @@
             continue;
         }
         // Write bigram entry. Target buffer is always the additional buffer.
-        if (!mBuffer->writeUintAndAdvancePosition(newBigramFlags, 1 /* size */,toPos)) {
+        if (!bufferToWrite->writeUintAndAdvancePosition(newBigramFlags, 1 /* size */,toPos)) {
             return false;
         }
-        if (!mBuffer->writeUintAndAdvancePosition(newBigramOffset, newBigramOffsetFieldSize,
+        if (!bufferToWrite->writeUintAndAdvancePosition(newBigramOffset, newBigramOffsetFieldSize,
                 toPos)) {
             return false;
         }
diff --git a/native/jni/src/suggest/policyimpl/dictionary/bigram/dynamic_bigram_list_policy.h b/native/jni/src/suggest/policyimpl/dictionary/bigram/dynamic_bigram_list_policy.h
index dafb62d..5d02d32 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/bigram/dynamic_bigram_list_policy.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/bigram/dynamic_bigram_list_policy.h
@@ -44,10 +44,11 @@
 
     void skipAllBigrams(int *const pos) const;
 
-    // Copy bigrams from the bigram list that starts at fromPos to toPos and advance these
-    // positions after bigram lists. This method skips invalid bigram entries and write the valid
-    // bigram entry count to outBigramsCount.
-    bool copyAllBigrams(int *const fromPos, int *const toPos, int *outBigramsCount);
+    // Copy bigrams from the bigram list that starts at fromPos in mBuffer to toPos in
+    // bufferToWrite and advance these positions after bigram lists. This method skips invalid
+    // bigram entries and write the valid bigram entry count to outBigramsCount.
+    bool copyAllBigrams(BufferWithExtendableBuffer *const bufferToWrite, int *const fromPos,
+            int *const toPos, int *const outBigramsCount) const;
 
     bool addNewBigramEntryToBigramList(const int bigramPos, const int probability, int *const pos);
 
diff --git a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_helper.cpp b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_helper.cpp
index 31178fb..f4c98d8 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_helper.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_helper.cpp
@@ -97,8 +97,8 @@
         return false;
     }
     int writingPos = newNodePos;
-    // Write a new PtNode using original PtNode's info to the tail of the dictionary.
-    if (!writePtNodeToBufferByCopyingPtNodeInfo(&nodeReader, nodeReader.getParentPos(),
+    // Write a new PtNode using original PtNode's info to the tail of the dictionary in mBuffer.
+    if (!writePtNodeToBufferByCopyingPtNodeInfo(mBuffer, &nodeReader, nodeReader.getParentPos(),
             mMergedNodeCodePoints, nodeReader.getCodePointCount(), nodeReader.getProbability(),
             &writingPos)) {
         return false;
@@ -143,38 +143,20 @@
     if (!headerPolicy->writeHeaderToBuffer(&headerBuffer, false /* updatesLastUpdatedTime */)) {
         return;
     }
-    const int tmpFileNameBufSize = strlen(fileName)
-            + strlen(TEMP_FILE_SUFFIX_FOR_WRITING_DICT_FILE) + 1;
-    char tmpFileName[tmpFileNameBufSize];
-    snprintf(tmpFileName, tmpFileNameBufSize, "%s%s", fileName,
-            TEMP_FILE_SUFFIX_FOR_WRITING_DICT_FILE);
-    FILE *const file = fopen(tmpFileName, "wb");
-    if (!file) {
+    flushAllToFile(fileName, &headerBuffer, mBuffer);
+}
+
+void DynamicPatriciaTrieWritingHelper::writeToDictFileWithGC(const int rootPtNodeArrayPos,
+        const char *const fileName, const HeaderPolicy *const headerPolicy) {
+    BufferWithExtendableBuffer headerBuffer(0 /* originalBuffer */, 0 /* originalBufferSize */);
+    if (!headerPolicy->writeHeaderToBuffer(&headerBuffer, true /* updatesLastUpdatedTime */)) {
         return;
     }
-    // Write header.
-    if (fwrite(headerBuffer.getBuffer(true /* usesAdditionalBuffer */),
-            headerBuffer.getTailPosition(), 1, file) < 1) {
-        fclose(file);
-        remove(tmpFileName);
+    BufferWithExtendableBuffer newDictBuffer(0 /* originalBuffer */, 0 /* originalBufferSize */);
+    if (!runGC(rootPtNodeArrayPos, &newDictBuffer)) {
         return;
     }
-    // Write data in original buffer.
-    if (fwrite(mBuffer->getBuffer(false /* usesAdditionalBuffer */),
-            mBuffer->getOriginalBufferSize(), 1, file) < 1) {
-        fclose(file);
-        remove(tmpFileName);
-        return;
-    }
-    // Write data in additional buffer.
-    if (fwrite(mBuffer->getBuffer(true /* usesAdditionalBuffer */),
-            mBuffer->getTailPosition() - mBuffer->getOriginalBufferSize(), 1, file) < 1) {
-        fclose(file);
-        remove(tmpFileName);
-        return;
-    }
-    fclose(file);
-    rename(tmpFileName, fileName);
+    flushAllToFile(fileName, &headerBuffer, &newDictBuffer);
 }
 
 bool DynamicPatriciaTrieWritingHelper::markNodeAsMovedAndSetPosition(
@@ -232,7 +214,8 @@
 }
 
 // Write new PtNode at writingPos.
-bool DynamicPatriciaTrieWritingHelper::writePtNodeWithFullInfoToBuffer(const bool isBlacklisted,
+bool DynamicPatriciaTrieWritingHelper::writePtNodeWithFullInfoToBuffer(
+        BufferWithExtendableBuffer *const bufferToWrite, const bool isBlacklisted,
         const bool isNotAWord, const int parentPos, const int *const codePoints,
         const int codePointCount, const int probability, const int childrenPos,
         const int originalBigramListPos, const int originalShortcutListPos,
@@ -240,38 +223,39 @@
     const int nodePos = *writingPos;
     // Write dummy flags. The Node flags are updated with appropriate flags at the last step of the
     // PtNode writing.
-    if (!DynamicPatriciaTrieWritingUtils::writeFlagsAndAdvancePosition(mBuffer, 0 /* nodeFlags */,
-            writingPos)) {
+    if (!DynamicPatriciaTrieWritingUtils::writeFlagsAndAdvancePosition(bufferToWrite,
+            0 /* nodeFlags */, writingPos)) {
         return false;
     }
     // Calculate a parent offset and write the offset.
     const int parentOffset = (parentPos != NOT_A_DICT_POS) ? parentPos - nodePos : NOT_A_DICT_POS;
-    if (!DynamicPatriciaTrieWritingUtils::writeParentOffsetAndAdvancePosition(mBuffer,
+    if (!DynamicPatriciaTrieWritingUtils::writeParentOffsetAndAdvancePosition(bufferToWrite,
             parentOffset, writingPos)) {
         return false;
     }
     // Write code points
-    if (!DynamicPatriciaTrieWritingUtils::writeCodePointsAndAdvancePosition(mBuffer, codePoints,
-            codePointCount, writingPos)) {
+    if (!DynamicPatriciaTrieWritingUtils::writeCodePointsAndAdvancePosition(bufferToWrite,
+            codePoints, codePointCount, writingPos)) {
         return false;
     }
     // Write probability when the probability is a valid probability, which means this node is
     // terminal.
     if (probability != NOT_A_PROBABILITY) {
-        if (!DynamicPatriciaTrieWritingUtils::writeProbabilityAndAdvancePosition(mBuffer,
+        if (!DynamicPatriciaTrieWritingUtils::writeProbabilityAndAdvancePosition(bufferToWrite,
                 probability, writingPos)) {
             return false;
         }
     }
     // Write children position
-    if (!DynamicPatriciaTrieWritingUtils::writeChildrenPositionAndAdvancePosition(mBuffer,
+    if (!DynamicPatriciaTrieWritingUtils::writeChildrenPositionAndAdvancePosition(bufferToWrite,
             childrenPos, writingPos)) {
         return false;
     }
     // Copy shortcut list when the originalShortcutListPos is valid dictionary position.
     if (originalShortcutListPos != NOT_A_DICT_POS) {
         int fromPos = originalShortcutListPos;
-        if (!mShortcutPolicy->copyAllShortcutsAndReturnIfSucceededOrNot(&fromPos, writingPos)) {
+        if (!mShortcutPolicy->copyAllShortcutsAndReturnIfSucceededOrNot(bufferToWrite, &fromPos,
+                writingPos)) {
             return false;
         }
     }
@@ -279,7 +263,7 @@
     int bigramCount = 0;
     if (originalBigramListPos != NOT_A_DICT_POS) {
         int fromPos = originalBigramListPos;
-        if (!mBigramPolicy->copyAllBigrams(&fromPos, writingPos, &bigramCount)) {
+        if (!mBigramPolicy->copyAllBigrams(bufferToWrite, &fromPos, writingPos, &bigramCount)) {
             return false;
         }
     }
@@ -291,27 +275,29 @@
                     bigramCount > 0 /* hasBigrams */, codePointCount > 1 /* hasMultipleChars */,
                     CHILDREN_POSITION_FIELD_SIZE);
     int flagsFieldPos = nodePos;
-    if (!DynamicPatriciaTrieWritingUtils::writeFlagsAndAdvancePosition(mBuffer, nodeFlags,
+    if (!DynamicPatriciaTrieWritingUtils::writeFlagsAndAdvancePosition(bufferToWrite, nodeFlags,
             &flagsFieldPos)) {
         return false;
     }
     return true;
 }
 
-bool DynamicPatriciaTrieWritingHelper::writePtNodeToBuffer(const int parentPos,
+bool DynamicPatriciaTrieWritingHelper::writePtNodeToBuffer(
+        BufferWithExtendableBuffer *const bufferToWrite, const int parentPos,
         const int *const codePoints, const int codePointCount, const int probability,
         int *const writingPos) {
-    return writePtNodeWithFullInfoToBuffer(false /* isBlacklisted */, false /* isNotAWord */,
-            parentPos, codePoints, codePointCount, probability,
+    return writePtNodeWithFullInfoToBuffer(bufferToWrite, false /* isBlacklisted */,
+            false /* isNotAWord */, parentPos, codePoints, codePointCount, probability,
             NOT_A_DICT_POS /* childrenPos */, NOT_A_DICT_POS /* originalBigramsPos */,
             NOT_A_DICT_POS /* originalShortcutPos */, writingPos);
 }
 
 bool DynamicPatriciaTrieWritingHelper::writePtNodeToBufferByCopyingPtNodeInfo(
+        BufferWithExtendableBuffer *const bufferToWrite,
         const DynamicPatriciaTrieNodeReader *const originalNode, const int parentPos,
         const int *const codePoints, const int codePointCount, const int probability,
         int *const writingPos) {
-    return writePtNodeWithFullInfoToBuffer(originalNode->isBlacklisted(),
+    return writePtNodeWithFullInfoToBuffer(bufferToWrite, originalNode->isBlacklisted(),
             originalNode->isNotAWord(), parentPos, codePoints, codePointCount, probability,
             originalNode->getChildrenPos(), originalNode->getBigramsPos(),
             originalNode->getShortcutPos(), writingPos);
@@ -345,8 +331,9 @@
         if (!markNodeAsMovedAndSetPosition(originalPtNode, movedPos, movedPos)) {
             return false;
         }
-        if (!writePtNodeToBufferByCopyingPtNodeInfo(originalPtNode, originalPtNode->getParentPos(),
-                codePoints, originalPtNode->getCodePointCount(), probability, &movedPos)) {
+        if (!writePtNodeToBufferByCopyingPtNodeInfo(mBuffer, originalPtNode,
+                originalPtNode->getParentPos(), codePoints, originalPtNode->getCodePointCount(),
+                probability, &movedPos)) {
             return false;
         }
     }
@@ -374,8 +361,8 @@
             1 /* arraySize */, &writingPos)) {
         return false;
     }
-    if (!writePtNodeToBuffer(parentPtNodePos, nodeCodePoints, nodeCodePointCount, probability,
-            &writingPos)) {
+    if (!writePtNodeToBuffer(mBuffer, parentPtNodePos, nodeCodePoints, nodeCodePointCount,
+            probability, &writingPos)) {
         return false;
     }
     if (!DynamicPatriciaTrieWritingUtils::writeForwardLinkPositionAndAdvancePosition(mBuffer,
@@ -404,8 +391,9 @@
     // Write the 1st part of the reallocating node. The children position will be updated later
     // with actual children position.
     const int newProbability = addsExtraChild ? NOT_A_PROBABILITY : probabilityOfNewPtNode;
-    if (!writePtNodeToBuffer(reallocatingPtNode->getParentPos(), reallocatingPtNodeCodePoints,
-            overlappingCodePointCount, newProbability, &writingPos)) {
+    if (!writePtNodeToBuffer(mBuffer, reallocatingPtNode->getParentPos(),
+            reallocatingPtNodeCodePoints, overlappingCodePointCount, newProbability,
+            &writingPos)) {
         return false;
     }
     const int actualChildrenPos = writingPos;
@@ -417,14 +405,15 @@
     }
     // Write the 2nd part of the reallocating node.
     const int secondPartOfReallocatedPtNodePos = writingPos;
-    if (!writePtNodeToBufferByCopyingPtNodeInfo(reallocatingPtNode, firstPartOfReallocatedPtNodePos,
+    if (!writePtNodeToBufferByCopyingPtNodeInfo(mBuffer, reallocatingPtNode,
+            firstPartOfReallocatedPtNodePos,
             reallocatingPtNodeCodePoints + overlappingCodePointCount,
             reallocatingPtNode->getCodePointCount() - overlappingCodePointCount,
             reallocatingPtNode->getProbability(), &writingPos)) {
         return false;
     }
     if (addsExtraChild) {
-        if (!writePtNodeToBuffer(firstPartOfReallocatedPtNodePos,
+        if (!writePtNodeToBuffer(mBuffer, firstPartOfReallocatedPtNodePos,
                 newNodeCodePoints + overlappingCodePointCount,
                 newNodeCodePointCount - overlappingCodePointCount, probabilityOfNewPtNode,
                 &writingPos)) {
@@ -452,4 +441,64 @@
     return true;
 }
 
+// TODO: Create a struct which contains header, body and etc... and use here as an argument.
+void DynamicPatriciaTrieWritingHelper::flushAllToFile(const char *const fileName,
+        BufferWithExtendableBuffer *const dictHeader,
+        BufferWithExtendableBuffer *const dictBody) const {
+    const int tmpFileNameBufSize = strlen(fileName)
+            + strlen(TEMP_FILE_SUFFIX_FOR_WRITING_DICT_FILE) + 1 /* terminator */;
+    // Name of a temporary file used for writing that is a connected string of original name and
+    // TEMP_FILE_SUFFIX_FOR_WRITING_DICT_FILE.
+    char tmpFileName[tmpFileNameBufSize];
+    snprintf(tmpFileName, tmpFileNameBufSize, "%s%s", fileName,
+            TEMP_FILE_SUFFIX_FOR_WRITING_DICT_FILE);
+    FILE *const file = fopen(tmpFileName, "wb");
+    if (!file) {
+        AKLOGI("Dictionary file %s cannnot be opened.", tmpFileName);
+        ASSERT(false);
+        return;
+    }
+    // Write the dictionary header.
+    if (!writeBufferToFilePointer(file, dictHeader)) {
+        remove(tmpFileName);
+        AKLOGI("Dictionary header cannnot be written. size: %d", dictHeader->getTailPosition());
+        ASSERT(false);
+        return;
+    }
+    // Write the dictionary body.
+    if (!writeBufferToFilePointer(file, dictBody)) {
+        remove(tmpFileName);
+        AKLOGI("Dictionary body cannnot be written. size: %d", dictBody->getTailPosition());
+        ASSERT(false);
+        return;
+    }
+    fclose(file);
+    rename(tmpFileName, fileName);
+}
+
+// This closes file pointer when an error is caused and returns whether the writing was succeeded
+// or not.
+bool DynamicPatriciaTrieWritingHelper::writeBufferToFilePointer(FILE *const file,
+        const BufferWithExtendableBuffer *const buffer) const {
+    const int originalBufSize = buffer->getOriginalBufferSize();
+    if (originalBufSize > 0 && fwrite(buffer->getBuffer(false /* usesAdditionalBuffer */),
+            originalBufSize, 1, file) < 1) {
+        fclose(file);
+        return false;
+    }
+    const int additionalBufSize = buffer->getTailPosition() - buffer->getOriginalBufferSize();
+    if (additionalBufSize > 0 && fwrite(buffer->getBuffer(true /* usesAdditionalBuffer */),
+            additionalBufSize, 1, file) < 1) {
+        fclose(file);
+        return false;
+    }
+    return true;
+}
+
+bool DynamicPatriciaTrieWritingHelper::runGC(const int rootPtNodeArrayPos,
+        BufferWithExtendableBuffer *const bufferToWrite) {
+    // TODO: Implement.
+    return false;
+}
+
 } // namespace latinime
diff --git a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_helper.h b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_helper.h
index 219ea98..8f78d84 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_helper.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_helper.h
@@ -17,6 +17,7 @@
 #ifndef LATINIME_DYNAMIC_PATRICIA_TRIE_WRITING_HELPER_H
 #define LATINIME_DYNAMIC_PATRICIA_TRIE_WRITING_HELPER_H
 
+#include <cstdio>
 #include <stdint.h>
 
 #include "defines.h"
@@ -51,7 +52,8 @@
 
     void writeToDictFile(const char *const fileName, const HeaderPolicy *const headerPolicy);
 
-    void writeToDictFileWithGC(const char *const fileName, const HeaderPolicy *const headerPolicy);
+    void writeToDictFileWithGC(const int rootPtNodeArrayPos, const char *const fileName,
+            const HeaderPolicy *const headerPolicy);
 
  private:
     DISALLOW_IMPLICIT_CONSTRUCTORS(DynamicPatriciaTrieWritingHelper);
@@ -66,15 +68,17 @@
     bool markNodeAsMovedAndSetPosition(const DynamicPatriciaTrieNodeReader *const nodeToUpdate,
             const int movedPos, const int bigramLinkedNodePos);
 
-    bool writePtNodeWithFullInfoToBuffer(const bool isBlacklisted, const bool isNotAWord,
+    bool writePtNodeWithFullInfoToBuffer(BufferWithExtendableBuffer *const bufferToWrite,
+            const bool isBlacklisted, const bool isNotAWord,
             const int parentPos,  const int *const codePoints, const int codePointCount,
             const int probability, const int childrenPos, const int originalBigramListPos,
             const int originalShortcutListPos, int *const writingPos);
 
-    bool writePtNodeToBuffer(const int parentPos, const int *const codePoints,
-            const int codePointCount, const int probability, int *const writingPos);
+    bool writePtNodeToBuffer(BufferWithExtendableBuffer *const bufferToWrite,
+            const int parentPos, const int *const codePoints, const int codePointCount,
+            const int probability, int *const writingPos);
 
-    bool writePtNodeToBufferByCopyingPtNodeInfo(
+    bool writePtNodeToBufferByCopyingPtNodeInfo(BufferWithExtendableBuffer *const bufferToWrite,
             const DynamicPatriciaTrieNodeReader *const originalNode, const int parentPos,
             const int *const codePoints, const int codePointCount, const int probability,
             int *const writingPos);
@@ -97,6 +101,15 @@
             const int *const reallocatingPtNodeCodePoints, const int overlappingCodePointCount,
             const int probabilityOfNewPtNode, const int *const newNodeCodePoints,
             const int newNodeCodePointCount);
+
+    void flushAllToFile(const char *const fileName,
+            BufferWithExtendableBuffer *const dictHeader,
+            BufferWithExtendableBuffer *const dictBody) const;
+
+    bool writeBufferToFilePointer(FILE *const file,
+            const BufferWithExtendableBuffer *const buffer) const;
+
+    bool runGC(const int rootPtNodeArrayPos, BufferWithExtendableBuffer *const bufferToWrite);
 };
 } // namespace latinime
 #endif /* LATINIME_DYNAMIC_PATRICIA_TRIE_WRITING_HELPER_H */
diff --git a/native/jni/src/suggest/policyimpl/dictionary/shortcut/dynamic_shortcut_list_policy.h b/native/jni/src/suggest/policyimpl/dictionary/shortcut/dynamic_shortcut_list_policy.h
index 1803c09..bd3211f 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/shortcut/dynamic_shortcut_list_policy.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/shortcut/dynamic_shortcut_list_policy.h
@@ -31,7 +31,7 @@
  */
 class DynamicShortcutListPolicy : public DictionaryShortcutsStructurePolicy {
  public:
-    explicit DynamicShortcutListPolicy(BufferWithExtendableBuffer *const buffer)
+    explicit DynamicShortcutListPolicy(const BufferWithExtendableBuffer *const buffer)
             : mBuffer(buffer) {}
 
     ~DynamicShortcutListPolicy() {}
@@ -82,18 +82,20 @@
         }
     }
 
-    // Copy shortcuts from the shortcut list that starts at fromPos to toPos and advance these
-    // positions after the shortcut lists. This returns whether the copy was succeeded or not.
-    bool copyAllShortcutsAndReturnIfSucceededOrNot(int *const fromPos, int *const toPos) {
+    // Copy shortcuts from the shortcut list that starts at fromPos in mBuffer to toPos in
+    // bufferToWrite and advance these positions after the shortcut lists. This returns whether
+    // the copy was succeeded or not.
+    bool copyAllShortcutsAndReturnIfSucceededOrNot(BufferWithExtendableBuffer *const bufferToWrite,
+            int *const fromPos, int *const toPos) const {
         const bool usesAdditionalBuffer = mBuffer->isInAdditionalBuffer(*fromPos);
-        const uint8_t *const buffer = mBuffer->getBuffer(usesAdditionalBuffer);
         if (usesAdditionalBuffer) {
             *fromPos -= mBuffer->getOriginalBufferSize();
         }
         const int shortcutListSize = ShortcutListReadingUtils
-                ::getShortcutListSizeAndForwardPointer(buffer, fromPos);
+                ::getShortcutListSizeAndForwardPointer(mBuffer->getBuffer(usesAdditionalBuffer),
+                        fromPos);
         // Copy shortcut list size.
-        if (!mBuffer->writeUintAndAdvancePosition(
+        if (!bufferToWrite->writeUintAndAdvancePosition(
                 shortcutListSize + ShortcutListReadingUtils::getShortcutListSizeFieldSize(),
                 ShortcutListReadingUtils::getShortcutListSizeFieldSize(), toPos)) {
             return false;
@@ -102,7 +104,7 @@
         for (int i = 0; i < shortcutListSize; ++i) {
             const uint8_t data = ByteArrayUtils::readUint8AndAdvancePosition(
                     mBuffer->getBuffer(usesAdditionalBuffer), fromPos);
-            if (!mBuffer->writeUintAndAdvancePosition(data, 1 /* size */, toPos)) {
+            if (!bufferToWrite->writeUintAndAdvancePosition(data, 1 /* size */, toPos)) {
                 return false;
             }
         }
@@ -115,7 +117,7 @@
  private:
     DISALLOW_IMPLICIT_CONSTRUCTORS(DynamicShortcutListPolicy);
 
-    BufferWithExtendableBuffer *const mBuffer;
+    const BufferWithExtendableBuffer *const mBuffer;
 };
 } // namespace latinime
 #endif // LATINIME_DYNAMIC_SHORTCUT_LIST_POLICY_H
diff --git a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderEncoderTests.java b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderEncoderTests.java
index 8bc0095..cedd0df 100644
--- a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderEncoderTests.java
+++ b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderEncoderTests.java
@@ -25,6 +25,7 @@
 import com.android.inputmethod.latin.makedict.BinaryDictDecoderUtils.CharEncoding;
 import com.android.inputmethod.latin.makedict.BinaryDictDecoderUtils.DictBuffer;
 import com.android.inputmethod.latin.makedict.FormatSpec.FileHeader;
+import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions;
 import com.android.inputmethod.latin.makedict.FusionDictionary.DictionaryOptions;
 import com.android.inputmethod.latin.makedict.FusionDictionary.PtNode;
 import com.android.inputmethod.latin.makedict.FusionDictionary.PtNodeArray;
@@ -75,6 +76,10 @@
             new FormatSpec.FormatOptions(3, false /* supportsDynamicUpdate */);
     private static final FormatSpec.FormatOptions VERSION3_WITH_DYNAMIC_UPDATE =
             new FormatSpec.FormatOptions(3, true /* supportsDynamicUpdate */);
+    private static final FormatSpec.FormatOptions VERSION4_WITHOUT_DYNAMIC_UPDATE =
+            new FormatSpec.FormatOptions(4, false /* supportsDynamicUpdate */);
+    private static final FormatSpec.FormatOptions VERSION4_WITH_DYNAMIC_UPDATE =
+            new FormatSpec.FormatOptions(4, true /* supportsDynamicUpdate */);
 
     private static final String TEST_DICT_FILE_EXTENSION = ".testDict";
 
@@ -114,6 +119,17 @@
         }
     }
 
+    private DictEncoder getDictEncoder(final File file, final FormatOptions formatOptions) {
+        if (formatOptions.mVersion == FormatSpec.VERSION4) {
+            return new Ver4DictEncoder(getContext().getCacheDir());
+        } else if (formatOptions.mVersion == 3 || formatOptions.mVersion == 2) {
+            return new Ver3DictEncoder(file);
+        } else {
+            throw new RuntimeException("The format option has a wrong version : "
+                    + formatOptions.mVersion);
+        }
+    }
+
     private void generateWords(final int number, final Random random, final int[] codePointSet) {
         final Set<String> wordSet = CollectionUtils.newHashSet();
         while (wordSet.size() < number) {
@@ -165,7 +181,7 @@
         long now = -1, diff = -1;
 
         try {
-            final DictEncoder dictEncoder = new Ver3DictEncoder(file);
+            final DictEncoder dictEncoder = getDictEncoder(file, formatOptions);
 
             now = System.currentTimeMillis();
             // If you need to dump the dict to a textual file, uncomment the line below and the
@@ -246,16 +262,28 @@
         return file;
     }
 
+    private DictDecoder getDictDecoder(final File file, final int bufferType,
+            final FormatOptions formatOptions, final DictionaryOptions dictOptions) {
+        if (formatOptions.mVersion == FormatSpec.VERSION4) {
+            final FileHeader header = new FileHeader(0, dictOptions, formatOptions);
+            return FormatSpec.getDictDecoder(new File(getContext().getCacheDir(),
+                    header.getId() + "." + header.getVersion()), bufferType);
+        } else {
+            return FormatSpec.getDictDecoder(file, bufferType);
+        }
+    }
     // Tests for readDictionaryBinary and writeDictionaryBinary
 
     private long timeReadingAndCheckDict(final File file, final List<String> words,
             final SparseArray<List<Integer>> bigrams,
-            final HashMap<String, List<String>> shortcutMap, final int bufferType) {
+            final HashMap<String, List<String>> shortcutMap, final int bufferType,
+            final FormatOptions formatOptions, final DictionaryOptions dictOptions) {
         long now, diff = -1;
 
         FusionDictionary dict = null;
         try {
-            final DictDecoder dictDecoder = FormatSpec.getDictDecoder(file, bufferType);
+            final DictDecoder dictDecoder = getDictDecoder(file, bufferType, formatOptions,
+                    dictOptions);
             now = System.currentTimeMillis();
             dict = dictDecoder.readDictionaryBinary(null, false /* deleteDictIfBroken */);
             diff  = System.currentTimeMillis() - now;
@@ -286,7 +314,8 @@
         checkDictionary(dict, words, bigrams, shortcuts);
 
         final long write = timeWritingDictToFile(file, dict, formatOptions);
-        final long read = timeReadingAndCheckDict(file, words, bigrams, shortcuts, bufferType);
+        final long read = timeReadingAndCheckDict(file, words, bigrams, shortcuts, bufferType,
+                formatOptions, dict.mOptions);
 
         return "PROF: read=" + read + "ms, write=" + write + "ms :" + message
                 + " : " + outputOptions(bufferType, formatOptions);
@@ -330,6 +359,8 @@
         runReadAndWriteTests(results, USE_BYTE_BUFFER, VERSION2);
         runReadAndWriteTests(results, USE_BYTE_BUFFER, VERSION3_WITHOUT_DYNAMIC_UPDATE);
         runReadAndWriteTests(results, USE_BYTE_BUFFER, VERSION3_WITH_DYNAMIC_UPDATE);
+        runReadAndWriteTests(results, USE_BYTE_BUFFER, VERSION4_WITHOUT_DYNAMIC_UPDATE);
+        runReadAndWriteTests(results, USE_BYTE_BUFFER, VERSION4_WITH_DYNAMIC_UPDATE);
 
         for (final String result : results) {
             Log.d(TAG, result);
@@ -342,6 +373,8 @@
         runReadAndWriteTests(results, USE_BYTE_ARRAY, VERSION2);
         runReadAndWriteTests(results, USE_BYTE_ARRAY, VERSION3_WITHOUT_DYNAMIC_UPDATE);
         runReadAndWriteTests(results, USE_BYTE_ARRAY, VERSION3_WITH_DYNAMIC_UPDATE);
+        runReadAndWriteTests(results, USE_BYTE_ARRAY, VERSION4_WITHOUT_DYNAMIC_UPDATE);
+        runReadAndWriteTests(results, USE_BYTE_ARRAY, VERSION4_WITH_DYNAMIC_UPDATE);
 
         for (final String result : results) {
             Log.d(TAG, result);
@@ -397,7 +430,8 @@
     }
 
     private long timeAndCheckReadUnigramsAndBigramsBinary(final File file, final List<String> words,
-            final SparseArray<List<Integer>> bigrams, final int bufferType) {
+            final SparseArray<List<Integer>> bigrams, final int bufferType,
+            final FormatOptions formatOptions, final DictionaryOptions dictOptions) {
         FileInputStream inStream = null;
 
         final TreeMap<Integer, String> resultWords = CollectionUtils.newTreeMap();
@@ -407,7 +441,8 @@
 
         long now = -1, diff = -1;
         try {
-            final DictDecoder dictDecoder = FormatSpec.getDictDecoder(file, bufferType);
+            final DictDecoder dictDecoder = getDictDecoder(file, bufferType, formatOptions,
+                    dictOptions);
             now = System.currentTimeMillis();
             dictDecoder.readUnigramsAndBigramsBinary(resultWords, resultFreqs, resultBigrams);
             diff = System.currentTimeMillis() - now;
@@ -444,9 +479,10 @@
 
         timeWritingDictToFile(file, dict, formatOptions);
 
-        long wordMap = timeAndCheckReadUnigramsAndBigramsBinary(file, words, bigrams, bufferType);
+        long wordMap = timeAndCheckReadUnigramsAndBigramsBinary(file, words, bigrams, bufferType,
+                formatOptions, dict.mOptions);
         long fullReading = timeReadingAndCheckDict(file, words, bigrams, null /* shortcutMap */,
-                bufferType);
+                bufferType, formatOptions, dict.mOptions);
 
         return "readDictionaryBinary=" + fullReading + ", readUnigramsAndBigramsBinary=" + wordMap
                 + " : " + message + " : " + outputOptions(bufferType, formatOptions);
@@ -468,6 +504,8 @@
         runReadUnigramsAndBigramsTests(results, USE_BYTE_BUFFER, VERSION2);
         runReadUnigramsAndBigramsTests(results, USE_BYTE_BUFFER, VERSION3_WITHOUT_DYNAMIC_UPDATE);
         runReadUnigramsAndBigramsTests(results, USE_BYTE_BUFFER, VERSION3_WITH_DYNAMIC_UPDATE);
+        runReadUnigramsAndBigramsTests(results, USE_BYTE_BUFFER, VERSION4_WITHOUT_DYNAMIC_UPDATE);
+        runReadUnigramsAndBigramsTests(results, USE_BYTE_BUFFER, VERSION4_WITH_DYNAMIC_UPDATE);
 
         for (final String result : results) {
             Log.d(TAG, result);
@@ -480,6 +518,8 @@
         runReadUnigramsAndBigramsTests(results, USE_BYTE_ARRAY, VERSION2);
         runReadUnigramsAndBigramsTests(results, USE_BYTE_ARRAY, VERSION3_WITHOUT_DYNAMIC_UPDATE);
         runReadUnigramsAndBigramsTests(results, USE_BYTE_ARRAY, VERSION3_WITH_DYNAMIC_UPDATE);
+        runReadUnigramsAndBigramsTests(results, USE_BYTE_ARRAY, VERSION4_WITHOUT_DYNAMIC_UPDATE);
+        runReadUnigramsAndBigramsTests(results, USE_BYTE_ARRAY, VERSION4_WITH_DYNAMIC_UPDATE);
 
         for (final String result : results) {
             Log.d(TAG, result);
@@ -503,7 +543,7 @@
                 address, fileHeader.mFormatOptions).mWord;
     }
 
-    private long runGetTerminalPosition(final DictDecoder dictDecoder, final String word,
+    private long checkGetTerminalPosition(final DictDecoder dictDecoder, final String word,
             int index, boolean contained) {
         final int expectedFrequency = (UNIGRAM_FREQ + index) % 255;
         long diff = -1;
@@ -523,7 +563,9 @@
         return diff;
     }
 
-    public void testGetTerminalPosition() {
+    private void runGetTerminalPosition(final ArrayList<String> words,
+            final SparseArray<List<Integer>> bigrams, final int bufferType,
+            final FormatOptions formatOptions, final String message) {
         final String dictName = "testGetTerminalPosition";
         final String dictVersion = Long.toString(System.currentTimeMillis());
         final File file = setUpDictionaryFile(dictName, dictVersion);
@@ -531,16 +573,18 @@
         final FusionDictionary dict = new FusionDictionary(new PtNodeArray(),
                 getDictionaryOptions(dictName, dictVersion));
         addUnigrams(sWords.size(), dict, sWords, null /* shortcutMap */);
-        timeWritingDictToFile(file, dict, VERSION3_WITH_DYNAMIC_UPDATE);
+        addBigrams(dict, words, bigrams);
+        timeWritingDictToFile(file, dict, formatOptions);
 
-        final DictDecoder dictDecoder = FormatSpec.getDictDecoder(file, DictDecoder.USE_BYTEARRAY);
+        final DictDecoder dictDecoder = getDictDecoder(file, DictDecoder.USE_BYTEARRAY,
+                formatOptions, dict.mOptions);
         try {
             dictDecoder.openDictBuffer();
         } catch (IOException e) {
             // ignore
             Log.e(TAG, "IOException while opening the buffer", e);
         }
-        assertTrue("Can't get the buffer", dictDecoder.isOpenedDictBuffer());
+        assertTrue("Can't get the buffer", dictDecoder.isDictBufferOpen());
 
         try {
             // too long word
@@ -559,10 +603,11 @@
         // Test a word that is contained within the dictionary.
         long sum = 0;
         for (int i = 0; i < sWords.size(); ++i) {
-            final long time = runGetTerminalPosition(dictDecoder, sWords.get(i), i, true);
+            final long time = checkGetTerminalPosition(dictDecoder, sWords.get(i), i, true);
             sum += time == -1 ? 0 : time;
         }
-        Log.d(TAG, "per a search : " + (((double)sum) / sWords.size() / 1000000));
+        Log.d(TAG, "per search : " + (((double)sum) / sWords.size() / 1000000) + " : " + message
+                + " : " + outputOptions(bufferType, formatOptions));
 
         // Test a word that isn't contained within the dictionary.
         final Random random = new Random((int)System.currentTimeMillis());
@@ -571,7 +616,32 @@
         for (int i = 0; i < 1000; ++i) {
             final String word = CodePointUtils.generateWord(random, codePointSet);
             if (sWords.indexOf(word) != -1) continue;
-            runGetTerminalPosition(dictDecoder, word, i, false);
+            checkGetTerminalPosition(dictDecoder, word, i, false);
+        }
+    }
+
+    private void runGetTerminalPositionTests(final ArrayList<String> results, final int bufferType,
+            final FormatOptions formatOptions) {
+        runGetTerminalPosition(sWords, sEmptyBigrams, bufferType, formatOptions, "unigram");
+    }
+
+    public void testGetTerminalPosition() {
+        final ArrayList<String> results = CollectionUtils.newArrayList();
+
+        runGetTerminalPositionTests(results, USE_BYTE_ARRAY, VERSION2);
+        runGetTerminalPositionTests(results, USE_BYTE_ARRAY, VERSION3_WITHOUT_DYNAMIC_UPDATE);
+        runGetTerminalPositionTests(results, USE_BYTE_ARRAY, VERSION3_WITH_DYNAMIC_UPDATE);
+        runGetTerminalPositionTests(results, USE_BYTE_ARRAY, VERSION4_WITHOUT_DYNAMIC_UPDATE);
+        runGetTerminalPositionTests(results, USE_BYTE_ARRAY, VERSION4_WITH_DYNAMIC_UPDATE);
+
+        runGetTerminalPositionTests(results, USE_BYTE_BUFFER, VERSION2);
+        runGetTerminalPositionTests(results, USE_BYTE_BUFFER, VERSION3_WITHOUT_DYNAMIC_UPDATE);
+        runGetTerminalPositionTests(results, USE_BYTE_BUFFER, VERSION3_WITH_DYNAMIC_UPDATE);
+        runGetTerminalPositionTests(results, USE_BYTE_BUFFER, VERSION4_WITHOUT_DYNAMIC_UPDATE);
+        runGetTerminalPositionTests(results, USE_BYTE_BUFFER, VERSION4_WITH_DYNAMIC_UPDATE);
+
+        for (final String result : results) {
+            Log.d(TAG, result);
         }
     }
 
@@ -593,7 +663,7 @@
             // ignore
             Log.e(TAG, "IOException while opening the buffer", e);
         }
-        assertTrue("Can't get the buffer", dictDecoder.isOpenedDictBuffer());
+        assertTrue("Can't get the buffer", dictDecoder.isDictBufferOpen());
 
         try {
             MoreAsserts.assertNotEqual(FormatSpec.NOT_VALID_WORD,
diff --git a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/DictionaryMaker.java b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/DictionaryMaker.java
index 5302e97..5c7e8b4 100644
--- a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/DictionaryMaker.java
+++ b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/DictionaryMaker.java
@@ -23,8 +23,8 @@
 import com.android.inputmethod.latin.makedict.FusionDictionary;
 import com.android.inputmethod.latin.makedict.MakedictLog;
 import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
-import com.android.inputmethod.latin.makedict.Ver3DictDecoder;
 import com.android.inputmethod.latin.makedict.Ver3DictEncoder;
+import com.android.inputmethod.latin.makedict.Ver4DictEncoder;
 
 import java.io.BufferedWriter;
 import java.io.File;
@@ -45,9 +45,9 @@
 public class DictionaryMaker {
 
     static class Arguments {
-        private static final String OPTION_VERSION_1 = "-1";
         private static final String OPTION_VERSION_2 = "-2";
         private static final String OPTION_VERSION_3 = "-3";
+        private static final String OPTION_VERSION_4 = "-4";
         private static final String OPTION_INPUT_SOURCE = "-s";
         private static final String OPTION_INPUT_BIGRAM_XML = "-b";
         private static final String OPTION_INPUT_SHORTCUT_XML = "-c";
@@ -128,12 +128,12 @@
                     + "| [-s <combined format input]"
                     + "| [-s <binary input>] [-d <binary output>] [-x <xml output>] "
                     + " [-o <combined output>]"
-                    + "[-1] [-2] [-3]\n"
+                    + "[-2] [-3] [-4]\n"
                     + "\n"
                     + "  Converts a source dictionary file to one or several outputs.\n"
                     + "  Source can be an XML file, with an optional XML bigrams file, or a\n"
                     + "  binary dictionary file.\n"
-                    + "  Binary version 1 (Ice Cream Sandwich), 2 (Jelly Bean), 3, XML and\n"
+                    + "  Binary version 2 (Jelly Bean), 3, 4, XML and\n"
                     + "  combined format outputs are supported.";
         }
 
@@ -160,8 +160,8 @@
                         // Do nothing, this is the default
                     } else if (OPTION_VERSION_3.equals(arg)) {
                         outputBinaryFormatVersion = 3;
-                    } else if (OPTION_VERSION_1.equals(arg)) {
-                        outputBinaryFormatVersion = 1;
+                    } else if (OPTION_VERSION_4.equals(arg)) {
+                        outputBinaryFormatVersion = 4;
                     } else if (OPTION_HELP.equals(arg)) {
                         displayHelp();
                     } else {
@@ -357,7 +357,12 @@
             throws FileNotFoundException, IOException, UnsupportedFormatException {
         final File outputFile = new File(outputFilename);
         final FormatSpec.FormatOptions formatOptions = new FormatSpec.FormatOptions(version);
-        final DictEncoder dictEncoder = new Ver3DictEncoder(outputFile);
+        final DictEncoder dictEncoder;
+        if (version == 4) {
+            dictEncoder = new Ver4DictEncoder(outputFile);
+        } else {
+            dictEncoder = new Ver3DictEncoder(outputFile);
+        }
         dictEncoder.writeDictionary(dict, formatOptions);
     }
 
diff --git a/tools/make-keyboard-text/res/values-km/donottranslate-more-keys.xml b/tools/make-keyboard-text/res/values-km/donottranslate-more-keys.xml
new file mode 100644
index 0000000..c33831c
--- /dev/null
+++ b/tools/make-keyboard-text/res/values-km/donottranslate-more-keys.xml
@@ -0,0 +1,29 @@
+<?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+1780: "ក" KHMER LETTER KA
+         U+1781: "ខ" KHMER LETTER KHA
+         U+1782: "គ" KHMER LETTER KO -->
+    <string name="label_to_alpha_key">&#x1780;&#x1781;&#x1782;</string>
+    <!-- U+17DB: "៛" KHMER CURRENCY SYMBOL RIEL -->
+    <string name="more_keys_for_currency_dollar">&#x17DB;,&#x00A2;,&#x00A3;,&#x20AC;,&#x00A5;,&#x20B1;</string>
+
+</resources>