keep history after reset to jb-ub-latinimegoogle-bayo
diff --git a/dictionaries/fr_wordlist.combined.gz b/dictionaries/fr_wordlist.combined.gz
index 0763b62..1d988d6 100644
--- a/dictionaries/fr_wordlist.combined.gz
+++ b/dictionaries/fr_wordlist.combined.gz
Binary files differ
diff --git a/dictionaries/iw_wordlist.combined.gz b/dictionaries/iw_wordlist.combined.gz
new file mode 100644
index 0000000..36b0478
--- /dev/null
+++ b/dictionaries/iw_wordlist.combined.gz
Binary files differ
diff --git a/dictionaries/pt_BR_wordlist.combined.gz b/dictionaries/pt_BR_wordlist.combined.gz
index 0dd8472..221ea75 100644
--- a/dictionaries/pt_BR_wordlist.combined.gz
+++ b/dictionaries/pt_BR_wordlist.combined.gz
Binary files differ
diff --git a/dictionaries/pt_PT_wordlist.combined.gz b/dictionaries/pt_PT_wordlist.combined.gz
index 00d50d0..6a041d9 100644
--- a/dictionaries/pt_PT_wordlist.combined.gz
+++ b/dictionaries/pt_PT_wordlist.combined.gz
Binary files differ
diff --git a/dictionaries/ru_wordlist.combined.gz b/dictionaries/ru_wordlist.combined.gz
index 1c85d66..572314d 100644
--- a/dictionaries/ru_wordlist.combined.gz
+++ b/dictionaries/ru_wordlist.combined.gz
Binary files differ
diff --git a/java/res/color/emoji_tab_label_color_gb.xml b/java/res/color/emoji_tab_label_color_gb.xml
new file mode 100644
index 0000000..e1d2f71
--- /dev/null
+++ b/java/res/color/emoji_tab_label_color_gb.xml
@@ -0,0 +1,33 @@
+<?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.
+*/
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item
+        android:state_focused="true"
+        android:color="@color/key_text_color_gb" />
+    <item
+        android:state_pressed="true"
+        android:color="@color/key_text_color_gb" />
+    <item
+        android:state_selected="true"
+        android:color="@color/key_text_color_gb" />
+    <item
+        android:color="@color/key_text_inactivated_color_gb" />
+</selector>
diff --git a/java/res/color/emoji_tab_label_color_ics.xml b/java/res/color/emoji_tab_label_color_ics.xml
new file mode 100644
index 0000000..36e1d30
--- /dev/null
+++ b/java/res/color/emoji_tab_label_color_ics.xml
@@ -0,0 +1,33 @@
+<?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.
+*/
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item
+        android:state_focused="true"
+        android:color="@color/key_text_color_ics" />
+    <item
+        android:state_pressed="true"
+        android:color="@color/key_text_color_ics" />
+    <item
+        android:state_selected="true"
+        android:color="@color/key_text_color_ics" />
+    <item
+        android:color="@color/key_text_inactivated_color_ics" />
+</selector>
diff --git a/java/res/drawable-hdpi/btn_center_default.9.png b/java/res/drawable-hdpi/btn_center_default.9.png
deleted file mode 100644
index 4f5f01c..0000000
--- a/java/res/drawable-hdpi/btn_center_default.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-hdpi/btn_center_pressed.9.png b/java/res/drawable-hdpi/btn_center_pressed.9.png
deleted file mode 100644
index 213b482..0000000
--- a/java/res/drawable-hdpi/btn_center_pressed.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-hdpi/btn_center_selected.9.png b/java/res/drawable-hdpi/btn_center_selected.9.png
deleted file mode 100644
index 213b482..0000000
--- a/java/res/drawable-hdpi/btn_center_selected.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-hdpi/btn_keyboard_key_dark_active_holo.9.png b/java/res/drawable-hdpi/btn_keyboard_key_dark_active_holo.9.png
index 9aa8db6..87211a5 100644
--- a/java/res/drawable-hdpi/btn_keyboard_key_dark_active_holo.9.png
+++ b/java/res/drawable-hdpi/btn_keyboard_key_dark_active_holo.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/btn_keyboard_key_dark_normal_holo.9.png b/java/res/drawable-hdpi/btn_keyboard_key_dark_normal_holo.9.png
index 5e6a9d6..fa2cb85 100644
--- a/java/res/drawable-hdpi/btn_keyboard_key_dark_normal_holo.9.png
+++ b/java/res/drawable-hdpi/btn_keyboard_key_dark_normal_holo.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/btn_keyboard_key_dark_normal_off_holo.9.png b/java/res/drawable-hdpi/btn_keyboard_key_dark_normal_off_holo.9.png
index a3ba223..b1af23b 100644
--- a/java/res/drawable-hdpi/btn_keyboard_key_dark_normal_off_holo.9.png
+++ b/java/res/drawable-hdpi/btn_keyboard_key_dark_normal_off_holo.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/btn_keyboard_key_dark_normal_on_holo.9.png b/java/res/drawable-hdpi/btn_keyboard_key_dark_normal_on_holo.9.png
index 9f4587b..814e402 100644
--- a/java/res/drawable-hdpi/btn_keyboard_key_dark_normal_on_holo.9.png
+++ b/java/res/drawable-hdpi/btn_keyboard_key_dark_normal_on_holo.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/btn_keyboard_key_dark_pressed_holo.9.png b/java/res/drawable-hdpi/btn_keyboard_key_dark_pressed_holo.9.png
index 7ec33dd..90abe39 100644
--- a/java/res/drawable-hdpi/btn_keyboard_key_dark_pressed_holo.9.png
+++ b/java/res/drawable-hdpi/btn_keyboard_key_dark_pressed_holo.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/btn_keyboard_key_dark_pressed_off_holo.9.png b/java/res/drawable-hdpi/btn_keyboard_key_dark_pressed_off_holo.9.png
index 655bc01..48eeb3f 100644
--- a/java/res/drawable-hdpi/btn_keyboard_key_dark_pressed_off_holo.9.png
+++ b/java/res/drawable-hdpi/btn_keyboard_key_dark_pressed_off_holo.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/btn_keyboard_key_dark_pressed_on_holo.9.png b/java/res/drawable-hdpi/btn_keyboard_key_dark_pressed_on_holo.9.png
index 138e915..71e0683 100644
--- a/java/res/drawable-hdpi/btn_keyboard_key_dark_pressed_on_holo.9.png
+++ b/java/res/drawable-hdpi/btn_keyboard_key_dark_pressed_on_holo.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/btn_keyboard_key_fulltrans_normal.9.png b/java/res/drawable-hdpi/btn_keyboard_key_fulltrans_normal.9.png
deleted file mode 100644
index 1163290..0000000
--- a/java/res/drawable-hdpi/btn_keyboard_key_fulltrans_normal.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-hdpi/btn_keyboard_key_fulltrans_pressed.9.png b/java/res/drawable-hdpi/btn_keyboard_key_fulltrans_pressed.9.png
deleted file mode 100644
index 207c90d..0000000
--- a/java/res/drawable-hdpi/btn_keyboard_key_fulltrans_pressed.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-hdpi/btn_keyboard_key_light_normal_holo.9.png b/java/res/drawable-hdpi/btn_keyboard_key_light_normal_holo.9.png
index baff858..6da273b 100644
--- a/java/res/drawable-hdpi/btn_keyboard_key_light_normal_holo.9.png
+++ b/java/res/drawable-hdpi/btn_keyboard_key_light_normal_holo.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/btn_keyboard_key_light_pressed_holo.9.png b/java/res/drawable-hdpi/btn_keyboard_key_light_pressed_holo.9.png
index 5612c51..6768241 100644
--- a/java/res/drawable-hdpi/btn_keyboard_key_light_pressed_holo.9.png
+++ b/java/res/drawable-hdpi/btn_keyboard_key_light_pressed_holo.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/btn_keyboard_key_normal_off_stone.9.png b/java/res/drawable-hdpi/btn_keyboard_key_normal_off_stone.9.png
deleted file mode 100644
index cdd6c8b..0000000
--- a/java/res/drawable-hdpi/btn_keyboard_key_normal_off_stone.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-hdpi/btn_keyboard_key_normal_on_stone.9.png b/java/res/drawable-hdpi/btn_keyboard_key_normal_on_stone.9.png
deleted file mode 100644
index d842174..0000000
--- a/java/res/drawable-hdpi/btn_keyboard_key_normal_on_stone.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-hdpi/btn_keyboard_key_normal_stone.9.png b/java/res/drawable-hdpi/btn_keyboard_key_normal_stone.9.png
deleted file mode 100644
index 671d4e5..0000000
--- a/java/res/drawable-hdpi/btn_keyboard_key_normal_stone.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-hdpi/btn_keyboard_key_popup_selected_holo.9.png b/java/res/drawable-hdpi/btn_keyboard_key_popup_selected_holo.9.png
index c2e8b37..10f8e97 100644
--- a/java/res/drawable-hdpi/btn_keyboard_key_popup_selected_holo.9.png
+++ b/java/res/drawable-hdpi/btn_keyboard_key_popup_selected_holo.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/ic_emoji_dark.png b/java/res/drawable-hdpi/ic_emoji_dark.png
deleted file mode 100644
index a9f18cd..0000000
--- a/java/res/drawable-hdpi/ic_emoji_dark.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-hdpi/ic_emoji_nature_light_activated.png b/java/res/drawable-hdpi/ic_emoji_nature_light_activated.png
new file mode 100644
index 0000000..5525df2
--- /dev/null
+++ b/java/res/drawable-hdpi/ic_emoji_nature_light_activated.png
Binary files differ
diff --git a/java/res/drawable-hdpi/ic_emoji_nature_light_normal.png b/java/res/drawable-hdpi/ic_emoji_nature_light_normal.png
new file mode 100644
index 0000000..34e16b9
--- /dev/null
+++ b/java/res/drawable-hdpi/ic_emoji_nature_light_normal.png
Binary files differ
diff --git a/java/res/drawable-hdpi/ic_emoji_objects_light_activated.png b/java/res/drawable-hdpi/ic_emoji_objects_light_activated.png
new file mode 100644
index 0000000..c3c7ec1
--- /dev/null
+++ b/java/res/drawable-hdpi/ic_emoji_objects_light_activated.png
Binary files differ
diff --git a/java/res/drawable-hdpi/ic_emoji_objects_light_normal.png b/java/res/drawable-hdpi/ic_emoji_objects_light_normal.png
new file mode 100644
index 0000000..f012d77
--- /dev/null
+++ b/java/res/drawable-hdpi/ic_emoji_objects_light_normal.png
Binary files differ
diff --git a/java/res/drawable-hdpi/ic_emoji_people_light_activated.png b/java/res/drawable-hdpi/ic_emoji_people_light_activated.png
new file mode 100644
index 0000000..cfacbc2
--- /dev/null
+++ b/java/res/drawable-hdpi/ic_emoji_people_light_activated.png
Binary files differ
diff --git a/java/res/drawable-hdpi/ic_emoji_light.png b/java/res/drawable-hdpi/ic_emoji_people_light_normal.png
similarity index 92%
rename from java/res/drawable-hdpi/ic_emoji_light.png
rename to java/res/drawable-hdpi/ic_emoji_people_light_normal.png
index 2e3638b..c54dbc1 100644
--- a/java/res/drawable-hdpi/ic_emoji_light.png
+++ b/java/res/drawable-hdpi/ic_emoji_people_light_normal.png
Binary files differ
diff --git a/java/res/drawable-hdpi/ic_emoji_places_light_activated.png b/java/res/drawable-hdpi/ic_emoji_places_light_activated.png
new file mode 100644
index 0000000..959dfdf
--- /dev/null
+++ b/java/res/drawable-hdpi/ic_emoji_places_light_activated.png
Binary files differ
diff --git a/java/res/drawable-hdpi/ic_emoji_places_light_normal.png b/java/res/drawable-hdpi/ic_emoji_places_light_normal.png
new file mode 100644
index 0000000..fc0d971
--- /dev/null
+++ b/java/res/drawable-hdpi/ic_emoji_places_light_normal.png
Binary files differ
diff --git a/java/res/drawable-hdpi/ic_emoji_recent_light_activated.png b/java/res/drawable-hdpi/ic_emoji_recent_light_activated.png
new file mode 100644
index 0000000..de570a1
--- /dev/null
+++ b/java/res/drawable-hdpi/ic_emoji_recent_light_activated.png
Binary files differ
diff --git a/java/res/drawable-hdpi/ic_emoji_recent_light_normal.png b/java/res/drawable-hdpi/ic_emoji_recent_light_normal.png
new file mode 100644
index 0000000..b256208
--- /dev/null
+++ b/java/res/drawable-hdpi/ic_emoji_recent_light_normal.png
Binary files differ
diff --git a/java/res/drawable-hdpi/ic_emoji_symbols_light_activated.png b/java/res/drawable-hdpi/ic_emoji_symbols_light_activated.png
new file mode 100644
index 0000000..af1fd27
--- /dev/null
+++ b/java/res/drawable-hdpi/ic_emoji_symbols_light_activated.png
Binary files differ
diff --git a/java/res/drawable-hdpi/ic_emoji_symbols_light_normal.png b/java/res/drawable-hdpi/ic_emoji_symbols_light_normal.png
new file mode 100644
index 0000000..02b84d5
--- /dev/null
+++ b/java/res/drawable-hdpi/ic_emoji_symbols_light_normal.png
Binary files differ
diff --git a/java/res/drawable-hdpi/ic_ime_switcher_dark.png b/java/res/drawable-hdpi/ic_ime_switcher_dark.png
new file mode 100644
index 0000000..7506af5
--- /dev/null
+++ b/java/res/drawable-hdpi/ic_ime_switcher_dark.png
Binary files differ
diff --git a/java/res/drawable-hdpi/ic_settings_language.png b/java/res/drawable-hdpi/ic_settings_language.png
deleted file mode 100644
index f635b2e..0000000
--- a/java/res/drawable-hdpi/ic_settings_language.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-hdpi/ic_subtype_keyboard.png b/java/res/drawable-hdpi/ic_subtype_keyboard.png
deleted file mode 100644
index 4843056..0000000
--- a/java/res/drawable-hdpi/ic_subtype_keyboard.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-hdpi/ic_subtype_mic_dark.png b/java/res/drawable-hdpi/ic_subtype_mic_dark.png
new file mode 100644
index 0000000..eacbcd2
--- /dev/null
+++ b/java/res/drawable-hdpi/ic_subtype_mic_dark.png
Binary files differ
diff --git a/java/res/drawable-hdpi/keyboard_background.9.png b/java/res/drawable-hdpi/keyboard_background.9.png
deleted file mode 100644
index d57463f..0000000
--- a/java/res/drawable-hdpi/keyboard_background.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-hdpi/keyboard_dark_background.9.png b/java/res/drawable-hdpi/keyboard_background_gb.9.png
similarity index 100%
rename from java/res/drawable-hdpi/keyboard_dark_background.9.png
rename to java/res/drawable-hdpi/keyboard_background_gb.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/keyboard_key_feedback_background_holo.9.png b/java/res/drawable-hdpi/keyboard_key_feedback_background_holo.9.png
index 28b406a..50ed568 100644
--- a/java/res/drawable-hdpi/keyboard_key_feedback_background_holo.9.png
+++ b/java/res/drawable-hdpi/keyboard_key_feedback_background_holo.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/keyboard_key_feedback_left_background_holo.9.png b/java/res/drawable-hdpi/keyboard_key_feedback_left_background_holo.9.png
index e42cd88..9fa6d00 100644
--- a/java/res/drawable-hdpi/keyboard_key_feedback_left_background_holo.9.png
+++ b/java/res/drawable-hdpi/keyboard_key_feedback_left_background_holo.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/keyboard_key_feedback_left_more_background_holo.9.png b/java/res/drawable-hdpi/keyboard_key_feedback_left_more_background_holo.9.png
index 1603440..c73269b 100644
--- a/java/res/drawable-hdpi/keyboard_key_feedback_left_more_background_holo.9.png
+++ b/java/res/drawable-hdpi/keyboard_key_feedback_left_more_background_holo.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/keyboard_key_feedback_more_background_holo.9.png b/java/res/drawable-hdpi/keyboard_key_feedback_more_background_holo.9.png
index a40d427..fffd402 100644
--- a/java/res/drawable-hdpi/keyboard_key_feedback_more_background_holo.9.png
+++ b/java/res/drawable-hdpi/keyboard_key_feedback_more_background_holo.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/keyboard_key_feedback_right_background_holo.9.png b/java/res/drawable-hdpi/keyboard_key_feedback_right_background_holo.9.png
index 1f68073..61c23c1 100644
--- a/java/res/drawable-hdpi/keyboard_key_feedback_right_background_holo.9.png
+++ b/java/res/drawable-hdpi/keyboard_key_feedback_right_background_holo.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/keyboard_key_feedback_right_more_background_holo.9.png b/java/res/drawable-hdpi/keyboard_key_feedback_right_more_background_holo.9.png
index ec53593..827d743 100644
--- a/java/res/drawable-hdpi/keyboard_key_feedback_right_more_background_holo.9.png
+++ b/java/res/drawable-hdpi/keyboard_key_feedback_right_more_background_holo.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/keyboard_popup_panel_background.9.png b/java/res/drawable-hdpi/keyboard_popup_panel_background_gb.9.png
similarity index 100%
rename from java/res/drawable-hdpi/keyboard_popup_panel_background.9.png
rename to java/res/drawable-hdpi/keyboard_popup_panel_background_gb.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/keyboard_popup_panel_background_holo.9.png b/java/res/drawable-hdpi/keyboard_popup_panel_background_holo.9.png
index 53d7b6f..dc2fc7d 100644
--- a/java/res/drawable-hdpi/keyboard_popup_panel_background_holo.9.png
+++ b/java/res/drawable-hdpi/keyboard_popup_panel_background_holo.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/keyboard_suggest_strip.9.png b/java/res/drawable-hdpi/keyboard_suggest_strip_gb.9.png
similarity index 100%
rename from java/res/drawable-hdpi/keyboard_suggest_strip.9.png
rename to java/res/drawable-hdpi/keyboard_suggest_strip_gb.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/keyboard_suggest_strip_holo.9.png b/java/res/drawable-hdpi/keyboard_suggest_strip_holo.9.png
index e173beb..32f4264 100644
--- a/java/res/drawable-hdpi/keyboard_suggest_strip_holo.9.png
+++ b/java/res/drawable-hdpi/keyboard_suggest_strip_holo.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_bkeyboard_delete.png b/java/res/drawable-hdpi/sym_bkeyboard_delete.png
deleted file mode 100644
index 1d24cc8..0000000
--- a/java/res/drawable-hdpi/sym_bkeyboard_delete.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_bkeyboard_label_mic.png b/java/res/drawable-hdpi/sym_bkeyboard_label_mic.png
deleted file mode 100644
index 25702cf..0000000
--- a/java/res/drawable-hdpi/sym_bkeyboard_label_mic.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_bkeyboard_mic.png b/java/res/drawable-hdpi/sym_bkeyboard_mic.png
deleted file mode 100644
index 512f460..0000000
--- a/java/res/drawable-hdpi/sym_bkeyboard_mic.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_bkeyboard_return.png b/java/res/drawable-hdpi/sym_bkeyboard_return.png
deleted file mode 100644
index 426e159..0000000
--- a/java/res/drawable-hdpi/sym_bkeyboard_return.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_bkeyboard_search.png b/java/res/drawable-hdpi/sym_bkeyboard_search.png
deleted file mode 100644
index 1b6f884..0000000
--- a/java/res/drawable-hdpi/sym_bkeyboard_search.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_bkeyboard_settings.png b/java/res/drawable-hdpi/sym_bkeyboard_settings.png
deleted file mode 100644
index 08ba18f..0000000
--- a/java/res/drawable-hdpi/sym_bkeyboard_settings.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_bkeyboard_shift.png b/java/res/drawable-hdpi/sym_bkeyboard_shift.png
deleted file mode 100644
index 5a22dd3..0000000
--- a/java/res/drawable-hdpi/sym_bkeyboard_shift.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_bkeyboard_shift_locked.png b/java/res/drawable-hdpi/sym_bkeyboard_shift_locked.png
deleted file mode 100644
index 5664491..0000000
--- a/java/res/drawable-hdpi/sym_bkeyboard_shift_locked.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_bkeyboard_space.png b/java/res/drawable-hdpi/sym_bkeyboard_space.png
deleted file mode 100644
index cd0ebe2..0000000
--- a/java/res/drawable-hdpi/sym_bkeyboard_space.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_bkeyboard_tab.png b/java/res/drawable-hdpi/sym_bkeyboard_tab.png
deleted file mode 100644
index 3466e12..0000000
--- a/java/res/drawable-hdpi/sym_bkeyboard_tab.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_bkeyboard_voice_off.png b/java/res/drawable-hdpi/sym_bkeyboard_voice_off.png
deleted file mode 100644
index 081a130..0000000
--- a/java/res/drawable-hdpi/sym_bkeyboard_voice_off.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_delete.png b/java/res/drawable-hdpi/sym_keyboard_delete.png
deleted file mode 100644
index 0591b82..0000000
--- a/java/res/drawable-hdpi/sym_keyboard_delete.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_delete_holo.png b/java/res/drawable-hdpi/sym_keyboard_delete_holo.png
deleted file mode 100644
index d3e1088..0000000
--- a/java/res/drawable-hdpi/sym_keyboard_delete_holo.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_delete_holo_dark.png b/java/res/drawable-hdpi/sym_keyboard_delete_holo_dark.png
new file mode 100644
index 0000000..d2d3560
--- /dev/null
+++ b/java/res/drawable-hdpi/sym_keyboard_delete_holo_dark.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_label_mic.png b/java/res/drawable-hdpi/sym_keyboard_label_mic.png
deleted file mode 100644
index 4e0a8ed..0000000
--- a/java/res/drawable-hdpi/sym_keyboard_label_mic.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_label_mic_holo.png b/java/res/drawable-hdpi/sym_keyboard_label_mic_holo.png
deleted file mode 100644
index f8df447..0000000
--- a/java/res/drawable-hdpi/sym_keyboard_label_mic_holo.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_language_switch.png b/java/res/drawable-hdpi/sym_keyboard_language_switch.png
deleted file mode 100644
index 7b980a0..0000000
--- a/java/res/drawable-hdpi/sym_keyboard_language_switch.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_language_switch_dark.png b/java/res/drawable-hdpi/sym_keyboard_language_switch_dark.png
new file mode 100644
index 0000000..78d3a1f
--- /dev/null
+++ b/java/res/drawable-hdpi/sym_keyboard_language_switch_dark.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_mic.png b/java/res/drawable-hdpi/sym_keyboard_mic.png
deleted file mode 100644
index 520a40f..0000000
--- a/java/res/drawable-hdpi/sym_keyboard_mic.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_mic_holo_dark.png b/java/res/drawable-hdpi/sym_keyboard_mic_holo_dark.png
new file mode 100644
index 0000000..3c54694
--- /dev/null
+++ b/java/res/drawable-hdpi/sym_keyboard_mic_holo_dark.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_return.png b/java/res/drawable-hdpi/sym_keyboard_return.png
deleted file mode 100644
index 9743c7f..0000000
--- a/java/res/drawable-hdpi/sym_keyboard_return.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_return_holo.png b/java/res/drawable-hdpi/sym_keyboard_return_holo.png
deleted file mode 100644
index 8978934..0000000
--- a/java/res/drawable-hdpi/sym_keyboard_return_holo.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_return_holo_dark.png b/java/res/drawable-hdpi/sym_keyboard_return_holo_dark.png
new file mode 100644
index 0000000..60d893c
--- /dev/null
+++ b/java/res/drawable-hdpi/sym_keyboard_return_holo_dark.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_search.png b/java/res/drawable-hdpi/sym_keyboard_search.png
deleted file mode 100644
index 8cd28c6..0000000
--- a/java/res/drawable-hdpi/sym_keyboard_search.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_search_holo.png b/java/res/drawable-hdpi/sym_keyboard_search_holo.png
deleted file mode 100644
index b987a20..0000000
--- a/java/res/drawable-hdpi/sym_keyboard_search_holo.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_search_holo_dark.png b/java/res/drawable-hdpi/sym_keyboard_search_holo_dark.png
new file mode 100644
index 0000000..fa0d1bd
--- /dev/null
+++ b/java/res/drawable-hdpi/sym_keyboard_search_holo_dark.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_settings.png b/java/res/drawable-hdpi/sym_keyboard_settings.png
deleted file mode 100644
index 1e5bf93..0000000
--- a/java/res/drawable-hdpi/sym_keyboard_settings.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_settings_holo.png b/java/res/drawable-hdpi/sym_keyboard_settings_holo.png
deleted file mode 100644
index 5af09ad..0000000
--- a/java/res/drawable-hdpi/sym_keyboard_settings_holo.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_settings_holo_dark.png b/java/res/drawable-hdpi/sym_keyboard_settings_holo_dark.png
new file mode 100644
index 0000000..c76008a
--- /dev/null
+++ b/java/res/drawable-hdpi/sym_keyboard_settings_holo_dark.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_shift.png b/java/res/drawable-hdpi/sym_keyboard_shift.png
deleted file mode 100644
index 8e3d032..0000000
--- a/java/res/drawable-hdpi/sym_keyboard_shift.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_shift_holo.png b/java/res/drawable-hdpi/sym_keyboard_shift_holo.png
deleted file mode 100644
index c58f9ab..0000000
--- a/java/res/drawable-hdpi/sym_keyboard_shift_holo.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_shift_holo_dark.png b/java/res/drawable-hdpi/sym_keyboard_shift_holo_dark.png
new file mode 100644
index 0000000..544b7e1
--- /dev/null
+++ b/java/res/drawable-hdpi/sym_keyboard_shift_holo_dark.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_shift_locked.png b/java/res/drawable-hdpi/sym_keyboard_shift_locked.png
deleted file mode 100644
index d345634..0000000
--- a/java/res/drawable-hdpi/sym_keyboard_shift_locked.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_shift_locked_holo.png b/java/res/drawable-hdpi/sym_keyboard_shift_locked_holo.png
deleted file mode 100644
index 7a5c037..0000000
--- a/java/res/drawable-hdpi/sym_keyboard_shift_locked_holo.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_shift_locked_holo_dark.png b/java/res/drawable-hdpi/sym_keyboard_shift_locked_holo_dark.png
new file mode 100644
index 0000000..9b1d6a0
--- /dev/null
+++ b/java/res/drawable-hdpi/sym_keyboard_shift_locked_holo_dark.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_space_holo.png b/java/res/drawable-hdpi/sym_keyboard_space_holo.png
deleted file mode 100644
index e8bc390..0000000
--- a/java/res/drawable-hdpi/sym_keyboard_space_holo.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_space_holo_dark.png b/java/res/drawable-hdpi/sym_keyboard_space_holo_dark.png
new file mode 100644
index 0000000..12e27ad
--- /dev/null
+++ b/java/res/drawable-hdpi/sym_keyboard_space_holo_dark.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_space_led.9.png b/java/res/drawable-hdpi/sym_keyboard_space_led_gb.9.png
similarity index 100%
rename from java/res/drawable-hdpi/sym_keyboard_space_led.9.png
rename to java/res/drawable-hdpi/sym_keyboard_space_led_gb.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_tab.png b/java/res/drawable-hdpi/sym_keyboard_tab.png
deleted file mode 100644
index 3d1c5c0..0000000
--- a/java/res/drawable-hdpi/sym_keyboard_tab.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_tab_holo.png b/java/res/drawable-hdpi/sym_keyboard_tab_holo.png
deleted file mode 100644
index 8d10d05..0000000
--- a/java/res/drawable-hdpi/sym_keyboard_tab_holo.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_tab_holo_dark.png b/java/res/drawable-hdpi/sym_keyboard_tab_holo_dark.png
new file mode 100644
index 0000000..2e5f811
--- /dev/null
+++ b/java/res/drawable-hdpi/sym_keyboard_tab_holo_dark.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_voice_holo.png b/java/res/drawable-hdpi/sym_keyboard_voice_holo.png
deleted file mode 100644
index 8a6336a..0000000
--- a/java/res/drawable-hdpi/sym_keyboard_voice_holo.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_voice_holo_dark.png b/java/res/drawable-hdpi/sym_keyboard_voice_holo_dark.png
new file mode 100644
index 0000000..c1e16a6
--- /dev/null
+++ b/java/res/drawable-hdpi/sym_keyboard_voice_holo_dark.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_voice_off_holo.png b/java/res/drawable-hdpi/sym_keyboard_voice_off_holo.png
deleted file mode 100644
index edf1379..0000000
--- a/java/res/drawable-hdpi/sym_keyboard_voice_off_holo.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_voice_off_holo_dark.png b/java/res/drawable-hdpi/sym_keyboard_voice_off_holo_dark.png
new file mode 100644
index 0000000..26d0684
--- /dev/null
+++ b/java/res/drawable-hdpi/sym_keyboard_voice_off_holo_dark.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_zwj_holo.png b/java/res/drawable-hdpi/sym_keyboard_zwj_holo.png
deleted file mode 100644
index 5fa30ce..0000000
--- a/java/res/drawable-hdpi/sym_keyboard_zwj_holo.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_zwj_holo_dark.png b/java/res/drawable-hdpi/sym_keyboard_zwj_holo_dark.png
new file mode 100644
index 0000000..9f9bc17
--- /dev/null
+++ b/java/res/drawable-hdpi/sym_keyboard_zwj_holo_dark.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_zwnj_holo.png b/java/res/drawable-hdpi/sym_keyboard_zwnj_holo.png
deleted file mode 100644
index 91367f3..0000000
--- a/java/res/drawable-hdpi/sym_keyboard_zwnj_holo.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_zwnj_holo_dark.png b/java/res/drawable-hdpi/sym_keyboard_zwnj_holo_dark.png
new file mode 100644
index 0000000..f0f832e
--- /dev/null
+++ b/java/res/drawable-hdpi/sym_keyboard_zwnj_holo_dark.png
Binary files differ
diff --git a/java/res/drawable-hdpi/tab_selected.9.png b/java/res/drawable-hdpi/tab_selected.9.png
new file mode 100644
index 0000000..84e63df
--- /dev/null
+++ b/java/res/drawable-hdpi/tab_selected.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/tab_unselected.9.png b/java/res/drawable-hdpi/tab_unselected.9.png
new file mode 100644
index 0000000..bbcfb2c
--- /dev/null
+++ b/java/res/drawable-hdpi/tab_unselected.9.png
Binary files differ
diff --git a/java/res/drawable-mdpi/btn_center_default.9.png b/java/res/drawable-mdpi/btn_center_default.9.png
deleted file mode 100644
index d5ec36b..0000000
--- a/java/res/drawable-mdpi/btn_center_default.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-mdpi/btn_center_pressed.9.png b/java/res/drawable-mdpi/btn_center_pressed.9.png
deleted file mode 100644
index 593a679..0000000
--- a/java/res/drawable-mdpi/btn_center_pressed.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-mdpi/btn_center_selected.9.png b/java/res/drawable-mdpi/btn_center_selected.9.png
deleted file mode 100644
index f1914a8..0000000
--- a/java/res/drawable-mdpi/btn_center_selected.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-mdpi/btn_keyboard_key_dark_active_holo.9.png b/java/res/drawable-mdpi/btn_keyboard_key_dark_active_holo.9.png
index e810c77..f98653e 100644
--- a/java/res/drawable-mdpi/btn_keyboard_key_dark_active_holo.9.png
+++ b/java/res/drawable-mdpi/btn_keyboard_key_dark_active_holo.9.png
Binary files differ
diff --git a/java/res/drawable-mdpi/btn_keyboard_key_dark_normal_holo.9.png b/java/res/drawable-mdpi/btn_keyboard_key_dark_normal_holo.9.png
index d449d76..8e9a349 100644
--- a/java/res/drawable-mdpi/btn_keyboard_key_dark_normal_holo.9.png
+++ b/java/res/drawable-mdpi/btn_keyboard_key_dark_normal_holo.9.png
Binary files differ
diff --git a/java/res/drawable-mdpi/btn_keyboard_key_dark_normal_off_holo.9.png b/java/res/drawable-mdpi/btn_keyboard_key_dark_normal_off_holo.9.png
index fa24d59..58a316f 100644
--- a/java/res/drawable-mdpi/btn_keyboard_key_dark_normal_off_holo.9.png
+++ b/java/res/drawable-mdpi/btn_keyboard_key_dark_normal_off_holo.9.png
Binary files differ
diff --git a/java/res/drawable-mdpi/btn_keyboard_key_dark_normal_on_holo.9.png b/java/res/drawable-mdpi/btn_keyboard_key_dark_normal_on_holo.9.png
index f3fc641..b7b2dca 100644
--- a/java/res/drawable-mdpi/btn_keyboard_key_dark_normal_on_holo.9.png
+++ b/java/res/drawable-mdpi/btn_keyboard_key_dark_normal_on_holo.9.png
Binary files differ
diff --git a/java/res/drawable-mdpi/btn_keyboard_key_dark_pressed_holo.9.png b/java/res/drawable-mdpi/btn_keyboard_key_dark_pressed_holo.9.png
index 8f340d3..4a92b80 100644
--- a/java/res/drawable-mdpi/btn_keyboard_key_dark_pressed_holo.9.png
+++ b/java/res/drawable-mdpi/btn_keyboard_key_dark_pressed_holo.9.png
Binary files differ
diff --git a/java/res/drawable-mdpi/btn_keyboard_key_dark_pressed_off_holo.9.png b/java/res/drawable-mdpi/btn_keyboard_key_dark_pressed_off_holo.9.png
index 53ea5f8..72125a0 100644
--- a/java/res/drawable-mdpi/btn_keyboard_key_dark_pressed_off_holo.9.png
+++ b/java/res/drawable-mdpi/btn_keyboard_key_dark_pressed_off_holo.9.png
Binary files differ
diff --git a/java/res/drawable-mdpi/btn_keyboard_key_dark_pressed_on_holo.9.png b/java/res/drawable-mdpi/btn_keyboard_key_dark_pressed_on_holo.9.png
index 69c84e7..82413d4 100644
--- a/java/res/drawable-mdpi/btn_keyboard_key_dark_pressed_on_holo.9.png
+++ b/java/res/drawable-mdpi/btn_keyboard_key_dark_pressed_on_holo.9.png
Binary files differ
diff --git a/java/res/drawable-mdpi/btn_keyboard_key_fulltrans_normal.9.png b/java/res/drawable-mdpi/btn_keyboard_key_fulltrans_normal.9.png
deleted file mode 100644
index 4b1a78c..0000000
--- a/java/res/drawable-mdpi/btn_keyboard_key_fulltrans_normal.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-mdpi/btn_keyboard_key_fulltrans_pressed.9.png b/java/res/drawable-mdpi/btn_keyboard_key_fulltrans_pressed.9.png
deleted file mode 100644
index 697683e..0000000
--- a/java/res/drawable-mdpi/btn_keyboard_key_fulltrans_pressed.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-mdpi/btn_keyboard_key_light_normal_holo.9.png b/java/res/drawable-mdpi/btn_keyboard_key_light_normal_holo.9.png
index 976083f..2915588 100644
--- a/java/res/drawable-mdpi/btn_keyboard_key_light_normal_holo.9.png
+++ b/java/res/drawable-mdpi/btn_keyboard_key_light_normal_holo.9.png
Binary files differ
diff --git a/java/res/drawable-mdpi/btn_keyboard_key_light_pressed_holo.9.png b/java/res/drawable-mdpi/btn_keyboard_key_light_pressed_holo.9.png
index c39dd4a..0493859 100644
--- a/java/res/drawable-mdpi/btn_keyboard_key_light_pressed_holo.9.png
+++ b/java/res/drawable-mdpi/btn_keyboard_key_light_pressed_holo.9.png
Binary files differ
diff --git a/java/res/drawable-mdpi/btn_keyboard_key_normal_off_stone.9.png b/java/res/drawable-mdpi/btn_keyboard_key_normal_off_stone.9.png
deleted file mode 100644
index cdd6c8b..0000000
--- a/java/res/drawable-mdpi/btn_keyboard_key_normal_off_stone.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-mdpi/btn_keyboard_key_normal_on_stone.9.png b/java/res/drawable-mdpi/btn_keyboard_key_normal_on_stone.9.png
deleted file mode 100644
index d842174..0000000
--- a/java/res/drawable-mdpi/btn_keyboard_key_normal_on_stone.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-mdpi/btn_keyboard_key_normal_stone.9.png b/java/res/drawable-mdpi/btn_keyboard_key_normal_stone.9.png
deleted file mode 100644
index 73cf35d..0000000
--- a/java/res/drawable-mdpi/btn_keyboard_key_normal_stone.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-mdpi/btn_keyboard_key_popup_selected_holo.9.png b/java/res/drawable-mdpi/btn_keyboard_key_popup_selected_holo.9.png
index 93a6e79..ee0aae2 100644
--- a/java/res/drawable-mdpi/btn_keyboard_key_popup_selected_holo.9.png
+++ b/java/res/drawable-mdpi/btn_keyboard_key_popup_selected_holo.9.png
Binary files differ
diff --git a/java/res/drawable-mdpi/ic_emoji_dark.png b/java/res/drawable-mdpi/ic_emoji_dark.png
deleted file mode 100644
index d0047a4..0000000
--- a/java/res/drawable-mdpi/ic_emoji_dark.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-mdpi/ic_emoji_nature_light_activated.png b/java/res/drawable-mdpi/ic_emoji_nature_light_activated.png
new file mode 100644
index 0000000..d4c8d8d
--- /dev/null
+++ b/java/res/drawable-mdpi/ic_emoji_nature_light_activated.png
Binary files differ
diff --git a/java/res/drawable-mdpi/ic_emoji_nature_light_normal.png b/java/res/drawable-mdpi/ic_emoji_nature_light_normal.png
new file mode 100644
index 0000000..1555aa7
--- /dev/null
+++ b/java/res/drawable-mdpi/ic_emoji_nature_light_normal.png
Binary files differ
diff --git a/java/res/drawable-mdpi/ic_emoji_objects_light_activated.png b/java/res/drawable-mdpi/ic_emoji_objects_light_activated.png
new file mode 100644
index 0000000..081dc66
--- /dev/null
+++ b/java/res/drawable-mdpi/ic_emoji_objects_light_activated.png
Binary files differ
diff --git a/java/res/drawable-mdpi/ic_emoji_objects_light_normal.png b/java/res/drawable-mdpi/ic_emoji_objects_light_normal.png
new file mode 100644
index 0000000..58e6f6e
--- /dev/null
+++ b/java/res/drawable-mdpi/ic_emoji_objects_light_normal.png
Binary files differ
diff --git a/java/res/drawable-mdpi/ic_emoji_people_light_activated.png b/java/res/drawable-mdpi/ic_emoji_people_light_activated.png
new file mode 100644
index 0000000..067ad54
--- /dev/null
+++ b/java/res/drawable-mdpi/ic_emoji_people_light_activated.png
Binary files differ
diff --git a/java/res/drawable-mdpi/ic_emoji_light.png b/java/res/drawable-mdpi/ic_emoji_people_light_normal.png
similarity index 88%
rename from java/res/drawable-mdpi/ic_emoji_light.png
rename to java/res/drawable-mdpi/ic_emoji_people_light_normal.png
index a319504..d835d4e 100644
--- a/java/res/drawable-mdpi/ic_emoji_light.png
+++ b/java/res/drawable-mdpi/ic_emoji_people_light_normal.png
Binary files differ
diff --git a/java/res/drawable-mdpi/ic_emoji_places_light_activated.png b/java/res/drawable-mdpi/ic_emoji_places_light_activated.png
new file mode 100644
index 0000000..1aecec5
--- /dev/null
+++ b/java/res/drawable-mdpi/ic_emoji_places_light_activated.png
Binary files differ
diff --git a/java/res/drawable-mdpi/ic_emoji_places_light_normal.png b/java/res/drawable-mdpi/ic_emoji_places_light_normal.png
new file mode 100644
index 0000000..c70e484
--- /dev/null
+++ b/java/res/drawable-mdpi/ic_emoji_places_light_normal.png
Binary files differ
diff --git a/java/res/drawable-mdpi/ic_emoji_recent_light_activated.png b/java/res/drawable-mdpi/ic_emoji_recent_light_activated.png
new file mode 100644
index 0000000..8009e93
--- /dev/null
+++ b/java/res/drawable-mdpi/ic_emoji_recent_light_activated.png
Binary files differ
diff --git a/java/res/drawable-mdpi/ic_emoji_recent_light_normal.png b/java/res/drawable-mdpi/ic_emoji_recent_light_normal.png
new file mode 100644
index 0000000..c2e598d
--- /dev/null
+++ b/java/res/drawable-mdpi/ic_emoji_recent_light_normal.png
Binary files differ
diff --git a/java/res/drawable-mdpi/ic_emoji_symbols_light_activated.png b/java/res/drawable-mdpi/ic_emoji_symbols_light_activated.png
new file mode 100644
index 0000000..caea871
--- /dev/null
+++ b/java/res/drawable-mdpi/ic_emoji_symbols_light_activated.png
Binary files differ
diff --git a/java/res/drawable-mdpi/ic_emoji_symbols_light_normal.png b/java/res/drawable-mdpi/ic_emoji_symbols_light_normal.png
new file mode 100644
index 0000000..0edada6
--- /dev/null
+++ b/java/res/drawable-mdpi/ic_emoji_symbols_light_normal.png
Binary files differ
diff --git a/java/res/drawable-mdpi/ic_ime_switcher_dark.png b/java/res/drawable-mdpi/ic_ime_switcher_dark.png
new file mode 100644
index 0000000..152f653
--- /dev/null
+++ b/java/res/drawable-mdpi/ic_ime_switcher_dark.png
Binary files differ
diff --git a/java/res/drawable-mdpi/ic_settings_language.png b/java/res/drawable-mdpi/ic_settings_language.png
deleted file mode 100644
index f8aca67..0000000
--- a/java/res/drawable-mdpi/ic_settings_language.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-mdpi/ic_subtype_keyboard.png b/java/res/drawable-mdpi/ic_subtype_keyboard.png
deleted file mode 100644
index d28efc1..0000000
--- a/java/res/drawable-mdpi/ic_subtype_keyboard.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-mdpi/keyboard_background.9.png b/java/res/drawable-mdpi/keyboard_background.9.png
deleted file mode 100644
index 2bd4b62..0000000
--- a/java/res/drawable-mdpi/keyboard_background.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-mdpi/keyboard_dark_background.9.png b/java/res/drawable-mdpi/keyboard_background_gb.9.png
similarity index 100%
rename from java/res/drawable-mdpi/keyboard_dark_background.9.png
rename to java/res/drawable-mdpi/keyboard_background_gb.9.png
Binary files differ
diff --git a/java/res/drawable-mdpi/keyboard_key_feedback_background_holo.9.png b/java/res/drawable-mdpi/keyboard_key_feedback_background_holo.9.png
index 7a9f640..564f546 100644
--- a/java/res/drawable-mdpi/keyboard_key_feedback_background_holo.9.png
+++ b/java/res/drawable-mdpi/keyboard_key_feedback_background_holo.9.png
Binary files differ
diff --git a/java/res/drawable-mdpi/keyboard_key_feedback_left_background_holo.9.png b/java/res/drawable-mdpi/keyboard_key_feedback_left_background_holo.9.png
index 5b06f09..427c870 100644
--- a/java/res/drawable-mdpi/keyboard_key_feedback_left_background_holo.9.png
+++ b/java/res/drawable-mdpi/keyboard_key_feedback_left_background_holo.9.png
Binary files differ
diff --git a/java/res/drawable-mdpi/keyboard_key_feedback_left_more_background_holo.9.png b/java/res/drawable-mdpi/keyboard_key_feedback_left_more_background_holo.9.png
index fd992d6..ea75729 100644
--- a/java/res/drawable-mdpi/keyboard_key_feedback_left_more_background_holo.9.png
+++ b/java/res/drawable-mdpi/keyboard_key_feedback_left_more_background_holo.9.png
Binary files differ
diff --git a/java/res/drawable-mdpi/keyboard_key_feedback_more_background_holo.9.png b/java/res/drawable-mdpi/keyboard_key_feedback_more_background_holo.9.png
index 128dcd6..1911c42 100644
--- a/java/res/drawable-mdpi/keyboard_key_feedback_more_background_holo.9.png
+++ b/java/res/drawable-mdpi/keyboard_key_feedback_more_background_holo.9.png
Binary files differ
diff --git a/java/res/drawable-mdpi/keyboard_key_feedback_right_background_holo.9.png b/java/res/drawable-mdpi/keyboard_key_feedback_right_background_holo.9.png
index 0b08d17..cdef116 100644
--- a/java/res/drawable-mdpi/keyboard_key_feedback_right_background_holo.9.png
+++ b/java/res/drawable-mdpi/keyboard_key_feedback_right_background_holo.9.png
Binary files differ
diff --git a/java/res/drawable-mdpi/keyboard_key_feedback_right_more_background_holo.9.png b/java/res/drawable-mdpi/keyboard_key_feedback_right_more_background_holo.9.png
index cf0b33c..dea5d07 100644
--- a/java/res/drawable-mdpi/keyboard_key_feedback_right_more_background_holo.9.png
+++ b/java/res/drawable-mdpi/keyboard_key_feedback_right_more_background_holo.9.png
Binary files differ
diff --git a/java/res/drawable-mdpi/keyboard_popup_panel_background.9.png b/java/res/drawable-mdpi/keyboard_popup_panel_background_gb.9.png
similarity index 100%
rename from java/res/drawable-mdpi/keyboard_popup_panel_background.9.png
rename to java/res/drawable-mdpi/keyboard_popup_panel_background_gb.9.png
Binary files differ
diff --git a/java/res/drawable-mdpi/keyboard_popup_panel_background_holo.9.png b/java/res/drawable-mdpi/keyboard_popup_panel_background_holo.9.png
index 61988a8..441edc3 100644
--- a/java/res/drawable-mdpi/keyboard_popup_panel_background_holo.9.png
+++ b/java/res/drawable-mdpi/keyboard_popup_panel_background_holo.9.png
Binary files differ
diff --git a/java/res/drawable-mdpi/keyboard_suggest_strip.9.png b/java/res/drawable-mdpi/keyboard_suggest_strip_gb.9.png
similarity index 100%
rename from java/res/drawable-mdpi/keyboard_suggest_strip.9.png
rename to java/res/drawable-mdpi/keyboard_suggest_strip_gb.9.png
Binary files differ
diff --git a/java/res/drawable-mdpi/suggestions_strip_divider.png b/java/res/drawable-mdpi/suggestions_strip_divider.png
index 2dbe2f9..21e9049 100644
--- a/java/res/drawable-mdpi/suggestions_strip_divider.png
+++ b/java/res/drawable-mdpi/suggestions_strip_divider.png
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_bkeyboard_delete.png b/java/res/drawable-mdpi/sym_bkeyboard_delete.png
deleted file mode 100644
index 1a5ff43..0000000
--- a/java/res/drawable-mdpi/sym_bkeyboard_delete.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_bkeyboard_label_mic.png b/java/res/drawable-mdpi/sym_bkeyboard_label_mic.png
deleted file mode 100644
index 7f0b135..0000000
--- a/java/res/drawable-mdpi/sym_bkeyboard_label_mic.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_bkeyboard_mic.png b/java/res/drawable-mdpi/sym_bkeyboard_mic.png
deleted file mode 100644
index a6cb1cc..0000000
--- a/java/res/drawable-mdpi/sym_bkeyboard_mic.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_bkeyboard_return.png b/java/res/drawable-mdpi/sym_bkeyboard_return.png
deleted file mode 100644
index e76225d..0000000
--- a/java/res/drawable-mdpi/sym_bkeyboard_return.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_bkeyboard_search.png b/java/res/drawable-mdpi/sym_bkeyboard_search.png
deleted file mode 100644
index 1f18015..0000000
--- a/java/res/drawable-mdpi/sym_bkeyboard_search.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_bkeyboard_settings.png b/java/res/drawable-mdpi/sym_bkeyboard_settings.png
deleted file mode 100644
index 08ba18f..0000000
--- a/java/res/drawable-mdpi/sym_bkeyboard_settings.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_bkeyboard_shift.png b/java/res/drawable-mdpi/sym_bkeyboard_shift.png
deleted file mode 100644
index c981188..0000000
--- a/java/res/drawable-mdpi/sym_bkeyboard_shift.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_bkeyboard_shift_locked.png b/java/res/drawable-mdpi/sym_bkeyboard_shift_locked.png
deleted file mode 100644
index b8cebd0..0000000
--- a/java/res/drawable-mdpi/sym_bkeyboard_shift_locked.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_bkeyboard_space.png b/java/res/drawable-mdpi/sym_bkeyboard_space.png
deleted file mode 100644
index 4da7ee8..0000000
--- a/java/res/drawable-mdpi/sym_bkeyboard_space.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_bkeyboard_tab.png b/java/res/drawable-mdpi/sym_bkeyboard_tab.png
deleted file mode 100644
index 2cb991c..0000000
--- a/java/res/drawable-mdpi/sym_bkeyboard_tab.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_bkeyboard_voice_off.png b/java/res/drawable-mdpi/sym_bkeyboard_voice_off.png
deleted file mode 100644
index 081a130..0000000
--- a/java/res/drawable-mdpi/sym_bkeyboard_voice_off.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_keyboard_delete.png b/java/res/drawable-mdpi/sym_keyboard_delete.png
deleted file mode 100644
index 1b0f3f8..0000000
--- a/java/res/drawable-mdpi/sym_keyboard_delete.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_keyboard_delete_holo.png b/java/res/drawable-mdpi/sym_keyboard_delete_holo.png
deleted file mode 100644
index 86be351..0000000
--- a/java/res/drawable-mdpi/sym_keyboard_delete_holo.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_keyboard_delete_holo_dark.png b/java/res/drawable-mdpi/sym_keyboard_delete_holo_dark.png
new file mode 100644
index 0000000..edd9d16
--- /dev/null
+++ b/java/res/drawable-mdpi/sym_keyboard_delete_holo_dark.png
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_keyboard_label_mic.png b/java/res/drawable-mdpi/sym_keyboard_label_mic.png
deleted file mode 100644
index a354d53..0000000
--- a/java/res/drawable-mdpi/sym_keyboard_label_mic.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_keyboard_label_mic_holo.png b/java/res/drawable-mdpi/sym_keyboard_label_mic_holo.png
deleted file mode 100644
index 15606e9..0000000
--- a/java/res/drawable-mdpi/sym_keyboard_label_mic_holo.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_keyboard_label_mic_holo_dark.png b/java/res/drawable-mdpi/sym_keyboard_label_mic_holo_dark.png
new file mode 100644
index 0000000..537f39b
--- /dev/null
+++ b/java/res/drawable-mdpi/sym_keyboard_label_mic_holo_dark.png
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_keyboard_language_switch.png b/java/res/drawable-mdpi/sym_keyboard_language_switch.png
deleted file mode 100644
index f840a63..0000000
--- a/java/res/drawable-mdpi/sym_keyboard_language_switch.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_keyboard_language_switch_dark.png b/java/res/drawable-mdpi/sym_keyboard_language_switch_dark.png
new file mode 100644
index 0000000..828929b
--- /dev/null
+++ b/java/res/drawable-mdpi/sym_keyboard_language_switch_dark.png
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_keyboard_mic.png b/java/res/drawable-mdpi/sym_keyboard_mic.png
deleted file mode 100644
index e926b3f..0000000
--- a/java/res/drawable-mdpi/sym_keyboard_mic.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_keyboard_mic_holo_dark.png b/java/res/drawable-mdpi/sym_keyboard_mic_holo_dark.png
new file mode 100644
index 0000000..5e58866
--- /dev/null
+++ b/java/res/drawable-mdpi/sym_keyboard_mic_holo_dark.png
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_keyboard_mic_holo_light.png b/java/res/drawable-mdpi/sym_keyboard_mic_holo_light.png
new file mode 100644
index 0000000..84a63dc
--- /dev/null
+++ b/java/res/drawable-mdpi/sym_keyboard_mic_holo_light.png
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_keyboard_return.png b/java/res/drawable-mdpi/sym_keyboard_return.png
deleted file mode 100644
index 0c10f00..0000000
--- a/java/res/drawable-mdpi/sym_keyboard_return.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_keyboard_return_holo.png b/java/res/drawable-mdpi/sym_keyboard_return_holo.png
deleted file mode 100644
index bfcb913..0000000
--- a/java/res/drawable-mdpi/sym_keyboard_return_holo.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_keyboard_return_holo_dark.png b/java/res/drawable-mdpi/sym_keyboard_return_holo_dark.png
new file mode 100644
index 0000000..e10103c
--- /dev/null
+++ b/java/res/drawable-mdpi/sym_keyboard_return_holo_dark.png
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_keyboard_search.png b/java/res/drawable-mdpi/sym_keyboard_search.png
deleted file mode 100644
index 614f85f..0000000
--- a/java/res/drawable-mdpi/sym_keyboard_search.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_keyboard_search_holo.png b/java/res/drawable-mdpi/sym_keyboard_search_holo.png
deleted file mode 100644
index dd3c83a..0000000
--- a/java/res/drawable-mdpi/sym_keyboard_search_holo.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_keyboard_search_holo_dark.png b/java/res/drawable-mdpi/sym_keyboard_search_holo_dark.png
new file mode 100644
index 0000000..290cde4
--- /dev/null
+++ b/java/res/drawable-mdpi/sym_keyboard_search_holo_dark.png
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_keyboard_settings.png b/java/res/drawable-mdpi/sym_keyboard_settings.png
deleted file mode 100644
index ad7618f..0000000
--- a/java/res/drawable-mdpi/sym_keyboard_settings.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_keyboard_settings_holo.png b/java/res/drawable-mdpi/sym_keyboard_settings_holo.png
deleted file mode 100644
index 36c8c96..0000000
--- a/java/res/drawable-mdpi/sym_keyboard_settings_holo.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_keyboard_settings_holo_dark.png b/java/res/drawable-mdpi/sym_keyboard_settings_holo_dark.png
new file mode 100644
index 0000000..a76a976
--- /dev/null
+++ b/java/res/drawable-mdpi/sym_keyboard_settings_holo_dark.png
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_keyboard_shift.png b/java/res/drawable-mdpi/sym_keyboard_shift.png
deleted file mode 100644
index 5109b04..0000000
--- a/java/res/drawable-mdpi/sym_keyboard_shift.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_keyboard_shift_holo.png b/java/res/drawable-mdpi/sym_keyboard_shift_holo.png
deleted file mode 100644
index 6219464..0000000
--- a/java/res/drawable-mdpi/sym_keyboard_shift_holo.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_keyboard_shift_holo_dark.png b/java/res/drawable-mdpi/sym_keyboard_shift_holo_dark.png
new file mode 100644
index 0000000..37375d9
--- /dev/null
+++ b/java/res/drawable-mdpi/sym_keyboard_shift_holo_dark.png
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_keyboard_shift_locked.png b/java/res/drawable-mdpi/sym_keyboard_shift_locked.png
deleted file mode 100644
index 244179c..0000000
--- a/java/res/drawable-mdpi/sym_keyboard_shift_locked.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_keyboard_shift_locked_holo.png b/java/res/drawable-mdpi/sym_keyboard_shift_locked_holo.png
deleted file mode 100644
index fb3a020..0000000
--- a/java/res/drawable-mdpi/sym_keyboard_shift_locked_holo.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_keyboard_shift_locked_holo_dark.png b/java/res/drawable-mdpi/sym_keyboard_shift_locked_holo_dark.png
new file mode 100644
index 0000000..3654868
--- /dev/null
+++ b/java/res/drawable-mdpi/sym_keyboard_shift_locked_holo_dark.png
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_keyboard_smiley_holo_dark.png b/java/res/drawable-mdpi/sym_keyboard_smiley_holo_dark.png
new file mode 100644
index 0000000..71272bb
--- /dev/null
+++ b/java/res/drawable-mdpi/sym_keyboard_smiley_holo_dark.png
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_keyboard_space_holo.png b/java/res/drawable-mdpi/sym_keyboard_space_holo.png
deleted file mode 100644
index 1f787d5..0000000
--- a/java/res/drawable-mdpi/sym_keyboard_space_holo.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_keyboard_space_holo_dark.png b/java/res/drawable-mdpi/sym_keyboard_space_holo_dark.png
new file mode 100644
index 0000000..a38f994
--- /dev/null
+++ b/java/res/drawable-mdpi/sym_keyboard_space_holo_dark.png
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_keyboard_space_led.9.png b/java/res/drawable-mdpi/sym_keyboard_space_led_gb.9.png
similarity index 100%
rename from java/res/drawable-mdpi/sym_keyboard_space_led.9.png
rename to java/res/drawable-mdpi/sym_keyboard_space_led_gb.9.png
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_keyboard_tab.png b/java/res/drawable-mdpi/sym_keyboard_tab.png
deleted file mode 100644
index eddb9a5..0000000
--- a/java/res/drawable-mdpi/sym_keyboard_tab.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_keyboard_tab_holo.png b/java/res/drawable-mdpi/sym_keyboard_tab_holo.png
deleted file mode 100644
index 8d20153..0000000
--- a/java/res/drawable-mdpi/sym_keyboard_tab_holo.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_keyboard_tab_holo_dark.png b/java/res/drawable-mdpi/sym_keyboard_tab_holo_dark.png
new file mode 100644
index 0000000..f883807
--- /dev/null
+++ b/java/res/drawable-mdpi/sym_keyboard_tab_holo_dark.png
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_keyboard_voice_holo.png b/java/res/drawable-mdpi/sym_keyboard_voice_holo.png
deleted file mode 100644
index 0795fcc..0000000
--- a/java/res/drawable-mdpi/sym_keyboard_voice_holo.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_keyboard_voice_holo_dark.png b/java/res/drawable-mdpi/sym_keyboard_voice_holo_dark.png
new file mode 100644
index 0000000..16be37d
--- /dev/null
+++ b/java/res/drawable-mdpi/sym_keyboard_voice_holo_dark.png
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_keyboard_voice_off_holo.png b/java/res/drawable-mdpi/sym_keyboard_voice_off_holo.png
deleted file mode 100644
index f76da57..0000000
--- a/java/res/drawable-mdpi/sym_keyboard_voice_off_holo.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_keyboard_voice_off_holo_dark.png b/java/res/drawable-mdpi/sym_keyboard_voice_off_holo_dark.png
new file mode 100644
index 0000000..95d718a
--- /dev/null
+++ b/java/res/drawable-mdpi/sym_keyboard_voice_off_holo_dark.png
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_keyboard_zwj_holo.png b/java/res/drawable-mdpi/sym_keyboard_zwj_holo.png
deleted file mode 100644
index 70370d8..0000000
--- a/java/res/drawable-mdpi/sym_keyboard_zwj_holo.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_keyboard_zwj_holo_dark.png b/java/res/drawable-mdpi/sym_keyboard_zwj_holo_dark.png
new file mode 100644
index 0000000..8957e28
--- /dev/null
+++ b/java/res/drawable-mdpi/sym_keyboard_zwj_holo_dark.png
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_keyboard_zwnj_holo.png b/java/res/drawable-mdpi/sym_keyboard_zwnj_holo.png
deleted file mode 100644
index a69eade..0000000
--- a/java/res/drawable-mdpi/sym_keyboard_zwnj_holo.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_keyboard_zwnj_holo_dark.png b/java/res/drawable-mdpi/sym_keyboard_zwnj_holo_dark.png
new file mode 100644
index 0000000..5f49e64
--- /dev/null
+++ b/java/res/drawable-mdpi/sym_keyboard_zwnj_holo_dark.png
Binary files differ
diff --git a/java/res/drawable-mdpi/tab_selected.9.png b/java/res/drawable-mdpi/tab_selected.9.png
new file mode 100644
index 0000000..4b00f35
--- /dev/null
+++ b/java/res/drawable-mdpi/tab_selected.9.png
Binary files differ
diff --git a/java/res/drawable-mdpi/tab_unselected.9.png b/java/res/drawable-mdpi/tab_unselected.9.png
new file mode 100644
index 0000000..bb45ab9
--- /dev/null
+++ b/java/res/drawable-mdpi/tab_unselected.9.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/btn_center_default.9.png b/java/res/drawable-xhdpi/btn_center_default.9.png
deleted file mode 100644
index e847425..0000000
--- a/java/res/drawable-xhdpi/btn_center_default.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xhdpi/btn_center_pressed.9.png b/java/res/drawable-xhdpi/btn_center_pressed.9.png
deleted file mode 100644
index facfd43..0000000
--- a/java/res/drawable-xhdpi/btn_center_pressed.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xhdpi/btn_center_selected.9.png b/java/res/drawable-xhdpi/btn_center_selected.9.png
deleted file mode 100644
index facfd43..0000000
--- a/java/res/drawable-xhdpi/btn_center_selected.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xhdpi/btn_keyboard_key_dark_active_holo.9.png b/java/res/drawable-xhdpi/btn_keyboard_key_dark_active_holo.9.png
index d990c02..738316d 100644
--- a/java/res/drawable-xhdpi/btn_keyboard_key_dark_active_holo.9.png
+++ b/java/res/drawable-xhdpi/btn_keyboard_key_dark_active_holo.9.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/btn_keyboard_key_dark_normal_holo.9.png b/java/res/drawable-xhdpi/btn_keyboard_key_dark_normal_holo.9.png
index d2cd029..a2f6ac0 100644
--- a/java/res/drawable-xhdpi/btn_keyboard_key_dark_normal_holo.9.png
+++ b/java/res/drawable-xhdpi/btn_keyboard_key_dark_normal_holo.9.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/btn_keyboard_key_dark_normal_off_holo.9.png b/java/res/drawable-xhdpi/btn_keyboard_key_dark_normal_off_holo.9.png
index bca39cf..2f00fc6 100644
--- a/java/res/drawable-xhdpi/btn_keyboard_key_dark_normal_off_holo.9.png
+++ b/java/res/drawable-xhdpi/btn_keyboard_key_dark_normal_off_holo.9.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/btn_keyboard_key_dark_normal_on_holo.9.png b/java/res/drawable-xhdpi/btn_keyboard_key_dark_normal_on_holo.9.png
index ab8fb2e..20251a0 100644
--- a/java/res/drawable-xhdpi/btn_keyboard_key_dark_normal_on_holo.9.png
+++ b/java/res/drawable-xhdpi/btn_keyboard_key_dark_normal_on_holo.9.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/btn_keyboard_key_dark_pressed_holo.9.png b/java/res/drawable-xhdpi/btn_keyboard_key_dark_pressed_holo.9.png
index 3871689..84d1739 100644
--- a/java/res/drawable-xhdpi/btn_keyboard_key_dark_pressed_holo.9.png
+++ b/java/res/drawable-xhdpi/btn_keyboard_key_dark_pressed_holo.9.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/btn_keyboard_key_dark_pressed_off_holo.9.png b/java/res/drawable-xhdpi/btn_keyboard_key_dark_pressed_off_holo.9.png
index 9125063..ee4490e 100644
--- a/java/res/drawable-xhdpi/btn_keyboard_key_dark_pressed_off_holo.9.png
+++ b/java/res/drawable-xhdpi/btn_keyboard_key_dark_pressed_off_holo.9.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/btn_keyboard_key_dark_pressed_on_holo.9.png b/java/res/drawable-xhdpi/btn_keyboard_key_dark_pressed_on_holo.9.png
index 35ce67f..e812477 100644
--- a/java/res/drawable-xhdpi/btn_keyboard_key_dark_pressed_on_holo.9.png
+++ b/java/res/drawable-xhdpi/btn_keyboard_key_dark_pressed_on_holo.9.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/btn_keyboard_key_fulltrans_normal.9.png b/java/res/drawable-xhdpi/btn_keyboard_key_fulltrans_normal.9.png
deleted file mode 100644
index f7e32f7..0000000
--- a/java/res/drawable-xhdpi/btn_keyboard_key_fulltrans_normal.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xhdpi/btn_keyboard_key_fulltrans_pressed.9.png b/java/res/drawable-xhdpi/btn_keyboard_key_fulltrans_pressed.9.png
deleted file mode 100644
index df3b5ba..0000000
--- a/java/res/drawable-xhdpi/btn_keyboard_key_fulltrans_pressed.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xhdpi/btn_keyboard_key_light_normal_holo.9.png b/java/res/drawable-xhdpi/btn_keyboard_key_light_normal_holo.9.png
index b26f1d2..0ef4a4b 100644
--- a/java/res/drawable-xhdpi/btn_keyboard_key_light_normal_holo.9.png
+++ b/java/res/drawable-xhdpi/btn_keyboard_key_light_normal_holo.9.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/btn_keyboard_key_light_pressed_holo.9.png b/java/res/drawable-xhdpi/btn_keyboard_key_light_pressed_holo.9.png
index c23a4b2..f770962 100644
--- a/java/res/drawable-xhdpi/btn_keyboard_key_light_pressed_holo.9.png
+++ b/java/res/drawable-xhdpi/btn_keyboard_key_light_pressed_holo.9.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/btn_keyboard_key_normal_off_stone.9.png b/java/res/drawable-xhdpi/btn_keyboard_key_normal_off_stone.9.png
deleted file mode 100644
index dec2193..0000000
--- a/java/res/drawable-xhdpi/btn_keyboard_key_normal_off_stone.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xhdpi/btn_keyboard_key_normal_on_stone.9.png b/java/res/drawable-xhdpi/btn_keyboard_key_normal_on_stone.9.png
deleted file mode 100644
index 3c77b3c..0000000
--- a/java/res/drawable-xhdpi/btn_keyboard_key_normal_on_stone.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xhdpi/btn_keyboard_key_normal_stone.9.png b/java/res/drawable-xhdpi/btn_keyboard_key_normal_stone.9.png
deleted file mode 100644
index 5cdfc42..0000000
--- a/java/res/drawable-xhdpi/btn_keyboard_key_normal_stone.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xhdpi/btn_keyboard_key_popup_selected_holo.9.png b/java/res/drawable-xhdpi/btn_keyboard_key_popup_selected_holo.9.png
index 0c7bfda..891d000 100644
--- a/java/res/drawable-xhdpi/btn_keyboard_key_popup_selected_holo.9.png
+++ b/java/res/drawable-xhdpi/btn_keyboard_key_popup_selected_holo.9.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/ic_emoji_dark.png b/java/res/drawable-xhdpi/ic_emoji_dark.png
deleted file mode 100644
index 22daec2..0000000
--- a/java/res/drawable-xhdpi/ic_emoji_dark.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xhdpi/ic_emoji_nature_light_activated.png b/java/res/drawable-xhdpi/ic_emoji_nature_light_activated.png
new file mode 100644
index 0000000..3e67443
--- /dev/null
+++ b/java/res/drawable-xhdpi/ic_emoji_nature_light_activated.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/ic_emoji_nature_light_normal.png b/java/res/drawable-xhdpi/ic_emoji_nature_light_normal.png
new file mode 100644
index 0000000..5344a9e
--- /dev/null
+++ b/java/res/drawable-xhdpi/ic_emoji_nature_light_normal.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/ic_emoji_objects_light_activated.png b/java/res/drawable-xhdpi/ic_emoji_objects_light_activated.png
new file mode 100644
index 0000000..75695d4
--- /dev/null
+++ b/java/res/drawable-xhdpi/ic_emoji_objects_light_activated.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/ic_emoji_objects_light_normal.png b/java/res/drawable-xhdpi/ic_emoji_objects_light_normal.png
new file mode 100644
index 0000000..2adb186
--- /dev/null
+++ b/java/res/drawable-xhdpi/ic_emoji_objects_light_normal.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/ic_emoji_people_light_activated.png b/java/res/drawable-xhdpi/ic_emoji_people_light_activated.png
new file mode 100644
index 0000000..e6baa2e
--- /dev/null
+++ b/java/res/drawable-xhdpi/ic_emoji_people_light_activated.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/ic_emoji_light.png b/java/res/drawable-xhdpi/ic_emoji_people_light_normal.png
similarity index 94%
rename from java/res/drawable-xhdpi/ic_emoji_light.png
rename to java/res/drawable-xhdpi/ic_emoji_people_light_normal.png
index 21bc909..c26aa4e 100644
--- a/java/res/drawable-xhdpi/ic_emoji_light.png
+++ b/java/res/drawable-xhdpi/ic_emoji_people_light_normal.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/ic_emoji_places_light_activated.png b/java/res/drawable-xhdpi/ic_emoji_places_light_activated.png
new file mode 100644
index 0000000..eaa3b86
--- /dev/null
+++ b/java/res/drawable-xhdpi/ic_emoji_places_light_activated.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/ic_emoji_places_light_normal.png b/java/res/drawable-xhdpi/ic_emoji_places_light_normal.png
new file mode 100644
index 0000000..d6e1eaa
--- /dev/null
+++ b/java/res/drawable-xhdpi/ic_emoji_places_light_normal.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/ic_emoji_recent_light_activated.png b/java/res/drawable-xhdpi/ic_emoji_recent_light_activated.png
new file mode 100644
index 0000000..06003b8
--- /dev/null
+++ b/java/res/drawable-xhdpi/ic_emoji_recent_light_activated.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/ic_emoji_recent_light_normal.png b/java/res/drawable-xhdpi/ic_emoji_recent_light_normal.png
new file mode 100644
index 0000000..da2effe
--- /dev/null
+++ b/java/res/drawable-xhdpi/ic_emoji_recent_light_normal.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/ic_emoji_symbols_light_activated.png b/java/res/drawable-xhdpi/ic_emoji_symbols_light_activated.png
new file mode 100644
index 0000000..438fde2
--- /dev/null
+++ b/java/res/drawable-xhdpi/ic_emoji_symbols_light_activated.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/ic_emoji_symbols_light_normal.png b/java/res/drawable-xhdpi/ic_emoji_symbols_light_normal.png
new file mode 100644
index 0000000..7578632
--- /dev/null
+++ b/java/res/drawable-xhdpi/ic_emoji_symbols_light_normal.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/ic_ime_switcher_dark.png b/java/res/drawable-xhdpi/ic_ime_switcher_dark.png
new file mode 100644
index 0000000..c567077
--- /dev/null
+++ b/java/res/drawable-xhdpi/ic_ime_switcher_dark.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/ic_settings_language.png b/java/res/drawable-xhdpi/ic_settings_language.png
deleted file mode 100644
index 2c42db3..0000000
--- a/java/res/drawable-xhdpi/ic_settings_language.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xhdpi/ic_subtype_keyboard.png b/java/res/drawable-xhdpi/ic_subtype_keyboard.png
deleted file mode 100644
index a79bb34..0000000
--- a/java/res/drawable-xhdpi/ic_subtype_keyboard.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xhdpi/ic_subtype_mic_dark.png b/java/res/drawable-xhdpi/ic_subtype_mic_dark.png
new file mode 100644
index 0000000..17581ba
--- /dev/null
+++ b/java/res/drawable-xhdpi/ic_subtype_mic_dark.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/keyboard_background.9.png b/java/res/drawable-xhdpi/keyboard_background.9.png
deleted file mode 100644
index 2639963..0000000
--- a/java/res/drawable-xhdpi/keyboard_background.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xhdpi/keyboard_dark_background.9.png b/java/res/drawable-xhdpi/keyboard_background_gb.9.png
similarity index 100%
rename from java/res/drawable-xhdpi/keyboard_dark_background.9.png
rename to java/res/drawable-xhdpi/keyboard_background_gb.9.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/keyboard_key_feedback_background_holo.9.png b/java/res/drawable-xhdpi/keyboard_key_feedback_background_holo.9.png
index d999127..e8c65f6 100644
--- a/java/res/drawable-xhdpi/keyboard_key_feedback_background_holo.9.png
+++ b/java/res/drawable-xhdpi/keyboard_key_feedback_background_holo.9.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/keyboard_key_feedback_left_background_holo.9.png b/java/res/drawable-xhdpi/keyboard_key_feedback_left_background_holo.9.png
index c4d6941..543bc76 100644
--- a/java/res/drawable-xhdpi/keyboard_key_feedback_left_background_holo.9.png
+++ b/java/res/drawable-xhdpi/keyboard_key_feedback_left_background_holo.9.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/keyboard_key_feedback_left_more_background_holo.9.png b/java/res/drawable-xhdpi/keyboard_key_feedback_left_more_background_holo.9.png
index 5429c17..ec42aad 100644
--- a/java/res/drawable-xhdpi/keyboard_key_feedback_left_more_background_holo.9.png
+++ b/java/res/drawable-xhdpi/keyboard_key_feedback_left_more_background_holo.9.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/keyboard_key_feedback_more_background_holo.9.png b/java/res/drawable-xhdpi/keyboard_key_feedback_more_background_holo.9.png
index 5135a08..319e9d7 100644
--- a/java/res/drawable-xhdpi/keyboard_key_feedback_more_background_holo.9.png
+++ b/java/res/drawable-xhdpi/keyboard_key_feedback_more_background_holo.9.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/keyboard_key_feedback_right_background_holo.9.png b/java/res/drawable-xhdpi/keyboard_key_feedback_right_background_holo.9.png
index 19a77a2..052032b 100644
--- a/java/res/drawable-xhdpi/keyboard_key_feedback_right_background_holo.9.png
+++ b/java/res/drawable-xhdpi/keyboard_key_feedback_right_background_holo.9.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/keyboard_key_feedback_right_more_background_holo.9.png b/java/res/drawable-xhdpi/keyboard_key_feedback_right_more_background_holo.9.png
index ae2ffff..c7e9d1c 100644
--- a/java/res/drawable-xhdpi/keyboard_key_feedback_right_more_background_holo.9.png
+++ b/java/res/drawable-xhdpi/keyboard_key_feedback_right_more_background_holo.9.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/keyboard_popup_panel_background.9.png b/java/res/drawable-xhdpi/keyboard_popup_panel_background_gb.9.png
similarity index 100%
rename from java/res/drawable-xhdpi/keyboard_popup_panel_background.9.png
rename to java/res/drawable-xhdpi/keyboard_popup_panel_background_gb.9.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/keyboard_popup_panel_background_holo.9.png b/java/res/drawable-xhdpi/keyboard_popup_panel_background_holo.9.png
index 1dee699..dde1856 100644
--- a/java/res/drawable-xhdpi/keyboard_popup_panel_background_holo.9.png
+++ b/java/res/drawable-xhdpi/keyboard_popup_panel_background_holo.9.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/keyboard_suggest_strip.9.png b/java/res/drawable-xhdpi/keyboard_suggest_strip_gb.9.png
similarity index 100%
rename from java/res/drawable-xhdpi/keyboard_suggest_strip.9.png
rename to java/res/drawable-xhdpi/keyboard_suggest_strip_gb.9.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/suggestions_strip_divider.png b/java/res/drawable-xhdpi/suggestions_strip_divider.png
index 0d8b984..4101ebc 100644
--- a/java/res/drawable-xhdpi/suggestions_strip_divider.png
+++ b/java/res/drawable-xhdpi/suggestions_strip_divider.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/sym_bkeyboard_delete.png b/java/res/drawable-xhdpi/sym_bkeyboard_delete.png
deleted file mode 100644
index b84ee76..0000000
--- a/java/res/drawable-xhdpi/sym_bkeyboard_delete.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xhdpi/sym_bkeyboard_label_mic.png b/java/res/drawable-xhdpi/sym_bkeyboard_label_mic.png
deleted file mode 100644
index 9bd1d65..0000000
--- a/java/res/drawable-xhdpi/sym_bkeyboard_label_mic.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xhdpi/sym_bkeyboard_mic.png b/java/res/drawable-xhdpi/sym_bkeyboard_mic.png
deleted file mode 100644
index 8c3f11d..0000000
--- a/java/res/drawable-xhdpi/sym_bkeyboard_mic.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xhdpi/sym_bkeyboard_return.png b/java/res/drawable-xhdpi/sym_bkeyboard_return.png
deleted file mode 100644
index 1632ecd..0000000
--- a/java/res/drawable-xhdpi/sym_bkeyboard_return.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xhdpi/sym_bkeyboard_search.png b/java/res/drawable-xhdpi/sym_bkeyboard_search.png
deleted file mode 100644
index 69d8b22..0000000
--- a/java/res/drawable-xhdpi/sym_bkeyboard_search.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xhdpi/sym_bkeyboard_settings.png b/java/res/drawable-xhdpi/sym_bkeyboard_settings.png
deleted file mode 100644
index 050154a..0000000
--- a/java/res/drawable-xhdpi/sym_bkeyboard_settings.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xhdpi/sym_bkeyboard_shift.png b/java/res/drawable-xhdpi/sym_bkeyboard_shift.png
deleted file mode 100644
index d15d11a..0000000
--- a/java/res/drawable-xhdpi/sym_bkeyboard_shift.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xhdpi/sym_bkeyboard_shift_locked.png b/java/res/drawable-xhdpi/sym_bkeyboard_shift_locked.png
deleted file mode 100644
index 83b287f..0000000
--- a/java/res/drawable-xhdpi/sym_bkeyboard_shift_locked.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xhdpi/sym_bkeyboard_space.png b/java/res/drawable-xhdpi/sym_bkeyboard_space.png
deleted file mode 100644
index 5ca62c7..0000000
--- a/java/res/drawable-xhdpi/sym_bkeyboard_space.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xhdpi/sym_bkeyboard_tab.png b/java/res/drawable-xhdpi/sym_bkeyboard_tab.png
deleted file mode 100644
index 6ca1997..0000000
--- a/java/res/drawable-xhdpi/sym_bkeyboard_tab.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xhdpi/sym_bkeyboard_voice_off.png b/java/res/drawable-xhdpi/sym_bkeyboard_voice_off.png
deleted file mode 100644
index fc6a4eb..0000000
--- a/java/res/drawable-xhdpi/sym_bkeyboard_voice_off.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xhdpi/sym_keyboard_delete.png b/java/res/drawable-xhdpi/sym_keyboard_delete.png
deleted file mode 100644
index 3c0b8b1..0000000
--- a/java/res/drawable-xhdpi/sym_keyboard_delete.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xhdpi/sym_keyboard_delete_holo.png b/java/res/drawable-xhdpi/sym_keyboard_delete_holo.png
deleted file mode 100644
index 354c09e..0000000
--- a/java/res/drawable-xhdpi/sym_keyboard_delete_holo.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xhdpi/sym_keyboard_delete_holo_dark.png b/java/res/drawable-xhdpi/sym_keyboard_delete_holo_dark.png
new file mode 100644
index 0000000..e3e37d5
--- /dev/null
+++ b/java/res/drawable-xhdpi/sym_keyboard_delete_holo_dark.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/sym_keyboard_label_mic.png b/java/res/drawable-xhdpi/sym_keyboard_label_mic.png
deleted file mode 100644
index 49810a0..0000000
--- a/java/res/drawable-xhdpi/sym_keyboard_label_mic.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xhdpi/sym_keyboard_label_mic_holo.png b/java/res/drawable-xhdpi/sym_keyboard_label_mic_holo.png
deleted file mode 100644
index 8eeb179..0000000
--- a/java/res/drawable-xhdpi/sym_keyboard_label_mic_holo.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xhdpi/sym_keyboard_language_switch.png b/java/res/drawable-xhdpi/sym_keyboard_language_switch.png
deleted file mode 100644
index 6c2fb53..0000000
--- a/java/res/drawable-xhdpi/sym_keyboard_language_switch.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xhdpi/sym_keyboard_language_switch_dark.png b/java/res/drawable-xhdpi/sym_keyboard_language_switch_dark.png
new file mode 100644
index 0000000..b8687f5
--- /dev/null
+++ b/java/res/drawable-xhdpi/sym_keyboard_language_switch_dark.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/sym_keyboard_mic.png b/java/res/drawable-xhdpi/sym_keyboard_mic.png
deleted file mode 100644
index 1323b6d..0000000
--- a/java/res/drawable-xhdpi/sym_keyboard_mic.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xhdpi/sym_keyboard_mic_holo_dark.png b/java/res/drawable-xhdpi/sym_keyboard_mic_holo_dark.png
new file mode 100644
index 0000000..566ba1f
--- /dev/null
+++ b/java/res/drawable-xhdpi/sym_keyboard_mic_holo_dark.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/sym_keyboard_return.png b/java/res/drawable-xhdpi/sym_keyboard_return.png
deleted file mode 100644
index ad06122..0000000
--- a/java/res/drawable-xhdpi/sym_keyboard_return.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xhdpi/sym_keyboard_return_holo.png b/java/res/drawable-xhdpi/sym_keyboard_return_holo.png
deleted file mode 100644
index ba424ad..0000000
--- a/java/res/drawable-xhdpi/sym_keyboard_return_holo.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xhdpi/sym_keyboard_return_holo_dark.png b/java/res/drawable-xhdpi/sym_keyboard_return_holo_dark.png
new file mode 100644
index 0000000..7b7ad17
--- /dev/null
+++ b/java/res/drawable-xhdpi/sym_keyboard_return_holo_dark.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/sym_keyboard_search.png b/java/res/drawable-xhdpi/sym_keyboard_search.png
deleted file mode 100644
index aa785a2..0000000
--- a/java/res/drawable-xhdpi/sym_keyboard_search.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xhdpi/sym_keyboard_search_holo.png b/java/res/drawable-xhdpi/sym_keyboard_search_holo.png
deleted file mode 100644
index f2fb2a2..0000000
--- a/java/res/drawable-xhdpi/sym_keyboard_search_holo.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xhdpi/sym_keyboard_search_holo_dark.png b/java/res/drawable-xhdpi/sym_keyboard_search_holo_dark.png
new file mode 100644
index 0000000..36b1646
--- /dev/null
+++ b/java/res/drawable-xhdpi/sym_keyboard_search_holo_dark.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/sym_keyboard_settings.png b/java/res/drawable-xhdpi/sym_keyboard_settings.png
deleted file mode 100644
index 5070425..0000000
--- a/java/res/drawable-xhdpi/sym_keyboard_settings.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xhdpi/sym_keyboard_settings_holo.png b/java/res/drawable-xhdpi/sym_keyboard_settings_holo.png
deleted file mode 100644
index 99ee97d..0000000
--- a/java/res/drawable-xhdpi/sym_keyboard_settings_holo.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xhdpi/sym_keyboard_settings_holo_dark.png b/java/res/drawable-xhdpi/sym_keyboard_settings_holo_dark.png
new file mode 100644
index 0000000..05eaffe
--- /dev/null
+++ b/java/res/drawable-xhdpi/sym_keyboard_settings_holo_dark.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/sym_keyboard_shift.png b/java/res/drawable-xhdpi/sym_keyboard_shift.png
deleted file mode 100644
index 2901706..0000000
--- a/java/res/drawable-xhdpi/sym_keyboard_shift.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xhdpi/sym_keyboard_shift_holo.png b/java/res/drawable-xhdpi/sym_keyboard_shift_holo.png
deleted file mode 100644
index 1046b45..0000000
--- a/java/res/drawable-xhdpi/sym_keyboard_shift_holo.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xhdpi/sym_keyboard_shift_holo_dark.png b/java/res/drawable-xhdpi/sym_keyboard_shift_holo_dark.png
new file mode 100644
index 0000000..5ab5491
--- /dev/null
+++ b/java/res/drawable-xhdpi/sym_keyboard_shift_holo_dark.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/sym_keyboard_shift_locked.png b/java/res/drawable-xhdpi/sym_keyboard_shift_locked.png
deleted file mode 100644
index a5deb60..0000000
--- a/java/res/drawable-xhdpi/sym_keyboard_shift_locked.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xhdpi/sym_keyboard_shift_locked_holo.png b/java/res/drawable-xhdpi/sym_keyboard_shift_locked_holo.png
deleted file mode 100644
index 6acb565..0000000
--- a/java/res/drawable-xhdpi/sym_keyboard_shift_locked_holo.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xhdpi/sym_keyboard_shift_locked_holo_dark.png b/java/res/drawable-xhdpi/sym_keyboard_shift_locked_holo_dark.png
new file mode 100644
index 0000000..b820eaa
--- /dev/null
+++ b/java/res/drawable-xhdpi/sym_keyboard_shift_locked_holo_dark.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/sym_keyboard_smiley_holo_dark.png b/java/res/drawable-xhdpi/sym_keyboard_smiley_holo_dark.png
new file mode 100644
index 0000000..686831f
--- /dev/null
+++ b/java/res/drawable-xhdpi/sym_keyboard_smiley_holo_dark.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/sym_keyboard_space_holo.png b/java/res/drawable-xhdpi/sym_keyboard_space_holo.png
deleted file mode 100644
index 504a3ed..0000000
--- a/java/res/drawable-xhdpi/sym_keyboard_space_holo.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xhdpi/sym_keyboard_space_holo_dark.png b/java/res/drawable-xhdpi/sym_keyboard_space_holo_dark.png
new file mode 100644
index 0000000..7114b74
--- /dev/null
+++ b/java/res/drawable-xhdpi/sym_keyboard_space_holo_dark.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/sym_keyboard_space_led.9.png b/java/res/drawable-xhdpi/sym_keyboard_space_led_gb.9.png
similarity index 100%
rename from java/res/drawable-xhdpi/sym_keyboard_space_led.9.png
rename to java/res/drawable-xhdpi/sym_keyboard_space_led_gb.9.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/sym_keyboard_tab.png b/java/res/drawable-xhdpi/sym_keyboard_tab.png
deleted file mode 100644
index 0ef2ab5..0000000
--- a/java/res/drawable-xhdpi/sym_keyboard_tab.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xhdpi/sym_keyboard_tab_holo.png b/java/res/drawable-xhdpi/sym_keyboard_tab_holo.png
deleted file mode 100644
index ff380ee..0000000
--- a/java/res/drawable-xhdpi/sym_keyboard_tab_holo.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xhdpi/sym_keyboard_tab_holo_dark.png b/java/res/drawable-xhdpi/sym_keyboard_tab_holo_dark.png
new file mode 100644
index 0000000..73ebfe5
--- /dev/null
+++ b/java/res/drawable-xhdpi/sym_keyboard_tab_holo_dark.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/sym_keyboard_voice_holo.png b/java/res/drawable-xhdpi/sym_keyboard_voice_holo.png
deleted file mode 100644
index b2bb9b8..0000000
--- a/java/res/drawable-xhdpi/sym_keyboard_voice_holo.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xhdpi/sym_keyboard_voice_holo_dark.png b/java/res/drawable-xhdpi/sym_keyboard_voice_holo_dark.png
new file mode 100644
index 0000000..944a852
--- /dev/null
+++ b/java/res/drawable-xhdpi/sym_keyboard_voice_holo_dark.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/sym_keyboard_voice_off_holo.png b/java/res/drawable-xhdpi/sym_keyboard_voice_off_holo.png
deleted file mode 100644
index 23e75bf..0000000
--- a/java/res/drawable-xhdpi/sym_keyboard_voice_off_holo.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xhdpi/sym_keyboard_voice_off_holo_dark.png b/java/res/drawable-xhdpi/sym_keyboard_voice_off_holo_dark.png
new file mode 100644
index 0000000..2016caf
--- /dev/null
+++ b/java/res/drawable-xhdpi/sym_keyboard_voice_off_holo_dark.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/sym_keyboard_zwj_holo.png b/java/res/drawable-xhdpi/sym_keyboard_zwj_holo.png
deleted file mode 100644
index 2669427..0000000
--- a/java/res/drawable-xhdpi/sym_keyboard_zwj_holo.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xhdpi/sym_keyboard_zwj_holo_dark.png b/java/res/drawable-xhdpi/sym_keyboard_zwj_holo_dark.png
new file mode 100644
index 0000000..2f9607a
--- /dev/null
+++ b/java/res/drawable-xhdpi/sym_keyboard_zwj_holo_dark.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/sym_keyboard_zwnj_holo.png b/java/res/drawable-xhdpi/sym_keyboard_zwnj_holo.png
deleted file mode 100644
index 75a22b6..0000000
--- a/java/res/drawable-xhdpi/sym_keyboard_zwnj_holo.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xhdpi/sym_keyboard_zwnj_holo_dark.png b/java/res/drawable-xhdpi/sym_keyboard_zwnj_holo_dark.png
new file mode 100644
index 0000000..ab07f75
--- /dev/null
+++ b/java/res/drawable-xhdpi/sym_keyboard_zwnj_holo_dark.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/tab_selected.9.png b/java/res/drawable-xhdpi/tab_selected.9.png
new file mode 100644
index 0000000..95e5f43
--- /dev/null
+++ b/java/res/drawable-xhdpi/tab_selected.9.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/tab_unselected.9.png b/java/res/drawable-xhdpi/tab_unselected.9.png
new file mode 100644
index 0000000..8cede8d
--- /dev/null
+++ b/java/res/drawable-xhdpi/tab_unselected.9.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/btn_keyboard_key_dark_active_holo.9.png b/java/res/drawable-xxhdpi/btn_keyboard_key_dark_active_holo.9.png
index 680421e..b35c29f 100644
--- a/java/res/drawable-xxhdpi/btn_keyboard_key_dark_active_holo.9.png
+++ b/java/res/drawable-xxhdpi/btn_keyboard_key_dark_active_holo.9.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/btn_keyboard_key_dark_normal_holo.9.png b/java/res/drawable-xxhdpi/btn_keyboard_key_dark_normal_holo.9.png
index ae26750..17f0a7a 100644
--- a/java/res/drawable-xxhdpi/btn_keyboard_key_dark_normal_holo.9.png
+++ b/java/res/drawable-xxhdpi/btn_keyboard_key_dark_normal_holo.9.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/btn_keyboard_key_dark_normal_off_holo.9.png b/java/res/drawable-xxhdpi/btn_keyboard_key_dark_normal_off_holo.9.png
index c92a669..b0e815e 100644
--- a/java/res/drawable-xxhdpi/btn_keyboard_key_dark_normal_off_holo.9.png
+++ b/java/res/drawable-xxhdpi/btn_keyboard_key_dark_normal_off_holo.9.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/btn_keyboard_key_dark_normal_on_holo.9.png b/java/res/drawable-xxhdpi/btn_keyboard_key_dark_normal_on_holo.9.png
index 40f5011..97f9625 100644
--- a/java/res/drawable-xxhdpi/btn_keyboard_key_dark_normal_on_holo.9.png
+++ b/java/res/drawable-xxhdpi/btn_keyboard_key_dark_normal_on_holo.9.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/btn_keyboard_key_dark_pressed_holo.9.png b/java/res/drawable-xxhdpi/btn_keyboard_key_dark_pressed_holo.9.png
index 6ff6319..dfb16a7 100644
--- a/java/res/drawable-xxhdpi/btn_keyboard_key_dark_pressed_holo.9.png
+++ b/java/res/drawable-xxhdpi/btn_keyboard_key_dark_pressed_holo.9.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/btn_keyboard_key_dark_pressed_off_holo.9.png b/java/res/drawable-xxhdpi/btn_keyboard_key_dark_pressed_off_holo.9.png
index 818ea70..bf1d346 100644
--- a/java/res/drawable-xxhdpi/btn_keyboard_key_dark_pressed_off_holo.9.png
+++ b/java/res/drawable-xxhdpi/btn_keyboard_key_dark_pressed_off_holo.9.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/btn_keyboard_key_dark_pressed_on_holo.9.png b/java/res/drawable-xxhdpi/btn_keyboard_key_dark_pressed_on_holo.9.png
index a476d2a..9622771 100644
--- a/java/res/drawable-xxhdpi/btn_keyboard_key_dark_pressed_on_holo.9.png
+++ b/java/res/drawable-xxhdpi/btn_keyboard_key_dark_pressed_on_holo.9.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/btn_keyboard_key_light_normal_holo.9.png b/java/res/drawable-xxhdpi/btn_keyboard_key_light_normal_holo.9.png
index 9c280a6..4ddfdcb 100644
--- a/java/res/drawable-xxhdpi/btn_keyboard_key_light_normal_holo.9.png
+++ b/java/res/drawable-xxhdpi/btn_keyboard_key_light_normal_holo.9.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/btn_keyboard_key_light_pressed_holo.9.png b/java/res/drawable-xxhdpi/btn_keyboard_key_light_pressed_holo.9.png
index 3c17c5e..17144b6 100644
--- a/java/res/drawable-xxhdpi/btn_keyboard_key_light_pressed_holo.9.png
+++ b/java/res/drawable-xxhdpi/btn_keyboard_key_light_pressed_holo.9.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/btn_keyboard_key_popup_selected_holo.9.png b/java/res/drawable-xxhdpi/btn_keyboard_key_popup_selected_holo.9.png
index 6d2af59..0cbb2ec 100644
--- a/java/res/drawable-xxhdpi/btn_keyboard_key_popup_selected_holo.9.png
+++ b/java/res/drawable-xxhdpi/btn_keyboard_key_popup_selected_holo.9.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/ic_emoji_nature_light_activated.png b/java/res/drawable-xxhdpi/ic_emoji_nature_light_activated.png
new file mode 100644
index 0000000..470fd69
--- /dev/null
+++ b/java/res/drawable-xxhdpi/ic_emoji_nature_light_activated.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/ic_emoji_nature_light_normal.png b/java/res/drawable-xxhdpi/ic_emoji_nature_light_normal.png
new file mode 100644
index 0000000..a7fde0e
--- /dev/null
+++ b/java/res/drawable-xxhdpi/ic_emoji_nature_light_normal.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/ic_emoji_objects_light_activated.png b/java/res/drawable-xxhdpi/ic_emoji_objects_light_activated.png
new file mode 100644
index 0000000..c582b70
--- /dev/null
+++ b/java/res/drawable-xxhdpi/ic_emoji_objects_light_activated.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/ic_emoji_objects_light_normal.png b/java/res/drawable-xxhdpi/ic_emoji_objects_light_normal.png
new file mode 100644
index 0000000..acc95d7
--- /dev/null
+++ b/java/res/drawable-xxhdpi/ic_emoji_objects_light_normal.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/ic_emoji_people_light_activated.png b/java/res/drawable-xxhdpi/ic_emoji_people_light_activated.png
new file mode 100644
index 0000000..5973ac3
--- /dev/null
+++ b/java/res/drawable-xxhdpi/ic_emoji_people_light_activated.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/ic_emoji_people_light_normal.png b/java/res/drawable-xxhdpi/ic_emoji_people_light_normal.png
new file mode 100644
index 0000000..22e06f8
--- /dev/null
+++ b/java/res/drawable-xxhdpi/ic_emoji_people_light_normal.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/ic_emoji_places_light_activated.png b/java/res/drawable-xxhdpi/ic_emoji_places_light_activated.png
new file mode 100644
index 0000000..690e95f
--- /dev/null
+++ b/java/res/drawable-xxhdpi/ic_emoji_places_light_activated.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/ic_emoji_places_light_normal.png b/java/res/drawable-xxhdpi/ic_emoji_places_light_normal.png
new file mode 100644
index 0000000..ced4b08
--- /dev/null
+++ b/java/res/drawable-xxhdpi/ic_emoji_places_light_normal.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/ic_emoji_recent_light_activated.png b/java/res/drawable-xxhdpi/ic_emoji_recent_light_activated.png
new file mode 100644
index 0000000..25e847e
--- /dev/null
+++ b/java/res/drawable-xxhdpi/ic_emoji_recent_light_activated.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/ic_emoji_recent_light_normal.png b/java/res/drawable-xxhdpi/ic_emoji_recent_light_normal.png
new file mode 100644
index 0000000..c86368d
--- /dev/null
+++ b/java/res/drawable-xxhdpi/ic_emoji_recent_light_normal.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/ic_emoji_symbols_light_activated.png b/java/res/drawable-xxhdpi/ic_emoji_symbols_light_activated.png
new file mode 100644
index 0000000..29dfc71
--- /dev/null
+++ b/java/res/drawable-xxhdpi/ic_emoji_symbols_light_activated.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/ic_emoji_symbols_light_normal.png b/java/res/drawable-xxhdpi/ic_emoji_symbols_light_normal.png
new file mode 100644
index 0000000..0570567
--- /dev/null
+++ b/java/res/drawable-xxhdpi/ic_emoji_symbols_light_normal.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/ic_ime_switcher_dark.png b/java/res/drawable-xxhdpi/ic_ime_switcher_dark.png
new file mode 100644
index 0000000..f99f7d0
--- /dev/null
+++ b/java/res/drawable-xxhdpi/ic_ime_switcher_dark.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/ic_subtype_keyboard.png b/java/res/drawable-xxhdpi/ic_subtype_keyboard.png
deleted file mode 100644
index 0bb4283..0000000
--- a/java/res/drawable-xxhdpi/ic_subtype_keyboard.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xxhdpi/ic_subtype_mic_dark.png b/java/res/drawable-xxhdpi/ic_subtype_mic_dark.png
new file mode 100644
index 0000000..811103a
--- /dev/null
+++ b/java/res/drawable-xxhdpi/ic_subtype_mic_dark.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/keyboard_key_feedback_background_holo.9.png b/java/res/drawable-xxhdpi/keyboard_key_feedback_background_holo.9.png
index bd1ef3c..11eee94 100644
--- a/java/res/drawable-xxhdpi/keyboard_key_feedback_background_holo.9.png
+++ b/java/res/drawable-xxhdpi/keyboard_key_feedback_background_holo.9.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/keyboard_key_feedback_left_background_holo.9.png b/java/res/drawable-xxhdpi/keyboard_key_feedback_left_background_holo.9.png
index 65af4b5..2079e04 100644
--- a/java/res/drawable-xxhdpi/keyboard_key_feedback_left_background_holo.9.png
+++ b/java/res/drawable-xxhdpi/keyboard_key_feedback_left_background_holo.9.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/keyboard_key_feedback_left_more_background_holo.9.png b/java/res/drawable-xxhdpi/keyboard_key_feedback_left_more_background_holo.9.png
index ac6750d..c4178d9 100644
--- a/java/res/drawable-xxhdpi/keyboard_key_feedback_left_more_background_holo.9.png
+++ b/java/res/drawable-xxhdpi/keyboard_key_feedback_left_more_background_holo.9.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/keyboard_key_feedback_more_background_holo.9.png b/java/res/drawable-xxhdpi/keyboard_key_feedback_more_background_holo.9.png
index cea7c05..121411a 100644
--- a/java/res/drawable-xxhdpi/keyboard_key_feedback_more_background_holo.9.png
+++ b/java/res/drawable-xxhdpi/keyboard_key_feedback_more_background_holo.9.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/keyboard_key_feedback_right_background_holo.9.png b/java/res/drawable-xxhdpi/keyboard_key_feedback_right_background_holo.9.png
index 520fa7c..d3d8733 100644
--- a/java/res/drawable-xxhdpi/keyboard_key_feedback_right_background_holo.9.png
+++ b/java/res/drawable-xxhdpi/keyboard_key_feedback_right_background_holo.9.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/keyboard_key_feedback_right_more_background_holo.9.png b/java/res/drawable-xxhdpi/keyboard_key_feedback_right_more_background_holo.9.png
index eee2217..d7ec8bc 100644
--- a/java/res/drawable-xxhdpi/keyboard_key_feedback_right_more_background_holo.9.png
+++ b/java/res/drawable-xxhdpi/keyboard_key_feedback_right_more_background_holo.9.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/keyboard_popup_panel_background_holo.9.png b/java/res/drawable-xxhdpi/keyboard_popup_panel_background_holo.9.png
index 721c244..ca576de 100644
--- a/java/res/drawable-xxhdpi/keyboard_popup_panel_background_holo.9.png
+++ b/java/res/drawable-xxhdpi/keyboard_popup_panel_background_holo.9.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/sym_keyboard_delete_holo.png b/java/res/drawable-xxhdpi/sym_keyboard_delete_holo.png
deleted file mode 100644
index be3cb7c..0000000
--- a/java/res/drawable-xxhdpi/sym_keyboard_delete_holo.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xxhdpi/sym_keyboard_delete_holo_dark.png b/java/res/drawable-xxhdpi/sym_keyboard_delete_holo_dark.png
new file mode 100644
index 0000000..92be792
--- /dev/null
+++ b/java/res/drawable-xxhdpi/sym_keyboard_delete_holo_dark.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/sym_keyboard_label_mic_holo.png b/java/res/drawable-xxhdpi/sym_keyboard_label_mic_holo.png
deleted file mode 100644
index b6d4477..0000000
--- a/java/res/drawable-xxhdpi/sym_keyboard_label_mic_holo.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xxhdpi/sym_keyboard_language_switch.png b/java/res/drawable-xxhdpi/sym_keyboard_language_switch.png
deleted file mode 100644
index 7cd0684..0000000
--- a/java/res/drawable-xxhdpi/sym_keyboard_language_switch.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xxhdpi/sym_keyboard_language_switch_dark.png b/java/res/drawable-xxhdpi/sym_keyboard_language_switch_dark.png
new file mode 100644
index 0000000..88b55bb
--- /dev/null
+++ b/java/res/drawable-xxhdpi/sym_keyboard_language_switch_dark.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/sym_keyboard_mic_holo_dark.png b/java/res/drawable-xxhdpi/sym_keyboard_mic_holo_dark.png
new file mode 100644
index 0000000..f55af30
--- /dev/null
+++ b/java/res/drawable-xxhdpi/sym_keyboard_mic_holo_dark.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/sym_keyboard_return_holo.png b/java/res/drawable-xxhdpi/sym_keyboard_return_holo.png
deleted file mode 100644
index 7d95807..0000000
--- a/java/res/drawable-xxhdpi/sym_keyboard_return_holo.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xxhdpi/sym_keyboard_return_holo_dark.png b/java/res/drawable-xxhdpi/sym_keyboard_return_holo_dark.png
new file mode 100644
index 0000000..46ee50e
--- /dev/null
+++ b/java/res/drawable-xxhdpi/sym_keyboard_return_holo_dark.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/sym_keyboard_search_holo.png b/java/res/drawable-xxhdpi/sym_keyboard_search_holo.png
deleted file mode 100644
index 6b09d8e..0000000
--- a/java/res/drawable-xxhdpi/sym_keyboard_search_holo.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xxhdpi/sym_keyboard_search_holo_dark.png b/java/res/drawable-xxhdpi/sym_keyboard_search_holo_dark.png
new file mode 100644
index 0000000..f518748
--- /dev/null
+++ b/java/res/drawable-xxhdpi/sym_keyboard_search_holo_dark.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/sym_keyboard_settings_holo.png b/java/res/drawable-xxhdpi/sym_keyboard_settings_holo.png
deleted file mode 100644
index 7041bb6..0000000
--- a/java/res/drawable-xxhdpi/sym_keyboard_settings_holo.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xxhdpi/sym_keyboard_settings_holo_dark.png b/java/res/drawable-xxhdpi/sym_keyboard_settings_holo_dark.png
new file mode 100644
index 0000000..e435846
--- /dev/null
+++ b/java/res/drawable-xxhdpi/sym_keyboard_settings_holo_dark.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/sym_keyboard_shift_holo.png b/java/res/drawable-xxhdpi/sym_keyboard_shift_holo.png
deleted file mode 100644
index 2b4fbbb..0000000
--- a/java/res/drawable-xxhdpi/sym_keyboard_shift_holo.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xxhdpi/sym_keyboard_shift_holo_dark.png b/java/res/drawable-xxhdpi/sym_keyboard_shift_holo_dark.png
new file mode 100644
index 0000000..523286e
--- /dev/null
+++ b/java/res/drawable-xxhdpi/sym_keyboard_shift_holo_dark.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/sym_keyboard_shift_locked_holo.png b/java/res/drawable-xxhdpi/sym_keyboard_shift_locked_holo.png
deleted file mode 100644
index 91c8603..0000000
--- a/java/res/drawable-xxhdpi/sym_keyboard_shift_locked_holo.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xxhdpi/sym_keyboard_shift_locked_holo_dark.png b/java/res/drawable-xxhdpi/sym_keyboard_shift_locked_holo_dark.png
new file mode 100644
index 0000000..87926d9
--- /dev/null
+++ b/java/res/drawable-xxhdpi/sym_keyboard_shift_locked_holo_dark.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/sym_keyboard_smiley_holo_dark.png b/java/res/drawable-xxhdpi/sym_keyboard_smiley_holo_dark.png
new file mode 100644
index 0000000..04b7216
--- /dev/null
+++ b/java/res/drawable-xxhdpi/sym_keyboard_smiley_holo_dark.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/sym_keyboard_space_holo.png b/java/res/drawable-xxhdpi/sym_keyboard_space_holo.png
deleted file mode 100644
index 65aa5ea..0000000
--- a/java/res/drawable-xxhdpi/sym_keyboard_space_holo.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xxhdpi/sym_keyboard_space_holo_dark.png b/java/res/drawable-xxhdpi/sym_keyboard_space_holo_dark.png
new file mode 100644
index 0000000..1dab1f4
--- /dev/null
+++ b/java/res/drawable-xxhdpi/sym_keyboard_space_holo_dark.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/sym_keyboard_tab_holo.png b/java/res/drawable-xxhdpi/sym_keyboard_tab_holo.png
deleted file mode 100644
index 1f4ae3d..0000000
--- a/java/res/drawable-xxhdpi/sym_keyboard_tab_holo.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xxhdpi/sym_keyboard_tab_holo_dark.png b/java/res/drawable-xxhdpi/sym_keyboard_tab_holo_dark.png
new file mode 100644
index 0000000..6eb3eb0
--- /dev/null
+++ b/java/res/drawable-xxhdpi/sym_keyboard_tab_holo_dark.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/sym_keyboard_voice_holo.png b/java/res/drawable-xxhdpi/sym_keyboard_voice_holo.png
deleted file mode 100644
index f04cadf..0000000
--- a/java/res/drawable-xxhdpi/sym_keyboard_voice_holo.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xxhdpi/sym_keyboard_voice_holo_dark.png b/java/res/drawable-xxhdpi/sym_keyboard_voice_holo_dark.png
new file mode 100644
index 0000000..6809f07
--- /dev/null
+++ b/java/res/drawable-xxhdpi/sym_keyboard_voice_holo_dark.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/sym_keyboard_voice_off_holo.png b/java/res/drawable-xxhdpi/sym_keyboard_voice_off_holo.png
deleted file mode 100644
index e74d523..0000000
--- a/java/res/drawable-xxhdpi/sym_keyboard_voice_off_holo.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xxhdpi/sym_keyboard_voice_off_holo_dark.png b/java/res/drawable-xxhdpi/sym_keyboard_voice_off_holo_dark.png
new file mode 100644
index 0000000..6bd506a
--- /dev/null
+++ b/java/res/drawable-xxhdpi/sym_keyboard_voice_off_holo_dark.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/sym_keyboard_zwj_holo.png b/java/res/drawable-xxhdpi/sym_keyboard_zwj_holo.png
deleted file mode 100644
index 85289b2..0000000
--- a/java/res/drawable-xxhdpi/sym_keyboard_zwj_holo.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xxhdpi/sym_keyboard_zwj_holo_dark.png b/java/res/drawable-xxhdpi/sym_keyboard_zwj_holo_dark.png
new file mode 100644
index 0000000..5e225b8
--- /dev/null
+++ b/java/res/drawable-xxhdpi/sym_keyboard_zwj_holo_dark.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/sym_keyboard_zwnj_holo.png b/java/res/drawable-xxhdpi/sym_keyboard_zwnj_holo.png
deleted file mode 100644
index e610678..0000000
--- a/java/res/drawable-xxhdpi/sym_keyboard_zwnj_holo.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xxhdpi/sym_keyboard_zwnj_holo_dark.png b/java/res/drawable-xxhdpi/sym_keyboard_zwnj_holo_dark.png
new file mode 100644
index 0000000..cdfc029
--- /dev/null
+++ b/java/res/drawable-xxhdpi/sym_keyboard_zwnj_holo_dark.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/tab_selected.9.png b/java/res/drawable-xxhdpi/tab_selected.9.png
new file mode 100644
index 0000000..e5efc58
--- /dev/null
+++ b/java/res/drawable-xxhdpi/tab_selected.9.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/tab_unselected.9.png b/java/res/drawable-xxhdpi/tab_unselected.9.png
new file mode 100644
index 0000000..3891886
--- /dev/null
+++ b/java/res/drawable-xxhdpi/tab_unselected.9.png
Binary files differ
diff --git a/java/res/drawable/btn_center.xml b/java/res/drawable/btn_center.xml
deleted file mode 100644
index 3ac2129..0000000
--- a/java/res/drawable/btn_center.xml
+++ /dev/null
@@ -1,40 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2011, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<selector
-        xmlns:android="http://schemas.android.com/apk/res/android"
-        android:exitFadeDuration="@android:integer/config_mediumAnimTime">
-    <item
-        android:state_window_focused="false"
-        android:state_enabled="true"
-        android:drawable="@drawable/btn_center_default" />
-    <item
-        android:state_pressed="true"
-        android:drawable="@drawable/btn_center_pressed" />
-    <item
-        android:state_focused="true"
-        android:state_enabled="true"
-        android:drawable="@drawable/btn_center_selected" />
-    <item
-        android:state_enabled="true"
-        android:drawable="@drawable/btn_center_default" />
-    <item
-        android:drawable="@drawable/btn_center_default" />
-</selector>
diff --git a/java/res/drawable/btn_keyboard_key.xml b/java/res/drawable/btn_keyboard_key.xml
deleted file mode 100644
index 797bc10..0000000
--- a/java/res/drawable/btn_keyboard_key.xml
+++ /dev/null
@@ -1,38 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2008 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.
--->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-
-    <!-- Toggle keys. Use checkable/checked state. -->
-
-    <item android:state_checkable="true" android:state_checked="true"
-          android:state_pressed="true"
-          android:drawable="@drawable/btn_keyboard_key_pressed_on" />
-    <item android:state_checkable="true" android:state_pressed="true"
-          android:drawable="@drawable/btn_keyboard_key_pressed_off" />
-    <item android:state_checkable="true" android:state_checked="true"
-          android:drawable="@drawable/btn_keyboard_key_normal_on" />
-    <item android:state_checkable="true"
-          android:drawable="@drawable/btn_keyboard_key_normal_off" />
-
-    <!-- Normal keys -->
-
-    <item android:state_pressed="true"
-          android:drawable="@drawable/btn_keyboard_key_pressed" />
-    <item
-          android:drawable="@drawable/btn_keyboard_key_normal" />
-
-</selector>
diff --git a/java/res/drawable/btn_keyboard_key3.xml b/java/res/drawable/btn_keyboard_key3.xml
deleted file mode 100644
index dbe82d5..0000000
--- a/java/res/drawable/btn_keyboard_key3.xml
+++ /dev/null
@@ -1,36 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2010 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.
--->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-
-    <!-- Toggle keys. Use checkable/checked state. -->
-
-    <item android:state_checkable="true" android:state_checked="true"
-          android:state_pressed="true"
-          android:drawable="@drawable/btn_keyboard_key_pressed_on" />
-    <item android:state_checkable="true" android:state_pressed="true"
-          android:drawable="@drawable/btn_keyboard_key_fulltrans_normal" />
-    <item android:state_checkable="true" android:state_checked="true"
-          android:drawable="@drawable/btn_keyboard_key_normal_on" />
-    <item android:state_checkable="true"
-          android:drawable="@drawable/btn_keyboard_key_fulltrans_pressed" />
-
-    <!-- Normal keys -->
-
-    <item android:state_pressed="true"
-          android:drawable="@drawable/btn_keyboard_key_fulltrans_normal" />
-    <item android:drawable="@drawable/btn_keyboard_key_fulltrans_pressed" />
-</selector>
diff --git a/java/res/drawable/btn_keyboard_key_functional_gb.xml b/java/res/drawable/btn_keyboard_key_functional_gb.xml
new file mode 100644
index 0000000..431359c
--- /dev/null
+++ b/java/res/drawable/btn_keyboard_key_functional_gb.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <!-- Functional keys. -->
+    <item android:state_pressed="true"
+          android:drawable="@drawable/btn_keyboard_key_dark_pressed" />
+    <item android:drawable="@drawable/btn_keyboard_key_dark_normal" />
+</selector>
diff --git a/java/res/drawable/btn_keyboard_key_functional_ics.xml b/java/res/drawable/btn_keyboard_key_functional_ics.xml
new file mode 100644
index 0000000..5dcde5f
--- /dev/null
+++ b/java/res/drawable/btn_keyboard_key_functional_ics.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <!-- Functional keys. -->
+    <item android:state_pressed="true"
+          android:drawable="@drawable/btn_keyboard_key_dark_pressed_holo" />
+    <item android:drawable="@drawable/btn_keyboard_key_dark_normal_holo" />
+</selector>
diff --git a/java/res/drawable/btn_keyboard_key_gingerbread.xml b/java/res/drawable/btn_keyboard_key_gb.xml
similarity index 94%
rename from java/res/drawable/btn_keyboard_key_gingerbread.xml
rename to java/res/drawable/btn_keyboard_key_gb.xml
index 5b4399e..3fc253e 100644
--- a/java/res/drawable/btn_keyboard_key_gingerbread.xml
+++ b/java/res/drawable/btn_keyboard_key_gb.xml
@@ -15,23 +15,19 @@
 -->
 
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
-
     <!-- Functional keys. -->
-
     <item android:state_single="true" android:state_pressed="true"
           android:drawable="@drawable/btn_keyboard_key_dark_pressed" />
     <item android:state_single="true"
           android:drawable="@drawable/btn_keyboard_key_dark_normal" />
 
     <!-- Action keys. -->
-
     <item android:state_active="true" android:state_pressed="true"
           android:drawable="@drawable/btn_keyboard_key_dark_pressed" />
     <item android:state_active="true"
           android:drawable="@drawable/btn_keyboard_key_dark_normal" />
 
     <!-- Toggle keys. Use checkable/checked state. -->
-
     <item android:state_checkable="true" android:state_checked="true" android:state_pressed="true"
           android:drawable="@drawable/btn_keyboard_key_dark_pressed_on" />
     <item android:state_checkable="true" android:state_pressed="true"
@@ -41,8 +37,11 @@
     <item android:state_checkable="true"
           android:drawable="@drawable/btn_keyboard_key_dark_normal_off" />
 
-    <!-- Normal keys. -->
+    <!-- Empty background keys. -->
+    <item android:state_empty="true"
+          android:drawable="@drawable/transparent" />
 
+    <!-- Normal keys. -->
     <item android:state_pressed="true"
           android:drawable="@drawable/btn_keyboard_key_light_pressed" />
     <item android:drawable="@drawable/btn_keyboard_key_light_normal" />
diff --git a/java/res/drawable/btn_keyboard_key_ics.xml b/java/res/drawable/btn_keyboard_key_ics.xml
index e893da1..0c86e16 100644
--- a/java/res/drawable/btn_keyboard_key_ics.xml
+++ b/java/res/drawable/btn_keyboard_key_ics.xml
@@ -15,23 +15,19 @@
 -->
 
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
-
     <!-- Functional keys. -->
-
     <item android:state_single="true" android:state_pressed="true"
           android:drawable="@drawable/btn_keyboard_key_dark_pressed_holo" />
     <item android:state_single="true"
           android:drawable="@drawable/btn_keyboard_key_dark_normal_holo" />
 
     <!-- Action keys. -->
-
     <item android:state_active="true" android:state_pressed="true"
           android:drawable="@drawable/btn_keyboard_key_dark_pressed_holo" />
     <item android:state_active="true"
           android:drawable="@drawable/btn_keyboard_key_dark_active_holo" />
 
     <!-- Toggle keys. Use checkable/checked state. -->
-
     <item android:state_checkable="true" android:state_checked="true" android:state_pressed="true"
           android:drawable="@drawable/btn_keyboard_key_dark_pressed_on_holo" />
     <item android:state_checkable="true" android:state_pressed="true"
@@ -41,8 +37,11 @@
     <item android:state_checkable="true"
           android:drawable="@drawable/btn_keyboard_key_dark_normal_off_holo" />
 
-    <!-- Normal keys. -->
+    <!-- Empty background keys. -->
+    <item android:state_empty="true"
+          android:drawable="@drawable/transparent" />
 
+    <!-- Normal keys. -->
     <item android:state_pressed="true"
           android:drawable="@drawable/btn_keyboard_key_light_pressed_holo" />
     <item android:drawable="@drawable/btn_keyboard_key_light_normal_holo" />
diff --git a/java/res/drawable/btn_keyboard_key_popup.xml b/java/res/drawable/btn_keyboard_key_popup_gb.xml
similarity index 100%
rename from java/res/drawable/btn_keyboard_key_popup.xml
rename to java/res/drawable/btn_keyboard_key_popup_gb.xml
diff --git a/java/res/drawable/btn_keyboard_key_stone.xml b/java/res/drawable/btn_keyboard_key_stone.xml
deleted file mode 100644
index 9bc3f18..0000000
--- a/java/res/drawable/btn_keyboard_key_stone.xml
+++ /dev/null
@@ -1,50 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2010 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.
--->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-
-    <!-- Functional keys. -->
-
-    <item android:state_single="true" android:state_pressed="true"
-          android:drawable="@drawable/btn_keyboard_key_fulltrans_pressed" />
-    <item android:state_single="true"
-          android:drawable="@drawable/btn_keyboard_key_normal_stone" />
-
-    <!-- Action keys. -->
-
-    <item android:state_active="true" android:state_pressed="true"
-          android:drawable="@drawable/btn_keyboard_key_fulltrans_pressed" />
-    <item android:state_active="true"
-          android:drawable="@drawable/btn_keyboard_key_normal_stone" />
-
-    <!-- Toggle keys. Use checkable/checked state. -->
-
-    <item android:state_checkable="true" android:state_checked="true"
-          android:state_pressed="true"
-          android:drawable="@drawable/btn_keyboard_key_normal_on_stone" />
-    <item android:state_checkable="true" android:state_pressed="true"
-          android:drawable="@drawable/btn_keyboard_key_normal_off_stone" />
-    <item android:state_checkable="true" android:state_checked="true"
-          android:drawable="@drawable/btn_keyboard_key_normal_on_stone" />
-    <item android:state_checkable="true"
-          android:drawable="@drawable/btn_keyboard_key_normal_off_stone" />
-
-    <!-- Normal keys. -->
-
-    <item android:state_pressed="true"
-          android:drawable="@drawable/btn_keyboard_key_fulltrans_pressed" />
-    <item android:drawable="@drawable/btn_keyboard_key_normal_stone" />
-</selector>
diff --git a/java/res/drawable/btn_suggestion.xml b/java/res/drawable/btn_suggestion_gb.xml
similarity index 100%
rename from java/res/drawable/btn_suggestion.xml
rename to java/res/drawable/btn_suggestion_gb.xml
diff --git a/java/res/drawable/ic_emoji_nature_light.xml b/java/res/drawable/ic_emoji_nature_light.xml
new file mode 100644
index 0000000..543409e
--- /dev/null
+++ b/java/res/drawable/ic_emoji_nature_light.xml
@@ -0,0 +1,33 @@
+<?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.
+*/
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item
+        android:state_focused="true"
+        android:drawable="@drawable/ic_emoji_nature_light_activated" />
+    <item
+        android:state_pressed="true"
+        android:drawable="@drawable/ic_emoji_nature_light_activated" />
+    <item
+        android:state_selected="true"
+        android:drawable="@drawable/ic_emoji_nature_light_activated" />
+    <item
+        android:drawable="@drawable/ic_emoji_nature_light_normal" />
+</selector>
diff --git a/java/res/drawable/ic_emoji_objects_light.xml b/java/res/drawable/ic_emoji_objects_light.xml
new file mode 100644
index 0000000..4096e69
--- /dev/null
+++ b/java/res/drawable/ic_emoji_objects_light.xml
@@ -0,0 +1,32 @@
+<?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.
+*/
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item
+        android:state_focused="true"
+        android:drawable="@drawable/ic_emoji_objects_light_activated" />
+    <item
+        android:state_pressed="true"
+        android:drawable="@drawable/ic_emoji_objects_light_activated" />
+    <item
+        android:state_selected="true"
+        android:drawable="@drawable/ic_emoji_objects_light_activated" />
+    <item android:drawable="@drawable/ic_emoji_objects_light_normal" />
+</selector>
diff --git a/java/res/drawable/ic_emoji_people_light.xml b/java/res/drawable/ic_emoji_people_light.xml
new file mode 100644
index 0000000..ea9e406
--- /dev/null
+++ b/java/res/drawable/ic_emoji_people_light.xml
@@ -0,0 +1,32 @@
+<?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.
+*/
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item
+        android:state_focused="true"
+        android:drawable="@drawable/ic_emoji_people_light_activated" />
+    <item
+        android:state_pressed="true"
+        android:drawable="@drawable/ic_emoji_people_light_activated" />
+    <item
+        android:state_selected="true"
+        android:drawable="@drawable/ic_emoji_people_light_activated" />
+    <item android:drawable="@drawable/ic_emoji_people_light_normal" />
+</selector>
diff --git a/java/res/drawable/ic_emoji_places_light.xml b/java/res/drawable/ic_emoji_places_light.xml
new file mode 100644
index 0000000..312cad9
--- /dev/null
+++ b/java/res/drawable/ic_emoji_places_light.xml
@@ -0,0 +1,32 @@
+<?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.
+*/
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item
+        android:state_focused="true"
+        android:drawable="@drawable/ic_emoji_places_light_activated" />
+    <item
+        android:state_pressed="true"
+        android:drawable="@drawable/ic_emoji_places_light_activated" />
+    <item
+        android:state_selected="true"
+        android:drawable="@drawable/ic_emoji_places_light_activated" />
+    <item android:drawable="@drawable/ic_emoji_places_light_normal" />
+</selector>
diff --git a/java/res/drawable/ic_emoji_recent_light.xml b/java/res/drawable/ic_emoji_recent_light.xml
new file mode 100644
index 0000000..8c2123f
--- /dev/null
+++ b/java/res/drawable/ic_emoji_recent_light.xml
@@ -0,0 +1,32 @@
+<?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.
+*/
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item
+        android:state_focused="true"
+        android:drawable="@drawable/ic_emoji_recent_light_activated" />
+    <item
+        android:state_pressed="true"
+        android:drawable="@drawable/ic_emoji_recent_light_activated" />
+    <item
+        android:state_selected="true"
+        android:drawable="@drawable/ic_emoji_recent_light_activated" />
+    <item android:drawable="@drawable/ic_emoji_recent_light_normal" />
+</selector>
diff --git a/java/res/drawable/ic_emoji_symbols_light.xml b/java/res/drawable/ic_emoji_symbols_light.xml
new file mode 100644
index 0000000..79aaf0f
--- /dev/null
+++ b/java/res/drawable/ic_emoji_symbols_light.xml
@@ -0,0 +1,32 @@
+<?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.
+*/
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item
+        android:state_focused="true"
+        android:drawable="@drawable/ic_emoji_symbols_light_activated" />
+    <item
+        android:state_pressed="true"
+        android:drawable="@drawable/ic_emoji_symbols_light_activated" />
+    <item
+        android:state_selected="true"
+        android:drawable="@drawable/ic_emoji_symbols_light_activated" />
+    <item android:drawable="@drawable/ic_emoji_symbols_light_normal" />
+</selector>
diff --git a/java/res/drawable/keyboard_key_feedback.xml b/java/res/drawable/keyboard_key_feedback_gb.xml
similarity index 100%
rename from java/res/drawable/keyboard_key_feedback.xml
rename to java/res/drawable/keyboard_key_feedback_gb.xml
diff --git a/java/res/layout/emoji_keyboard_page.xml b/java/res/layout/emoji_keyboard_page.xml
new file mode 100644
index 0000000..e0b752b
--- /dev/null
+++ b/java/res/layout/emoji_keyboard_page.xml
@@ -0,0 +1,33 @@
+<?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.
+*/
+-->
+
+<com.android.inputmethod.keyboard.internal.ScrollViewWithNotifier
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/emoji_keyboard_scroller"
+    android:clipToPadding="false"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+>
+    <com.android.inputmethod.keyboard.internal.ScrollKeyboardView
+        android:id="@+id/emoji_keyboard_page"
+        android:layoutDirection="ltr"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content" />
+</com.android.inputmethod.keyboard.internal.ScrollViewWithNotifier>
diff --git a/java/res/layout/key_preview.xml b/java/res/layout/emoji_keyboard_tab_icon.xml
similarity index 73%
copy from java/res/layout/key_preview.xml
copy to java/res/layout/emoji_keyboard_tab_icon.xml
index 2fcd0c4..d79276e 100644
--- a/java/res/layout/key_preview.xml
+++ b/java/res/layout/emoji_keyboard_tab_icon.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2010, The Android Open Source Project
+** 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.
@@ -18,10 +18,9 @@
 */
 -->
 
-<TextView xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="wrap_content"
+<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="0dip"
+    android:layout_weight="1.0"
     android:layout_height="wrap_content"
-    android:background="@drawable/keyboard_key_feedback"
-    android:minWidth="32dp"
     android:gravity="center"
 />
diff --git a/java/res/layout/key_preview.xml b/java/res/layout/emoji_keyboard_tab_label.xml
similarity index 80%
copy from java/res/layout/key_preview.xml
copy to java/res/layout/emoji_keyboard_tab_label.xml
index 2fcd0c4..62c552d 100644
--- a/java/res/layout/key_preview.xml
+++ b/java/res/layout/emoji_keyboard_tab_label.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2010, The Android Open Source Project
+** 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.
@@ -19,9 +19,8 @@
 -->
 
 <TextView xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="wrap_content"
+    android:layout_width="0dip"
+    android:layout_weight="1.0"
     android:layout_height="wrap_content"
-    android:background="@drawable/keyboard_key_feedback"
-    android:minWidth="32dp"
     android:gravity="center"
 />
diff --git a/java/res/layout/emoji_keyboard_view.xml b/java/res/layout/emoji_keyboard_view.xml
new file mode 100644
index 0000000..4566a5a
--- /dev/null
+++ b/java/res/layout/emoji_keyboard_view.xml
@@ -0,0 +1,110 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2013, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<com.android.inputmethod.keyboard.EmojiKeyboardView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/emoji_keyboard_view"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    style="?attr/emojiKeyboardViewStyle"
+>
+    <LinearLayout
+        android:orientation="horizontal"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/suggestions_strip_height"
+    >
+        <TabHost
+            android:id="@+id/emoji_category_tabhost"
+            android:layout_width="0dip"
+            android:layout_weight="87.5"
+            android:layout_height="match_parent"
+        >
+            <TabWidget
+                android:id="@android:id/tabs"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:background="@drawable/tab_selected"
+                android:divider="@null"
+                android:tabStripEnabled="true"
+                android:tabStripLeft="@drawable/tab_unselected"
+                android:tabStripRight="@drawable/tab_unselected" />
+            <FrameLayout
+                android:id="@android:id/tabcontent"
+                android:layout_width="0dip"
+                android:layout_height="0dip"
+            >
+                <!-- Empty placeholder that TabHost requires. But we don't use it to actually
+                     display anything. We monitor the tab changes and change the ViewPager.
+                     Similarly the ViewPager swipes are intercepted and passed to the TabHost. -->
+                <View
+                    android:id="@+id/emoji_keyboard_dummy"
+                    android:layout_width="0dip"
+                    android:layout_height="0dip"
+                    android:visibility="gone" />
+            </FrameLayout>
+        </TabHost>
+        <View
+            android:layout_width="2dip"
+            android:layout_height="match_parent"
+            android:background="@drawable/suggestions_strip_divider" />
+        <ImageButton
+            android:id="@+id/emoji_keyboard_delete"
+            android:layout_width="0dip"
+            android:layout_weight="12.5"
+            android:layout_height="match_parent"
+            android:background="@color/emoji_key_background_color"
+            android:src="@drawable/sym_keyboard_delete_holo_dark" />
+    </LinearLayout>
+    <android.support.v4.view.ViewPager
+        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"
+        android:layout_width="match_parent"
+        android:layout_height="0dip"
+        android:layout_weight="1"
+    >
+        <ImageButton
+            android:id="@+id/emoji_keyboard_alphabet"
+            android:layout_width="0dip"
+            android:layout_weight="0.15"
+            android:layout_height="match_parent"
+            android:src="@drawable/ic_ime_switcher_dark" />
+        <ImageButton
+            android:id="@+id/emoji_keyboard_space"
+            android:layout_width="0dip"
+            android:layout_weight="0.70"
+            android:layout_height="match_parent" />
+        <ImageButton
+            android:id="@+id/emoji_keyboard_send"
+            android:layout_width="0dip"
+            android:layout_weight="0.15"
+            android:layout_height="match_parent"
+            android:src="@drawable/sym_keyboard_return_holo_dark" />
+    </LinearLayout>
+</com.android.inputmethod.keyboard.EmojiKeyboardView>
diff --git a/java/res/layout/hint_add_to_dictionary.xml b/java/res/layout/hint_add_to_dictionary.xml
index 73de44f..68a9faf 100644
--- a/java/res/layout/hint_add_to_dictionary.xml
+++ b/java/res/layout/hint_add_to_dictionary.xml
@@ -33,4 +33,4 @@
     android:clickable="false"
     android:singleLine="true"
     android:ellipsize="none"
-    style="?attr/suggestionBackgroundStyle" />
+    style="?attr/suggestionWordStyle" />
diff --git a/java/res/layout/input_view.xml b/java/res/layout/input_view.xml
index 1ffb8a3..0b682d1 100644
--- a/java/res/layout/input_view.xml
+++ b/java/res/layout/input_view.xml
@@ -20,34 +20,41 @@
 
 <com.android.inputmethod.latin.InputView
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:orientation="vertical"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
->
+    android:gravity="bottom|center_horizontal"
+    android:orientation="vertical" >
     <!-- The height of key_preview_backing view will automatically be determined by code. -->
     <View
         android:id="@+id/key_preview_backing"
         android:layout_width="match_parent"
         android:layout_height="0dp" />
+    <LinearLayout
+        android:id="@+id/main_keyboard_frame"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="vertical" >
 
-    <!-- To ensure that key preview popup is correctly placed when the current system locale is
-         one of RTL locales, layoutDirection="ltr" is needed in the SDK version 17+. -->
-    <com.android.inputmethod.latin.suggestions.SuggestionStripView
-        android:id="@+id/suggestion_strip_view"
-        android:layoutDirection="ltr"
-        android:layout_width="match_parent"
-        android:layout_height="@dimen/suggestions_strip_height"
-        android:gravity="center_vertical"
-        android:paddingRight="@dimen/suggestions_strip_padding"
-        android:paddingLeft="@dimen/suggestions_strip_padding"
-        style="?attr/suggestionStripViewStyle" />
+        <!-- To ensure that key preview popup is correctly placed when the current system locale is
+             one of RTL locales, layoutDirection="ltr" is needed in the SDK version 17+. -->
+        <com.android.inputmethod.latin.suggestions.SuggestionStripView
+            android:id="@+id/suggestion_strip_view"
+            android:layoutDirection="ltr"
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/suggestions_strip_height"
+            android:gravity="center_vertical"
+            android:paddingRight="@dimen/suggestions_strip_padding"
+            android:paddingLeft="@dimen/suggestions_strip_padding"
+            style="?attr/suggestionStripViewStyle" />
 
-    <!-- To ensure that key preview popup is correctly placed when the current system locale is
-         one of RTL locales, layoutDirection="ltr" is needed in the SDK version 17+. -->
-    <com.android.inputmethod.keyboard.MainKeyboardView
-        android:id="@+id/keyboard_view"
-        android:layoutDirection="ltr"
-        android:layout_alignParentBottom="true"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content" />
+        <!-- To ensure that key preview popup is correctly placed when the current system locale is
+             one of RTL locales, layoutDirection="ltr" is needed in the SDK version 17+. -->
+        <com.android.inputmethod.keyboard.MainKeyboardView
+            android:id="@+id/keyboard_view"
+            android:layoutDirection="ltr"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content" />
+    </LinearLayout>
+    <include
+        layout="@layout/emoji_keyboard_view" />
 </com.android.inputmethod.latin.InputView>
diff --git a/java/res/layout/key_preview.xml b/java/res/layout/key_preview_gb.xml
similarity index 93%
rename from java/res/layout/key_preview.xml
rename to java/res/layout/key_preview_gb.xml
index 2fcd0c4..2f2a321 100644
--- a/java/res/layout/key_preview.xml
+++ b/java/res/layout/key_preview_gb.xml
@@ -21,7 +21,7 @@
 <TextView xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
-    android:background="@drawable/keyboard_key_feedback"
+    android:background="@drawable/keyboard_key_feedback_gb"
     android:minWidth="32dp"
     android:gravity="center"
 />
diff --git a/java/res/layout/more_keys_keyboard.xml b/java/res/layout/more_keys_keyboard.xml
index 6b2464b..6637117 100644
--- a/java/res/layout/more_keys_keyboard.xml
+++ b/java/res/layout/more_keys_keyboard.xml
@@ -17,17 +17,17 @@
 ** limitations under the License.
 */
 -->
+
 <LinearLayout
-        xmlns:android="http://schemas.android.com/apk/res/android"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:orientation="horizontal"
-        style="?attr/moreKeysKeyboardPanelStyle"
-        >
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:orientation="horizontal"
+    style="?attr/moreKeysKeyboardContainerStyle"
+>
     <com.android.inputmethod.keyboard.MoreKeysKeyboardView
-            xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-            android:id="@+id/more_keys_keyboard_view"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            />
+        xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+        android:id="@+id/more_keys_keyboard_view"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content" />
 </LinearLayout>
diff --git a/java/res/layout/more_suggestions.xml b/java/res/layout/more_suggestions.xml
index b41bb8a..8659f07 100644
--- a/java/res/layout/more_suggestions.xml
+++ b/java/res/layout/more_suggestions.xml
@@ -17,21 +17,21 @@
 ** limitations under the License.
 */
 -->
+
 <LinearLayout
-        xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:orientation="horizontal"
+    style="?attr/moreKeysKeyboardContainerStyle"
+>
+    <com.android.inputmethod.latin.suggestions.MoreSuggestionsView
+        xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+        android:id="@+id/more_suggestions_view"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:orientation="horizontal"
-        style="?attr/moreKeysKeyboardPanelStyle"
-        >
-    <com.android.inputmethod.latin.suggestions.MoreSuggestionsView
-            xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-            android:id="@+id/more_suggestions_view"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            latin:keyLetterSize="@dimen/suggestion_text_size"
-            latin:keyLabelSize="@dimen/suggestion_text_size"
-            latin:keyHintLetterRatio="@fraction/more_suggestions_info_ratio"
-            latin:keyHintLetterColor="@android:color/white"
-            />
+        latin:keyLetterSize="@dimen/suggestion_text_size"
+        latin:keyLabelSize="@dimen/suggestion_text_size"
+        latin:keyHintLetterRatio="@fraction/more_suggestions_info_ratio"
+        latin:keyHintLetterColor="@android:color/white" />
 </LinearLayout>
diff --git a/java/res/layout/suggestion_info.xml b/java/res/layout/suggestion_info.xml
index a4ad6df..0aa2600 100644
--- a/java/res/layout/suggestion_info.xml
+++ b/java/res/layout/suggestion_info.xml
@@ -24,4 +24,4 @@
     android:layout_height="wrap_content"
     android:textSize="6dp"
     android:textColor="@android:color/white"
-    style="?attr/suggestionBackgroundStyle" />
+    style="?attr/suggestionWordStyle" />
diff --git a/java/res/layout/suggestion_word.xml b/java/res/layout/suggestion_word.xml
index fa00e04..c82a13c 100644
--- a/java/res/layout/suggestion_word.xml
+++ b/java/res/layout/suggestion_word.xml
@@ -36,4 +36,4 @@
     android:clickable="false"
     android:singleLine="true"
     android:ellipsize="none"
-    style="?attr/suggestionBackgroundStyle" />
+    style="?attr/suggestionWordStyle" />
diff --git a/java/res/raw/main_fr.dict b/java/res/raw/main_fr.dict
index 31fb2af..10adad0 100644
--- a/java/res/raw/main_fr.dict
+++ b/java/res/raw/main_fr.dict
Binary files differ
diff --git a/java/res/raw/main_pt_br.dict b/java/res/raw/main_pt_br.dict
index 557d46e..f9ae9b5 100644
--- a/java/res/raw/main_pt_br.dict
+++ b/java/res/raw/main_pt_br.dict
Binary files differ
diff --git a/java/res/raw/main_ru.dict b/java/res/raw/main_ru.dict
index 86c368e..7dec624 100644
--- a/java/res/raw/main_ru.dict
+++ b/java/res/raw/main_ru.dict
Binary files differ
diff --git a/java/res/values-af/strings.xml b/java/res/values-af/strings.xml
index 986e29b..01a57e7 100644
--- a/java/res/values-af/strings.xml
+++ b/java/res/values-af/strings.xml
@@ -64,8 +64,8 @@
     <string name="auto_correction_summary" msgid="5625751551134658006">"Spasiebalk en leestekens korrigeer outomaties woorde wat verkeerd gespel is"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Af"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Matig"</string>
-    <string name="auto_correction_threshold_mode_aggressive" msgid="3524029103734923819">"Aggressief"</string>
-    <string name="auto_correction_threshold_mode_very_aggressive" msgid="3386782235540547678">"Baie aggressief"</string>
+    <string name="auto_correction_threshold_mode_aggressive" msgid="7319007299148899623">"Aggressief"</string>
+    <string name="auto_correction_threshold_mode_very_aggressive" msgid="1853309024129480416">"Baie aggressief"</string>
     <string name="bigram_prediction" msgid="1084449187723948550">"Stel volgende woord voor"</string>
     <string name="bigram_prediction_summary" msgid="3896362682751109677">"Gebruik die vorige woord om voorstelle te maak"</string>
     <string name="gesture_input" msgid="826951152254563827">"Aktiveer gebaar-tik"</string>
@@ -143,13 +143,15 @@
     <string name="subtype_with_layout_en_GB" msgid="2179097748724725906">"Engels (VK) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_en_US" msgid="1362581347576714579">"Engels (VS) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_es_US" msgid="6261791057007890189">"Spaans (VS) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
-    <string name="subtype_no_language" msgid="141420857808801746">"Geen taal nie"</string>
-    <string name="subtype_no_language_qwerty" msgid="2956121451616633133">"Geen taal (QWERTY)"</string>
-    <string name="subtype_no_language_qwertz" msgid="1177848172397202890">"Geen taal nie (QWERTZ)"</string>
-    <string name="subtype_no_language_azerty" msgid="8721460968141187394">"Geen taal nie (AZERTY)"</string>
-    <string name="subtype_no_language_dvorak" msgid="3122976737669823935">"Geen taal nie (Dvorak)"</string>
-    <string name="subtype_no_language_colemak" msgid="4205992994906097244">"Geen taal nie (Colemak)"</string>
-    <string name="subtype_no_language_pcqwerty" msgid="8840928374394180189">"Geen taal nie (PC)"</string>
+    <string name="subtype_nepali_traditional" msgid="9032247506728040447">"<xliff:g id="LANGUAGE">%s</xliff:g> (Tradisioneel)"</string>
+    <string name="subtype_no_language" msgid="7137390094240139495">"Geen taal nie (alfabet)"</string>
+    <string name="subtype_no_language_qwerty" msgid="244337630616742604">"Alfabet (QWERTY)"</string>
+    <string name="subtype_no_language_qwertz" msgid="443066912507547976">"Alfabet (QWERTZ)"</string>
+    <string name="subtype_no_language_azerty" msgid="8144348527575640087">"Alfabet (AZERTY)"</string>
+    <string name="subtype_no_language_dvorak" msgid="1564494667584718094">"Alfabet (Dvorak)"</string>
+    <string name="subtype_no_language_colemak" msgid="5837418400010302623">"Alfabet (Colemak)"</string>
+    <string name="subtype_no_language_pcqwerty" msgid="5354918232046200018">"Alfabet (PC)"</string>
+    <string name="subtype_emoji" msgid="7483586578074549196">"Emosiekone"</string>
     <string name="custom_input_styles_title" msgid="8429952441821251512">"Gepasmaakte invoerstyle"</string>
     <string name="add_style" msgid="6163126614514489951">"Voeg styl by"</string>
     <string name="add" msgid="8299699805688017798">"Voeg by"</string>
diff --git a/java/res/values-am/strings.xml b/java/res/values-am/strings.xml
index 32faf0f..774f2c5 100644
--- a/java/res/values-am/strings.xml
+++ b/java/res/values-am/strings.xml
@@ -64,8 +64,8 @@
     <string name="auto_correction_summary" msgid="5625751551134658006">"የቦታ ቁልፍ እና ሥርዓተ ነጥብ በስህተት የተተየቡ ቃላትን  በራስሰር ያስተካክላሉ ።"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"ውጪ"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"መጠነኛ"</string>
-    <string name="auto_correction_threshold_mode_aggressive" msgid="3524029103734923819">"ኃይለኛ"</string>
-    <string name="auto_correction_threshold_mode_very_aggressive" msgid="3386782235540547678">"በጣም ቁጡ"</string>
+    <string name="auto_correction_threshold_mode_aggressive" msgid="7319007299148899623">"ኃይለኛ"</string>
+    <string name="auto_correction_threshold_mode_very_aggressive" msgid="1853309024129480416">"በጣም ኃይለኛ"</string>
     <string name="bigram_prediction" msgid="1084449187723948550">"የቀጣይ ቃል አስተያየቶች"</string>
     <string name="bigram_prediction_summary" msgid="3896362682751109677">"አስተያየቶች መስጠት ላይ ቀዳሚውን ቃል ተጠቀም"</string>
     <string name="gesture_input" msgid="826951152254563827">"በምልክት መተየብ ያንቁ"</string>
@@ -143,13 +143,15 @@
     <string name="subtype_with_layout_en_GB" msgid="2179097748724725906">"እንግሊዘኛ (ዩናይትድ ኪንግደም) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_en_US" msgid="1362581347576714579">"እንግሊዘኛ (አሜሪካ) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_es_US" msgid="6261791057007890189">"ስፓኒሽኛ (ዩኤስ) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
-    <string name="subtype_no_language" msgid="141420857808801746">"ምንም ቋንቋ"</string>
-    <string name="subtype_no_language_qwerty" msgid="2956121451616633133">"ቋንቋ አልባ (QWERTY)"</string>
-    <string name="subtype_no_language_qwertz" msgid="1177848172397202890">"ቋንቋ አልባ (QWERTZ)"</string>
-    <string name="subtype_no_language_azerty" msgid="8721460968141187394">"ቋንቋ አልባ (AZERTY)"</string>
-    <string name="subtype_no_language_dvorak" msgid="3122976737669823935">"ቋንቋ አልባ (Dvorak)"</string>
-    <string name="subtype_no_language_colemak" msgid="4205992994906097244">"ቋንቋ አልባ (Colemak)"</string>
-    <string name="subtype_no_language_pcqwerty" msgid="8840928374394180189">"ቋንቋ አልባ (PC)"</string>
+    <string name="subtype_nepali_traditional" msgid="9032247506728040447">"<xliff:g id="LANGUAGE">%s</xliff:g> (ተለምዷዊ)"</string>
+    <string name="subtype_no_language" msgid="7137390094240139495">"ምንም ቋንቋ (ፊደላት)"</string>
+    <string name="subtype_no_language_qwerty" msgid="244337630616742604">"ፊደላት (QWERTY)"</string>
+    <string name="subtype_no_language_qwertz" msgid="443066912507547976">"ፊደላት (QWERTZ)"</string>
+    <string name="subtype_no_language_azerty" msgid="8144348527575640087">"ፊደላት (AZERTY)"</string>
+    <string name="subtype_no_language_dvorak" msgid="1564494667584718094">"ፊደላት (Dvorak)"</string>
+    <string name="subtype_no_language_colemak" msgid="5837418400010302623">"ፊደላት (Colemak)"</string>
+    <string name="subtype_no_language_pcqwerty" msgid="5354918232046200018">"ፊደላት (ፒሲ)"</string>
+    <string name="subtype_emoji" msgid="7483586578074549196">"ኢሞጂ"</string>
     <string name="custom_input_styles_title" msgid="8429952441821251512">"የተበጁ የግቤት ስታይሎች"</string>
     <string name="add_style" msgid="6163126614514489951">"ስታይል አክል"</string>
     <string name="add" msgid="8299699805688017798">"አክል"</string>
diff --git a/java/res/values-ar/strings.xml b/java/res/values-ar/strings.xml
index 7079a0a..7e39b59 100644
--- a/java/res/values-ar/strings.xml
+++ b/java/res/values-ar/strings.xml
@@ -64,8 +64,8 @@
     <string name="auto_correction_summary" msgid="5625751551134658006">"تؤدي المسافة والترقيم إلى تصحيح الكلمات المكتوبة بشكل غير صحيح"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"إيقاف"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"معتدل"</string>
-    <string name="auto_correction_threshold_mode_aggressive" msgid="3524029103734923819">"حاد"</string>
-    <string name="auto_correction_threshold_mode_very_aggressive" msgid="3386782235540547678">"شديد الصرامة"</string>
+    <string name="auto_correction_threshold_mode_aggressive" msgid="7319007299148899623">"صارم"</string>
+    <string name="auto_correction_threshold_mode_very_aggressive" msgid="1853309024129480416">"شديد الصرامة"</string>
     <string name="bigram_prediction" msgid="1084449187723948550">"اقتراحات الكلمات التالية"</string>
     <string name="bigram_prediction_summary" msgid="3896362682751109677">"استخدام الكلمة السابقة في تقديم الاقتراحات"</string>
     <string name="gesture_input" msgid="826951152254563827">"تمكين الكتابة بالإيماءة"</string>
@@ -143,13 +143,15 @@
     <string name="subtype_with_layout_en_GB" msgid="2179097748724725906">"الإنجليزية (المملكة المتحدة) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_en_US" msgid="1362581347576714579">"الإنجليزية (الولايات المتحدة) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_es_US" msgid="6261791057007890189">"الإسبانية (الأمريكية) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
-    <string name="subtype_no_language" msgid="141420857808801746">"بدون لغة"</string>
-    <string name="subtype_no_language_qwerty" msgid="2956121451616633133">"بدون لغة (QWERTY)"</string>
-    <string name="subtype_no_language_qwertz" msgid="1177848172397202890">"بدون لغة (QWERTZ)"</string>
-    <string name="subtype_no_language_azerty" msgid="8721460968141187394">"بدون لغة (AZERTY)"</string>
-    <string name="subtype_no_language_dvorak" msgid="3122976737669823935">"بدون لغة (Dvorak)"</string>
-    <string name="subtype_no_language_colemak" msgid="4205992994906097244">"بدون لغة (Colemak)"</string>
-    <string name="subtype_no_language_pcqwerty" msgid="8840928374394180189">"بدون لغة (PC)"</string>
+    <string name="subtype_nepali_traditional" msgid="9032247506728040447">"<xliff:g id="LANGUAGE">%s</xliff:g> (التقليدية)"</string>
+    <string name="subtype_no_language" msgid="7137390094240139495">"بدون لغة (أبجدية)"</string>
+    <string name="subtype_no_language_qwerty" msgid="244337630616742604">"الأبجدية (QWERTY)"</string>
+    <string name="subtype_no_language_qwertz" msgid="443066912507547976">"الأبجدية (QWERTZ)"</string>
+    <string name="subtype_no_language_azerty" msgid="8144348527575640087">"الأبجدية (AZERTY)"</string>
+    <string name="subtype_no_language_dvorak" msgid="1564494667584718094">"الأبجدية (Dvorak)"</string>
+    <string name="subtype_no_language_colemak" msgid="5837418400010302623">"الأبجدية (Colemak)"</string>
+    <string name="subtype_no_language_pcqwerty" msgid="5354918232046200018">"الأبجدية (PC)"</string>
+    <string name="subtype_emoji" msgid="7483586578074549196">"الرموز التعبيرية"</string>
     <string name="custom_input_styles_title" msgid="8429952441821251512">"أنماط الإدخال المخصصة"</string>
     <string name="add_style" msgid="6163126614514489951">"إضافة نمط"</string>
     <string name="add" msgid="8299699805688017798">"إضافة"</string>
diff --git a/java/res/values-be/strings.xml b/java/res/values-be/strings.xml
index 68b0b61..db40c8a 100644
--- a/java/res/values-be/strings.xml
+++ b/java/res/values-be/strings.xml
@@ -65,8 +65,10 @@
     <string name="auto_correction_summary" msgid="5625751551134658006">"Прабелы і пунктуацыйныя знакі дазваляюць аўтаматычна выпраўляць памылкова ўведзеныя словы"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Адключаны"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Сціплы"</string>
-    <string name="auto_correction_threshold_mode_aggressive" msgid="3524029103734923819">"Агрэсіўны"</string>
-    <string name="auto_correction_threshold_mode_very_aggressive" msgid="3386782235540547678">"Вельмі агрэсіўны"</string>
+    <!-- no translation found for auto_correction_threshold_mode_aggressive (7319007299148899623) -->
+    <skip />
+    <!-- no translation found for auto_correction_threshold_mode_very_aggressive (1853309024129480416) -->
+    <skip />
     <string name="bigram_prediction" msgid="1084449187723948550">"Падказкi для наступнага слова"</string>
     <string name="bigram_prediction_summary" msgid="3896362682751109677">"Выкарыстоўваць папярэдняе слова, каб атрымлiваць падказкi"</string>
     <string name="gesture_input" msgid="826951152254563827">"Уключыць набор жэстамі"</string>
@@ -144,13 +146,24 @@
     <string name="subtype_with_layout_en_GB" msgid="2179097748724725906">"Англійская (Вялікабрытанія) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_en_US" msgid="1362581347576714579">"Англійская (ЗША) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_es_US" msgid="6261791057007890189">"iспанская (ЗША) ( <xliff:g id="LAYOUT">%s</xliff:g> )"</string>
-    <string name="subtype_no_language" msgid="141420857808801746">"Мова не выбрана"</string>
-    <string name="subtype_no_language_qwerty" msgid="2956121451616633133">"Мова не выбрана (QWERTY)"</string>
-    <string name="subtype_no_language_qwertz" msgid="1177848172397202890">"Няма мовы (QWERTZ)"</string>
-    <string name="subtype_no_language_azerty" msgid="8721460968141187394">"Няма мовы (AZERTY)"</string>
-    <string name="subtype_no_language_dvorak" msgid="3122976737669823935">"Няма мовы  (Dvorak)"</string>
-    <string name="subtype_no_language_colemak" msgid="4205992994906097244">"Няма мовы (Colemak)"</string>
-    <string name="subtype_no_language_pcqwerty" msgid="8840928374394180189">"Няма мовы (PC)"</string>
+    <!-- no translation found for subtype_nepali_traditional (9032247506728040447) -->
+    <skip />
+    <!-- no translation found for subtype_no_language (7137390094240139495) -->
+    <skip />
+    <!-- no translation found for subtype_no_language_qwerty (244337630616742604) -->
+    <skip />
+    <!-- no translation found for subtype_no_language_qwertz (443066912507547976) -->
+    <skip />
+    <!-- no translation found for subtype_no_language_azerty (8144348527575640087) -->
+    <skip />
+    <!-- no translation found for subtype_no_language_dvorak (1564494667584718094) -->
+    <skip />
+    <!-- no translation found for subtype_no_language_colemak (5837418400010302623) -->
+    <skip />
+    <!-- no translation found for subtype_no_language_pcqwerty (5354918232046200018) -->
+    <skip />
+    <!-- no translation found for subtype_emoji (7483586578074549196) -->
+    <skip />
     <string name="custom_input_styles_title" msgid="8429952441821251512">"Карыстальніцкія стылі ўводу"</string>
     <string name="add_style" msgid="6163126614514489951">"Дадаць стыль"</string>
     <string name="add" msgid="8299699805688017798">"Дадаць"</string>
diff --git a/java/res/values-bg/strings.xml b/java/res/values-bg/strings.xml
index 3de6b8e..d3879cc 100644
--- a/java/res/values-bg/strings.xml
+++ b/java/res/values-bg/strings.xml
@@ -64,8 +64,8 @@
     <string name="auto_correction_summary" msgid="5625751551134658006">"Клавишът за интервал и пунктуация авт. поправя сгрешени думи"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Изкл."</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Умерено"</string>
-    <string name="auto_correction_threshold_mode_aggressive" msgid="3524029103734923819">"Агресивно"</string>
-    <string name="auto_correction_threshold_mode_very_aggressive" msgid="3386782235540547678">"Много агресивно"</string>
+    <string name="auto_correction_threshold_mode_aggressive" msgid="7319007299148899623">"Агресивно"</string>
+    <string name="auto_correction_threshold_mode_very_aggressive" msgid="1853309024129480416">"Много агресивно"</string>
     <string name="bigram_prediction" msgid="1084449187723948550">"Предложения за следващата дума"</string>
     <string name="bigram_prediction_summary" msgid="3896362682751109677">"Използване на предишната дума при даване на предложения"</string>
     <string name="gesture_input" msgid="826951152254563827">"Активиране на въвеждането чрез жест"</string>
@@ -143,13 +143,15 @@
     <string name="subtype_with_layout_en_GB" msgid="2179097748724725906">"английски (Великобр.) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_en_US" msgid="1362581347576714579">"английски (САЩ) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_es_US" msgid="6261791057007890189">"испански (САЩ) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
-    <string name="subtype_no_language" msgid="141420857808801746">"Без език"</string>
-    <string name="subtype_no_language_qwerty" msgid="2956121451616633133">"без език („QWERTY“)"</string>
-    <string name="subtype_no_language_qwertz" msgid="1177848172397202890">"Без език (QWERTZ)"</string>
-    <string name="subtype_no_language_azerty" msgid="8721460968141187394">"Без език (AZERTY)"</string>
-    <string name="subtype_no_language_dvorak" msgid="3122976737669823935">"Без език (Дворак)"</string>
-    <string name="subtype_no_language_colemak" msgid="4205992994906097244">"Без език (Коулмак)"</string>
-    <string name="subtype_no_language_pcqwerty" msgid="8840928374394180189">"Без език (PC)"</string>
+    <string name="subtype_nepali_traditional" msgid="9032247506728040447">"<xliff:g id="LANGUAGE">%s</xliff:g> (традиционен)"</string>
+    <string name="subtype_no_language" msgid="7137390094240139495">"Без език (латиница)"</string>
+    <string name="subtype_no_language_qwerty" msgid="244337630616742604">"Латиница (QWERTY)"</string>
+    <string name="subtype_no_language_qwertz" msgid="443066912507547976">"Латиница (QWERTZ)"</string>
+    <string name="subtype_no_language_azerty" msgid="8144348527575640087">"Латиница (AZERTY)"</string>
+    <string name="subtype_no_language_dvorak" msgid="1564494667584718094">"Латиница (Дворак)"</string>
+    <string name="subtype_no_language_colemak" msgid="5837418400010302623">"Латиница (Коулмак)"</string>
+    <string name="subtype_no_language_pcqwerty" msgid="5354918232046200018">"Латиница (PC)"</string>
+    <string name="subtype_emoji" msgid="7483586578074549196">"Емотикони"</string>
     <string name="custom_input_styles_title" msgid="8429952441821251512">"Персон. стилове за въвежд."</string>
     <string name="add_style" msgid="6163126614514489951">"+ стил"</string>
     <string name="add" msgid="8299699805688017798">"Добавяне"</string>
diff --git a/java/res/values-ca/strings.xml b/java/res/values-ca/strings.xml
index f718387..03e2f3f 100644
--- a/java/res/values-ca/strings.xml
+++ b/java/res/values-ca/strings.xml
@@ -64,8 +64,8 @@
     <string name="auto_correction_summary" msgid="5625751551134658006">"Prémer tecla d\'espai o punt. per corregir errors"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Desactiva"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Moderada"</string>
-    <string name="auto_correction_threshold_mode_aggressive" msgid="3524029103734923819">"Total"</string>
-    <string name="auto_correction_threshold_mode_very_aggressive" msgid="3386782235540547678">"Molt agressiu"</string>
+    <string name="auto_correction_threshold_mode_aggressive" msgid="7319007299148899623">"Agressiu"</string>
+    <string name="auto_correction_threshold_mode_very_aggressive" msgid="1853309024129480416">"Molt agressiu"</string>
     <string name="bigram_prediction" msgid="1084449187723948550">"Suggeriments de paraula següent"</string>
     <string name="bigram_prediction_summary" msgid="3896362682751109677">"Utilitza la paraula anterior a l\'hora de fer suggeriments"</string>
     <string name="gesture_input" msgid="826951152254563827">"Activa l\'escriptura gestual"</string>
@@ -143,13 +143,15 @@
     <string name="subtype_with_layout_en_GB" msgid="2179097748724725906">"Anglès (Regne Unit) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_en_US" msgid="1362581347576714579">"Anglès (Estats Units) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_es_US" msgid="6261791057007890189">"Espanyol (EUA) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
-    <string name="subtype_no_language" msgid="141420857808801746">"Cap idioma"</string>
-    <string name="subtype_no_language_qwerty" msgid="2956121451616633133">"Cap idioma (QWERTY)"</string>
-    <string name="subtype_no_language_qwertz" msgid="1177848172397202890">"Cap idioma (QWERTZ)"</string>
-    <string name="subtype_no_language_azerty" msgid="8721460968141187394">"Cap idioma (AZERTY)"</string>
-    <string name="subtype_no_language_dvorak" msgid="3122976737669823935">"Cap idioma (Dvorak)"</string>
-    <string name="subtype_no_language_colemak" msgid="4205992994906097244">"Cap idioma (Colemak)"</string>
-    <string name="subtype_no_language_pcqwerty" msgid="8840928374394180189">"Cap idioma (PC)"</string>
+    <string name="subtype_nepali_traditional" msgid="9032247506728040447">"<xliff:g id="LANGUAGE">%s</xliff:g> (tradicional)"</string>
+    <string name="subtype_no_language" msgid="7137390094240139495">"Cap idioma (alfabet)"</string>
+    <string name="subtype_no_language_qwerty" msgid="244337630616742604">"Alfabet (QWERTY)"</string>
+    <string name="subtype_no_language_qwertz" msgid="443066912507547976">"Alfabet (QWERTZ)"</string>
+    <string name="subtype_no_language_azerty" msgid="8144348527575640087">"Alfabet (AZERTY)"</string>
+    <string name="subtype_no_language_dvorak" msgid="1564494667584718094">"Alfabet (Dvorak)"</string>
+    <string name="subtype_no_language_colemak" msgid="5837418400010302623">"Alfabet (Colemak)"</string>
+    <string name="subtype_no_language_pcqwerty" msgid="5354918232046200018">"Alfabet (PC)"</string>
+    <string name="subtype_emoji" msgid="7483586578074549196">"Emoji"</string>
     <string name="custom_input_styles_title" msgid="8429952441821251512">"Estils d\'entrada personalitzats"</string>
     <string name="add_style" msgid="6163126614514489951">"Afeg. estil"</string>
     <string name="add" msgid="8299699805688017798">"Afegeix"</string>
diff --git a/java/res/values-cs/strings.xml b/java/res/values-cs/strings.xml
index 1089f48..a707f26 100644
--- a/java/res/values-cs/strings.xml
+++ b/java/res/values-cs/strings.xml
@@ -64,8 +64,8 @@
     <string name="auto_correction_summary" msgid="5625751551134658006">"Stisknutím mezerníku a interpunkce se automaticky opravují chybně napsaná slova"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Vypnuto"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Mírné"</string>
-    <string name="auto_correction_threshold_mode_aggressive" msgid="3524029103734923819">"Agresivní"</string>
-    <string name="auto_correction_threshold_mode_very_aggressive" msgid="3386782235540547678">"Velmi agresivní"</string>
+    <string name="auto_correction_threshold_mode_aggressive" msgid="7319007299148899623">"Agresivní"</string>
+    <string name="auto_correction_threshold_mode_very_aggressive" msgid="1853309024129480416">"Velmi agresivní"</string>
     <string name="bigram_prediction" msgid="1084449187723948550">"Návrhy dalšího slova"</string>
     <string name="bigram_prediction_summary" msgid="3896362682751109677">"Návrhy na základě předchozího slova"</string>
     <string name="gesture_input" msgid="826951152254563827">"Aktivovat psaní gesty"</string>
@@ -143,13 +143,15 @@
     <string name="subtype_with_layout_en_GB" msgid="2179097748724725906">"angličtina (VB) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_en_US" msgid="1362581347576714579">"angličtina (USA) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_es_US" msgid="6261791057007890189">"španělština (USA) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
-    <string name="subtype_no_language" msgid="141420857808801746">"Žádný jazyk"</string>
-    <string name="subtype_no_language_qwerty" msgid="2956121451616633133">"Žádný jazyk (QWERTY)"</string>
-    <string name="subtype_no_language_qwertz" msgid="1177848172397202890">"Žádný jazyk (QWERTZ)"</string>
-    <string name="subtype_no_language_azerty" msgid="8721460968141187394">"Žádný jazyk (AZERTY)"</string>
-    <string name="subtype_no_language_dvorak" msgid="3122976737669823935">"Žádný jazyk (Dvorak)"</string>
-    <string name="subtype_no_language_colemak" msgid="4205992994906097244">"Žádný jazyk (Colemak)"</string>
-    <string name="subtype_no_language_pcqwerty" msgid="8840928374394180189">"Žádný jazyk (PC)"</string>
+    <string name="subtype_nepali_traditional" msgid="9032247506728040447">"<xliff:g id="LANGUAGE">%s</xliff:g> (tradiční)"</string>
+    <string name="subtype_no_language" msgid="7137390094240139495">"Žádný jazyk (latinka)"</string>
+    <string name="subtype_no_language_qwerty" msgid="244337630616742604">"Latinka (QWERTY)"</string>
+    <string name="subtype_no_language_qwertz" msgid="443066912507547976">"Latinka (QWERTZ)"</string>
+    <string name="subtype_no_language_azerty" msgid="8144348527575640087">"Latinka (AZERTY)"</string>
+    <string name="subtype_no_language_dvorak" msgid="1564494667584718094">"Latinka (Dvorak)"</string>
+    <string name="subtype_no_language_colemak" msgid="5837418400010302623">"Latinka (Colemak)"</string>
+    <string name="subtype_no_language_pcqwerty" msgid="5354918232046200018">"Latinka (PC)"</string>
+    <string name="subtype_emoji" msgid="7483586578074549196">"Emodži"</string>
     <string name="custom_input_styles_title" msgid="8429952441821251512">"Vlastní styl zadávání"</string>
     <string name="add_style" msgid="6163126614514489951">"Přidat styl"</string>
     <string name="add" msgid="8299699805688017798">"Přidat"</string>
diff --git a/java/res/values-da/strings.xml b/java/res/values-da/strings.xml
index 929802f..1876895 100644
--- a/java/res/values-da/strings.xml
+++ b/java/res/values-da/strings.xml
@@ -64,8 +64,8 @@
     <string name="auto_correction_summary" msgid="5625751551134658006">"Mellemrumstast og tegnsætning retter automatisk forkerte ord"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Fra"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Moderat"</string>
-    <string name="auto_correction_threshold_mode_aggressive" msgid="3524029103734923819">"Aggressiv"</string>
-    <string name="auto_correction_threshold_mode_very_aggressive" msgid="3386782235540547678">"Meget aggressiv"</string>
+    <string name="auto_correction_threshold_mode_aggressive" msgid="7319007299148899623">"Aggressiv"</string>
+    <string name="auto_correction_threshold_mode_very_aggressive" msgid="1853309024129480416">"Meget aggressiv"</string>
     <string name="bigram_prediction" msgid="1084449187723948550">"Forslag til næste ord"</string>
     <string name="bigram_prediction_summary" msgid="3896362682751109677">"Brug det forrige ord til at give forslag"</string>
     <string name="gesture_input" msgid="826951152254563827">"Aktivér skrivning med berøring"</string>
@@ -143,13 +143,15 @@
     <string name="subtype_with_layout_en_GB" msgid="2179097748724725906">"Engelsk (Storbritannien) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_en_US" msgid="1362581347576714579">"Engelsk (USA) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_es_US" msgid="6261791057007890189">"Spansk (USA) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
-    <string name="subtype_no_language" msgid="141420857808801746">"Intet sprog"</string>
-    <string name="subtype_no_language_qwerty" msgid="2956121451616633133">"Ingen sprog (QWERTY)"</string>
-    <string name="subtype_no_language_qwertz" msgid="1177848172397202890">"Intet sprog (QWERTZ)"</string>
-    <string name="subtype_no_language_azerty" msgid="8721460968141187394">"Intet sprog (AZERTY)"</string>
-    <string name="subtype_no_language_dvorak" msgid="3122976737669823935">"Intet sprog (Dvorak)"</string>
-    <string name="subtype_no_language_colemak" msgid="4205992994906097244">"Intet sprog (Colemak)"</string>
-    <string name="subtype_no_language_pcqwerty" msgid="8840928374394180189">"Intet sprog (PC)"</string>
+    <string name="subtype_nepali_traditional" msgid="9032247506728040447">"<xliff:g id="LANGUAGE">%s</xliff:g> (traditionelt)"</string>
+    <string name="subtype_no_language" msgid="7137390094240139495">"Intet sprog (Alfabet)"</string>
+    <string name="subtype_no_language_qwerty" msgid="244337630616742604">"Alfabet (QWERTY)"</string>
+    <string name="subtype_no_language_qwertz" msgid="443066912507547976">"Alfabet (QWERTZ)"</string>
+    <string name="subtype_no_language_azerty" msgid="8144348527575640087">"Alfabet (AZERTY)"</string>
+    <string name="subtype_no_language_dvorak" msgid="1564494667584718094">"Alfabet (Dvorak)"</string>
+    <string name="subtype_no_language_colemak" msgid="5837418400010302623">"Alfabet (Colemak)"</string>
+    <string name="subtype_no_language_pcqwerty" msgid="5354918232046200018">"Alfabet (PC)"</string>
+    <string name="subtype_emoji" msgid="7483586578074549196">"Emoji"</string>
     <string name="custom_input_styles_title" msgid="8429952441821251512">"Tilpasset inputtypografi"</string>
     <string name="add_style" msgid="6163126614514489951">"Tilføj typografi"</string>
     <string name="add" msgid="8299699805688017798">"Tilføj"</string>
diff --git a/java/res/values-de/strings.xml b/java/res/values-de/strings.xml
index cff23a8..5f3e619 100644
--- a/java/res/values-de/strings.xml
+++ b/java/res/values-de/strings.xml
@@ -64,8 +64,8 @@
     <string name="auto_correction_summary" msgid="5625751551134658006">"Korrektur fehlerhafter Wörter durch Leertaste und Satzzeichen"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Aus"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Mäßig"</string>
-    <string name="auto_correction_threshold_mode_aggressive" msgid="3524029103734923819">"Stark"</string>
-    <string name="auto_correction_threshold_mode_very_aggressive" msgid="3386782235540547678">"Sehr stark"</string>
+    <string name="auto_correction_threshold_mode_aggressive" msgid="7319007299148899623">"Stark"</string>
+    <string name="auto_correction_threshold_mode_very_aggressive" msgid="1853309024129480416">"Sehr stark"</string>
     <string name="bigram_prediction" msgid="1084449187723948550">"Vorschläge für nächstes Wort"</string>
     <string name="bigram_prediction_summary" msgid="3896362682751109677">"Vorschläge anhand des vorherigen Wortes machen"</string>
     <string name="gesture_input" msgid="826951152254563827">"Bewegungseingabe aktivieren"</string>
@@ -143,13 +143,15 @@
     <string name="subtype_with_layout_en_GB" msgid="2179097748724725906">"Englisch (GB) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_en_US" msgid="1362581347576714579">"Englisch (USA) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_es_US" msgid="6261791057007890189">"Spanisch (USA) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
-    <string name="subtype_no_language" msgid="141420857808801746">"Keine Sprache"</string>
-    <string name="subtype_no_language_qwerty" msgid="2956121451616633133">"Keine Sprache (QWERTY)"</string>
-    <string name="subtype_no_language_qwertz" msgid="1177848172397202890">"Keine Sprache (QWERTZ)"</string>
-    <string name="subtype_no_language_azerty" msgid="8721460968141187394">"Keine Sprache (AZERTY)"</string>
-    <string name="subtype_no_language_dvorak" msgid="3122976737669823935">"Keine Sprache (Dvorak)"</string>
-    <string name="subtype_no_language_colemak" msgid="4205992994906097244">"Keine Sprache (Colemak)"</string>
-    <string name="subtype_no_language_pcqwerty" msgid="8840928374394180189">"Keine Sprache (PC)"</string>
+    <string name="subtype_nepali_traditional" msgid="9032247506728040447">"<xliff:g id="LANGUAGE">%s</xliff:g> (traditionell)"</string>
+    <string name="subtype_no_language" msgid="7137390094240139495">"Keine Sprache (lat. Alphabet)"</string>
+    <string name="subtype_no_language_qwerty" msgid="244337630616742604">"Lat. Alphabet (QWERTY)"</string>
+    <string name="subtype_no_language_qwertz" msgid="443066912507547976">"Lat. Alphabet (QWERTZ)"</string>
+    <string name="subtype_no_language_azerty" msgid="8144348527575640087">"Lat. Alphabet (AZERTY)"</string>
+    <string name="subtype_no_language_dvorak" msgid="1564494667584718094">"Lat. Alphabet (Dvorak)"</string>
+    <string name="subtype_no_language_colemak" msgid="5837418400010302623">"Lat. Alphabet (Colemak)"</string>
+    <string name="subtype_no_language_pcqwerty" msgid="5354918232046200018">"Lat. Alphabet (PC)"</string>
+    <string name="subtype_emoji" msgid="7483586578074549196">"Emoji"</string>
     <string name="custom_input_styles_title" msgid="8429952441821251512">"Benutzerdefinierte Eingabestile"</string>
     <string name="add_style" msgid="6163126614514489951">"Stil hinzufügen"</string>
     <string name="add" msgid="8299699805688017798">"Hinzufügen"</string>
diff --git a/java/res/values-el/strings.xml b/java/res/values-el/strings.xml
index a71a5be..27eb865 100644
--- a/java/res/values-el/strings.xml
+++ b/java/res/values-el/strings.xml
@@ -64,8 +64,8 @@
     <string name="auto_correction_summary" msgid="5625751551134658006">"Τα πλήκτρα διαστήματος και στίξης διορθ. αυτόμ. λάθος λέξεις"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Απενεργοποίηση"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Μέτρια"</string>
-    <string name="auto_correction_threshold_mode_aggressive" msgid="3524029103734923819">"Υψηλή"</string>
-    <string name="auto_correction_threshold_mode_very_aggressive" msgid="3386782235540547678">"Πολύ επιθετική"</string>
+    <string name="auto_correction_threshold_mode_aggressive" msgid="7319007299148899623">"Υψηλή λεπτομέρεια"</string>
+    <string name="auto_correction_threshold_mode_very_aggressive" msgid="1853309024129480416">"Πολύ υψηλή λεπτομέρεια"</string>
     <string name="bigram_prediction" msgid="1084449187723948550">"Προτάσεις επόμενων λέξεων"</string>
     <string name="bigram_prediction_summary" msgid="3896362682751109677">"Χρήση της προηγούμενης λέξης για τη δημιουργία προτάσεων"</string>
     <string name="gesture_input" msgid="826951152254563827">"Ενεργ. πληκτρολ. με κινήσεις"</string>
@@ -143,13 +143,15 @@
     <string name="subtype_with_layout_en_GB" msgid="2179097748724725906">"Αγγλικά (ΗΒ) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_en_US" msgid="1362581347576714579">"Αγγλικά (ΗΠΑ) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_es_US" msgid="6261791057007890189">"Ισπανικά (ΗΠΑ) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
-    <string name="subtype_no_language" msgid="141420857808801746">"Καμία γλώσσα"</string>
-    <string name="subtype_no_language_qwerty" msgid="2956121451616633133">"Καμία γλώσσα (QWERTY)"</string>
-    <string name="subtype_no_language_qwertz" msgid="1177848172397202890">"Καμία γλώσσα (QWERTZ)"</string>
-    <string name="subtype_no_language_azerty" msgid="8721460968141187394">"Καμία γλώσσα (AZERTY)"</string>
-    <string name="subtype_no_language_dvorak" msgid="3122976737669823935">"Καμία γλώσσα (Dvorak)"</string>
-    <string name="subtype_no_language_colemak" msgid="4205992994906097244">"Καμία γλώσσα (Colemak)"</string>
-    <string name="subtype_no_language_pcqwerty" msgid="8840928374394180189">"Καμία γλώσσα (PC)"</string>
+    <string name="subtype_nepali_traditional" msgid="9032247506728040447">"<xliff:g id="LANGUAGE">%s</xliff:g> (Παραδοσιακά)"</string>
+    <string name="subtype_no_language" msgid="7137390094240139495">"Καμία γλώσσα (Αλφάβητο)"</string>
+    <string name="subtype_no_language_qwerty" msgid="244337630616742604">"Αλφάβητο (QWERTY)"</string>
+    <string name="subtype_no_language_qwertz" msgid="443066912507547976">"Αλφάβητο (QWERTZ)"</string>
+    <string name="subtype_no_language_azerty" msgid="8144348527575640087">"Αλφάβητο (AZERTY)"</string>
+    <string name="subtype_no_language_dvorak" msgid="1564494667584718094">"Αλφάβητο (Dvorak)"</string>
+    <string name="subtype_no_language_colemak" msgid="5837418400010302623">"Αλφάβητο (Colemak)"</string>
+    <string name="subtype_no_language_pcqwerty" msgid="5354918232046200018">"Αλφάβητο (PC)"</string>
+    <string name="subtype_emoji" msgid="7483586578074549196">"Emoticon"</string>
     <string name="custom_input_styles_title" msgid="8429952441821251512">"Προσαρμοσ. στυλ εισαγ."</string>
     <string name="add_style" msgid="6163126614514489951">"Προσθ. στυλ"</string>
     <string name="add" msgid="8299699805688017798">"Προσθήκη"</string>
diff --git a/java/res/values-en-rGB/strings.xml b/java/res/values-en-rGB/strings.xml
index a999a62..6337aa0 100644
--- a/java/res/values-en-rGB/strings.xml
+++ b/java/res/values-en-rGB/strings.xml
@@ -24,13 +24,13 @@
     <string name="english_ime_research_log" msgid="8492602295696577851">"Research Log Commands"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Look up contact names"</string>
     <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Spell checker uses entries from your contact list"</string>
-    <string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibrate on key-press"</string>
-    <string name="sound_on_keypress" msgid="6093592297198243644">"Sound on key-press"</string>
+    <string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibrate on keypress"</string>
+    <string name="sound_on_keypress" msgid="6093592297198243644">"Sound on keypress"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"Pop-up on key press"</string>
     <string name="general_category" msgid="1859088467017573195">"General"</string>
     <string name="correction_category" msgid="2236750915056607613">"Text correction"</string>
     <string name="gesture_typing_category" msgid="497263612130532630">"Gesture typing"</string>
-    <string name="misc_category" msgid="6894192814868233453">"Other Options"</string>
+    <string name="misc_category" msgid="6894192814868233453">"Other options"</string>
     <string name="advanced_settings" msgid="362895144495591463">"Advanced settings"</string>
     <string name="advanced_settings_summary" msgid="4487980456152830271">"Options for experts"</string>
     <string name="include_other_imes_in_language_switch_list" msgid="4533689960308565519">"Switch to other input methods"</string>
@@ -64,9 +64,9 @@
     <string name="auto_correction_summary" msgid="5625751551134658006">"Correct mistyped words automatically with spacebar and punctuation"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Off"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Modest"</string>
-    <string name="auto_correction_threshold_mode_aggressive" msgid="3524029103734923819">"Aggressive"</string>
-    <string name="auto_correction_threshold_mode_very_aggressive" msgid="3386782235540547678">"Very aggressive"</string>
-    <string name="bigram_prediction" msgid="1084449187723948550">"Next word suggestions"</string>
+    <string name="auto_correction_threshold_mode_aggressive" msgid="7319007299148899623">"Aggressive"</string>
+    <string name="auto_correction_threshold_mode_very_aggressive" msgid="1853309024129480416">"Very aggressive"</string>
+    <string name="bigram_prediction" msgid="1084449187723948550">"Next-word suggestions"</string>
     <string name="bigram_prediction_summary" msgid="3896362682751109677">"Use the previous word when making suggestions"</string>
     <string name="gesture_input" msgid="826951152254563827">"Enable gesture typing"</string>
     <string name="gesture_input_summary" msgid="9180350639305731231">"Input a word by sliding through the letters"</string>
@@ -77,7 +77,7 @@
     <string name="label_go_key" msgid="1635148082137219148">"Go"</string>
     <string name="label_next_key" msgid="362972844525672568">"Next"</string>
     <string name="label_previous_key" msgid="1211868118071386787">"Prev"</string>
-    <string name="label_done_key" msgid="2441578748772529288">"Done"</string>
+    <string name="label_done_key" msgid="2441578748772529288">"Finished"</string>
     <string name="label_send_key" msgid="2815056534433717444">"Send"</string>
     <string name="label_pause_key" msgid="181098308428035340">"Pause"</string>
     <string name="label_wait_key" msgid="6402152600878093134">"Wait"</string>
@@ -143,21 +143,23 @@
     <string name="subtype_with_layout_en_GB" msgid="2179097748724725906">"English (UK) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_en_US" msgid="1362581347576714579">"English (US) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_es_US" msgid="6261791057007890189">"Spanish (US) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
-    <string name="subtype_no_language" msgid="141420857808801746">"No language"</string>
-    <string name="subtype_no_language_qwerty" msgid="2956121451616633133">"No language (QWERTY)"</string>
-    <string name="subtype_no_language_qwertz" msgid="1177848172397202890">"No language (QWERTZ)"</string>
-    <string name="subtype_no_language_azerty" msgid="8721460968141187394">"No language (AZERTY)"</string>
-    <string name="subtype_no_language_dvorak" msgid="3122976737669823935">"No language (Dvorak)"</string>
-    <string name="subtype_no_language_colemak" msgid="4205992994906097244">"No language (Colemak)"</string>
-    <string name="subtype_no_language_pcqwerty" msgid="8840928374394180189">"No language (PC)"</string>
-    <string name="custom_input_styles_title" msgid="8429952441821251512">"Custom input styles"</string>
+    <string name="subtype_nepali_traditional" msgid="9032247506728040447">"<xliff:g id="LANGUAGE">%s</xliff:g> (Traditional)"</string>
+    <string name="subtype_no_language" msgid="7137390094240139495">"No language (Alphabet)"</string>
+    <string name="subtype_no_language_qwerty" msgid="244337630616742604">"Alphabet (QWERTY)"</string>
+    <string name="subtype_no_language_qwertz" msgid="443066912507547976">"Alphabet (QWERTZ)"</string>
+    <string name="subtype_no_language_azerty" msgid="8144348527575640087">"Alphabet (AZERTY)"</string>
+    <string name="subtype_no_language_dvorak" msgid="1564494667584718094">"Alphabet (Dvorak)"</string>
+    <string name="subtype_no_language_colemak" msgid="5837418400010302623">"Alphabet (Colemak)"</string>
+    <string name="subtype_no_language_pcqwerty" msgid="5354918232046200018">"Alphabet (PC)"</string>
+    <string name="subtype_emoji" msgid="7483586578074549196">"Emoji"</string>
+    <string name="custom_input_styles_title" msgid="8429952441821251512">"Customised input styles"</string>
     <string name="add_style" msgid="6163126614514489951">"Add style"</string>
     <string name="add" msgid="8299699805688017798">"Add"</string>
     <string name="remove" msgid="4486081658752944606">"Remove"</string>
     <string name="save" msgid="7646738597196767214">"Save"</string>
     <string name="subtype_locale" msgid="8576443440738143764">"Language"</string>
     <string name="keyboard_layout_set" msgid="4309233698194565609">"Layout"</string>
-    <string name="custom_input_style_note_message" msgid="8826731320846363423">"Your custom input style needs to be enabled before you start using it. Do you want to enable it now?"</string>
+    <string name="custom_input_style_note_message" msgid="8826731320846363423">"Your customised input style needs to be enabled before you start using it. Do you want to enable it now?"</string>
     <string name="enable" msgid="5031294444630523247">"Enable"</string>
     <string name="not_now" msgid="6172462888202790482">"Not now"</string>
     <string name="custom_input_style_already_exists" msgid="8008728952215449707">"The same input style already exists: <xliff:g id="INPUT_STYLE_NAME">%s</xliff:g>"</string>
diff --git a/java/res/values-es-rUS/strings.xml b/java/res/values-es-rUS/strings.xml
index cda4e25..b1a4214 100644
--- a/java/res/values-es-rUS/strings.xml
+++ b/java/res/values-es-rUS/strings.xml
@@ -64,8 +64,8 @@
     <string name="auto_correction_summary" msgid="5625751551134658006">"La barra espaciadora y las teclas de puntuación insertan automáticamente la palabra corregida"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Desactivado"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Moderado"</string>
-    <string name="auto_correction_threshold_mode_aggressive" msgid="3524029103734923819">"Total"</string>
-    <string name="auto_correction_threshold_mode_very_aggressive" msgid="3386782235540547678">"Muy agresivo"</string>
+    <string name="auto_correction_threshold_mode_aggressive" msgid="7319007299148899623">"Intensa"</string>
+    <string name="auto_correction_threshold_mode_very_aggressive" msgid="1853309024129480416">"Muy intensa"</string>
     <string name="bigram_prediction" msgid="1084449187723948550">"Suger. de próxima palabra"</string>
     <string name="bigram_prediction_summary" msgid="3896362682751109677">"Usar la palabra anterior para hacer sugerencias"</string>
     <string name="gesture_input" msgid="826951152254563827">"Activar escritura gestual"</string>
@@ -143,13 +143,15 @@
     <string name="subtype_with_layout_en_GB" msgid="2179097748724725906">"Inglés (Reino Unido) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_en_US" msgid="1362581347576714579">"Inglés (EE.UU.) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_es_US" msgid="6261791057007890189">"Español (EE.UU.) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
-    <string name="subtype_no_language" msgid="141420857808801746">"Ningún idioma"</string>
-    <string name="subtype_no_language_qwerty" msgid="2956121451616633133">"Ningún idioma (QWERTY)"</string>
-    <string name="subtype_no_language_qwertz" msgid="1177848172397202890">"Sin idioma (QWERTZ)"</string>
-    <string name="subtype_no_language_azerty" msgid="8721460968141187394">"Sin idioma (AZERTY)"</string>
-    <string name="subtype_no_language_dvorak" msgid="3122976737669823935">"Sin idioma (Dvorak)"</string>
-    <string name="subtype_no_language_colemak" msgid="4205992994906097244">"Sin idioma (Colemak)"</string>
-    <string name="subtype_no_language_pcqwerty" msgid="8840928374394180189">"Sin idioma (PC)"</string>
+    <string name="subtype_nepali_traditional" msgid="9032247506728040447">"<xliff:g id="LANGUAGE">%s</xliff:g> (tradicional)"</string>
+    <string name="subtype_no_language" msgid="7137390094240139495">"Ningún idioma (alfabeto)"</string>
+    <string name="subtype_no_language_qwerty" msgid="244337630616742604">"Alfabeto (QWERTY)"</string>
+    <string name="subtype_no_language_qwertz" msgid="443066912507547976">"Alfabeto (QWERTZ)"</string>
+    <string name="subtype_no_language_azerty" msgid="8144348527575640087">"Alfabeto (AZERTY)"</string>
+    <string name="subtype_no_language_dvorak" msgid="1564494667584718094">"Alfabeto (Dvorak)"</string>
+    <string name="subtype_no_language_colemak" msgid="5837418400010302623">"Alfabeto (Colemak)"</string>
+    <string name="subtype_no_language_pcqwerty" msgid="5354918232046200018">"Alfabeto (PC)"</string>
+    <string name="subtype_emoji" msgid="7483586578074549196">"Emoji"</string>
     <string name="custom_input_styles_title" msgid="8429952441821251512">"Estilos de entrada personalizados"</string>
     <string name="add_style" msgid="6163126614514489951">"Agr. estilo"</string>
     <string name="add" msgid="8299699805688017798">"Agregar"</string>
diff --git a/java/res/values-es/strings.xml b/java/res/values-es/strings.xml
index 297db55..9ff9430 100644
--- a/java/res/values-es/strings.xml
+++ b/java/res/values-es/strings.xml
@@ -64,8 +64,8 @@
     <string name="auto_correction_summary" msgid="5625751551134658006">"Pulsar la tecla de espacio o punto para corregir errores"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Desactivada"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Parcial"</string>
-    <string name="auto_correction_threshold_mode_aggressive" msgid="3524029103734923819">"Total"</string>
-    <string name="auto_correction_threshold_mode_very_aggressive" msgid="3386782235540547678">"Muy agresiva"</string>
+    <string name="auto_correction_threshold_mode_aggressive" msgid="7319007299148899623">"Total"</string>
+    <string name="auto_correction_threshold_mode_very_aggressive" msgid="1853309024129480416">"Casi total"</string>
     <string name="bigram_prediction" msgid="1084449187723948550">"Sugerir siguiente palabra"</string>
     <string name="bigram_prediction_summary" msgid="3896362682751109677">"Usar la palabra anterior para hacer sugerencias"</string>
     <string name="gesture_input" msgid="826951152254563827">"Habilitar escritura gestual"</string>
@@ -143,13 +143,15 @@
     <string name="subtype_with_layout_en_GB" msgid="2179097748724725906">"Inglés (Reino Unido) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_en_US" msgid="1362581347576714579">"Inglés (EE.UU.) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_es_US" msgid="6261791057007890189">"Español (EE.UU.) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
-    <string name="subtype_no_language" msgid="141420857808801746">"ningún idioma"</string>
-    <string name="subtype_no_language_qwerty" msgid="2956121451616633133">"ningún idioma (QWERTY)"</string>
-    <string name="subtype_no_language_qwertz" msgid="1177848172397202890">"ningún idioma (QWERTZ)"</string>
-    <string name="subtype_no_language_azerty" msgid="8721460968141187394">"ningún idioma (AZERTY)"</string>
-    <string name="subtype_no_language_dvorak" msgid="3122976737669823935">"ningún idioma (Dvorak)"</string>
-    <string name="subtype_no_language_colemak" msgid="4205992994906097244">"ningún idioma (Colemak)"</string>
-    <string name="subtype_no_language_pcqwerty" msgid="8840928374394180189">"ningún idioma (PC)"</string>
+    <string name="subtype_nepali_traditional" msgid="9032247506728040447">"<xliff:g id="LANGUAGE">%s</xliff:g> (tradicional)"</string>
+    <string name="subtype_no_language" msgid="7137390094240139495">"Ningún idioma (alfabeto)"</string>
+    <string name="subtype_no_language_qwerty" msgid="244337630616742604">"Alfabeto (QWERTY)"</string>
+    <string name="subtype_no_language_qwertz" msgid="443066912507547976">"Alfabeto (QWERTZ)"</string>
+    <string name="subtype_no_language_azerty" msgid="8144348527575640087">"Alfabeto (AZERTY)"</string>
+    <string name="subtype_no_language_dvorak" msgid="1564494667584718094">"Alfabeto (Dvorak)"</string>
+    <string name="subtype_no_language_colemak" msgid="5837418400010302623">"Alfabeto (Colemak)"</string>
+    <string name="subtype_no_language_pcqwerty" msgid="5354918232046200018">"Alfabeto (PC)"</string>
+    <string name="subtype_emoji" msgid="7483586578074549196">"Emoji"</string>
     <string name="custom_input_styles_title" msgid="8429952441821251512">"Estilos de entrada personalizados"</string>
     <string name="add_style" msgid="6163126614514489951">"Añadir estilo"</string>
     <string name="add" msgid="8299699805688017798">"Añadir"</string>
diff --git a/java/res/values-et-rEE/strings.xml b/java/res/values-et-rEE/strings.xml
new file mode 100644
index 0000000..62197d7
--- /dev/null
+++ b/java/res/values-et-rEE/strings.xml
@@ -0,0 +1,244 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+**
+** Copyright 2008, 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:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="english_ime_input_options" msgid="3909945612939668554">"Sisestusvalikud"</string>
+    <string name="english_ime_research_log" msgid="8492602295696577851">"Uuringulogi käsud"</string>
+    <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Kontakti nimede kontroll."</string>
+    <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Õigekirjakontroll kasutab teie kontaktisikute loendi sissekandeid"</string>
+    <string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibreeri klahvivajutusel"</string>
+    <string name="sound_on_keypress" msgid="6093592297198243644">"Heli klahvivajutusel"</string>
+    <string name="popup_on_keypress" msgid="123894815723512944">"Klahvivajutusel kuva hüpik"</string>
+    <string name="general_category" msgid="1859088467017573195">"Üldine"</string>
+    <string name="correction_category" msgid="2236750915056607613">"Teksti parandamine"</string>
+    <string name="gesture_typing_category" msgid="497263612130532630">"Joonistusega sisestamine"</string>
+    <string name="misc_category" msgid="6894192814868233453">"Muud valikud"</string>
+    <string name="advanced_settings" msgid="362895144495591463">"Täpsemad seaded"</string>
+    <string name="advanced_settings_summary" msgid="4487980456152830271">"Valikud ekspertidele"</string>
+    <string name="include_other_imes_in_language_switch_list" msgid="4533689960308565519">"Vaheta sisestusmeetodit"</string>
+    <string name="include_other_imes_in_language_switch_list_summary" msgid="840637129103317635">"Keelevahetuse võti hõlmab ka muid sisestusmeetodeid"</string>
+    <string name="show_language_switch_key" msgid="5915478828318774384">"Keelevahetuse nupp"</string>
+    <string name="show_language_switch_key_summary" msgid="7343403647474265713">"Kuva, kui lubatud on mitu sisendkeelt"</string>
+    <string name="sliding_key_input_preview" msgid="6604262359510068370">"Kuva lohistamisnäidik"</string>
+    <string name="sliding_key_input_preview_summary" msgid="6340524345729093886">"Tõstu- või sümboliklahvidelt lohistades anna visuaalselt märku"</string>
+    <string name="key_preview_popup_dismiss_delay" msgid="6213164897443068248">"Hüpiku loobumisviivitus"</string>
+    <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Viivituseta"</string>
+    <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Vaikeseade"</string>
+    <string name="abbreviation_unit_milliseconds" msgid="8700286094028323363">"<xliff:g id="MILLISECONDS">%s</xliff:g> ms"</string>
+    <string name="settings_system_default" msgid="6268225104743331821">"Süsteemi vaikeväärt."</string>
+    <string name="use_contacts_dict" msgid="4435317977804180815">"Soovita kontaktkirjeid"</string>
+    <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Kasuta soovitusteks ja parandusteks nimesid kontaktiloendist"</string>
+    <string name="use_double_space_period" msgid="8781529969425082860">"Punkt tühikuklahviga"</string>
+    <string name="use_double_space_period_summary" msgid="6532892187247952799">"Tühikuklahvi kaks korda puudutades sisestatakse punkt ja tühik"</string>
+    <string name="auto_cap" msgid="1719746674854628252">"Automaatne suurtähtede kasutamine"</string>
+    <string name="auto_cap_summary" msgid="7934452761022946874">"Iga lause esimese sõna kirjutamine suure algustähega"</string>
+    <string name="edit_personal_dictionary" msgid="3996910038952940420">"Isiklik sõnastik"</string>
+    <string name="configure_dictionaries_title" msgid="4238652338556902049">"Pistiksõnaraamatud"</string>
+    <string name="main_dictionary" msgid="4798763781818361168">"Peamine sõnaraamat"</string>
+    <string name="prefs_show_suggestions" msgid="8026799663445531637">"Kuva parandussoovitusi"</string>
+    <string name="prefs_show_suggestions_summary" msgid="1583132279498502825">"Kuva sisestamise ajal sõnasoovitusi"</string>
+    <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Kuva alati"</string>
+    <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3859783767435239118">"Kuva vertikaalrežiimis"</string>
+    <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Peida alati"</string>
+    <string name="prefs_block_potentially_offensive_title" msgid="5078480071057408934">"Blokeeri solvavad sõnad"</string>
+    <string name="prefs_block_potentially_offensive_summary" msgid="2371835479734991364">"Ära soovita potentsiaals. solvavaid sõnu"</string>
+    <string name="auto_correction" msgid="7630720885194996950">"Automaatparandus"</string>
+    <string name="auto_correction_summary" msgid="5625751551134658006">"Tühik ja kirjavahemärgid parand. autom. kirjavigadega sõnad"</string>
+    <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Väljas"</string>
+    <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Mõõdukas"</string>
+    <string name="auto_correction_threshold_mode_aggressive" msgid="7319007299148899623">"Agressiivne"</string>
+    <string name="auto_correction_threshold_mode_very_aggressive" msgid="1853309024129480416">"Väga agressiivne"</string>
+    <string name="bigram_prediction" msgid="1084449187723948550">"Järgmise sõna soovitused"</string>
+    <string name="bigram_prediction_summary" msgid="3896362682751109677">"Soovituste tegemisel eelmise sõna kasutamine"</string>
+    <string name="gesture_input" msgid="826951152254563827">"Luba joonistusega sisestamine"</string>
+    <string name="gesture_input_summary" msgid="9180350639305731231">"Sõna sisestamine tähtede lohistamisega"</string>
+    <string name="gesture_preview_trail" msgid="3802333369335722221">"Näita liigutuse jälge"</string>
+    <string name="gesture_floating_preview_text" msgid="4443240334739381053">"Dünaamiline ujuv eelvaade"</string>
+    <string name="gesture_floating_preview_text_summary" msgid="4472696213996203533">"Soovitatud sõna vaatamine joonistusega sisestamise ajal"</string>
+    <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : salvestatud"</string>
+    <string name="label_go_key" msgid="1635148082137219148">"Mine"</string>
+    <string name="label_next_key" msgid="362972844525672568">"Edasi"</string>
+    <string name="label_previous_key" msgid="1211868118071386787">"Eelm."</string>
+    <string name="label_done_key" msgid="2441578748772529288">"Valmis"</string>
+    <string name="label_send_key" msgid="2815056534433717444">"Saada"</string>
+    <string name="label_pause_key" msgid="181098308428035340">"Peata"</string>
+    <string name="label_wait_key" msgid="6402152600878093134">"Oota"</string>
+    <string name="spoken_use_headphones" msgid="896961781287283493">"Ühendage peakomplekt, et kuulata paroole."</string>
+    <string name="spoken_current_text_is" msgid="2485723011272583845">"Praegune tekst on %s"</string>
+    <string name="spoken_no_text_entered" msgid="7479685225597344496">"Teksti ei ole sisestatud"</string>
+    <string name="spoken_description_unknown" msgid="3197434010402179157">"Klahvi kood: %d"</string>
+    <string name="spoken_description_shift" msgid="244197883292549308">"Tõstuklahv"</string>
+    <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Tõstuklahv sees (puudutage keelamiseks)"</string>
+    <string name="spoken_description_caps_lock" msgid="3276478269526304432">"Suurtähelukk on sees (puudutage keelamiseks)"</string>
+    <string name="spoken_description_delete" msgid="8740376944276199801">"Kustuta"</string>
+    <string name="spoken_description_to_symbol" msgid="5486340107500448969">"Sümbolid"</string>
+    <string name="spoken_description_to_alpha" msgid="23129338819771807">"Tähed"</string>
+    <string name="spoken_description_to_numeric" msgid="591752092685161732">"Numbrid"</string>
+    <string name="spoken_description_settings" msgid="4627462689603838099">"Seaded"</string>
+    <string name="spoken_description_tab" msgid="2667716002663482248">"Tabulaator"</string>
+    <string name="spoken_description_space" msgid="2582521050049860859">"Tühik"</string>
+    <string name="spoken_description_mic" msgid="615536748882611950">"Kõnesisend"</string>
+    <string name="spoken_description_smiley" msgid="2256309826200113918">"Naerunägu"</string>
+    <string name="spoken_description_return" msgid="8178083177238315647">"Tagasi"</string>
+    <string name="spoken_description_search" msgid="1247236163755920808">"Otsing"</string>
+    <string name="spoken_description_dot" msgid="40711082435231673">"Punkt"</string>
+    <string name="spoken_description_language_switch" msgid="5507091328222331316">"Keele vahetamine"</string>
+    <string name="spoken_description_action_next" msgid="8636078276664150324">"Järgmine"</string>
+    <string name="spoken_description_action_previous" msgid="800872415009336208">"Eelmine"</string>
+    <string name="spoken_description_shiftmode_on" msgid="5700440798609574589">"Tõstuklahv on lubatud"</string>
+    <string name="spoken_description_shiftmode_locked" msgid="593175803181701830">"Suurtähelukk on lubatud"</string>
+    <string name="spoken_description_shiftmode_off" msgid="657219998449174808">"Tõstuklahv on keelatud"</string>
+    <string name="spoken_description_mode_symbol" msgid="7183343879909747642">"Sümbolite režiim"</string>
+    <string name="spoken_description_mode_alpha" msgid="3528307674390156956">"Tähtede režiim"</string>
+    <string name="spoken_description_mode_phone" msgid="6520207943132026264">"Telefonirežiim"</string>
+    <string name="spoken_description_mode_phone_shift" msgid="5499629753962641227">"Telefoni sümbolite režiim"</string>
+    <string name="announce_keyboard_hidden" msgid="8718927835531429807">"Klaviatuur on peidetud"</string>
+    <string name="announce_keyboard_mode" msgid="4729081055438508321">"Näitab klaviatuuri režiimil <xliff:g id="MODE">%s</xliff:g>"</string>
+    <string name="keyboard_mode_date" msgid="3137520166817128102">"kuupäev"</string>
+    <string name="keyboard_mode_date_time" msgid="339593358488851072">"kuupäev ja kellaaeg"</string>
+    <string name="keyboard_mode_email" msgid="6216248078128294262">"e-post"</string>
+    <string name="keyboard_mode_im" msgid="1137405089766557048">"sõnumiside"</string>
+    <string name="keyboard_mode_number" msgid="7991623440699957069">"number"</string>
+    <string name="keyboard_mode_phone" msgid="6851627527401433229">"telefon"</string>
+    <string name="keyboard_mode_text" msgid="6479436687899701619">"tekst"</string>
+    <string name="keyboard_mode_time" msgid="4381856885582143277">"aeg"</string>
+    <string name="keyboard_mode_url" msgid="1519819835514911218">"URL"</string>
+    <string name="voice_input" msgid="3583258583521397548">"Häälesisendi klahv"</string>
+    <string name="voice_input_modes_main_keyboard" msgid="3360660341121083174">"Peamisel klaviatuuril"</string>
+    <string name="voice_input_modes_symbols_keyboard" msgid="7203213240786084067">"Sümbolite klaviatuuril"</string>
+    <string name="voice_input_modes_off" msgid="3745699748218082014">"Väljas"</string>
+    <string name="voice_input_modes_summary_main_keyboard" msgid="6586544292900314339">"Mikrofon peamisel klaviatuuril"</string>
+    <string name="voice_input_modes_summary_symbols_keyboard" msgid="5233725927281932391">"Mikr. sümb. klaviat."</string>
+    <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Kõnesisend on keelatud"</string>
+    <string name="configure_input_method" msgid="373356270290742459">"Sisestusmeetodite seadistamine"</string>
+    <string name="language_selection_title" msgid="1651299598555326750">"Sisestuskeeled"</string>
+    <string name="send_feedback" msgid="1780431884109392046">"Saatke tagasisidet"</string>
+    <string name="select_language" msgid="3693815588777926848">"Sisestuskeeled"</string>
+    <string name="hint_add_to_dictionary" msgid="573678656946085380">"Salvestamiseks puudutage uuesti"</string>
+    <string name="has_dictionary" msgid="6071847973466625007">"Sõnastik saadaval"</string>
+    <string name="prefs_enable_log" msgid="6620424505072963557">"Luba kasutaja tagasiside"</string>
+    <string name="prefs_description_log" msgid="7525225584555429211">"Aidake seda sisestusmeetodi redigeerijat parandada, saates automaatselt kasutusstatistikat ja krahhiaruandeid."</string>
+    <string name="keyboard_layout" msgid="8451164783510487501">"Klaviatuuri teema"</string>
+    <string name="subtype_en_GB" msgid="88170601942311355">"Inglise (UK)"</string>
+    <string name="subtype_en_US" msgid="6160452336634534239">"Inglise (USA)"</string>
+    <string name="subtype_es_US" msgid="5583145191430180200">"hispaania (USA)"</string>
+    <string name="subtype_with_layout_en_GB" msgid="2179097748724725906">"Inglise (UK) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
+    <string name="subtype_with_layout_en_US" msgid="1362581347576714579">"Inglise (USA) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
+    <string name="subtype_with_layout_es_US" msgid="6261791057007890189">"hispaania (USA) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
+    <string name="subtype_nepali_traditional" msgid="9032247506728040447">"<xliff:g id="LANGUAGE">%s</xliff:g> (traditsiooniline)"</string>
+    <string name="subtype_no_language" msgid="7137390094240139495">"Keel puudub (tähestik)"</string>
+    <string name="subtype_no_language_qwerty" msgid="244337630616742604">"Tähestik (QWERTY)"</string>
+    <string name="subtype_no_language_qwertz" msgid="443066912507547976">"Tähestik (QWERTZ)"</string>
+    <string name="subtype_no_language_azerty" msgid="8144348527575640087">"Tähestik (AZERTY)"</string>
+    <string name="subtype_no_language_dvorak" msgid="1564494667584718094">"Tähestik (Dvorak)"</string>
+    <string name="subtype_no_language_colemak" msgid="5837418400010302623">"Tähestik (Colemak)"</string>
+    <string name="subtype_no_language_pcqwerty" msgid="5354918232046200018">"Tähestik (PC)"</string>
+    <string name="subtype_emoji" msgid="7483586578074549196">"Emotikon"</string>
+    <string name="custom_input_styles_title" msgid="8429952441821251512">"Kohandage sisendlaadid"</string>
+    <string name="add_style" msgid="6163126614514489951">"Lisage laad"</string>
+    <string name="add" msgid="8299699805688017798">"Lisa"</string>
+    <string name="remove" msgid="4486081658752944606">"Eemalda"</string>
+    <string name="save" msgid="7646738597196767214">"Salvesta"</string>
+    <string name="subtype_locale" msgid="8576443440738143764">"Keel"</string>
+    <string name="keyboard_layout_set" msgid="4309233698194565609">"Paigutus"</string>
+    <string name="custom_input_style_note_message" msgid="8826731320846363423">"Kohandatud sisendi laad tuleb enne kasutamist lubada. Lubada?"</string>
+    <string name="enable" msgid="5031294444630523247">"Luba"</string>
+    <string name="not_now" msgid="6172462888202790482">"Mitte kohe"</string>
+    <string name="custom_input_style_already_exists" msgid="8008728952215449707">"Sama sisendstiil on juba olemas: <xliff:g id="INPUT_STYLE_NAME">%s</xliff:g>"</string>
+    <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Kasutatavuse uurimisrežiim"</string>
+    <string name="prefs_key_longpress_timeout_settings" msgid="6102240298932897873">"Pika klahvivajutuse viide"</string>
+    <string name="prefs_keypress_vibration_duration_settings" msgid="7918341459947439226">"Klahvivajutuse vibreerimise kestus"</string>
+    <string name="prefs_keypress_sound_volume_settings" msgid="6027007337036891623">"Klahvivajutuse helitugevus"</string>
+    <string name="prefs_read_external_dictionary" msgid="2588931418575013067">"Välise sõnastikufaili lugemine"</string>
+    <string name="read_external_dictionary_no_files_message" msgid="4947420942224623792">"Kaustas Allalaadimised pole ühtegi sõnastikufaili"</string>
+    <string name="read_external_dictionary_multiple_files_title" msgid="7637749044265808628">"Installitava sõnastikufaili valimine"</string>
+    <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"Kas soovite tõesti installida faili lokaadile <xliff:g id="LOCALE_NAME">%s</xliff:g>?"</string>
+    <string name="error" msgid="8940763624668513648">"Ilmnes viga"</string>
+    <string name="button_default" msgid="3988017840431881491">"Vaikeväärtus"</string>
+    <string name="setup_welcome_title" msgid="6112821709832031715">"Tere tulemast rakendusse <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_welcome_additional_description" msgid="8150252008545768953">"joonistusega sisestamisega"</string>
+    <string name="setup_start_action" msgid="8936036460897347708">"Alustamine"</string>
+    <string name="setup_next_action" msgid="371821437915144603">"Järgmine toiming"</string>
+    <string name="setup_steps_title" msgid="6400373034871816182">"Rakenduse <xliff:g id="APPLICATION_NAME">%s</xliff:g> seadistamine"</string>
+    <string name="setup_step1_title" msgid="3147967630253462315">"Lubage <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step1_instruction" msgid="2578631936624637241">"Märkige oma keele ja sisestamise seadetes rakendus „<xliff:g id="APPLICATION_NAME">%s</xliff:g>”. See lubab rakenduse käitamise teie seadmes."</string>
+    <string name="setup_step1_finished_instruction" msgid="10761482004957994">"<xliff:g id="APPLICATION_NAME">%s</xliff:g> on teie keele- ja sisestusseadetes juba lubatud, seega on see toiming tehtud. Asuge järgmise toimingu juurde."</string>
+    <string name="setup_step1_action" msgid="4366513534999901728">"Luba seadetes"</string>
+    <string name="setup_step2_title" msgid="6860725447906690594">"Minge üle rakendusele <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step2_instruction" msgid="9141481964870023336">"Järgmisena valige aktiivseks tekstisisestusmeetodiks rakendus „<xliff:g id="APPLICATION_NAME">%s</xliff:g>”."</string>
+    <string name="setup_step2_action" msgid="1660330307159824337">"Vaheta sisestusmeetodeid"</string>
+    <string name="setup_step3_title" msgid="3154757183631490281">"Õnnitleme. Kõik on valmis!"</string>
+    <string name="setup_step3_instruction" msgid="8025981829605426000">"Nüüd saate rakendusega <xliff:g id="APPLICATION_NAME">%s</xliff:g> sisestada kõikides oma lemmikrakendustes."</string>
+    <string name="setup_step3_action" msgid="600879797256942259">"Seadista lisakeeled"</string>
+    <string name="setup_finish_action" msgid="276559243409465389">"Lõpetatud"</string>
+    <string name="show_setup_wizard_icon" msgid="5008028590593710830">"Kuva rakenduse ikoon"</string>
+    <string name="show_setup_wizard_icon_summary" msgid="4119998322536880213">"Rakenduse ikooni kuvamine käivitajas"</string>
+    <string name="app_name" msgid="6320102637491234792">"Sõnastikupakkuja"</string>
+    <string name="dictionary_provider_name" msgid="3027315045397363079">"Sõnastikupakkuja"</string>
+    <string name="dictionary_service_name" msgid="6237472350693511448">"Sõnastikuteenus"</string>
+    <string name="download_description" msgid="6014835283119198591">"Sõnastiku värskendamisteave"</string>
+    <string name="dictionary_settings_title" msgid="8091417676045693313">"Pistiksõnastikud"</string>
+    <string name="dictionary_install_over_metered_network_prompt" msgid="3587517870006332980">"Sõnastik on saadaval"</string>
+    <string name="dictionary_settings_summary" msgid="5305694987799824349">"Sõnastike seaded"</string>
+    <string name="user_dictionaries" msgid="3582332055892252845">"Kasutaja sõnastikud"</string>
+    <string name="default_user_dict_pref_name" msgid="1625055720489280530">"Kasutaja sõnastik"</string>
+    <string name="dictionary_available" msgid="4728975345815214218">"Sõnastik on saadaval"</string>
+    <string name="dictionary_downloading" msgid="2982650524622620983">"Praegu allalaadimisel"</string>
+    <string name="dictionary_installed" msgid="8081558343559342962">"Installitud"</string>
+    <string name="dictionary_disabled" msgid="8950383219564621762">"Installitud, keelatud"</string>
+    <string name="cannot_connect_to_dict_service" msgid="9216933695765732398">"Probleem sõnastikuga ühendumisel"</string>
+    <string name="no_dictionaries_available" msgid="8039920716566132611">"Sõnastikke pole"</string>
+    <string name="check_for_updates_now" msgid="8087688440916388581">"Värskenda"</string>
+    <string name="last_update" msgid="730467549913588780">"Viimati värskendatud"</string>
+    <string name="message_updating" msgid="4457761393932375219">"Värskenduste otsimine"</string>
+    <string name="message_loading" msgid="8689096636874758814">"Laadimine ..."</string>
+    <string name="main_dict_description" msgid="3072821352793492143">"Peamine sõnastik"</string>
+    <string name="cancel" msgid="6830980399865683324">"Tühista"</string>
+    <string name="install_dict" msgid="180852772562189365">"Installi"</string>
+    <string name="cancel_download_dict" msgid="7843340278507019303">"Tühista"</string>
+    <string name="delete_dict" msgid="756853268088330054">"Kustuta"</string>
+    <string name="should_download_over_metered_prompt" msgid="2878629598667658845">"Mobiilseadmes valitud keelele on saadaval sõnastik.&lt;br/&gt; Teksti mugavamaks sisestamiseks soovitame &lt;b&gt;alla laadida&lt;/b&gt; <xliff:g id="LANGUAGE">%1$s</xliff:g> keele sõnastiku.&lt;br/&gt; &lt;br/&gt; 3G kaudu allalaadimisele võib kuluda minut või paar. Kehtida võivad tasud, kui te ei kasuta &lt;b&gt;piiramatut andmepaketti&lt;/b&gt;.&lt;br/&gt; Kui te ei tea, millist andmepaketti kasutate, soovitame allalaadimise automaatseks käivitamiseks leida WiFi-ühenduse.&lt;br/&gt; &lt;br/&gt; Nõuanne: sõnastikke saate alla laadida ja eemaldada, tehes valiku &lt;b&gt;Keel ja sisestamine&lt;/b&gt; mobiilseadme menüüs &lt;b&gt;Seaded&lt;/b&gt;."</string>
+    <string name="download_over_metered" msgid="1643065851159409546">"Laadi kohe alla (<xliff:g id="SIZE_IN_MEGABYTES">%1$.1f</xliff:g> MB)"</string>
+    <string name="do_not_download_over_metered" msgid="2176209579313941583">"Laadi alla WiFi kaudu"</string>
+    <string name="dict_available_notification_title" msgid="6514288591959117288">"Sõnastik on <xliff:g id="LANGUAGE">%1$s</xliff:g> keele jaoks saadaval"</string>
+    <string name="dict_available_notification_description" msgid="1075194169443163487">"Vajutage ülevaatamiseks ja allalaadimiseks"</string>
+    <string name="toast_downloading_suggestions" msgid="1313027353588566660">"Allalaadimine: <xliff:g id="LANGUAGE">%1$s</xliff:g> keele soovitused on varsti saadaval."</string>
+    <string name="version_text" msgid="2715354215568469385">"Versioon <xliff:g id="VERSION_NUMBER">%1$s</xliff:g>"</string>
+    <string name="user_dict_settings_add_menu_title" msgid="1254195365689387076">"Lisa"</string>
+    <string name="user_dict_settings_add_dialog_title" msgid="4096700390211748168">"Sõnaraamatusse lisamine"</string>
+    <string name="user_dict_settings_add_screen_title" msgid="5818914331629278758">"Fraas"</string>
+    <string name="user_dict_settings_add_dialog_more_options" msgid="5671682004887093112">"Rohkem valikuid"</string>
+    <string name="user_dict_settings_add_dialog_less_options" msgid="2716586567241724126">"Vähem valikuid"</string>
+    <string name="user_dict_settings_add_dialog_confirm" msgid="4703129507388332950">"OK"</string>
+    <string name="user_dict_settings_add_word_option_name" msgid="6665558053408962865">"Sõna:"</string>
+    <string name="user_dict_settings_add_shortcut_option_name" msgid="3094731590655523777">"Otsetee:"</string>
+    <string name="user_dict_settings_add_locale_option_name" msgid="4738643440987277705">"Keel:"</string>
+    <string name="user_dict_settings_add_word_hint" msgid="4902434148985906707">"Sisestage sõna"</string>
+    <string name="user_dict_settings_add_shortcut_hint" msgid="2265453012555060178">"Valikuline otsetee"</string>
+    <string name="user_dict_settings_edit_dialog_title" msgid="3765774633869590352">"Sõna muutmine"</string>
+    <string name="user_dict_settings_context_menu_edit_title" msgid="6812255903472456302">"Muuda"</string>
+    <string name="user_dict_settings_context_menu_delete_title" msgid="8142932447689461181">"Kustuta"</string>
+    <string name="user_dict_settings_empty_text" msgid="558499587532668203">"Teie kasutaja sõnaraamatus ei ole ühtegi sõna. Sõna saate lisada, puudutades nuppu Lisa (+)."</string>
+    <string name="user_dict_settings_all_languages" msgid="8276126583216298886">"Kõikides keeltes"</string>
+    <string name="user_dict_settings_more_languages" msgid="7131268499685180461">"Rohkem keeli ..."</string>
+    <string name="user_dict_settings_delete" msgid="110413335187193859">"Kustuta"</string>
+    <string name="user_dict_fast_scroll_alphabet" msgid="5431919401558285473">" ABCDEFGHIJKLMNOPQRSŠZŽTUVWÕÄÖÜXY"</string>
+</resources>
diff --git a/java/res/values-et/strings.xml b/java/res/values-et/strings.xml
index 0874a93..9b9c93a 100644
--- a/java/res/values-et/strings.xml
+++ b/java/res/values-et/strings.xml
@@ -64,8 +64,8 @@
     <string name="auto_correction_summary" msgid="5625751551134658006">"Tühik ja kirjavahemärgid parand. autom. kirjavigadega sõnad"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Väljas"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Mõõdukas"</string>
-    <string name="auto_correction_threshold_mode_aggressive" msgid="3524029103734923819">"Agressiivne"</string>
-    <string name="auto_correction_threshold_mode_very_aggressive" msgid="3386782235540547678">"Väga agressiivne"</string>
+    <string name="auto_correction_threshold_mode_aggressive" msgid="7319007299148899623">"Agressiivne"</string>
+    <string name="auto_correction_threshold_mode_very_aggressive" msgid="1853309024129480416">"Väga agressiivne"</string>
     <string name="bigram_prediction" msgid="1084449187723948550">"Järgmise sõna soovitused"</string>
     <string name="bigram_prediction_summary" msgid="3896362682751109677">"Soovituste tegemisel eelmise sõna kasutamine"</string>
     <string name="gesture_input" msgid="826951152254563827">"Luba joonistusega sisestamine"</string>
@@ -143,13 +143,13 @@
     <string name="subtype_with_layout_en_GB" msgid="2179097748724725906">"Inglise (UK) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_en_US" msgid="1362581347576714579">"Inglise (USA) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_es_US" msgid="6261791057007890189">"hispaania (USA) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
-    <string name="subtype_no_language" msgid="141420857808801746">"Keel puudub"</string>
-    <string name="subtype_no_language_qwerty" msgid="2956121451616633133">"Keel puudub (QWERTY)"</string>
-    <string name="subtype_no_language_qwertz" msgid="1177848172397202890">"Keel puudub (QWERTZ)"</string>
-    <string name="subtype_no_language_azerty" msgid="8721460968141187394">"Keel puudub (AZERTY)"</string>
-    <string name="subtype_no_language_dvorak" msgid="3122976737669823935">"Keel puudub (Dvorak)"</string>
-    <string name="subtype_no_language_colemak" msgid="4205992994906097244">"Keel puudub (Colemak)"</string>
-    <string name="subtype_no_language_pcqwerty" msgid="8840928374394180189">"Keel puudub (PC)"</string>
+    <string name="subtype_no_language" msgid="7137390094240139495">"Keel puudub (tähestik)"</string>
+    <string name="subtype_no_language_qwerty" msgid="244337630616742604">"Tähestik (QWERTY)"</string>
+    <string name="subtype_no_language_qwertz" msgid="443066912507547976">"Tähestik (QWERTZ)"</string>
+    <string name="subtype_no_language_azerty" msgid="8144348527575640087">"Tähestik (AZERTY)"</string>
+    <string name="subtype_no_language_dvorak" msgid="1564494667584718094">"Tähestik (Dvorak)"</string>
+    <string name="subtype_no_language_colemak" msgid="5837418400010302623">"Tähestik (Colemak)"</string>
+    <string name="subtype_no_language_pcqwerty" msgid="5354918232046200018">"Tähestik (PC)"</string>
     <string name="custom_input_styles_title" msgid="8429952441821251512">"Kohandage sisendlaadid"</string>
     <string name="add_style" msgid="6163126614514489951">"Lisage laad"</string>
     <string name="add" msgid="8299699805688017798">"Lisa"</string>
diff --git a/java/res/values-fa/strings.xml b/java/res/values-fa/strings.xml
index 501b678..5d89516 100644
--- a/java/res/values-fa/strings.xml
+++ b/java/res/values-fa/strings.xml
@@ -64,8 +64,8 @@
     <string name="auto_correction_summary" msgid="5625751551134658006">"کلید فاصله و علائم نگارشی به صورت خودکار کلماتی را که غلط تایپ شده‌اند تصحیح می‌کنند"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"خاموش"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"متوسط"</string>
-    <string name="auto_correction_threshold_mode_aggressive" msgid="3524029103734923819">"فعال"</string>
-    <string name="auto_correction_threshold_mode_very_aggressive" msgid="3386782235540547678">"بسیار پرخاشگرانه"</string>
+    <string name="auto_correction_threshold_mode_aggressive" msgid="7319007299148899623">"شدید"</string>
+    <string name="auto_correction_threshold_mode_very_aggressive" msgid="1853309024129480416">"بسیار شدید"</string>
     <string name="bigram_prediction" msgid="1084449187723948550">"پیشنهادات کلمه بعدی"</string>
     <string name="bigram_prediction_summary" msgid="3896362682751109677">"استفاده از کلمه قبلی در ایجاد پیشنهادات"</string>
     <string name="gesture_input" msgid="826951152254563827">"فعال کردن تایپ حرکتی"</string>
@@ -147,13 +147,15 @@
     <string name="subtype_with_layout_en_GB" msgid="2179097748724725906">"انگلیسی (انگلستان) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_en_US" msgid="1362581347576714579">"انگلیسی (ایالات متحده) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_es_US" msgid="6261791057007890189">"اسپانیایی (آمریکا) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
-    <string name="subtype_no_language" msgid="141420857808801746">"زبانی موجود نیست"</string>
-    <string name="subtype_no_language_qwerty" msgid="2956121451616633133">"بدون زبان (QWERTY)"</string>
-    <string name="subtype_no_language_qwertz" msgid="1177848172397202890">"هیچکدام از زبان‌ها (QWERTZ)"</string>
-    <string name="subtype_no_language_azerty" msgid="8721460968141187394">"هیچکدام از زبان‌ها (AZERTY)"</string>
-    <string name="subtype_no_language_dvorak" msgid="3122976737669823935">"هیچکدام از زبان‌ها (Dvorak)"</string>
-    <string name="subtype_no_language_colemak" msgid="4205992994906097244">"هیچکدام از زبان‌ها (Colemak)"</string>
-    <string name="subtype_no_language_pcqwerty" msgid="8840928374394180189">"هیچکدام از زبان‌ها (PC)"</string>
+    <string name="subtype_nepali_traditional" msgid="9032247506728040447">"<xliff:g id="LANGUAGE">%s</xliff:g> (سنتی)"</string>
+    <string name="subtype_no_language" msgid="7137390094240139495">"بدون زبان (حروف الفبا)"</string>
+    <string name="subtype_no_language_qwerty" msgid="244337630616742604">"حروف الفبا (QWERTY)"</string>
+    <string name="subtype_no_language_qwertz" msgid="443066912507547976">"حروف الفبا (QWERTZ)"</string>
+    <string name="subtype_no_language_azerty" msgid="8144348527575640087">"حروف الفبا (AZERTY)"</string>
+    <string name="subtype_no_language_dvorak" msgid="1564494667584718094">"حروف الفبا (Dvorak)"</string>
+    <string name="subtype_no_language_colemak" msgid="5837418400010302623">"حروف الفبا (Colemak)"</string>
+    <string name="subtype_no_language_pcqwerty" msgid="5354918232046200018">"حروف الفبا (PC)"</string>
+    <string name="subtype_emoji" msgid="7483586578074549196">"Emoji"</string>
     <string name="custom_input_styles_title" msgid="8429952441821251512">"سبک‌های ورودی سفارشی"</string>
     <string name="add_style" msgid="6163126614514489951">"افزودن سبک"</string>
     <string name="add" msgid="8299699805688017798">"افزودن"</string>
@@ -163,7 +165,7 @@
     <string name="keyboard_layout_set" msgid="4309233698194565609">"چیدمان"</string>
     <string name="custom_input_style_note_message" msgid="8826731320846363423">"سبک ورودی سفارشی شما باید قبل از شروع به استفاده از آن فعال شود. می‌خواهید اکنون آن را فعال کنید؟"</string>
     <string name="enable" msgid="5031294444630523247">"فعال کردن"</string>
-    <string name="not_now" msgid="6172462888202790482">"اکنون خیر"</string>
+    <string name="not_now" msgid="6172462888202790482">"الآن نه"</string>
     <string name="custom_input_style_already_exists" msgid="8008728952215449707">"سبک ورودی مشابهی در حال حاضر وجود دارد: <xliff:g id="INPUT_STYLE_NAME">%s</xliff:g>"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"حالت بررسی قابلیت استفاده"</string>
     <string name="prefs_key_longpress_timeout_settings" msgid="6102240298932897873">"تأخیر فشار طولانی کلید"</string>
@@ -205,7 +207,7 @@
     <string name="dictionary_available" msgid="4728975345815214218">"فرهنگ لغت موجود است"</string>
     <string name="dictionary_downloading" msgid="2982650524622620983">"موارد در حال دانلود فعلی"</string>
     <string name="dictionary_installed" msgid="8081558343559342962">"نصب شده"</string>
-    <string name="dictionary_disabled" msgid="8950383219564621762">"نصب شد، غیرفعال شد"</string>
+    <string name="dictionary_disabled" msgid="8950383219564621762">"نصب‌شده، غیرفعال شد"</string>
     <string name="cannot_connect_to_dict_service" msgid="9216933695765732398">"مشکل اتصال به سرویس فرهنگ لغت"</string>
     <string name="no_dictionaries_available" msgid="8039920716566132611">"هیچ فرهنگ لغتی موجود نیست"</string>
     <string name="check_for_updates_now" msgid="8087688440916388581">"بازخوانی"</string>
diff --git a/java/res/values-fi/strings.xml b/java/res/values-fi/strings.xml
index 0696662..4cdf621 100644
--- a/java/res/values-fi/strings.xml
+++ b/java/res/values-fi/strings.xml
@@ -22,12 +22,12 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="english_ime_input_options" msgid="3909945612939668554">"Syöttövalinnat"</string>
     <string name="english_ime_research_log" msgid="8492602295696577851">"Tutkimuslokin komennot"</string>
-    <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Hae kontaktien nimiä"</string>
-    <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Oikeinkirjoituksen tarkistus käyttää kontaktiluettelosi tietoja."</string>
+    <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Hae yht.tietojen nimiä"</string>
+    <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Oikeinkirjoituksen tarkistus käyttää yhteystietojasi."</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Käytä värinää näppäimiä painettaessa"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"Toista ääni näppäimiä painettaessa"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"Ponnahdusikkuna painalluksella"</string>
-    <string name="general_category" msgid="1859088467017573195">"Yleinen"</string>
+    <string name="general_category" msgid="1859088467017573195">"Yleiset"</string>
     <string name="correction_category" msgid="2236750915056607613">"Tekstin korjaus"</string>
     <string name="gesture_typing_category" msgid="497263612130532630">"Piirtokirjoitus"</string>
     <string name="misc_category" msgid="6894192814868233453">"Muut vaihtoehdot"</string>
@@ -38,34 +38,34 @@
     <string name="show_language_switch_key" msgid="5915478828318774384">"Kielenvaihtonäppäin"</string>
     <string name="show_language_switch_key_summary" msgid="7343403647474265713">"Näytä, kun käytössä on useita syöttökieliä"</string>
     <string name="sliding_key_input_preview" msgid="6604262359510068370">"Näytä liu\'utuksen tilaosoitin"</string>
-    <string name="sliding_key_input_preview_summary" msgid="6340524345729093886">"Näytä visuaalinen vihje liu\'uttaessasi Shift- tai Symbol-näppäim."</string>
+    <string name="sliding_key_input_preview_summary" msgid="6340524345729093886">"Näytä visuaalinen vihje Vaihto- tai Symbol-näppäim. liu\'uttam."</string>
     <string name="key_preview_popup_dismiss_delay" msgid="6213164897443068248">"Näppäimen hylkäysviive"</string>
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Ei viivettä"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Oletus"</string>
     <string name="abbreviation_unit_milliseconds" msgid="8700286094028323363">"<xliff:g id="MILLISECONDS">%s</xliff:g> ms"</string>
-    <string name="settings_system_default" msgid="6268225104743331821">"Järjest. oletusarvo"</string>
-    <string name="use_contacts_dict" msgid="4435317977804180815">"Ehdota yhteystietojen nimiä"</string>
+    <string name="settings_system_default" msgid="6268225104743331821">"Järjestelmän oletusarvo"</string>
+    <string name="use_contacts_dict" msgid="4435317977804180815">"Ehdota yht.tietojen nimiä"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Käytä yhteystietojen nimiä ehdotuksissa ja korjauksissa"</string>
     <string name="use_double_space_period" msgid="8781529969425082860">"Kaksoisvälilyönti = piste"</string>
     <string name="use_double_space_period_summary" msgid="6532892187247952799">"Välilyönnin kaksoisnapautus lisää tekstiin pisteen ja välilyönnin"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Automaattiset isot kirjaimet"</string>
     <string name="auto_cap_summary" msgid="7934452761022946874">"Kirjoita jokaisen lauseen ensimmäinen sana isolla alkukirjaimella"</string>
     <string name="edit_personal_dictionary" msgid="3996910038952940420">"Oma sanakirja"</string>
-    <string name="configure_dictionaries_title" msgid="4238652338556902049">"Lisäsanakirjat"</string>
+    <string name="configure_dictionaries_title" msgid="4238652338556902049">"Sanakirjalisäosat"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"Pääsanakirja"</string>
     <string name="prefs_show_suggestions" msgid="8026799663445531637">"Näytä korjausehdotukset"</string>
     <string name="prefs_show_suggestions_summary" msgid="1583132279498502825">"Näytä sanaehdotukset kirjoitettaessa"</string>
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Näytä aina"</string>
-    <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3859783767435239118">"Näytä pystyasennossa"</string>
+    <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3859783767435239118">"Näytä pystysuunnassa"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Piilota aina"</string>
     <string name="prefs_block_potentially_offensive_title" msgid="5078480071057408934">"Estä loukkaavat sanat"</string>
-    <string name="prefs_block_potentially_offensive_summary" msgid="2371835479734991364">"Älä ehdota mahdollisesti loukkaavia sanoja"</string>
+    <string name="prefs_block_potentially_offensive_summary" msgid="2371835479734991364">"Älä ehdota mahd. loukkaavia sanoja"</string>
     <string name="auto_correction" msgid="7630720885194996950">"Autom. korjaus"</string>
-    <string name="auto_correction_summary" msgid="5625751551134658006">"Välilyönnit ja välimerkit korjaavat väärinkirjoitetut sanat automaattisesti"</string>
-    <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Älä käytä"</string>
+    <string name="auto_correction_summary" msgid="5625751551134658006">"Välilyönti ja välimerkit korjaavat väärinkirjoitetut sanat autom."</string>
+    <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Ei käytössä"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Osittainen"</string>
-    <string name="auto_correction_threshold_mode_aggressive" msgid="3524029103734923819">"Täysi"</string>
-    <string name="auto_correction_threshold_mode_very_aggressive" msgid="3386782235540547678">"Hyvin aggressiivinen"</string>
+    <string name="auto_correction_threshold_mode_aggressive" msgid="7319007299148899623">"Aggressiivinen"</string>
+    <string name="auto_correction_threshold_mode_very_aggressive" msgid="1853309024129480416">"Hyvin aggressiivinen"</string>
     <string name="bigram_prediction" msgid="1084449187723948550">"Seuraavan sanan ehdotukset"</string>
     <string name="bigram_prediction_summary" msgid="3896362682751109677">"Käytä edellistä sanaa ehdotuksien perusteena"</string>
     <string name="gesture_input" msgid="826951152254563827">"Ota piirtokirjoitus käyttöön"</string>
@@ -73,38 +73,38 @@
     <string name="gesture_preview_trail" msgid="3802333369335722221">"Näytä eleen jälki"</string>
     <string name="gesture_floating_preview_text" msgid="4443240334739381053">"Dynaaminen kelluva esikatselu"</string>
     <string name="gesture_floating_preview_text_summary" msgid="4472696213996203533">"Näytä ehdotettu sana piirron aikana"</string>
-    <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : Tallennettu"</string>
-    <string name="label_go_key" msgid="1635148082137219148">"Siirry"</string>
+    <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g>: tallennettu"</string>
+    <string name="label_go_key" msgid="1635148082137219148">"Mene"</string>
     <string name="label_next_key" msgid="362972844525672568">"Seur."</string>
-    <string name="label_previous_key" msgid="1211868118071386787">"Edell"</string>
+    <string name="label_previous_key" msgid="1211868118071386787">"Edel."</string>
     <string name="label_done_key" msgid="2441578748772529288">"Valmis"</string>
-    <string name="label_send_key" msgid="2815056534433717444">"Lähetä"</string>
+    <string name="label_send_key" msgid="2815056534433717444">"Läh."</string>
     <string name="label_pause_key" msgid="181098308428035340">"Tauko"</string>
     <string name="label_wait_key" msgid="6402152600878093134">"Odota"</string>
-    <string name="spoken_use_headphones" msgid="896961781287283493">"Liitä kuulokkeet kuullaksesi, mitä näppäimiä painat kirjoittaessasi salasanaa."</string>
+    <string name="spoken_use_headphones" msgid="896961781287283493">"Liitä kuulokkeet, niin kuulet mitä näppäimiä painat kirjoittaessasi salasanaa."</string>
     <string name="spoken_current_text_is" msgid="2485723011272583845">"Nykyinen teksti on %s"</string>
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"Ei kirjoitettua tekstiä"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"Näppäimen koodi %d"</string>
-    <string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
-    <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Vaihto päällä (napauta poistaaksesi käytöstä)"</string>
-    <string name="spoken_description_caps_lock" msgid="3276478269526304432">"Caps Lock päällä (napauta poistaaksesi käytöstä)"</string>
-    <string name="spoken_description_delete" msgid="8740376944276199801">"Poisto"</string>
+    <string name="spoken_description_shift" msgid="244197883292549308">"Vaihto"</string>
+    <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Vaihto päällä (poista käytöstä napauttamalla)"</string>
+    <string name="spoken_description_caps_lock" msgid="3276478269526304432">"Caps Lock päällä (poista käytöstä napauttamalla)"</string>
+    <string name="spoken_description_delete" msgid="8740376944276199801">"Poista"</string>
     <string name="spoken_description_to_symbol" msgid="5486340107500448969">"Symbolit"</string>
     <string name="spoken_description_to_alpha" msgid="23129338819771807">"Kirjaimet"</string>
     <string name="spoken_description_to_numeric" msgid="591752092685161732">"Numerot"</string>
     <string name="spoken_description_settings" msgid="4627462689603838099">"Asetukset"</string>
     <string name="spoken_description_tab" msgid="2667716002663482248">"Sarkain"</string>
     <string name="spoken_description_space" msgid="2582521050049860859">"Välilyönti"</string>
-    <string name="spoken_description_mic" msgid="615536748882611950">"Puheohjaus"</string>
+    <string name="spoken_description_mic" msgid="615536748882611950">"Äänisyöte"</string>
     <string name="spoken_description_smiley" msgid="2256309826200113918">"Hymiö"</string>
-    <string name="spoken_description_return" msgid="8178083177238315647">"Enter"</string>
+    <string name="spoken_description_return" msgid="8178083177238315647">"Takaisin"</string>
     <string name="spoken_description_search" msgid="1247236163755920808">"Haku"</string>
     <string name="spoken_description_dot" msgid="40711082435231673">"Piste"</string>
     <string name="spoken_description_language_switch" msgid="5507091328222331316">"Vaihda kieli"</string>
     <string name="spoken_description_action_next" msgid="8636078276664150324">"Seuraava"</string>
     <string name="spoken_description_action_previous" msgid="800872415009336208">"Edellinen"</string>
     <string name="spoken_description_shiftmode_on" msgid="5700440798609574589">"Vaihto päällä"</string>
-    <string name="spoken_description_shiftmode_locked" msgid="593175803181701830">"Caps lock päällä"</string>
+    <string name="spoken_description_shiftmode_locked" msgid="593175803181701830">"Caps Lock päällä"</string>
     <string name="spoken_description_shiftmode_off" msgid="657219998449174808">"Vaihto pois käytöstä"</string>
     <string name="spoken_description_mode_symbol" msgid="7183343879909747642">"Symbolit-tila"</string>
     <string name="spoken_description_mode_alpha" msgid="3528307674390156956">"Näppäimistötila"</string>
@@ -121,13 +121,13 @@
     <string name="keyboard_mode_text" msgid="6479436687899701619">"teksti"</string>
     <string name="keyboard_mode_time" msgid="4381856885582143277">"aika"</string>
     <string name="keyboard_mode_url" msgid="1519819835514911218">"URL-osoite"</string>
-    <string name="voice_input" msgid="3583258583521397548">"Ääniohjausavain"</string>
+    <string name="voice_input" msgid="3583258583521397548">"Äänisyöteavain"</string>
     <string name="voice_input_modes_main_keyboard" msgid="3360660341121083174">"Päänäppäimistössä"</string>
-    <string name="voice_input_modes_symbols_keyboard" msgid="7203213240786084067">"Symbolinäppäimistössä"</string>
-    <string name="voice_input_modes_off" msgid="3745699748218082014">"Älä näytä"</string>
+    <string name="voice_input_modes_symbols_keyboard" msgid="7203213240786084067">"Symbolinäppäim."</string>
+    <string name="voice_input_modes_off" msgid="3745699748218082014">"Ei käytössä"</string>
     <string name="voice_input_modes_summary_main_keyboard" msgid="6586544292900314339">"Mikr. päänäppäim."</string>
-    <string name="voice_input_modes_summary_symbols_keyboard" msgid="5233725927281932391">"Mikr. symbolinäppäim."</string>
-    <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Ääniohjaus on pois käytöstä"</string>
+    <string name="voice_input_modes_summary_symbols_keyboard" msgid="5233725927281932391">"Mikr. symbolinäpp."</string>
+    <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Äänisyöte ei käyt."</string>
     <string name="configure_input_method" msgid="373356270290742459">"Määritä syöttötavat"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Syöttökielet"</string>
     <string name="send_feedback" msgid="1780431884109392046">"Lähetä palautetta"</string>
@@ -140,16 +140,18 @@
     <string name="subtype_en_GB" msgid="88170601942311355">"englanti (Iso-Britannia)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"englanti (Yhdysvallat)"</string>
     <string name="subtype_es_US" msgid="5583145191430180200">"espanja (Yhdysvallat)"</string>
-    <string name="subtype_with_layout_en_GB" msgid="2179097748724725906">"englanti (Iso-Britannia) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
+    <string name="subtype_with_layout_en_GB" msgid="2179097748724725906">"englanti (Iso-Br.) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_en_US" msgid="1362581347576714579">"englanti (USA) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_es_US" msgid="6261791057007890189">"espanja (Yhdysvallat) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
-    <string name="subtype_no_language" msgid="141420857808801746">"Ei kieltä"</string>
-    <string name="subtype_no_language_qwerty" msgid="2956121451616633133">"Ei kieltä (QWERTY)"</string>
-    <string name="subtype_no_language_qwertz" msgid="1177848172397202890">"Ei kieltä (QWERTZ)"</string>
-    <string name="subtype_no_language_azerty" msgid="8721460968141187394">"Ei kieltä (AZERTY)"</string>
-    <string name="subtype_no_language_dvorak" msgid="3122976737669823935">"Ei kieltä (Dvorak)"</string>
-    <string name="subtype_no_language_colemak" msgid="4205992994906097244">"Ei kieltä (Colemak)"</string>
-    <string name="subtype_no_language_pcqwerty" msgid="8840928374394180189">"Ei kieltä (PC)"</string>
+    <string name="subtype_nepali_traditional" msgid="9032247506728040447">"<xliff:g id="LANGUAGE">%s</xliff:g> (perinteinen)"</string>
+    <string name="subtype_no_language" msgid="7137390094240139495">"Ei kieltä (aakkoset)"</string>
+    <string name="subtype_no_language_qwerty" msgid="244337630616742604">"Aakkoset (QWERTY)"</string>
+    <string name="subtype_no_language_qwertz" msgid="443066912507547976">"Aakkoset (QWERTZ)"</string>
+    <string name="subtype_no_language_azerty" msgid="8144348527575640087">"Aakkoset (AZERTY)"</string>
+    <string name="subtype_no_language_dvorak" msgid="1564494667584718094">"Aakkoset (Dvorak)"</string>
+    <string name="subtype_no_language_colemak" msgid="5837418400010302623">"Aakkoset (Colemak)"</string>
+    <string name="subtype_no_language_pcqwerty" msgid="5354918232046200018">"Aakkoset (PC)"</string>
+    <string name="subtype_emoji" msgid="7483586578074549196">"Emoji"</string>
     <string name="custom_input_styles_title" msgid="8429952441821251512">"Muokatut syöttötyylit"</string>
     <string name="add_style" msgid="6163126614514489951">"Lisää tyyli"</string>
     <string name="add" msgid="8299699805688017798">"Lisää"</string>
@@ -157,10 +159,10 @@
     <string name="save" msgid="7646738597196767214">"Tallenna"</string>
     <string name="subtype_locale" msgid="8576443440738143764">"Kieli"</string>
     <string name="keyboard_layout_set" msgid="4309233698194565609">"Asettelu"</string>
-    <string name="custom_input_style_note_message" msgid="8826731320846363423">"Ota muokattu syötetyyli käyttöön käyttääksesi sitä."</string>
-    <string name="enable" msgid="5031294444630523247">"Käyttöön"</string>
+    <string name="custom_input_style_note_message" msgid="8826731320846363423">"Muokattua syöttötyyliä ei ole otettu käyttöön. Haluatko ottaa sen käyttöön nyt?"</string>
+    <string name="enable" msgid="5031294444630523247">"Ota käyttöön"</string>
     <string name="not_now" msgid="6172462888202790482">"Ei nyt"</string>
-    <string name="custom_input_style_already_exists" msgid="8008728952215449707">"Sama tulotyyli on jo olemassa: <xliff:g id="INPUT_STYLE_NAME">%s</xliff:g>"</string>
+    <string name="custom_input_style_already_exists" msgid="8008728952215449707">"Syöttötyyli on jo olemassa: <xliff:g id="INPUT_STYLE_NAME">%s</xliff:g>"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Käytettävyystutkimustila"</string>
     <string name="prefs_key_longpress_timeout_settings" msgid="6102240298932897873">"Pitkän painalluksen viive"</string>
     <string name="prefs_keypress_vibration_duration_settings" msgid="7918341459947439226">"Painalluksen värinän kesto"</string>
@@ -172,19 +174,19 @@
     <string name="error" msgid="8940763624668513648">"Tapahtui virhe"</string>
     <string name="button_default" msgid="3988017840431881491">"Oletusarvot"</string>
     <string name="setup_welcome_title" msgid="6112821709832031715">"Tervetuloa käyttämään sovellusta <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
-    <string name="setup_welcome_additional_description" msgid="8150252008545768953">"sekä piirtokirjoitus"</string>
+    <string name="setup_welcome_additional_description" msgid="8150252008545768953">"ja piirtokirjoitus"</string>
     <string name="setup_start_action" msgid="8936036460897347708">"Aloita"</string>
     <string name="setup_next_action" msgid="371821437915144603">"Seuraava vaihe"</string>
     <string name="setup_steps_title" msgid="6400373034871816182">"Sovelluksen <xliff:g id="APPLICATION_NAME">%s</xliff:g> asetukset"</string>
     <string name="setup_step1_title" msgid="3147967630253462315">"Ota <xliff:g id="APPLICATION_NAME">%s</xliff:g> käyttöön"</string>
-    <string name="setup_step1_instruction" msgid="2578631936624637241">"Valitse <xliff:g id="APPLICATION_NAME">%s</xliff:g> kieli- ja syöttötapa-asetuksissa, mikä valtuuttaa sovel. laitteellesi."</string>
-    <string name="setup_step1_finished_instruction" msgid="10761482004957994">"<xliff:g id="APPLICATION_NAME">%s</xliff:g> on jo käytössä Kieli- ja syöttöasetuksissa, joten tämä vaihe on tehty. Siirrytään eteenpäin!"</string>
+    <string name="setup_step1_instruction" msgid="2578631936624637241">"Valitse <xliff:g id="APPLICATION_NAME">%s</xliff:g> Kieli ja syöttötapa -asetuksissa. Se antaa sovellukselle luvan toimia laitteessasi."</string>
+    <string name="setup_step1_finished_instruction" msgid="10761482004957994">"<xliff:g id="APPLICATION_NAME">%s</xliff:g> on jo käytössä Kieli ja syöttötapa -asetuksissa, joten tämä vaihe on tehty. Siirrytään eteenpäin!"</string>
     <string name="setup_step1_action" msgid="4366513534999901728">"Ota käyttöön asetuksissa"</string>
     <string name="setup_step2_title" msgid="6860725447906690594">"Siirry sovellukseen <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
     <string name="setup_step2_instruction" msgid="9141481964870023336">"Valitse <xliff:g id="APPLICATION_NAME">%s</xliff:g> käytössä olevaksi tekstinsyöttötavaksi."</string>
     <string name="setup_step2_action" msgid="1660330307159824337">"Vaihda syöttötapaa"</string>
     <string name="setup_step3_title" msgid="3154757183631490281">"Onneksi olkoon, valmista tuli!"</string>
-    <string name="setup_step3_instruction" msgid="8025981829605426000">"Nyt voit kirjoittaa kaikkiin lempisovelluksiisi sovelluksen <xliff:g id="APPLICATION_NAME">%s</xliff:g> avulla."</string>
+    <string name="setup_step3_instruction" msgid="8025981829605426000">"Nyt voit kirjoittaa kaikissa lempisovelluksissasi sovelluksen <xliff:g id="APPLICATION_NAME">%s</xliff:g> avulla."</string>
     <string name="setup_step3_action" msgid="600879797256942259">"Määritä lisää kieliä"</string>
     <string name="setup_finish_action" msgid="276559243409465389">"Valmis"</string>
     <string name="show_setup_wizard_icon" msgid="5008028590593710830">"Näytä sovelluskuvake"</string>
@@ -199,11 +201,11 @@
     <string name="user_dictionaries" msgid="3582332055892252845">"Käyttäjän sanakirjat"</string>
     <string name="default_user_dict_pref_name" msgid="1625055720489280530">"Käyttäjän sanakirja"</string>
     <string name="dictionary_available" msgid="4728975345815214218">"Sanakirja saatavilla"</string>
-    <string name="dictionary_downloading" msgid="2982650524622620983">"Ladataan parhaillaan"</string>
+    <string name="dictionary_downloading" msgid="2982650524622620983">"Ladataan"</string>
     <string name="dictionary_installed" msgid="8081558343559342962">"Asennettu"</string>
     <string name="dictionary_disabled" msgid="8950383219564621762">"Asennettu, poistettu käytöstä"</string>
-    <string name="cannot_connect_to_dict_service" msgid="9216933695765732398">"Ongelma yhdistettässä sanakirjapalveluun"</string>
-    <string name="no_dictionaries_available" msgid="8039920716566132611">"Ei sanakirjoja saatavilla"</string>
+    <string name="cannot_connect_to_dict_service" msgid="9216933695765732398">"Ei yhteyttä sanak."</string>
+    <string name="no_dictionaries_available" msgid="8039920716566132611">"Ei sanak. saatavilla"</string>
     <string name="check_for_updates_now" msgid="8087688440916388581">"Päivitä"</string>
     <string name="last_update" msgid="730467549913588780">"Päivitetty viimeksi"</string>
     <string name="message_updating" msgid="4457761393932375219">"Tarkistetaan päivityksiä"</string>
@@ -217,8 +219,8 @@
     <string name="download_over_metered" msgid="1643065851159409546">"Lataa nyt (<xliff:g id="SIZE_IN_MEGABYTES">%1$.1f</xliff:g> Mt)"</string>
     <string name="do_not_download_over_metered" msgid="2176209579313941583">"Lataa wifi-yhteydellä"</string>
     <string name="dict_available_notification_title" msgid="6514288591959117288">"Kielen <xliff:g id="LANGUAGE">%1$s</xliff:g> sanakirja on saatavilla"</string>
-    <string name="dict_available_notification_description" msgid="1075194169443163487">"Paina tätä, jos haluat tarkastella kohdetta tai ladata sen"</string>
-    <string name="toast_downloading_suggestions" msgid="1313027353588566660">"Ladataan: pian ehdotuksia näytetään kielellä <xliff:g id="LANGUAGE">%1$s</xliff:g>."</string>
+    <string name="dict_available_notification_description" msgid="1075194169443163487">"Paina tätä, jos haluat tarkastella kohdetta ja ladata sen"</string>
+    <string name="toast_downloading_suggestions" msgid="1313027353588566660">"Ladataan: ehdotuksia näytetään pian kielellä <xliff:g id="LANGUAGE">%1$s</xliff:g>."</string>
     <string name="version_text" msgid="2715354215568469385">"Versio <xliff:g id="VERSION_NUMBER">%1$s</xliff:g>"</string>
     <string name="user_dict_settings_add_menu_title" msgid="1254195365689387076">"Lisää"</string>
     <string name="user_dict_settings_add_dialog_title" msgid="4096700390211748168">"Lisää sanakirjaan"</string>
@@ -227,7 +229,7 @@
     <string name="user_dict_settings_add_dialog_less_options" msgid="2716586567241724126">"Väh. vaihtoeht."</string>
     <string name="user_dict_settings_add_dialog_confirm" msgid="4703129507388332950">"OK"</string>
     <string name="user_dict_settings_add_word_option_name" msgid="6665558053408962865">"Sana:"</string>
-    <string name="user_dict_settings_add_shortcut_option_name" msgid="3094731590655523777">"Pikanäppäin"</string>
+    <string name="user_dict_settings_add_shortcut_option_name" msgid="3094731590655523777">"Pikanäppäin:"</string>
     <string name="user_dict_settings_add_locale_option_name" msgid="4738643440987277705">"Kieli:"</string>
     <string name="user_dict_settings_add_word_hint" msgid="4902434148985906707">"Kirjoita sana"</string>
     <string name="user_dict_settings_add_shortcut_hint" msgid="2265453012555060178">"Valinnainen pikanäppäin"</string>
diff --git a/java/res/values-fr-rCA/donottranslate.xml b/java/res/values-fr-rCA/donottranslate.xml
new file mode 100644
index 0000000..21f18d8
--- /dev/null
+++ b/java/res/values-fr-rCA/donottranslate.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2009, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Symbols that are normally preceded by a space (used to add an auto-space before these) -->
+    <!-- This is similar to French with the exception of "!" "?" and ";" which do not take a space before in Canadian French. Note that ":" does take a space before according to Canadian rules. -->
+    <string name="symbols_preceded_by_space">([{&amp;:</string>
+    <!-- Symbols that are normally followed by a space (used to add an auto-space after these) -->
+    <string name="symbols_followed_by_space">.,;:!?)]}&amp;</string>
+    <!-- Symbols that separate words -->
+    <!-- Don't remove the enclosing double quotes, they protect whitespace (not just U+0020) -->
+    <string name="symbols_word_separators">"&#x0009;&#x0020;\n"()[]{}*&amp;&lt;&gt;+=|.,;:!?/_\"</string>
+    <!-- Word connectors -->
+    <string name="symbols_word_connectors">\'-</string>
+</resources>
diff --git a/java/res/values-fr/strings.xml b/java/res/values-fr/strings.xml
index b5db430..e096a1f 100644
--- a/java/res/values-fr/strings.xml
+++ b/java/res/values-fr/strings.xml
@@ -64,8 +64,8 @@
     <string name="auto_correction_summary" msgid="5625751551134658006">"Corriger autom. orthographe (pression sur barre espace/signes ponctuation)"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Désactiver"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Simple"</string>
-    <string name="auto_correction_threshold_mode_aggressive" msgid="3524029103734923819">"Proactive"</string>
-    <string name="auto_correction_threshold_mode_very_aggressive" msgid="3386782235540547678">"Très exigeante"</string>
+    <string name="auto_correction_threshold_mode_aggressive" msgid="7319007299148899623">"Proactive"</string>
+    <string name="auto_correction_threshold_mode_very_aggressive" msgid="1853309024129480416">"Très proactive"</string>
     <string name="bigram_prediction" msgid="1084449187723948550">"Suggestions pour le mot suivant"</string>
     <string name="bigram_prediction_summary" msgid="3896362682751109677">"Utiliser le mot précédent pour les suggestions"</string>
     <string name="gesture_input" msgid="826951152254563827">"Activer la saisie gestuelle"</string>
@@ -143,13 +143,15 @@
     <string name="subtype_with_layout_en_GB" msgid="2179097748724725906">"Anglais (Royaume-Uni) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_en_US" msgid="1362581347576714579">"Anglais (États-Unis) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_es_US" msgid="6261791057007890189">"Espagnol (États-Unis) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
-    <string name="subtype_no_language" msgid="141420857808801746">"Aucune langue"</string>
-    <string name="subtype_no_language_qwerty" msgid="2956121451616633133">"Aucune langue (QWERTY)"</string>
-    <string name="subtype_no_language_qwertz" msgid="1177848172397202890">"Aucune langue (QWERTZ)"</string>
-    <string name="subtype_no_language_azerty" msgid="8721460968141187394">"Aucune langue (AZERTY)"</string>
-    <string name="subtype_no_language_dvorak" msgid="3122976737669823935">"Aucune langue (Dvorak)"</string>
-    <string name="subtype_no_language_colemak" msgid="4205992994906097244">"Aucune langue (Colemak)"</string>
-    <string name="subtype_no_language_pcqwerty" msgid="8840928374394180189">"Aucune langue (PC)"</string>
+    <string name="subtype_nepali_traditional" msgid="9032247506728040447">"<xliff:g id="LANGUAGE">%s</xliff:g> (traditionnel)"</string>
+    <string name="subtype_no_language" msgid="7137390094240139495">"Aucune langue (latin)"</string>
+    <string name="subtype_no_language_qwerty" msgid="244337630616742604">"Alphabet latin (QWERTY)"</string>
+    <string name="subtype_no_language_qwertz" msgid="443066912507547976">"Alphabet latin (QWERTZ)"</string>
+    <string name="subtype_no_language_azerty" msgid="8144348527575640087">"Alphabet latin (AZERTY)"</string>
+    <string name="subtype_no_language_dvorak" msgid="1564494667584718094">"Alphabet latin (Dvorak)"</string>
+    <string name="subtype_no_language_colemak" msgid="5837418400010302623">"Alphabet latin (Colemak)"</string>
+    <string name="subtype_no_language_pcqwerty" msgid="5354918232046200018">"Alphabet latin (PC)"</string>
+    <string name="subtype_emoji" msgid="7483586578074549196">"Emoji"</string>
     <string name="custom_input_styles_title" msgid="8429952441821251512">"Styles saisie personnalisés"</string>
     <string name="add_style" msgid="6163126614514489951">"Ajouter style"</string>
     <string name="add" msgid="8299699805688017798">"Ajouter"</string>
diff --git a/java/res/values-hi/strings.xml b/java/res/values-hi/strings.xml
index ca1c838..dcbbcd9 100644
--- a/java/res/values-hi/strings.xml
+++ b/java/res/values-hi/strings.xml
@@ -31,7 +31,7 @@
     <string name="correction_category" msgid="2236750915056607613">"पाठ सुधार"</string>
     <string name="gesture_typing_category" msgid="497263612130532630">"जेस्चर लिखना"</string>
     <string name="misc_category" msgid="6894192814868233453">"अन्य विकल्प"</string>
-    <string name="advanced_settings" msgid="362895144495591463">"उन्नत सेटिंग"</string>
+    <string name="advanced_settings" msgid="362895144495591463">"अतिरिक्त सेटिंग"</string>
     <string name="advanced_settings_summary" msgid="4487980456152830271">"विशेषज्ञों के लिए विकल्‍प"</string>
     <string name="include_other_imes_in_language_switch_list" msgid="4533689960308565519">"अन्‍य इनपुट पद्धतियों पर जाएं"</string>
     <string name="include_other_imes_in_language_switch_list_summary" msgid="840637129103317635">"भाषा स्‍विच कुंजी में अन्‍य इनपुट पद्धतियां भी शामिल हैं"</string>
@@ -41,7 +41,7 @@
     <string name="sliding_key_input_preview_summary" msgid="6340524345729093886">"Shift या Symbol कुंजियो से स्लाइड करते समय विज़ुअल क्यू दिखाएं"</string>
     <string name="key_preview_popup_dismiss_delay" msgid="6213164897443068248">"कुंजी पॉपअप खारिज़ विलंब"</string>
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"कोई विलंब नहीं"</string>
-    <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"डिफ़ॉल्ट"</string>
+    <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"सामान्य"</string>
     <string name="abbreviation_unit_milliseconds" msgid="8700286094028323363">"<xliff:g id="MILLISECONDS">%s</xliff:g> मिलीसेकंड"</string>
     <string name="settings_system_default" msgid="6268225104743331821">"सिस्टम डिफ़ॉल्ट"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"संपर्क नाम सुझाएं"</string>
@@ -50,8 +50,8 @@
     <string name="use_double_space_period_summary" msgid="6532892187247952799">"स्पेसबार पर डबल टैप करने से पीरियड शामिल हो जाता है जिसके बाद एक रिक्ति होती है"</string>
     <string name="auto_cap" msgid="1719746674854628252">"स्‍वत: अक्षर बड़े करना"</string>
     <string name="auto_cap_summary" msgid="7934452761022946874">"प्रत्येक वाक्य के पहले शब्द को बड़ा लिखें"</string>
-    <string name="edit_personal_dictionary" msgid="3996910038952940420">"व्यक्तिगत डिक्शनरी"</string>
-    <string name="configure_dictionaries_title" msgid="4238652338556902049">"एड-ऑन डिक्शनरी"</string>
+    <string name="edit_personal_dictionary" msgid="3996910038952940420">"व्यक्तिगत शब्दकोश"</string>
+    <string name="configure_dictionaries_title" msgid="4238652338556902049">"एड-ऑन शब्दकोश"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"मुख्‍य डिक्‍शनरी"</string>
     <string name="prefs_show_suggestions" msgid="8026799663445531637">"सुधार सुझाव दिखाएं"</string>
     <string name="prefs_show_suggestions_summary" msgid="1583132279498502825">"लिखते समय सुझाए गए शब्‍द दिखाएं"</string>
@@ -64,8 +64,8 @@
     <string name="auto_correction_summary" msgid="5625751551134658006">"Spacebar और विराम चिह्न गलत लिखे गए शब्‍दों को स्‍वचालित रूप से ठीक करते हैं"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"बंद"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"साधारण"</string>
-    <string name="auto_correction_threshold_mode_aggressive" msgid="3524029103734923819">"तीव्र"</string>
-    <string name="auto_correction_threshold_mode_very_aggressive" msgid="3386782235540547678">"बहुत तीव्र"</string>
+    <string name="auto_correction_threshold_mode_aggressive" msgid="7319007299148899623">"तेज़"</string>
+    <string name="auto_correction_threshold_mode_very_aggressive" msgid="1853309024129480416">"बहुत तेज़"</string>
     <string name="bigram_prediction" msgid="1084449187723948550">"अगले शब्द के सुझाव"</string>
     <string name="bigram_prediction_summary" msgid="3896362682751109677">"सुझाव बनाने में पिछले शब्द का उपयोग करें"</string>
     <string name="gesture_input" msgid="826951152254563827">"जेस्‍चर लिखना सक्षम करें"</string>
@@ -130,7 +130,7 @@
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"ध्‍वनि इनपुट अक्षम है"</string>
     <string name="configure_input_method" msgid="373356270290742459">"इनपुट पद्धति कॉन्‍फ़िगर करें"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"इनपुट भाषा"</string>
-    <string name="send_feedback" msgid="1780431884109392046">"फ़ीडबैक भेजें"</string>
+    <string name="send_feedback" msgid="1780431884109392046">"सुझाव भेजें"</string>
     <string name="select_language" msgid="3693815588777926848">"इनपुट भाषाएं"</string>
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"सहेजने के लिए पुन: स्‍पर्श करें"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"शब्‍दकोश उपलब्‍ध है"</string>
@@ -143,13 +143,15 @@
     <string name="subtype_with_layout_en_GB" msgid="2179097748724725906">"अंग्रेज़ी (यूके) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_en_US" msgid="1362581347576714579">"अंग्रेज़ी (यूएस) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_es_US" msgid="6261791057007890189">"स्पेनिश (यूएस) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
-    <string name="subtype_no_language" msgid="141420857808801746">"कोई भाषा नहीं"</string>
-    <string name="subtype_no_language_qwerty" msgid="2956121451616633133">"कोई भाषा नहीं (QWERTY)"</string>
-    <string name="subtype_no_language_qwertz" msgid="1177848172397202890">"कोई भाषा नहीं (QWERTZ)"</string>
-    <string name="subtype_no_language_azerty" msgid="8721460968141187394">"कोई भाषा नहीं (AZERTY)"</string>
-    <string name="subtype_no_language_dvorak" msgid="3122976737669823935">"कोई भाषा नहीं (Dvorak)"</string>
-    <string name="subtype_no_language_colemak" msgid="4205992994906097244">"कोई भाषा नहीं (Colemak)"</string>
-    <string name="subtype_no_language_pcqwerty" msgid="8840928374394180189">"कोई भाषा नहीं (PC)"</string>
+    <string name="subtype_nepali_traditional" msgid="9032247506728040447">"<xliff:g id="LANGUAGE">%s</xliff:g> (पारंपरिक)"</string>
+    <string name="subtype_no_language" msgid="7137390094240139495">"भाषा उपलब्ध नहीं है (लैटिन वर्णाक्षर)"</string>
+    <string name="subtype_no_language_qwerty" msgid="244337630616742604">"वर्णाक्षर (QWERTY)"</string>
+    <string name="subtype_no_language_qwertz" msgid="443066912507547976">"वर्णाक्षर (QWERTZ)"</string>
+    <string name="subtype_no_language_azerty" msgid="8144348527575640087">"वर्णाक्षर (AZERTY)"</string>
+    <string name="subtype_no_language_dvorak" msgid="1564494667584718094">"वर्णाक्षर (Dvorak)"</string>
+    <string name="subtype_no_language_colemak" msgid="5837418400010302623">"वर्णाक्षर (Colemak)"</string>
+    <string name="subtype_no_language_pcqwerty" msgid="5354918232046200018">"वर्णाक्षर (PC)"</string>
+    <string name="subtype_emoji" msgid="7483586578074549196">"Emoji"</string>
     <string name="custom_input_styles_title" msgid="8429952441821251512">"कस्‍टम इनपुट शैलियां"</string>
     <string name="add_style" msgid="6163126614514489951">"शैली जोड़ें"</string>
     <string name="add" msgid="8299699805688017798">"जोड़ें"</string>
@@ -164,56 +166,56 @@
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"उपयोगिता अध्ययन मोड"</string>
     <string name="prefs_key_longpress_timeout_settings" msgid="6102240298932897873">"कुंजी को देर तक दबाने का विलंब"</string>
     <string name="prefs_keypress_vibration_duration_settings" msgid="7918341459947439226">"कुंजी-स्पर्श कंपन अवधि"</string>
-    <string name="prefs_keypress_sound_volume_settings" msgid="6027007337036891623">"कुंजी-स्पर्श ध्वनि वॉल्यूम"</string>
-    <string name="prefs_read_external_dictionary" msgid="2588931418575013067">"बाहरी डिक्शनरी फ़ाइल पढ़ें"</string>
+    <string name="prefs_keypress_sound_volume_settings" msgid="6027007337036891623">"कुंजी-स्पर्श ध्वनि आवाज़"</string>
+    <string name="prefs_read_external_dictionary" msgid="2588931418575013067">"बाहरी शब्दकोश फ़ाइल पढ़ें"</string>
     <string name="read_external_dictionary_no_files_message" msgid="4947420942224623792">"डाउनलोड फ़ोल्डर में कोई शब्दकोश फ़ाइल नहीं है"</string>
     <string name="read_external_dictionary_multiple_files_title" msgid="7637749044265808628">"इंस्टॉल करने के लिए कोई शब्दकोश फ़ाइल चुनें"</string>
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"<xliff:g id="LOCALE_NAME">%s</xliff:g> के लिए वास्तव में यह फ़ाइल इंस्टॉल करें?"</string>
     <string name="error" msgid="8940763624668513648">"कोई त्रुटि हुई थी"</string>
-    <string name="button_default" msgid="3988017840431881491">"डिफ़ॉल्ट"</string>
+    <string name="button_default" msgid="3988017840431881491">"सामान्य"</string>
     <string name="setup_welcome_title" msgid="6112821709832031715">"<xliff:g id="APPLICATION_NAME">%s</xliff:g> में आपका स्वागत है"</string>
     <string name="setup_welcome_additional_description" msgid="8150252008545768953">"हावभाव लेखन के साथ"</string>
     <string name="setup_start_action" msgid="8936036460897347708">"आरंभ करें"</string>
     <string name="setup_next_action" msgid="371821437915144603">"अगला चरण"</string>
     <string name="setup_steps_title" msgid="6400373034871816182">"<xliff:g id="APPLICATION_NAME">%s</xliff:g> सेट करना"</string>
     <string name="setup_step1_title" msgid="3147967630253462315">"<xliff:g id="APPLICATION_NAME">%s</xliff:g> को सक्षम करें"</string>
-    <string name="setup_step1_instruction" msgid="2578631936624637241">"कृपया अपनी भाषा और इनपुट सेटिंग में \"<xliff:g id="APPLICATION_NAME">%s</xliff:g>\" को चेक करें. इससे वह आपके उपकरण पर चलने के लिए अधिकृत हो जाएगा."</string>
-    <string name="setup_step1_finished_instruction" msgid="10761482004957994">"<xliff:g id="APPLICATION_NAME">%s</xliff:g> आपकी भाषा और इनपुट सेटिंग में पहले से सक्षम है, इसलिए यह चरण पूर्ण हो गया है. अगले चरण पर जाएं!"</string>
+    <string name="setup_step1_instruction" msgid="2578631936624637241">"कृपया अपनी भाषा और अक्षर सेटिंग में \"<xliff:g id="APPLICATION_NAME">%s</xliff:g>\" को चेक करें. इससे वह आपके उपकरण पर चलने के लिए अधिकृत हो जाएगा."</string>
+    <string name="setup_step1_finished_instruction" msgid="10761482004957994">"<xliff:g id="APPLICATION_NAME">%s</xliff:g> आपकी भाषा और अक्षर सेटिंग में पहले से सक्षम है, इसलिए यह चरण पूर्ण हो गया है. अगले चरण पर जाएं!"</string>
     <string name="setup_step1_action" msgid="4366513534999901728">"सेटिंग में सक्षम करें"</string>
     <string name="setup_step2_title" msgid="6860725447906690594">"<xliff:g id="APPLICATION_NAME">%s</xliff:g> पर स्विच करें"</string>
     <string name="setup_step2_instruction" msgid="9141481964870023336">"इसके बाद, \"<xliff:g id="APPLICATION_NAME">%s</xliff:g>\" को अपनी सक्रिय पाठ-इनपुट पद्धति के रूप में चुनें."</string>
     <string name="setup_step2_action" msgid="1660330307159824337">"इनपुट पद्धतियां स्विच करें"</string>
     <string name="setup_step3_title" msgid="3154757183631490281">"बधाई हो, आप बिल्कुल तैयार हैं!"</string>
-    <string name="setup_step3_instruction" msgid="8025981829605426000">"अब आप <xliff:g id="APPLICATION_NAME">%s</xliff:g> के साथ अपने सभी पसंदीदा एप्लिकेशन में लिख सकते हैं."</string>
+    <string name="setup_step3_instruction" msgid="8025981829605426000">"अब आप <xliff:g id="APPLICATION_NAME">%s</xliff:g> के साथ अपने सभी पसंदीदा एप्स में लिख सकते हैं."</string>
     <string name="setup_step3_action" msgid="600879797256942259">"अतिरिक्त भाषाएं कॉन्फ़िगर करना"</string>
     <string name="setup_finish_action" msgid="276559243409465389">"समाप्त"</string>
-    <string name="show_setup_wizard_icon" msgid="5008028590593710830">"एप्लिकेशन आइकन दिखाएं"</string>
-    <string name="show_setup_wizard_icon_summary" msgid="4119998322536880213">"लॉन्चर में एप्लिकेशन आइकन प्रदर्शित करें"</string>
+    <string name="show_setup_wizard_icon" msgid="5008028590593710830">"एप्स आइकन दिखाएं"</string>
+    <string name="show_setup_wizard_icon_summary" msgid="4119998322536880213">"लॉन्चर में एप्स आइकन प्रदर्शित करें"</string>
     <string name="app_name" msgid="6320102637491234792">"डिक्‍शनरी प्रदाता"</string>
     <string name="dictionary_provider_name" msgid="3027315045397363079">"डिक्‍शनरी प्रदाता"</string>
     <string name="dictionary_service_name" msgid="6237472350693511448">"डिक्‍शनरी सेवा"</string>
-    <string name="download_description" msgid="6014835283119198591">"डिक्‍शनरी अपडेट जानकारी"</string>
-    <string name="dictionary_settings_title" msgid="8091417676045693313">"एड-ऑन डिक्शनरी"</string>
-    <string name="dictionary_install_over_metered_network_prompt" msgid="3587517870006332980">"डिक्शनरी उपलब्‍ध"</string>
+    <string name="download_description" msgid="6014835283119198591">"शब्दकोश की नई जानकारी"</string>
+    <string name="dictionary_settings_title" msgid="8091417676045693313">"एड-ऑन शब्दकोश"</string>
+    <string name="dictionary_install_over_metered_network_prompt" msgid="3587517870006332980">"शब्दकोश उपलब्‍ध"</string>
     <string name="dictionary_settings_summary" msgid="5305694987799824349">"डिक्‍शनरी की सेटिंग"</string>
-    <string name="user_dictionaries" msgid="3582332055892252845">"उपयोगकर्ता डिक्शनरी"</string>
-    <string name="default_user_dict_pref_name" msgid="1625055720489280530">"उपयोगकर्ता डिक्शनरी"</string>
-    <string name="dictionary_available" msgid="4728975345815214218">"डिक्शनरी उपलब्‍ध"</string>
+    <string name="user_dictionaries" msgid="3582332055892252845">"उपयोगकर्ता शब्दकोश"</string>
+    <string name="default_user_dict_pref_name" msgid="1625055720489280530">"उपयोगकर्ता शब्दकोश"</string>
+    <string name="dictionary_available" msgid="4728975345815214218">"शब्दकोश उपलब्‍ध"</string>
     <string name="dictionary_downloading" msgid="2982650524622620983">"वर्तमान में डाउनलोड हो रही है"</string>
     <string name="dictionary_installed" msgid="8081558343559342962">"इंस्‍टॉल है"</string>
     <string name="dictionary_disabled" msgid="8950383219564621762">"इंस्‍टॉल है, अक्षम है"</string>
-    <string name="cannot_connect_to_dict_service" msgid="9216933695765732398">"डिक्शनरी सेवा से कनेक्ट करने में समस्या"</string>
+    <string name="cannot_connect_to_dict_service" msgid="9216933695765732398">"शब्दकोश सेवा से कनेक्ट करने में समस्या"</string>
     <string name="no_dictionaries_available" msgid="8039920716566132611">"डिक्‍शनरी अनुपलब्‍ध"</string>
     <string name="check_for_updates_now" msgid="8087688440916388581">"रीफ़्रेश करें"</string>
-    <string name="last_update" msgid="730467549913588780">"अंतिम अपडेट"</string>
-    <string name="message_updating" msgid="4457761393932375219">"अपडेट देखे जा रहे हैं"</string>
+    <string name="last_update" msgid="730467549913588780">"अंतिम बार का नई जानकारी"</string>
+    <string name="message_updating" msgid="4457761393932375219">"नई जानकारी देखा जा रहा हैं"</string>
     <string name="message_loading" msgid="8689096636874758814">"लोड हो रही है..."</string>
     <string name="main_dict_description" msgid="3072821352793492143">"मुख्‍य डिक्‍शनरी"</string>
     <string name="cancel" msgid="6830980399865683324">"रद्द करें"</string>
     <string name="install_dict" msgid="180852772562189365">"इंस्टॉल करें"</string>
     <string name="cancel_download_dict" msgid="7843340278507019303">"रद्द करें"</string>
     <string name="delete_dict" msgid="756853268088330054">"हटाएं"</string>
-    <string name="should_download_over_metered_prompt" msgid="2878629598667658845">"आपके मोबाइल उपकरण पर चयनित भाषा में डिक्‍शनरी उपलब्‍ध है.&lt;br/&gt; आपके लेखन अनुभव को बेहतर बनाने के लिए हम <xliff:g id="LANGUAGE">%1$s</xliff:g> डिक्‍शनरी को &lt;b&gt;डाउनलोड करने&lt;/b&gt; की अनुशंसा करते हैं.&lt;br/&gt; &lt;br/&gt; 3G पर डाउनलोड होने में एक या दो मिनट लग सकते हैं. यदि आपके पास &lt;b&gt;असीमित डेटा प्लान&lt;/b&gt; नहीं है, तो शुल्‍क लग सकते हैं.&lt;br/&gt; यदि आप अपने डेटा प्लान के बारे में सुनिश्चित नहीं हैं, तो हम अपने आप डाउनलोड प्रारंभ करने के लिए Wi-Fi कनेक्‍शन ढूंढने की अनुशंसा करते हैं.&lt;br/&gt; &lt;br/&gt; युक्ति: आप अपने मोबाइल उपकरण पर &lt;b&gt;सेटिंग&lt;/b&gt; मेनू में &lt;b&gt;भाषा और इनपुट&lt;/b&gt; पर जाकर डिक्‍शनरी डाउनलोड कर सकते हैं और निकाल सकते हैं."</string>
+    <string name="should_download_over_metered_prompt" msgid="2878629598667658845">"आपके मोबाइल उपकरण पर चयनित भाषा में डिक्‍शनरी उपलब्‍ध है.&lt;br/&gt; आपके लेखन अनुभव को बेहतर बनाने के लिए हम <xliff:g id="LANGUAGE">%1$s</xliff:g> डिक्‍शनरी को &lt;b&gt;डाउनलोड करने&lt;/b&gt; की अनुशंसा करते हैं.&lt;br/&gt; &lt;br/&gt; 3G पर डाउनलोड होने में एक या दो मिनट लग सकते हैं. यदि आपके पास &lt;b&gt;असीमित डेटा प्लान&lt;/b&gt; नहीं है, तो शुल्‍क लग सकते हैं.&lt;br/&gt; यदि आप अपने डेटा प्लान के बारे में सुनिश्चित नहीं हैं, तो हम अपने आप डाउनलोड प्रारंभ करने के लिए Wi-Fi कनेक्‍शन ढूंढने की अनुशंसा करते हैं.&lt;br/&gt; &lt;br/&gt; युक्ति: आप अपने मोबाइल उपकरण पर &lt;b&gt;सेटिंग&lt;/b&gt; मेनू में &lt;b&gt;भाषा और अक्षर&lt;/b&gt; पर जाकर डिक्‍शनरी डाउनलोड कर सकते हैं और निकाल सकते हैं."</string>
     <string name="download_over_metered" msgid="1643065851159409546">"अभी डाउनलोड करें (<xliff:g id="SIZE_IN_MEGABYTES">%1$.1f</xliff:g>MB)"</string>
     <string name="do_not_download_over_metered" msgid="2176209579313941583">"Wi-Fi से डाउनलोड करें"</string>
     <string name="dict_available_notification_title" msgid="6514288591959117288">"<xliff:g id="LANGUAGE">%1$s</xliff:g> के लिए डिक्‍शनरी उपलब्‍ध है"</string>
@@ -225,7 +227,7 @@
     <string name="user_dict_settings_add_screen_title" msgid="5818914331629278758">"वाक्यांश"</string>
     <string name="user_dict_settings_add_dialog_more_options" msgid="5671682004887093112">"अधिक विकल्प"</string>
     <string name="user_dict_settings_add_dialog_less_options" msgid="2716586567241724126">"कम विकल्‍प"</string>
-    <string name="user_dict_settings_add_dialog_confirm" msgid="4703129507388332950">"ठीक"</string>
+    <string name="user_dict_settings_add_dialog_confirm" msgid="4703129507388332950">"ठीक है"</string>
     <string name="user_dict_settings_add_word_option_name" msgid="6665558053408962865">"शब्द:"</string>
     <string name="user_dict_settings_add_shortcut_option_name" msgid="3094731590655523777">"शॉर्टकट:"</string>
     <string name="user_dict_settings_add_locale_option_name" msgid="4738643440987277705">"भाषा:"</string>
diff --git a/java/res/values-hr/strings.xml b/java/res/values-hr/strings.xml
index 8f64ccf..0dca64e 100644
--- a/java/res/values-hr/strings.xml
+++ b/java/res/values-hr/strings.xml
@@ -64,8 +64,8 @@
     <string name="auto_correction_summary" msgid="5625751551134658006">"Razmak i interpunkcija automatski ispravljaju krive riječi"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Isključeno"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Skromno"</string>
-    <string name="auto_correction_threshold_mode_aggressive" msgid="3524029103734923819">"Agresivno"</string>
-    <string name="auto_correction_threshold_mode_very_aggressive" msgid="3386782235540547678">"Vrlo agresivno"</string>
+    <string name="auto_correction_threshold_mode_aggressive" msgid="7319007299148899623">"Agresivno"</string>
+    <string name="auto_correction_threshold_mode_very_aggressive" msgid="1853309024129480416">"Vrlo agresivno"</string>
     <string name="bigram_prediction" msgid="1084449187723948550">"Prijedlozi za sljedeću riječ"</string>
     <string name="bigram_prediction_summary" msgid="3896362682751109677">"Koristi se prethodnom riječi u izradi prijedloga"</string>
     <string name="gesture_input" msgid="826951152254563827">"Omogući pisanje kretnjama"</string>
@@ -143,13 +143,15 @@
     <string name="subtype_with_layout_en_GB" msgid="2179097748724725906">"engleski (UK) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_en_US" msgid="1362581347576714579">"engleski (SAD) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_es_US" msgid="6261791057007890189">"španjolski (SAD) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
-    <string name="subtype_no_language" msgid="141420857808801746">"Nema jezika"</string>
-    <string name="subtype_no_language_qwerty" msgid="2956121451616633133">"Nema jezika (QWERTY)"</string>
-    <string name="subtype_no_language_qwertz" msgid="1177848172397202890">"Nema jezika (QWERTZ)"</string>
-    <string name="subtype_no_language_azerty" msgid="8721460968141187394">"Nema jezika (AZERTY)"</string>
-    <string name="subtype_no_language_dvorak" msgid="3122976737669823935">"Nema jezika (Dvorak)"</string>
-    <string name="subtype_no_language_colemak" msgid="4205992994906097244">"Nema jezika (Colemak)"</string>
-    <string name="subtype_no_language_pcqwerty" msgid="8840928374394180189">"Nema jezika (PC)"</string>
+    <string name="subtype_nepali_traditional" msgid="9032247506728040447">"<xliff:g id="LANGUAGE">%s</xliff:g> (tradicionalni)"</string>
+    <string name="subtype_no_language" msgid="7137390094240139495">"Nema jezika (abeceda)"</string>
+    <string name="subtype_no_language_qwerty" msgid="244337630616742604">"Abeceda (QWERTY)"</string>
+    <string name="subtype_no_language_qwertz" msgid="443066912507547976">"Abeceda (QWERTZ)"</string>
+    <string name="subtype_no_language_azerty" msgid="8144348527575640087">"Abeceda (AZERTY)"</string>
+    <string name="subtype_no_language_dvorak" msgid="1564494667584718094">"Abeceda (Dvorak)"</string>
+    <string name="subtype_no_language_colemak" msgid="5837418400010302623">"Abeceda (Colemak)"</string>
+    <string name="subtype_no_language_pcqwerty" msgid="5354918232046200018">"Abeceda (PC)"</string>
+    <string name="subtype_emoji" msgid="7483586578074549196">"Emoji"</string>
     <string name="custom_input_styles_title" msgid="8429952441821251512">"Prilagođeni stilovi unosa"</string>
     <string name="add_style" msgid="6163126614514489951">"Dodaj stil"</string>
     <string name="add" msgid="8299699805688017798">"Dodaj"</string>
diff --git a/java/res/values-hu/strings.xml b/java/res/values-hu/strings.xml
index 6aaf88c..2abed74 100644
--- a/java/res/values-hu/strings.xml
+++ b/java/res/values-hu/strings.xml
@@ -64,8 +64,8 @@
     <string name="auto_correction_summary" msgid="5625751551134658006">"Szóköz és központozás automatikusan javítja az elgépelést"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Ki"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Mérsékelt"</string>
-    <string name="auto_correction_threshold_mode_aggressive" msgid="3524029103734923819">"Agresszív"</string>
-    <string name="auto_correction_threshold_mode_very_aggressive" msgid="3386782235540547678">"Nagyon agresszív"</string>
+    <string name="auto_correction_threshold_mode_aggressive" msgid="7319007299148899623">"Agresszív"</string>
+    <string name="auto_correction_threshold_mode_very_aggressive" msgid="1853309024129480416">"Nagyon agresszív"</string>
     <string name="bigram_prediction" msgid="1084449187723948550">"Következő szóra vonatkozó javaslatok"</string>
     <string name="bigram_prediction_summary" msgid="3896362682751109677">"Az előző szó felhasználása a javaslatoknál"</string>
     <string name="gesture_input" msgid="826951152254563827">"Kézmozdulatokkal gépelés"</string>
@@ -143,13 +143,15 @@
     <string name="subtype_with_layout_en_GB" msgid="2179097748724725906">"angol (brit) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_en_US" msgid="1362581347576714579">"angol (amerikai) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_es_US" msgid="6261791057007890189">"spanyol (USA) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
-    <string name="subtype_no_language" msgid="141420857808801746">"Nincs nyelv"</string>
-    <string name="subtype_no_language_qwerty" msgid="2956121451616633133">"Nincs nyelv (QWERTY)"</string>
-    <string name="subtype_no_language_qwertz" msgid="1177848172397202890">"Nincs nyelv (QWERTZ)"</string>
-    <string name="subtype_no_language_azerty" msgid="8721460968141187394">"Nincs nyelv (AZERTY)"</string>
-    <string name="subtype_no_language_dvorak" msgid="3122976737669823935">"Nincs nyelv (Dvorak)"</string>
-    <string name="subtype_no_language_colemak" msgid="4205992994906097244">"Nincs nyelv (Colemak)"</string>
-    <string name="subtype_no_language_pcqwerty" msgid="8840928374394180189">"Nincs nyelv (PC)"</string>
+    <string name="subtype_nepali_traditional" msgid="9032247506728040447">"<xliff:g id="LANGUAGE">%s</xliff:g> (hagyományos)"</string>
+    <string name="subtype_no_language" msgid="7137390094240139495">"Nincs nyelv (ábécé)"</string>
+    <string name="subtype_no_language_qwerty" msgid="244337630616742604">"Ábécé (QWERTY)"</string>
+    <string name="subtype_no_language_qwertz" msgid="443066912507547976">"Ábécé (QWERTZ)"</string>
+    <string name="subtype_no_language_azerty" msgid="8144348527575640087">"Ábécé (AZERTY)"</string>
+    <string name="subtype_no_language_dvorak" msgid="1564494667584718094">"Ábécé (Dvorak)"</string>
+    <string name="subtype_no_language_colemak" msgid="5837418400010302623">"Ábécé (Colemak)"</string>
+    <string name="subtype_no_language_pcqwerty" msgid="5354918232046200018">"Ábécé (PC)"</string>
+    <string name="subtype_emoji" msgid="7483586578074549196">"Hangulatjel"</string>
     <string name="custom_input_styles_title" msgid="8429952441821251512">"Egyedi bevitelstílusok"</string>
     <string name="add_style" msgid="6163126614514489951">"Új stílus"</string>
     <string name="add" msgid="8299699805688017798">"Hozzáadás"</string>
diff --git a/java/res/values-hy/donottranslate.xml b/java/res/values-hy/donottranslate.xml
new file mode 100644
index 0000000..4a6d188
--- /dev/null
+++ b/java/res/values-hy/donottranslate.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">
+    <!-- Same list as in English, but add armenian period and comma: -->
+    <!-- U+055D: "՝" ARMENIAN COMMA -->
+    <!-- U+0589: "։" ARMENIAN FULL STOP -->
+    <!-- Symbols that are normally followed by a space (used to add an auto-space after these) -->
+    <string name="symbols_followed_by_space">.,;:!?)]}&amp;&#x0589;&#x055D;</string>
+    <!-- Symbols that separate words. Adding armenian period and comma. -->
+    <!-- Don't remove the enclosing double quotes, they protect whitespace (not just U+0020) -->
+    <string name="symbols_word_separators">"&#x0009;&#x0020;\n"()[]{}*&amp;&lt;&gt;+=|.,;:!?/_\"&#x0589;&#x055D;</string>
+</resources>
diff --git a/java/res/values-in/strings.xml b/java/res/values-in/strings.xml
index dbac362..c73c26c 100644
--- a/java/res/values-in/strings.xml
+++ b/java/res/values-in/strings.xml
@@ -64,8 +64,8 @@
     <string name="auto_correction_summary" msgid="5625751551134658006">"Bilah spasi dan tanda baca secara otomatis dikoreksi pada kata yang salah ketik"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Mati"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Sederhana"</string>
-    <string name="auto_correction_threshold_mode_aggressive" msgid="3524029103734923819">"Agresif"</string>
-    <string name="auto_correction_threshold_mode_very_aggressive" msgid="3386782235540547678">"Sangat agresif"</string>
+    <string name="auto_correction_threshold_mode_aggressive" msgid="7319007299148899623">"Agresif"</string>
+    <string name="auto_correction_threshold_mode_very_aggressive" msgid="1853309024129480416">"Sangat agresif"</string>
     <string name="bigram_prediction" msgid="1084449187723948550">"Saran kata berikutnya"</string>
     <string name="bigram_prediction_summary" msgid="3896362682751109677">"Gunakan kata sebelumnya dalam membuat saran"</string>
     <string name="gesture_input" msgid="826951152254563827">"Aktifkan pengetikan isyarat"</string>
@@ -143,13 +143,15 @@
     <string name="subtype_with_layout_en_GB" msgid="2179097748724725906">"Inggris (Inggris) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_en_US" msgid="1362581347576714579">"Inggris (AS) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_es_US" msgid="6261791057007890189">"Spanyol (AS) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
-    <string name="subtype_no_language" msgid="141420857808801746">"Tidak ada bahasa"</string>
-    <string name="subtype_no_language_qwerty" msgid="2956121451616633133">"Tanpa bahasa (QWERTY)"</string>
-    <string name="subtype_no_language_qwertz" msgid="1177848172397202890">"Tanpa bahasa (QWERTZ)"</string>
-    <string name="subtype_no_language_azerty" msgid="8721460968141187394">"Tanpa bahasa (AZERTY)"</string>
-    <string name="subtype_no_language_dvorak" msgid="3122976737669823935">"Tanpa bahasa (Dvorak)"</string>
-    <string name="subtype_no_language_colemak" msgid="4205992994906097244">"Tanpa bahasa (Colemak)"</string>
-    <string name="subtype_no_language_pcqwerty" msgid="8840928374394180189">"Tanpa bahasa (PC)"</string>
+    <string name="subtype_nepali_traditional" msgid="9032247506728040447">"<xliff:g id="LANGUAGE">%s</xliff:g> (Tradisional)"</string>
+    <string name="subtype_no_language" msgid="7137390094240139495">"Tidak ada bahasa (Abjad)"</string>
+    <string name="subtype_no_language_qwerty" msgid="244337630616742604">"Abjad (QWERTY)"</string>
+    <string name="subtype_no_language_qwertz" msgid="443066912507547976">"Abjad (QWERTZ)"</string>
+    <string name="subtype_no_language_azerty" msgid="8144348527575640087">"Abjad (AZERTY)"</string>
+    <string name="subtype_no_language_dvorak" msgid="1564494667584718094">"Abjad (Dvorak)"</string>
+    <string name="subtype_no_language_colemak" msgid="5837418400010302623">"Abjad (Colemak)"</string>
+    <string name="subtype_no_language_pcqwerty" msgid="5354918232046200018">"Abjad (PC)"</string>
+    <string name="subtype_emoji" msgid="7483586578074549196">"Emoji"</string>
     <string name="custom_input_styles_title" msgid="8429952441821251512">"Gaya masukan khusus"</string>
     <string name="add_style" msgid="6163126614514489951">"Tambah gaya"</string>
     <string name="add" msgid="8299699805688017798">"Tambahkan"</string>
diff --git a/java/res/values-it/strings.xml b/java/res/values-it/strings.xml
index a20acc2..eb8e1e9 100644
--- a/java/res/values-it/strings.xml
+++ b/java/res/values-it/strings.xml
@@ -64,8 +64,8 @@
     <string name="auto_correction_summary" msgid="5625751551134658006">"Barra spaziatrice/punteggiatura correggono parole con errori"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Off"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Media"</string>
-    <string name="auto_correction_threshold_mode_aggressive" msgid="3524029103734923819">"Massima"</string>
-    <string name="auto_correction_threshold_mode_very_aggressive" msgid="3386782235540547678">"Massima"</string>
+    <string name="auto_correction_threshold_mode_aggressive" msgid="7319007299148899623">"Molto elevata"</string>
+    <string name="auto_correction_threshold_mode_very_aggressive" msgid="1853309024129480416">"Massima"</string>
     <string name="bigram_prediction" msgid="1084449187723948550">"Suggerimenti parola successiva"</string>
     <string name="bigram_prediction_summary" msgid="3896362682751109677">"Usa la parola precedente per i suggerimenti"</string>
     <string name="gesture_input" msgid="826951152254563827">"Abilita digitazione a gesti"</string>
@@ -143,13 +143,15 @@
     <string name="subtype_with_layout_en_GB" msgid="2179097748724725906">"Inglese (Regno Unito) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_en_US" msgid="1362581347576714579">"Inglese (Stati Uniti) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_es_US" msgid="6261791057007890189">"Spagnolo (USA) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
-    <string name="subtype_no_language" msgid="141420857808801746">"Nessuna lingua"</string>
-    <string name="subtype_no_language_qwerty" msgid="2956121451616633133">"Nessuna lingua (QWERTY)"</string>
-    <string name="subtype_no_language_qwertz" msgid="1177848172397202890">"Nessuna lingua (QWERTZ)"</string>
-    <string name="subtype_no_language_azerty" msgid="8721460968141187394">"Nessuna lingua (AZERTY)"</string>
-    <string name="subtype_no_language_dvorak" msgid="3122976737669823935">"Nessuna lingua (Dvorak)"</string>
-    <string name="subtype_no_language_colemak" msgid="4205992994906097244">"Nessuna lingua (Colemak)"</string>
-    <string name="subtype_no_language_pcqwerty" msgid="8840928374394180189">"Nessuna lingua (PC)"</string>
+    <string name="subtype_nepali_traditional" msgid="9032247506728040447">"<xliff:g id="LANGUAGE">%s</xliff:g> (tradizionale)"</string>
+    <string name="subtype_no_language" msgid="7137390094240139495">"Nessuna lingua (alfabeto)"</string>
+    <string name="subtype_no_language_qwerty" msgid="244337630616742604">"Alfabeto (QWERTY)"</string>
+    <string name="subtype_no_language_qwertz" msgid="443066912507547976">"Alfabeto (QWERTZ)"</string>
+    <string name="subtype_no_language_azerty" msgid="8144348527575640087">"Alfabeto (AZERTY)"</string>
+    <string name="subtype_no_language_dvorak" msgid="1564494667584718094">"Alfabeto (Dvorak)"</string>
+    <string name="subtype_no_language_colemak" msgid="5837418400010302623">"Alfabeto (Colemak)"</string>
+    <string name="subtype_no_language_pcqwerty" msgid="5354918232046200018">"Alfabeto (PC)"</string>
+    <string name="subtype_emoji" msgid="7483586578074549196">"Emoji"</string>
     <string name="custom_input_styles_title" msgid="8429952441821251512">"Stili personalizzati"</string>
     <string name="add_style" msgid="6163126614514489951">"Aggiungi stile"</string>
     <string name="add" msgid="8299699805688017798">"Aggiungi"</string>
diff --git a/java/res/values-iw/strings.xml b/java/res/values-iw/strings.xml
index 526c431..e71bd31 100644
--- a/java/res/values-iw/strings.xml
+++ b/java/res/values-iw/strings.xml
@@ -39,7 +39,7 @@
     <string name="show_language_switch_key_summary" msgid="7343403647474265713">"הצג כאשר ניתן להשתמש בשפות קלט מרובות"</string>
     <string name="sliding_key_input_preview" msgid="6604262359510068370">"הצג את סמן ההסטה"</string>
     <string name="sliding_key_input_preview_summary" msgid="6340524345729093886">"הצג סימון ויזואלי בעת הסטה מ-Shift או ממקשי סמלים"</string>
-    <string name="key_preview_popup_dismiss_delay" msgid="6213164897443068248">"עיכוב סגירת חלון קופץ של מקש"</string>
+    <string name="key_preview_popup_dismiss_delay" msgid="6213164897443068248">"עיכוב בסגירת חלון קופץ של מקש"</string>
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"ללא עיכוב"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"ברירת מחדל"</string>
     <string name="abbreviation_unit_milliseconds" msgid="8700286094028323363">"<xliff:g id="MILLISECONDS">%s</xliff:g> אלפ\' שניה"</string>
@@ -51,7 +51,7 @@
     <string name="auto_cap" msgid="1719746674854628252">"הפיכת אותיות לרישיות באופן אוטומטי"</string>
     <string name="auto_cap_summary" msgid="7934452761022946874">"השתמש באות גדולה במילה הראשונה של כל משפט"</string>
     <string name="edit_personal_dictionary" msgid="3996910038952940420">"מילון אישי"</string>
-    <string name="configure_dictionaries_title" msgid="4238652338556902049">"הוספת מילונים"</string>
+    <string name="configure_dictionaries_title" msgid="4238652338556902049">"תוספי מילונים"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"מילון ראשי"</string>
     <string name="prefs_show_suggestions" msgid="8026799663445531637">"הצג הצעות לתיקונים"</string>
     <string name="prefs_show_suggestions_summary" msgid="1583132279498502825">"הצג הצעות למילים בעת הקלדה"</string>
@@ -64,20 +64,20 @@
     <string name="auto_correction_summary" msgid="5625751551134658006">"מקש הרווח ופיסוק מתקנים אוטומטית שגיאות הקלדה"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"כבוי"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"מצומצם"</string>
-    <string name="auto_correction_threshold_mode_aggressive" msgid="3524029103734923819">"מחמיר"</string>
-    <string name="auto_correction_threshold_mode_very_aggressive" msgid="3386782235540547678">"מחמיר מאוד"</string>
+    <string name="auto_correction_threshold_mode_aggressive" msgid="7319007299148899623">"מחמיר"</string>
+    <string name="auto_correction_threshold_mode_very_aggressive" msgid="1853309024129480416">"מחמיר מאוד"</string>
     <string name="bigram_prediction" msgid="1084449187723948550">"הצעות למילה הבאה"</string>
     <string name="bigram_prediction_summary" msgid="3896362682751109677">"השתמש במילה הקודמת ביצירת הצעות"</string>
     <string name="gesture_input" msgid="826951152254563827">"אפשר הקלדה ללא הרמת אצבע"</string>
     <string name="gesture_input_summary" msgid="9180350639305731231">"הזן מילה על ידי החלקת האצבע מאות לאות"</string>
     <string name="gesture_preview_trail" msgid="3802333369335722221">"הצג שובל תנועות"</string>
-    <string name="gesture_floating_preview_text" msgid="4443240334739381053">"תצוגה מקדימה דינמית צפה"</string>
+    <string name="gesture_floating_preview_text" msgid="4443240334739381053">"תצוגה צפה דינמית"</string>
     <string name="gesture_floating_preview_text_summary" msgid="4472696213996203533">"ראה את המילה המוצעת תוך כדי הזזת האצבע"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : נשמרה"</string>
-    <string name="label_go_key" msgid="1635148082137219148">"בצע"</string>
+    <string name="label_go_key" msgid="1635148082137219148">"התחל"</string>
     <string name="label_next_key" msgid="362972844525672568">"הבא"</string>
     <string name="label_previous_key" msgid="1211868118071386787">"הקודם"</string>
-    <string name="label_done_key" msgid="2441578748772529288">"סיום"</string>
+    <string name="label_done_key" msgid="2441578748772529288">"בוצע"</string>
     <string name="label_send_key" msgid="2815056534433717444">"שלח"</string>
     <string name="label_pause_key" msgid="181098308428035340">"השהה"</string>
     <string name="label_wait_key" msgid="6402152600878093134">"המתן"</string>
@@ -93,18 +93,18 @@
     <string name="spoken_description_to_alpha" msgid="23129338819771807">"אותיות"</string>
     <string name="spoken_description_to_numeric" msgid="591752092685161732">"מספרים"</string>
     <string name="spoken_description_settings" msgid="4627462689603838099">"הגדרות"</string>
-    <string name="spoken_description_tab" msgid="2667716002663482248">"טאב"</string>
+    <string name="spoken_description_tab" msgid="2667716002663482248">"כרטיסייה"</string>
     <string name="spoken_description_space" msgid="2582521050049860859">"רווח"</string>
     <string name="spoken_description_mic" msgid="615536748882611950">"קלט קולי"</string>
-    <string name="spoken_description_smiley" msgid="2256309826200113918">"פרצוף סמיילי"</string>
-    <string name="spoken_description_return" msgid="8178083177238315647">"חזור"</string>
-    <string name="spoken_description_search" msgid="1247236163755920808">"חיפוש"</string>
+    <string name="spoken_description_smiley" msgid="2256309826200113918">"סמיילי"</string>
+    <string name="spoken_description_return" msgid="8178083177238315647">"חזרה"</string>
+    <string name="spoken_description_search" msgid="1247236163755920808">"חפש"</string>
     <string name="spoken_description_dot" msgid="40711082435231673">"נקודה"</string>
     <string name="spoken_description_language_switch" msgid="5507091328222331316">"החלף שפה"</string>
     <string name="spoken_description_action_next" msgid="8636078276664150324">"הבא"</string>
     <string name="spoken_description_action_previous" msgid="800872415009336208">"הקודם"</string>
-    <string name="spoken_description_shiftmode_on" msgid="5700440798609574589">"Shift מופעל"</string>
-    <string name="spoken_description_shiftmode_locked" msgid="593175803181701830">"Caps Lock מופעל"</string>
+    <string name="spoken_description_shiftmode_on" msgid="5700440798609574589">"Shift פועל"</string>
+    <string name="spoken_description_shiftmode_locked" msgid="593175803181701830">"Caps Lock פועל"</string>
     <string name="spoken_description_shiftmode_off" msgid="657219998449174808">"Shift מושבת"</string>
     <string name="spoken_description_mode_symbol" msgid="7183343879909747642">"מצב סמלים"</string>
     <string name="spoken_description_mode_alpha" msgid="3528307674390156956">"מצב אותיות"</string>
@@ -134,7 +134,7 @@
     <string name="select_language" msgid="3693815588777926848">"שפות קלט"</string>
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"גע שוב כדי לשמור"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"מילון זמין"</string>
-    <string name="prefs_enable_log" msgid="6620424505072963557">"הפוך משוב ממשתמשים לפעיל"</string>
+    <string name="prefs_enable_log" msgid="6620424505072963557">"הפעל משוב ממשתמשים"</string>
     <string name="prefs_description_log" msgid="7525225584555429211">"עזור לשפר את עורך שיטת הקלט על ידי שליחה אוטומטית של סטטיסטיקת שימוש ודוחות קריסה."</string>
     <string name="keyboard_layout" msgid="8451164783510487501">"עיצוב מקלדת"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"אנגלית (בריטניה)"</string>
@@ -143,13 +143,15 @@
     <string name="subtype_with_layout_en_GB" msgid="2179097748724725906">"אנגלית (בריטניה) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_en_US" msgid="1362581347576714579">"אנגלית (ארה\"ב) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_es_US" msgid="6261791057007890189">"ספרדית (ארצות הברית) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
-    <string name="subtype_no_language" msgid="141420857808801746">"ללא שפה"</string>
-    <string name="subtype_no_language_qwerty" msgid="2956121451616633133">"אין שפה (QWERTY)"</string>
-    <string name="subtype_no_language_qwertz" msgid="1177848172397202890">"אין שפה (QWERTZ)"</string>
-    <string name="subtype_no_language_azerty" msgid="8721460968141187394">"אין שפה (AZERTY)"</string>
-    <string name="subtype_no_language_dvorak" msgid="3122976737669823935">"אין שפה (Dvorak)"</string>
-    <string name="subtype_no_language_colemak" msgid="4205992994906097244">"אין שפה (Colemak)"</string>
-    <string name="subtype_no_language_pcqwerty" msgid="8840928374394180189">"אין שפה (PC)"</string>
+    <string name="subtype_nepali_traditional" msgid="9032247506728040447">"<xliff:g id="LANGUAGE">%s</xliff:g> (מסורתית)"</string>
+    <string name="subtype_no_language" msgid="7137390094240139495">"ללא שפה (אלף-בית)"</string>
+    <string name="subtype_no_language_qwerty" msgid="244337630616742604">"אלף-בית (QWERTY)"</string>
+    <string name="subtype_no_language_qwertz" msgid="443066912507547976">"אלף-בית (QWERTZ)"</string>
+    <string name="subtype_no_language_azerty" msgid="8144348527575640087">"אלף-בית (AZERTY)"</string>
+    <string name="subtype_no_language_dvorak" msgid="1564494667584718094">"אלף-בית (Dvorak)"</string>
+    <string name="subtype_no_language_colemak" msgid="5837418400010302623">"אלף-בית (Colemak)"</string>
+    <string name="subtype_no_language_pcqwerty" msgid="5354918232046200018">"אלף-בית (PC)"</string>
+    <string name="subtype_emoji" msgid="7483586578074549196">"אמוג\'י"</string>
     <string name="custom_input_styles_title" msgid="8429952441821251512">"סגנונות קלט מותאמים אישית"</string>
     <string name="add_style" msgid="6163126614514489951">"הוסף סגנון"</string>
     <string name="add" msgid="8299699805688017798">"הוסף"</string>
@@ -161,7 +163,7 @@
     <string name="enable" msgid="5031294444630523247">"הפעל"</string>
     <string name="not_now" msgid="6172462888202790482">"לא עכשיו"</string>
     <string name="custom_input_style_already_exists" msgid="8008728952215449707">"סגנון קלט זהה כבר קיים: <xliff:g id="INPUT_STYLE_NAME">%s</xliff:g>"</string>
-    <string name="prefs_usability_study_mode" msgid="1261130555134595254">"מצב מחקר שימושיות"</string>
+    <string name="prefs_usability_study_mode" msgid="1261130555134595254">"מצב לחקירת שימושיות"</string>
     <string name="prefs_key_longpress_timeout_settings" msgid="6102240298932897873">"השהיית לחיצה ארוכה על מקש"</string>
     <string name="prefs_keypress_vibration_duration_settings" msgid="7918341459947439226">"משך רטט של לחיצת מקש"</string>
     <string name="prefs_keypress_sound_volume_settings" msgid="6027007337036891623">"עוצמת קול של לחיצת מקש"</string>
@@ -173,7 +175,7 @@
     <string name="button_default" msgid="3988017840431881491">"ברירת מחדל"</string>
     <string name="setup_welcome_title" msgid="6112821709832031715">"ברוך הבא אל <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
     <string name="setup_welcome_additional_description" msgid="8150252008545768953">"עם הקלדת החלקה"</string>
-    <string name="setup_start_action" msgid="8936036460897347708">"התחל"</string>
+    <string name="setup_start_action" msgid="8936036460897347708">"תחילת העבודה"</string>
     <string name="setup_next_action" msgid="371821437915144603">"השלב הבא"</string>
     <string name="setup_steps_title" msgid="6400373034871816182">"הגדרת <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
     <string name="setup_step1_title" msgid="3147967630253462315">"הפעל את <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
@@ -184,11 +186,11 @@
     <string name="setup_step2_instruction" msgid="9141481964870023336">"בשלב הבא, בחר ב-\'<xliff:g id="APPLICATION_NAME">%s</xliff:g>\' כאמצעי הקלט הפעיל להזנת טקסט."</string>
     <string name="setup_step2_action" msgid="1660330307159824337">"החלף שיטות קלט"</string>
     <string name="setup_step3_title" msgid="3154757183631490281">"ברכותינו, הכל מוכן!"</string>
-    <string name="setup_step3_instruction" msgid="8025981829605426000">"כעת תוכל להקליד בכל היישומים המועדפים עליך באמצעות <xliff:g id="APPLICATION_NAME">%s</xliff:g>."</string>
+    <string name="setup_step3_instruction" msgid="8025981829605426000">"כעת תוכל להקליד בכל האפליקציות המועדפות עליך באמצעות <xliff:g id="APPLICATION_NAME">%s</xliff:g>."</string>
     <string name="setup_step3_action" msgid="600879797256942259">"הגדר שפות נוספות"</string>
     <string name="setup_finish_action" msgid="276559243409465389">"סיום"</string>
-    <string name="show_setup_wizard_icon" msgid="5008028590593710830">"הצג את סמל היישום"</string>
-    <string name="show_setup_wizard_icon_summary" msgid="4119998322536880213">"הצג את סמל היישום במפעיל"</string>
+    <string name="show_setup_wizard_icon" msgid="5008028590593710830">"הצג את סמל האפליקציה"</string>
+    <string name="show_setup_wizard_icon_summary" msgid="4119998322536880213">"הצג את סמל האפליקציה במפעיל"</string>
     <string name="app_name" msgid="6320102637491234792">"ספק המילון"</string>
     <string name="dictionary_provider_name" msgid="3027315045397363079">"ספק המילון"</string>
     <string name="dictionary_service_name" msgid="6237472350693511448">"שירות מילון"</string>
@@ -209,7 +211,7 @@
     <string name="message_updating" msgid="4457761393932375219">"מחפש עדכונים"</string>
     <string name="message_loading" msgid="8689096636874758814">"טוען..."</string>
     <string name="main_dict_description" msgid="3072821352793492143">"מילון ראשי"</string>
-    <string name="cancel" msgid="6830980399865683324">"ביטול"</string>
+    <string name="cancel" msgid="6830980399865683324">"בטל"</string>
     <string name="install_dict" msgid="180852772562189365">"התקן"</string>
     <string name="cancel_download_dict" msgid="7843340278507019303">"בטל"</string>
     <string name="delete_dict" msgid="756853268088330054">"מחק"</string>
diff --git a/java/res/values-ja/strings.xml b/java/res/values-ja/strings.xml
index 5a5ff43..4d3f155 100644
--- a/java/res/values-ja/strings.xml
+++ b/java/res/values-ja/strings.xml
@@ -64,8 +64,8 @@
     <string name="auto_correction_summary" msgid="5625751551134658006">"誤入力をスペースまたは句読点キーで修正する"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"OFF"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"中"</string>
-    <string name="auto_correction_threshold_mode_aggressive" msgid="3524029103734923819">"強"</string>
-    <string name="auto_correction_threshold_mode_very_aggressive" msgid="3386782235540547678">"最も強い"</string>
+    <string name="auto_correction_threshold_mode_aggressive" msgid="7319007299148899623">"強"</string>
+    <string name="auto_correction_threshold_mode_very_aggressive" msgid="1853309024129480416">"最も強い"</string>
     <string name="bigram_prediction" msgid="1084449187723948550">"次の入力候補"</string>
     <string name="bigram_prediction_summary" msgid="3896362682751109677">"前の単語に基づいて入力候補を表示します"</string>
     <string name="gesture_input" msgid="826951152254563827">"ジェスチャー入力を有効にする"</string>
@@ -143,13 +143,15 @@
     <string name="subtype_with_layout_en_GB" msgid="2179097748724725906">"英語 (英国) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_en_US" msgid="1362581347576714579">"英語 (米国) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_es_US" msgid="6261791057007890189">"スペイン語 (米国) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
-    <string name="subtype_no_language" msgid="141420857808801746">"言語設定なし"</string>
-    <string name="subtype_no_language_qwerty" msgid="2956121451616633133">"言語設定なし (QWERTY)"</string>
-    <string name="subtype_no_language_qwertz" msgid="1177848172397202890">"言語設定なし (QWERTZ)"</string>
-    <string name="subtype_no_language_azerty" msgid="8721460968141187394">"言語設定なし (AZERTY)"</string>
-    <string name="subtype_no_language_dvorak" msgid="3122976737669823935">"言語設定なし (Dvorak)"</string>
-    <string name="subtype_no_language_colemak" msgid="4205992994906097244">"言語設定なし (Colemak)"</string>
-    <string name="subtype_no_language_pcqwerty" msgid="8840928374394180189">"言語設定なし (PC)"</string>
+    <string name="subtype_nepali_traditional" msgid="9032247506728040447">"<xliff:g id="LANGUAGE">%s</xliff:g>(伝統言語)"</string>
+    <string name="subtype_no_language" msgid="7137390094240139495">"言語なし(アルファベット)"</string>
+    <string name="subtype_no_language_qwerty" msgid="244337630616742604">"アルファベット(QWERTY)"</string>
+    <string name="subtype_no_language_qwertz" msgid="443066912507547976">"アルファベット(QWERTZ)"</string>
+    <string name="subtype_no_language_azerty" msgid="8144348527575640087">"アルファベット(AZERTY)"</string>
+    <string name="subtype_no_language_dvorak" msgid="1564494667584718094">"アルファベット(Dvorak)"</string>
+    <string name="subtype_no_language_colemak" msgid="5837418400010302623">"アルファベット(Colemak)"</string>
+    <string name="subtype_no_language_pcqwerty" msgid="5354918232046200018">"アルファベット(PC)"</string>
+    <string name="subtype_emoji" msgid="7483586578074549196">"絵文字"</string>
     <string name="custom_input_styles_title" msgid="8429952441821251512">"カスタム入力スタイル"</string>
     <string name="add_style" msgid="6163126614514489951">"スタイル追加"</string>
     <string name="add" msgid="8299699805688017798">"追加"</string>
diff --git a/java/res/values-ka-rGE/strings.xml b/java/res/values-ka-rGE/strings.xml
new file mode 100644
index 0000000..043c14c
--- /dev/null
+++ b/java/res/values-ka-rGE/strings.xml
@@ -0,0 +1,244 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+**
+** Copyright 2008, 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:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="english_ime_input_options" msgid="3909945612939668554">"შეყვანის მეთოდები"</string>
+    <string name="english_ime_research_log" msgid="8492602295696577851">"კვლევის აღრიცხვის ბრძანებები"</string>
+    <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"კონტაქტებში ძებნა"</string>
+    <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"კონტაქტების სიის გამოყენება მართლწერის შემოწმებისას"</string>
+    <string name="vibrate_on_keypress" msgid="5258079494276955460">"ვიბრაცია კლავიშზე დაჭერისას"</string>
+    <string name="sound_on_keypress" msgid="6093592297198243644">"ხმა კლავიშზე დაჭერისას"</string>
+    <string name="popup_on_keypress" msgid="123894815723512944">"გადიდება ღილაკზე დაჭერისას"</string>
+    <string name="general_category" msgid="1859088467017573195">"ძირითადი"</string>
+    <string name="correction_category" msgid="2236750915056607613">"ტექსტის კორექცია"</string>
+    <string name="gesture_typing_category" msgid="497263612130532630">"ჟესტებით წერა"</string>
+    <string name="misc_category" msgid="6894192814868233453">"სხვა პარამეტრები"</string>
+    <string name="advanced_settings" msgid="362895144495591463">"გაფართოებული პარამეტრები"</string>
+    <string name="advanced_settings_summary" msgid="4487980456152830271">"პარამეტრები ექსპერტთათვის"</string>
+    <string name="include_other_imes_in_language_switch_list" msgid="4533689960308565519">"შეყვანის სხვა მეთოდებზე გადართვა"</string>
+    <string name="include_other_imes_in_language_switch_list_summary" msgid="840637129103317635">"ენის გადართვის ღილაკს შეყვანის სხვა მეთოდებსაც შეიცავს"</string>
+    <string name="show_language_switch_key" msgid="5915478828318774384">"ენის გადართვის კლავიში"</string>
+    <string name="show_language_switch_key_summary" msgid="7343403647474265713">"აჩვენე, როდესაც ჩართულია სხვადასხვა შეყვანის ენა"</string>
+    <string name="sliding_key_input_preview" msgid="6604262359510068370">"გასრიალების ინდიკატ. ჩვენება"</string>
+    <string name="sliding_key_input_preview_summary" msgid="6340524345729093886">"Shift ან Symbol კლავიშებიდან გასრიალებისას ვიზუალური მინიშნების ჩვენება"</string>
+    <string name="key_preview_popup_dismiss_delay" msgid="6213164897443068248">"ამომხტ.კლავიშის დაყოვნება"</string>
+    <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"არ დაყოვნდეს"</string>
+    <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"ნაგულისხმევი"</string>
+    <string name="abbreviation_unit_milliseconds" msgid="8700286094028323363">"<xliff:g id="MILLISECONDS">%s</xliff:g>მწმ"</string>
+    <string name="settings_system_default" msgid="6268225104743331821">"სისტემის ნაგულისხმევი"</string>
+    <string name="use_contacts_dict" msgid="4435317977804180815">"კონტაქტის სახელების შეთავაზება"</string>
+    <string name="use_contacts_dict_summary" msgid="6599983334507879959">"კონტაქტებიდან სახელების გამოყენება შეთავაზებებისთვის და კორექციისთვის"</string>
+    <string name="use_double_space_period" msgid="8781529969425082860">"წერტილი ორმაგი შორისით"</string>
+    <string name="use_double_space_period_summary" msgid="6532892187247952799">"შორისზე ორჯერ შეხება დაწერს წერტილს და შორისის სიმბოლოს"</string>
+    <string name="auto_cap" msgid="1719746674854628252">"ავტო-კაპიტალიზაცია"</string>
+    <string name="auto_cap_summary" msgid="7934452761022946874">"ყოველი წინადადების პირველი სიტყვის კაპიტალიზაცია"</string>
+    <string name="edit_personal_dictionary" msgid="3996910038952940420">"პერსონალური ლექსიკონი"</string>
+    <string name="configure_dictionaries_title" msgid="4238652338556902049">"დამატებითი ლექსიკონები"</string>
+    <string name="main_dictionary" msgid="4798763781818361168">"მთავარი ლექსიკონი"</string>
+    <string name="prefs_show_suggestions" msgid="8026799663445531637">"კორექციის შეთავაზებების ჩვენება"</string>
+    <string name="prefs_show_suggestions_summary" msgid="1583132279498502825">"წერისას შეთავაზებული სიტყვების ჩვენება"</string>
+    <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"ყოველთვის ჩვენება"</string>
+    <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3859783767435239118">"პორტრეტის რეჟიმში ჩვენება"</string>
+    <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"ყოველთვის დამალვა"</string>
+    <string name="prefs_block_potentially_offensive_title" msgid="5078480071057408934">"შეურაცხმყოფელი სიტყვების დაბლოკვა"</string>
+    <string name="prefs_block_potentially_offensive_summary" msgid="2371835479734991364">"არ მოხდეს პოტენციურად შეურაცხმყოფელი სიტყვების შეთავაზება"</string>
+    <string name="auto_correction" msgid="7630720885194996950">"ავტოკორექცია"</string>
+    <string name="auto_correction_summary" msgid="5625751551134658006">"შორისი და პუნქტუაცია ავტომატურად ასწორებს არასწორად აკრეფილ სიტყვებს"</string>
+    <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"გამორთვა"</string>
+    <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"მოკრძალებული"</string>
+    <string name="auto_correction_threshold_mode_aggressive" msgid="7319007299148899623">"აგრესიული"</string>
+    <string name="auto_correction_threshold_mode_very_aggressive" msgid="1853309024129480416">"ძალიან აგრესიული"</string>
+    <string name="bigram_prediction" msgid="1084449187723948550">"შემდეგი სიტყვის შეთავაზებები"</string>
+    <string name="bigram_prediction_summary" msgid="3896362682751109677">"შეთავაზებებისას წინა სიტყვის გამოყენება"</string>
+    <string name="gesture_input" msgid="826951152254563827">"ჟესტებით წერის ჩართვა"</string>
+    <string name="gesture_input_summary" msgid="9180350639305731231">"სიტყვის შეყვანა ასო-ნიშნებზე გასრიალებით"</string>
+    <string name="gesture_preview_trail" msgid="3802333369335722221">"ჟესტიკულაციის კუდის ჩვენება"</string>
+    <string name="gesture_floating_preview_text" msgid="4443240334739381053">"დინამიურად მოლივლივე გადახედვა"</string>
+    <string name="gesture_floating_preview_text_summary" msgid="4472696213996203533">"ჟესტიკულაციისას შეთავაზებული სიტყვის ნახვა"</string>
+    <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : შეინახა"</string>
+    <string name="label_go_key" msgid="1635148082137219148">"წასვლა"</string>
+    <string name="label_next_key" msgid="362972844525672568">"შემდ."</string>
+    <string name="label_previous_key" msgid="1211868118071386787">"წინა"</string>
+    <string name="label_done_key" msgid="2441578748772529288">"დასრულდა"</string>
+    <string name="label_send_key" msgid="2815056534433717444">"გაგზ."</string>
+    <string name="label_pause_key" msgid="181098308428035340">"პაუზა"</string>
+    <string name="label_wait_key" msgid="6402152600878093134">"მოცდა"</string>
+    <string name="spoken_use_headphones" msgid="896961781287283493">"შეაერთეთ ყურსაცვამი, რათა მოისმინოთ აკრეფილი პაროლის კლავიშების სახელები."</string>
+    <string name="spoken_current_text_is" msgid="2485723011272583845">"მიმდინარე ტექსტი არის %s"</string>
+    <string name="spoken_no_text_entered" msgid="7479685225597344496">"ტექსტი არ შეყვანილა"</string>
+    <string name="spoken_description_unknown" msgid="3197434010402179157">"კლავიატურის კოდი %d"</string>
+    <string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
+    <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Shift ჩართულია (შეეხეთ გამოსართავად)"</string>
+    <string name="spoken_description_caps_lock" msgid="3276478269526304432">"ჩართულია Caps (შეეხეთ გამოსართავად)"</string>
+    <string name="spoken_description_delete" msgid="8740376944276199801">"Delete"</string>
+    <string name="spoken_description_to_symbol" msgid="5486340107500448969">"სიმბოლოები"</string>
+    <string name="spoken_description_to_alpha" msgid="23129338819771807">"ასოები"</string>
+    <string name="spoken_description_to_numeric" msgid="591752092685161732">"ნომრები"</string>
+    <string name="spoken_description_settings" msgid="4627462689603838099">"პარამეტრები"</string>
+    <string name="spoken_description_tab" msgid="2667716002663482248">"Tab"</string>
+    <string name="spoken_description_space" msgid="2582521050049860859">"შორისი"</string>
+    <string name="spoken_description_mic" msgid="615536748882611950">"ხმოვანი შეყვანა"</string>
+    <string name="spoken_description_smiley" msgid="2256309826200113918">"ღიმილი"</string>
+    <string name="spoken_description_return" msgid="8178083177238315647">"დაბრუნება"</string>
+    <string name="spoken_description_search" msgid="1247236163755920808">"ძიება"</string>
+    <string name="spoken_description_dot" msgid="40711082435231673">"წერტილი"</string>
+    <string name="spoken_description_language_switch" msgid="5507091328222331316">"ენის გადართვა"</string>
+    <string name="spoken_description_action_next" msgid="8636078276664150324">"შემდეგი"</string>
+    <string name="spoken_description_action_previous" msgid="800872415009336208">"წინა"</string>
+    <string name="spoken_description_shiftmode_on" msgid="5700440798609574589">"Shift ჩართულია"</string>
+    <string name="spoken_description_shiftmode_locked" msgid="593175803181701830">"ჩართულია Caps"</string>
+    <string name="spoken_description_shiftmode_off" msgid="657219998449174808">"Shift გამორთულია"</string>
+    <string name="spoken_description_mode_symbol" msgid="7183343879909747642">"სიმბოლოების რეჟიმი"</string>
+    <string name="spoken_description_mode_alpha" msgid="3528307674390156956">"ასოების რეჟიმი"</string>
+    <string name="spoken_description_mode_phone" msgid="6520207943132026264">"ტელეფონის რეჟიმი"</string>
+    <string name="spoken_description_mode_phone_shift" msgid="5499629753962641227">"ტელეფონის სიმბოლოების რეჟიმი"</string>
+    <string name="announce_keyboard_hidden" msgid="8718927835531429807">"კლავიატურა დამალულია"</string>
+    <string name="announce_keyboard_mode" msgid="4729081055438508321">"ნაჩვენებია <xliff:g id="MODE">%s</xliff:g> კლავიატურა"</string>
+    <string name="keyboard_mode_date" msgid="3137520166817128102">"თარიღი"</string>
+    <string name="keyboard_mode_date_time" msgid="339593358488851072">"თარიღი და დრო"</string>
+    <string name="keyboard_mode_email" msgid="6216248078128294262">"ელფოსტა"</string>
+    <string name="keyboard_mode_im" msgid="1137405089766557048">"შეტყობინებები"</string>
+    <string name="keyboard_mode_number" msgid="7991623440699957069">"რიცხვები"</string>
+    <string name="keyboard_mode_phone" msgid="6851627527401433229">"ტელეფონი"</string>
+    <string name="keyboard_mode_text" msgid="6479436687899701619">"ტექსტი"</string>
+    <string name="keyboard_mode_time" msgid="4381856885582143277">"დრო"</string>
+    <string name="keyboard_mode_url" msgid="1519819835514911218">"URL"</string>
+    <string name="voice_input" msgid="3583258583521397548">"ხმოვანი შეყვანის კლავიში"</string>
+    <string name="voice_input_modes_main_keyboard" msgid="3360660341121083174">"მთავარ კლავიატურაზე"</string>
+    <string name="voice_input_modes_symbols_keyboard" msgid="7203213240786084067">"სიმბოლოთა კლავიატურაზე"</string>
+    <string name="voice_input_modes_off" msgid="3745699748218082014">"გამორთვა"</string>
+    <string name="voice_input_modes_summary_main_keyboard" msgid="6586544292900314339">"მიკროფონი მთავარ კლავიატურაზე"</string>
+    <string name="voice_input_modes_summary_symbols_keyboard" msgid="5233725927281932391">"მიკროფონი სიმბოლოთა კლავიატურაზე"</string>
+    <string name="voice_input_modes_summary_off" msgid="63875609591897607">"ხმოვანი შეყვანა გამორთულია"</string>
+    <string name="configure_input_method" msgid="373356270290742459">"შეყვანის მეთოდების კონფიგურაცია"</string>
+    <string name="language_selection_title" msgid="1651299598555326750">"შეყვანის ენები"</string>
+    <string name="send_feedback" msgid="1780431884109392046">"უკუკავშირის გაგზავნა"</string>
+    <string name="select_language" msgid="3693815588777926848">"შეყვანის ენები"</string>
+    <string name="hint_add_to_dictionary" msgid="573678656946085380">"შეეხეთ ისევ შესანახად"</string>
+    <string name="has_dictionary" msgid="6071847973466625007">"ხელმისაწვდომია ლექსიკონი"</string>
+    <string name="prefs_enable_log" msgid="6620424505072963557">"მომხმარებლის უკუკავშირის ჩართვა"</string>
+    <string name="prefs_description_log" msgid="7525225584555429211">"შეიტანეთ წვლილი შეყვანის ამ მეთოდის გაუმჯობესებაში — გააგზავნეთ მოხმარების სტატისტიკა და ავარიული გათიშვების ანგარიშები"</string>
+    <string name="keyboard_layout" msgid="8451164783510487501">"კლავიატურის თემა"</string>
+    <string name="subtype_en_GB" msgid="88170601942311355">"ინგლისური (გართ. სამ.)"</string>
+    <string name="subtype_en_US" msgid="6160452336634534239">"ინგლისური (აშშ)"</string>
+    <string name="subtype_es_US" msgid="5583145191430180200">"ესპანური (აშშ)"</string>
+    <string name="subtype_with_layout_en_GB" msgid="2179097748724725906">"ინგლისური (გაერთ. სამ.) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
+    <string name="subtype_with_layout_en_US" msgid="1362581347576714579">"ინგლისური (აშშ) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
+    <string name="subtype_with_layout_es_US" msgid="6261791057007890189">"ესპანური (აშშ) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
+    <string name="subtype_nepali_traditional" msgid="9032247506728040447">"<xliff:g id="LANGUAGE">%s</xliff:g> (ტრადიციული)"</string>
+    <string name="subtype_no_language" msgid="7137390094240139495">"ენის გარეშე (ანბანი)"</string>
+    <string name="subtype_no_language_qwerty" msgid="244337630616742604">"ანბანი (QWERTY)"</string>
+    <string name="subtype_no_language_qwertz" msgid="443066912507547976">"ანბანი (QWERTZ)"</string>
+    <string name="subtype_no_language_azerty" msgid="8144348527575640087">"ანბანი (AZERTY)"</string>
+    <string name="subtype_no_language_dvorak" msgid="1564494667584718094">"ანბანი (Dvorak)"</string>
+    <string name="subtype_no_language_colemak" msgid="5837418400010302623">"ანბანი (Colemak)"</string>
+    <string name="subtype_no_language_pcqwerty" msgid="5354918232046200018">"ანბანი (PC)"</string>
+    <string name="subtype_emoji" msgid="7483586578074549196">"სიცილაკები"</string>
+    <string name="custom_input_styles_title" msgid="8429952441821251512">"შეყვანის სტილების კონფიგურაცია"</string>
+    <string name="add_style" msgid="6163126614514489951">"სტილის დამატება"</string>
+    <string name="add" msgid="8299699805688017798">"დამატება"</string>
+    <string name="remove" msgid="4486081658752944606">"ამოშლა"</string>
+    <string name="save" msgid="7646738597196767214">"შენახვა"</string>
+    <string name="subtype_locale" msgid="8576443440738143764">"ენა"</string>
+    <string name="keyboard_layout_set" msgid="4309233698194565609">"განლაგება"</string>
+    <string name="custom_input_style_note_message" msgid="8826731320846363423">"თქვენი მორგებული შეყვანის სტილი გამოყენებამდე უნდა გაააქტიუროთ. გსურთ მისი აქტივაცია ახლა?"</string>
+    <string name="enable" msgid="5031294444630523247">"ჩართვა"</string>
+    <string name="not_now" msgid="6172462888202790482">"ახლა არა"</string>
+    <string name="custom_input_style_already_exists" msgid="8008728952215449707">"შეყვანის იგივე სტილი უკვე არსებობს: <xliff:g id="INPUT_STYLE_NAME">%s</xliff:g>"</string>
+    <string name="prefs_usability_study_mode" msgid="1261130555134595254">"გამოყენებადობის კვლევის რეჟიმი"</string>
+    <string name="prefs_key_longpress_timeout_settings" msgid="6102240298932897873">"კლავიშზე გრძელი დაჭერის დაყოვნება"</string>
+    <string name="prefs_keypress_vibration_duration_settings" msgid="7918341459947439226">"კლავიშზე დაჭერის ვიბრაციის ხანგრძლივობა"</string>
+    <string name="prefs_keypress_sound_volume_settings" msgid="6027007337036891623">"კლავიშზე დაჭერის ხმა"</string>
+    <string name="prefs_read_external_dictionary" msgid="2588931418575013067">"გარე ლექსიკონის ფაილის წაკითხვა"</string>
+    <string name="read_external_dictionary_no_files_message" msgid="4947420942224623792">"ჩამოტვირთვების საქაღალდეში ლექსიკონის ფაილები არ არის"</string>
+    <string name="read_external_dictionary_multiple_files_title" msgid="7637749044265808628">"ინსტალაციისათვის აირჩიეთ ლექსიკონის ფაილი"</string>
+    <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"ნამდვილად გსურთ ამ ფაილის <xliff:g id="LOCALE_NAME">%s</xliff:g>-ისთვის ინსტალაცია?"</string>
+    <string name="error" msgid="8940763624668513648">"წარმოიშვა შეცდომა"</string>
+    <string name="button_default" msgid="3988017840431881491">"ნაგულისხმევი"</string>
+    <string name="setup_welcome_title" msgid="6112821709832031715">"კეთილი იყოს თქვენი მობრძანება <xliff:g id="APPLICATION_NAME">%s</xliff:g>-ში"</string>
+    <string name="setup_welcome_additional_description" msgid="8150252008545768953">"ჟესტებით წერით"</string>
+    <string name="setup_start_action" msgid="8936036460897347708">"დაწყება"</string>
+    <string name="setup_next_action" msgid="371821437915144603">"შემდეგი საფეხური"</string>
+    <string name="setup_steps_title" msgid="6400373034871816182">"მიმდინარეობს <xliff:g id="APPLICATION_NAME">%s</xliff:g>-ის დაყენება"</string>
+    <string name="setup_step1_title" msgid="3147967630253462315">"<xliff:g id="APPLICATION_NAME">%s</xliff:g>-ის ჩართვა"</string>
+    <string name="setup_step1_instruction" msgid="2578631936624637241">"გთხოვთ მონიშნოთ „<xliff:g id="APPLICATION_NAME">%s</xliff:g>“ თქვენი ენის და შეყვანის პარამეტრებში. ეს უფლებას მიცემს მას გაეშვას თქვენს მოწყობილობაზე."</string>
+    <string name="setup_step1_finished_instruction" msgid="10761482004957994">"<xliff:g id="APPLICATION_NAME">%s</xliff:g> უკვე გააქტიურებულია თქვენი ენის შეყვანის პარამეტრებში, ასე რომ ეს საფეხური დასრულებულია. გადადით შემდეგ საფეხურზე!"</string>
+    <string name="setup_step1_action" msgid="4366513534999901728">"პარამეტრებში გააქტიურება"</string>
+    <string name="setup_step2_title" msgid="6860725447906690594">"გადართეთ <xliff:g id="APPLICATION_NAME">%s</xliff:g>-ზე"</string>
+    <string name="setup_step2_instruction" msgid="9141481964870023336">"შემდეგ, აირჩიეთ „<xliff:g id="APPLICATION_NAME">%s</xliff:g>“ თქვენს აქტიურ შეყვანის მეთოდად."</string>
+    <string name="setup_step2_action" msgid="1660330307159824337">"შეყვანის მეთოდების გადართვა"</string>
+    <string name="setup_step3_title" msgid="3154757183631490281">"გილოცავთ, პროცესი დასრულდა!"</string>
+    <string name="setup_step3_instruction" msgid="8025981829605426000">"ამიერიდან შეძლებთ ყველა სასურველ აპში <xliff:g id="APPLICATION_NAME">%s</xliff:g>-ით წერას."</string>
+    <string name="setup_step3_action" msgid="600879797256942259">"დამატებითი ენების კონფიგურაცია"</string>
+    <string name="setup_finish_action" msgid="276559243409465389">"დასრულებული"</string>
+    <string name="show_setup_wizard_icon" msgid="5008028590593710830">"აპის ხატულის ჩვენება"</string>
+    <string name="show_setup_wizard_icon_summary" msgid="4119998322536880213">"გაშვების ხატულის ჩვენება გამშვებში"</string>
+    <string name="app_name" msgid="6320102637491234792">"ლექსიკონის პროვაიდერი"</string>
+    <string name="dictionary_provider_name" msgid="3027315045397363079">"ლექსიკონის პროვაიდერი"</string>
+    <string name="dictionary_service_name" msgid="6237472350693511448">"ლექსიკონის სერვისი"</string>
+    <string name="download_description" msgid="6014835283119198591">"ლექსიკონის განახლების მონაცემები"</string>
+    <string name="dictionary_settings_title" msgid="8091417676045693313">"დამატებითი ლექსიკონები"</string>
+    <string name="dictionary_install_over_metered_network_prompt" msgid="3587517870006332980">"ხელმისაწვდომია ლექსიკონი"</string>
+    <string name="dictionary_settings_summary" msgid="5305694987799824349">"ლექსიკონების პარამეტრები"</string>
+    <string name="user_dictionaries" msgid="3582332055892252845">"მომხმარებლის ლექსიკონები"</string>
+    <string name="default_user_dict_pref_name" msgid="1625055720489280530">"მომხმარებლის ლექსიკონი"</string>
+    <string name="dictionary_available" msgid="4728975345815214218">"ხელმისაწვდომია ლექსიკონი"</string>
+    <string name="dictionary_downloading" msgid="2982650524622620983">"მიმდინარეობს ჩამოტვირთვა"</string>
+    <string name="dictionary_installed" msgid="8081558343559342962">"დაყენებულია"</string>
+    <string name="dictionary_disabled" msgid="8950383219564621762">"დაყენებულია, გაუქმებულია"</string>
+    <string name="cannot_connect_to_dict_service" msgid="9216933695765732398">"ლექსიკონის სერვისთან დაკავშირებით პრობლემა წარმოიშვა"</string>
+    <string name="no_dictionaries_available" msgid="8039920716566132611">"ლექსიკონები მიუწვდომელია"</string>
+    <string name="check_for_updates_now" msgid="8087688440916388581">"განახლება"</string>
+    <string name="last_update" msgid="730467549913588780">"ბოლო განახლება"</string>
+    <string name="message_updating" msgid="4457761393932375219">"მიმდინარეობს განახლებების შემოწმება"</string>
+    <string name="message_loading" msgid="8689096636874758814">"იტვირთება…"</string>
+    <string name="main_dict_description" msgid="3072821352793492143">"მთავარი ლექსიკონი"</string>
+    <string name="cancel" msgid="6830980399865683324">"გაუქმება"</string>
+    <string name="install_dict" msgid="180852772562189365">"ინსტალაცია"</string>
+    <string name="cancel_download_dict" msgid="7843340278507019303">"გაუქმება"</string>
+    <string name="delete_dict" msgid="756853268088330054">"წაშლა"</string>
+    <string name="should_download_over_metered_prompt" msgid="2878629598667658845">"თქვენ მიერ მობილურ მოწყობილობაზე არჩეული ენისათვის ხელმისაწვდომია ლექსიკონი.&lt;br/&gt; გირჩევთ, &lt;b&gt;ჩამოტვირთოთ&lt;/b&gt; <xliff:g id="LANGUAGE">%1$s</xliff:g> ლექსიკონი, რათა გაიმარტივოთ ტექსტის შეყვანა.&lt;br/&gt; &lt;br/&gt; ჩამოტვირთვას შესაძლოა დაჭირდეს ერთი ან ორი წუთი 3G სისწრაფეზე. თუ ულიმიტო არ გაქვთ &lt;b&gt; მობილური ინტერნეტის ტარიფი&lt;/b&gt;.&lt;br/&amp;gt, შესაძლოა ჩამოტვირთვა დამატებით გადასახადებთან იყოს დაკავშირებული; თუ არ ხართ დარწმუნებული მობილური ინტერნეტის აქტიური ტარიფის შესახებ, გირჩევთ იპოვოთ Wi-Fi კავშირი და ავტომატურად დაიწყოთ ჩამოტვირთვა.&lt;br/&gt; &lt;br/&gt; რჩევა: ლექსიკონების ჩამოტვირთვა და ამოშლა შესაძლებელია სექციიდან &lt;b&gt;ენა და შეყვანა&lt;/b&gt; სექციიდან, თქვენი მობილური მოწყობილობის &lt;b&gt;პარამეტრების&lt;/b&gt; მენიუში."</string>
+    <string name="download_over_metered" msgid="1643065851159409546">"ახლა ჩამოტვირთვა (<xliff:g id="SIZE_IN_MEGABYTES">%1$.1f</xliff:g>მბაიტი)"</string>
+    <string name="do_not_download_over_metered" msgid="2176209579313941583">"Wi-Fi კავშირზე ჩამოტვირთვა"</string>
+    <string name="dict_available_notification_title" msgid="6514288591959117288">"<xliff:g id="LANGUAGE">%1$s</xliff:g>-ისთვის ხელმისაწვდომია ლექსიკონი"</string>
+    <string name="dict_available_notification_description" msgid="1075194169443163487">"დააჭირეთ განხილვას და ჩამოტვირთეთ"</string>
+    <string name="toast_downloading_suggestions" msgid="1313027353588566660">"ჩამოტვირთვა: <xliff:g id="LANGUAGE">%1$s</xliff:g>-ის შემოთავაზებები მალე მომზადდება."</string>
+    <string name="version_text" msgid="2715354215568469385">"ვერსია <xliff:g id="VERSION_NUMBER">%1$s</xliff:g>"</string>
+    <string name="user_dict_settings_add_menu_title" msgid="1254195365689387076">"დამატება"</string>
+    <string name="user_dict_settings_add_dialog_title" msgid="4096700390211748168">"ლექსიკონში დამატება"</string>
+    <string name="user_dict_settings_add_screen_title" msgid="5818914331629278758">"ფრაზა"</string>
+    <string name="user_dict_settings_add_dialog_more_options" msgid="5671682004887093112">"მეტი ვარიანტები"</string>
+    <string name="user_dict_settings_add_dialog_less_options" msgid="2716586567241724126">"ნაკლები ვარიანტები"</string>
+    <string name="user_dict_settings_add_dialog_confirm" msgid="4703129507388332950">"კარგი"</string>
+    <string name="user_dict_settings_add_word_option_name" msgid="6665558053408962865">"სიტყვა:"</string>
+    <string name="user_dict_settings_add_shortcut_option_name" msgid="3094731590655523777">"მალსახმობი:"</string>
+    <string name="user_dict_settings_add_locale_option_name" msgid="4738643440987277705">"ენა:"</string>
+    <string name="user_dict_settings_add_word_hint" msgid="4902434148985906707">"შიყვანეთ სიტყვა"</string>
+    <string name="user_dict_settings_add_shortcut_hint" msgid="2265453012555060178">"არასავალდებულო მალსა"</string>
+    <string name="user_dict_settings_edit_dialog_title" msgid="3765774633869590352">"სიტყვის შესწორება"</string>
+    <string name="user_dict_settings_context_menu_edit_title" msgid="6812255903472456302">"რედაქტირება"</string>
+    <string name="user_dict_settings_context_menu_delete_title" msgid="8142932447689461181">"წაშლა"</string>
+    <string name="user_dict_settings_empty_text" msgid="558499587532668203">"თქვენ არ გაქვთ სიტყვები მომხმარებლის ლექსიკონში. დაამატეთ სიტყვები ღილაკ Add (+) შეხებით."</string>
+    <string name="user_dict_settings_all_languages" msgid="8276126583216298886">"ყველა ენისთვის"</string>
+    <string name="user_dict_settings_more_languages" msgid="7131268499685180461">"სხვა ენები…"</string>
+    <string name="user_dict_settings_delete" msgid="110413335187193859">"წაშლა"</string>
+    <string name="user_dict_fast_scroll_alphabet" msgid="5431919401558285473">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
+</resources>
diff --git a/java/res/values-ka/strings-appname.xml b/java/res/values-ka/strings-appname.xml
deleted file mode 100644
index 703c66a..0000000
--- a/java/res/values-ka/strings-appname.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?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:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="english_ime_name" msgid="5940510615957428904">"Android-ის კლავიატურა (AOSP)"</string>
-    <string name="spell_checker_service_name" msgid="1254221805440242662">"Android-ის მართლწერის შემმოწმებელი (AOSP)"</string>
-    <string name="english_ime_settings" msgid="5760361067176802794">"Android-ის კლავიატურის პარამეტრები (AOSP)"</string>
-    <string name="android_spell_checker_settings" msgid="6123949487832861885">"Android-ის მართლწერის შემმოწმებლის პარამეტრები (AOSP)"</string>
-</resources>
diff --git a/java/res/values-ka/strings.xml b/java/res/values-ka/strings.xml
index f83e21d..211e923 100644
--- a/java/res/values-ka/strings.xml
+++ b/java/res/values-ka/strings.xml
@@ -64,8 +64,8 @@
     <string name="auto_correction_summary" msgid="5625751551134658006">"შორისი და პუნქტუაცია ავტომატურად ასწორებს არასწორად აკრეფილ სიტყვებს"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"გამორთულია"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"მოკრძალებული"</string>
-    <string name="auto_correction_threshold_mode_aggressive" msgid="3524029103734923819">"აგრესიული"</string>
-    <string name="auto_correction_threshold_mode_very_aggressive" msgid="3386782235540547678">"ძალიან აგრესიული"</string>
+    <string name="auto_correction_threshold_mode_aggressive" msgid="7319007299148899623">"აგრესიული"</string>
+    <string name="auto_correction_threshold_mode_very_aggressive" msgid="1853309024129480416">"ძალიან აგრესიული"</string>
     <string name="bigram_prediction" msgid="1084449187723948550">"შემდეგი სიტყვის შეთავაზებები"</string>
     <string name="bigram_prediction_summary" msgid="3896362682751109677">"შეთავაზებებისას წინა სიტყვის გამოყენება"</string>
     <string name="gesture_input" msgid="826951152254563827">"ჟესტებით წერის ჩართვა"</string>
@@ -143,13 +143,13 @@
     <string name="subtype_with_layout_en_GB" msgid="2179097748724725906">"ინგლისური (გაერთ. სამ.) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_en_US" msgid="1362581347576714579">"ინგლისური (აშშ) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_es_US" msgid="6261791057007890189">"ესპანური (აშშ) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
-    <string name="subtype_no_language" msgid="141420857808801746">"ენის გარეშე"</string>
-    <string name="subtype_no_language_qwerty" msgid="2956121451616633133">"ენის გარეშე (QWERTY)"</string>
-    <string name="subtype_no_language_qwertz" msgid="1177848172397202890">"ენის გარეშე (QWERTZ)"</string>
-    <string name="subtype_no_language_azerty" msgid="8721460968141187394">"ენის გარეშე (AZERTY)"</string>
-    <string name="subtype_no_language_dvorak" msgid="3122976737669823935">"ენის გარეშე (Dvorak)"</string>
-    <string name="subtype_no_language_colemak" msgid="4205992994906097244">"ენის გარეშე (Colemak)"</string>
-    <string name="subtype_no_language_pcqwerty" msgid="8840928374394180189">"ენის გარეშე (PC)"</string>
+    <string name="subtype_no_language" msgid="7137390094240139495">"ენის გარეშე (ანბანი)"</string>
+    <string name="subtype_no_language_qwerty" msgid="244337630616742604">"ანბანი (QWERTY)"</string>
+    <string name="subtype_no_language_qwertz" msgid="443066912507547976">"ანბანი (QWERTZ)"</string>
+    <string name="subtype_no_language_azerty" msgid="8144348527575640087">"ანბანი (AZERTY)"</string>
+    <string name="subtype_no_language_dvorak" msgid="1564494667584718094">"ანბანი (Dvorak)"</string>
+    <string name="subtype_no_language_colemak" msgid="5837418400010302623">"ანბანი (Colemak)"</string>
+    <string name="subtype_no_language_pcqwerty" msgid="5354918232046200018">"ანბანი (PC)"</string>
     <string name="custom_input_styles_title" msgid="8429952441821251512">"შეყვანის სტილების კონფიგურაცია"</string>
     <string name="add_style" msgid="6163126614514489951">"სტილის დამატება"</string>
     <string name="add" msgid="8299699805688017798">"დამატება"</string>
diff --git a/java/res/xml-sw768dp/kbd_more_keys_keyboard_template.xml b/java/res/values-km/donottranslate.xml
similarity index 68%
copy from java/res/xml-sw768dp/kbd_more_keys_keyboard_template.xml
copy to java/res/values-km/donottranslate.xml
index f89a0a6..a9893fe 100644
--- a/java/res/xml-sw768dp/kbd_more_keys_keyboard_template.xml
+++ b/java/res/values-km/donottranslate.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2008, The Android Open Source Project
+** 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.
@@ -17,10 +17,7 @@
 ** limitations under the License.
 */
 -->
-
-<Keyboard xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyWidth="5.0%p"
-    latin:rowHeight="@dimen/popup_key_height"
-    style="?attr/moreKeysKeyboardStyle"
-    >
-</Keyboard>
+<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-ko/strings.xml b/java/res/values-ko/strings.xml
index b1bfe11..a74b4f1 100644
--- a/java/res/values-ko/strings.xml
+++ b/java/res/values-ko/strings.xml
@@ -64,8 +64,8 @@
     <string name="auto_correction_summary" msgid="5625751551134658006">"스페이스바와 문장부호 키를 사용하면 오타가 자동으로 교정됩니다."</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"사용 안함"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"약"</string>
-    <string name="auto_correction_threshold_mode_aggressive" msgid="3524029103734923819">"중"</string>
-    <string name="auto_correction_threshold_mode_very_aggressive" msgid="3386782235540547678">"강"</string>
+    <string name="auto_correction_threshold_mode_aggressive" msgid="7319007299148899623">"강력"</string>
+    <string name="auto_correction_threshold_mode_very_aggressive" msgid="1853309024129480416">"매우 강력"</string>
     <string name="bigram_prediction" msgid="1084449187723948550">"다음 단어 추천"</string>
     <string name="bigram_prediction_summary" msgid="3896362682751109677">"추천할 때 이전 단어를 사용"</string>
     <string name="gesture_input" msgid="826951152254563827">"제스처 타이핑 사용"</string>
@@ -143,13 +143,15 @@
     <string name="subtype_with_layout_en_GB" msgid="2179097748724725906">"영어(영국) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_en_US" msgid="1362581347576714579">"영어(미국) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_es_US" msgid="6261791057007890189">"스페인어(미국)(<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
-    <string name="subtype_no_language" msgid="141420857808801746">"언어가 없음"</string>
-    <string name="subtype_no_language_qwerty" msgid="2956121451616633133">"언어가 없음(QWERTY)"</string>
-    <string name="subtype_no_language_qwertz" msgid="1177848172397202890">"언어 없음(QWERTZ)"</string>
-    <string name="subtype_no_language_azerty" msgid="8721460968141187394">"언어 없음(AZERTY)"</string>
-    <string name="subtype_no_language_dvorak" msgid="3122976737669823935">"언어 없음(드보락)"</string>
-    <string name="subtype_no_language_colemak" msgid="4205992994906097244">"언어 없음(콜맥)"</string>
-    <string name="subtype_no_language_pcqwerty" msgid="8840928374394180189">"언어 없음(PC)"</string>
+    <string name="subtype_nepali_traditional" msgid="9032247506728040447">"<xliff:g id="LANGUAGE">%s</xliff:g>(일반)"</string>
+    <string name="subtype_no_language" msgid="7137390094240139495">"언어 없음(알파벳)"</string>
+    <string name="subtype_no_language_qwerty" msgid="244337630616742604">"알파벳(QWERTY)"</string>
+    <string name="subtype_no_language_qwertz" msgid="443066912507547976">"알파벳(QWERTZ)"</string>
+    <string name="subtype_no_language_azerty" msgid="8144348527575640087">"알파벳(AZERTY)"</string>
+    <string name="subtype_no_language_dvorak" msgid="1564494667584718094">"알파벳(드보락)"</string>
+    <string name="subtype_no_language_colemak" msgid="5837418400010302623">"알파벳(콜맥)"</string>
+    <string name="subtype_no_language_pcqwerty" msgid="5354918232046200018">"알파벳(PC)"</string>
+    <string name="subtype_emoji" msgid="7483586578074549196">"이모티콘"</string>
     <string name="custom_input_styles_title" msgid="8429952441821251512">"맞춤 입력 스타일"</string>
     <string name="add_style" msgid="6163126614514489951">"스타일 추가"</string>
     <string name="add" msgid="8299699805688017798">"추가"</string>
diff --git a/java/res/values-land/dimens.xml b/java/res/values-land/dimens.xml
index c78c25f..42a746b 100644
--- a/java/res/values-land/dimens.xml
+++ b/java/res/values-land/dimens.xml
@@ -26,14 +26,8 @@
     <!-- key_height + key_bottom_gap = popup_key_height -->
     <dimen name="popup_key_height">44.8dp</dimen>
 
-    <fraction name="keyboard_top_padding">1.818%p</fraction>
-    <fraction name="keyboard_bottom_padding">0.0%p</fraction>
-    <fraction name="key_bottom_gap">4.330%p</fraction>
-    <fraction name="key_horizontal_gap">0.405%p</fraction>
-
-    <fraction name="key_bottom_gap_stone">5.010%p</fraction>
-    <fraction name="key_horizontal_gap_stone">1.159%p</fraction>
-
+    <fraction name="keyboard_top_padding_gb">1.818%p</fraction>
+    <fraction name="keyboard_bottom_padding_gb">0.0%p</fraction>
     <fraction name="key_bottom_gap_gb">5.941%p</fraction>
     <fraction name="key_horizontal_gap_gb">0.997%p</fraction>
 
@@ -53,7 +47,7 @@
     <fraction name="key_uppercase_letter_ratio">40%</fraction>
     <fraction name="key_preview_text_ratio">90%</fraction>
     <fraction name="spacebar_text_ratio">40.000%</fraction>
-    <dimen name="key_preview_offset">0.0dp</dimen>
+    <dimen name="key_preview_offset_gb">0.0dp</dimen>
 
     <!-- For 5-row keyboard -->
     <fraction name="key_bottom_gap_5row">3.20%p</fraction>
@@ -72,11 +66,18 @@
     <!-- popup_key_height x 1.2 -->
     <dimen name="more_keys_keyboard_slide_allowance">53.76dp</dimen>
     <!-- popup_key_height x -1.0 -->
-    <dimen name="more_keys_keyboard_vertical_correction">-44.8dp</dimen>
+    <dimen name="more_keys_keyboard_vertical_correction_gb">-44.8dp</dimen>
 
     <!-- Gesture floating preview text parameters -->
     <dimen name="gesture_floating_preview_text_size">23dp</dimen>
     <dimen name="gesture_floating_preview_text_offset">54dp</dimen>
     <dimen name="gesture_floating_preview_horizontal_padding">23dp</dimen>
     <dimen name="gesture_floating_preview_vertical_padding">15dp</dimen>
+
+    <!-- Emoji keyboard -->
+    <fraction name="emoji_keyboard_key_width">10%p</fraction>
+    <fraction name="emoji_keyboard_row_height">50%p</fraction>
+    <fraction name="emoji_keyboard_key_letter_size">60%p</fraction>
+    <integer name="emoji_keyboard_max_key_count">20</integer>
+
 </resources>
diff --git a/java/res/xml-sw768dp/kbd_more_keys_keyboard_template.xml b/java/res/values-lo/donottranslate.xml
similarity index 68%
copy from java/res/xml-sw768dp/kbd_more_keys_keyboard_template.xml
copy to java/res/values-lo/donottranslate.xml
index f89a0a6..a9893fe 100644
--- a/java/res/xml-sw768dp/kbd_more_keys_keyboard_template.xml
+++ b/java/res/values-lo/donottranslate.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2008, The Android Open Source Project
+** 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.
@@ -17,10 +17,7 @@
 ** limitations under the License.
 */
 -->
-
-<Keyboard xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyWidth="5.0%p"
-    latin:rowHeight="@dimen/popup_key_height"
-    style="?attr/moreKeysKeyboardStyle"
-    >
-</Keyboard>
+<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-lt/strings.xml b/java/res/values-lt/strings.xml
index 26f1b27..6110858 100644
--- a/java/res/values-lt/strings.xml
+++ b/java/res/values-lt/strings.xml
@@ -64,8 +64,8 @@
     <string name="auto_correction_summary" msgid="5625751551134658006">"Tarpo kl. ir skyr. ženkl. aut. išt. neteis. įv. žodž."</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Išjungta"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Vidutinis"</string>
-    <string name="auto_correction_threshold_mode_aggressive" msgid="3524029103734923819">"Atkaklus"</string>
-    <string name="auto_correction_threshold_mode_very_aggressive" msgid="3386782235540547678">"Labai agresyviai"</string>
+    <string name="auto_correction_threshold_mode_aggressive" msgid="7319007299148899623">"Atkakliai"</string>
+    <string name="auto_correction_threshold_mode_very_aggressive" msgid="1853309024129480416">"Labai atkakliai"</string>
     <string name="bigram_prediction" msgid="1084449187723948550">"Kito žodžio pasiūlymai"</string>
     <string name="bigram_prediction_summary" msgid="3896362682751109677">"Sudarant pasiūlymus naudoti ankstesnį žodį"</string>
     <string name="gesture_input" msgid="826951152254563827">"Įgalinti teksto vedimą gestais"</string>
@@ -143,13 +143,15 @@
     <string name="subtype_with_layout_en_GB" msgid="2179097748724725906">"Angliška (JK) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_en_US" msgid="1362581347576714579">"Angliška (JAV) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_es_US" msgid="6261791057007890189">"Ispanų k. (JAV) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
-    <string name="subtype_no_language" msgid="141420857808801746">"Kalbos nėra"</string>
-    <string name="subtype_no_language_qwerty" msgid="2956121451616633133">"Nėra kalbos (QWERTY)"</string>
-    <string name="subtype_no_language_qwertz" msgid="1177848172397202890">"Nėra kalbos (QWERTZ)"</string>
-    <string name="subtype_no_language_azerty" msgid="8721460968141187394">"Nėra kalbos (AZERTY)"</string>
-    <string name="subtype_no_language_dvorak" msgid="3122976737669823935">"Nėra kalbos (Dvorak)"</string>
-    <string name="subtype_no_language_colemak" msgid="4205992994906097244">"Nėra kalbos (Colemak)"</string>
-    <string name="subtype_no_language_pcqwerty" msgid="8840928374394180189">"Nėra kalbos (PC)"</string>
+    <string name="subtype_nepali_traditional" msgid="9032247506728040447">"<xliff:g id="LANGUAGE">%s</xliff:g> (tradicinė)"</string>
+    <string name="subtype_no_language" msgid="7137390094240139495">"Kalbos nėra (abėcėlė)"</string>
+    <string name="subtype_no_language_qwerty" msgid="244337630616742604">"Abėcėlė (QWERTY)"</string>
+    <string name="subtype_no_language_qwertz" msgid="443066912507547976">"Abėcėlė (QWERTZ)"</string>
+    <string name="subtype_no_language_azerty" msgid="8144348527575640087">"Abėcėlė (AZERTY)"</string>
+    <string name="subtype_no_language_dvorak" msgid="1564494667584718094">"Abėcėlė (Dvorako)"</string>
+    <string name="subtype_no_language_colemak" msgid="5837418400010302623">"Abėcėlė („Colemak“)"</string>
+    <string name="subtype_no_language_pcqwerty" msgid="5354918232046200018">"Abėcėlė (PC)"</string>
+    <string name="subtype_emoji" msgid="7483586578074549196">"Jaustukai"</string>
     <string name="custom_input_styles_title" msgid="8429952441821251512">"Pasirinkti įvesties stilių"</string>
     <string name="add_style" msgid="6163126614514489951">"Prid. stilių"</string>
     <string name="add" msgid="8299699805688017798">"Pridėti"</string>
diff --git a/java/res/values-lv/strings.xml b/java/res/values-lv/strings.xml
index cdf04cc..ecd4e51 100644
--- a/java/res/values-lv/strings.xml
+++ b/java/res/values-lv/strings.xml
@@ -64,8 +64,8 @@
     <string name="auto_correction_summary" msgid="5625751551134658006">"Atstarpes taustiņš un interpunkcija; automātiska kļūdainu vārdu labošana"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Izslēgta"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Mērena"</string>
-    <string name="auto_correction_threshold_mode_aggressive" msgid="3524029103734923819">"Agresīva"</string>
-    <string name="auto_correction_threshold_mode_very_aggressive" msgid="3386782235540547678">"Ļoti radikāla"</string>
+    <string name="auto_correction_threshold_mode_aggressive" msgid="7319007299148899623">"Agresīvi"</string>
+    <string name="auto_correction_threshold_mode_very_aggressive" msgid="1853309024129480416">"Ļoti agresīvi"</string>
     <string name="bigram_prediction" msgid="1084449187723948550">"Nākamā vārda ieteikumi"</string>
     <string name="bigram_prediction_summary" msgid="3896362682751109677">"Veidojot ieteikumus, izmantot iepriekšējo vārdu."</string>
     <string name="gesture_input" msgid="826951152254563827">"Iespējot ievadi ar žestiem"</string>
@@ -143,13 +143,15 @@
     <string name="subtype_with_layout_en_GB" msgid="2179097748724725906">"Angļu (Lielbritānija) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_en_US" msgid="1362581347576714579">"Angļu (ASV) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_es_US" msgid="6261791057007890189">"Spāņu (ASV) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
-    <string name="subtype_no_language" msgid="141420857808801746">"Nav valodas"</string>
-    <string name="subtype_no_language_qwerty" msgid="2956121451616633133">"Nav valodas (QWERTY)"</string>
-    <string name="subtype_no_language_qwertz" msgid="1177848172397202890">"Nav valodas (QWERTZ)"</string>
-    <string name="subtype_no_language_azerty" msgid="8721460968141187394">"Nav valodas (AZERTY)"</string>
-    <string name="subtype_no_language_dvorak" msgid="3122976737669823935">"Nav valodas (Dvorak)"</string>
-    <string name="subtype_no_language_colemak" msgid="4205992994906097244">"Nav valodas (Colemak)"</string>
-    <string name="subtype_no_language_pcqwerty" msgid="8840928374394180189">"Nav valodas (PC)"</string>
+    <string name="subtype_nepali_traditional" msgid="9032247506728040447">"<xliff:g id="LANGUAGE">%s</xliff:g> (tradicionālā)"</string>
+    <string name="subtype_no_language" msgid="7137390094240139495">"Nav valodas (alfabēts)"</string>
+    <string name="subtype_no_language_qwerty" msgid="244337630616742604">"Alfabēts (QWERTY)"</string>
+    <string name="subtype_no_language_qwertz" msgid="443066912507547976">"Alfabēts (QWERTZ)"</string>
+    <string name="subtype_no_language_azerty" msgid="8144348527575640087">"Alfabēts (AZERTY)"</string>
+    <string name="subtype_no_language_dvorak" msgid="1564494667584718094">"Alfabēts (Dvorak)"</string>
+    <string name="subtype_no_language_colemak" msgid="5837418400010302623">"Alfabēts (Colemak)"</string>
+    <string name="subtype_no_language_pcqwerty" msgid="5354918232046200018">"Alfabēts (PC)"</string>
+    <string name="subtype_emoji" msgid="7483586578074549196">"Japāņu emocijzīmes"</string>
     <string name="custom_input_styles_title" msgid="8429952441821251512">"Pielāg. ievades stili"</string>
     <string name="add_style" msgid="6163126614514489951">"Piev. stilu"</string>
     <string name="add" msgid="8299699805688017798">"Pievienot"</string>
diff --git a/java/res/values-mn-rMN/strings.xml b/java/res/values-mn-rMN/strings.xml
new file mode 100644
index 0000000..92c2bef
--- /dev/null
+++ b/java/res/values-mn-rMN/strings.xml
@@ -0,0 +1,244 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+**
+** Copyright 2008, 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:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="english_ime_input_options" msgid="3909945612939668554">"Оруулах сонголтууд"</string>
+    <string name="english_ime_research_log" msgid="8492602295696577851">"Судалгааны протоколын командууд"</string>
+    <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Харилцагчийн нэр хайх"</string>
+    <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Алдаа шалгагч нь таны харилцагчдын жагсаалтаас ашиглана"</string>
+    <string name="vibrate_on_keypress" msgid="5258079494276955460">"Товч дарахад чичрэх"</string>
+    <string name="sound_on_keypress" msgid="6093592297198243644">"Товч дарахад дуу гаргах"</string>
+    <string name="popup_on_keypress" msgid="123894815723512944">"Товч дарахад попап гарна"</string>
+    <string name="general_category" msgid="1859088467017573195">"Ерөнхий"</string>
+    <string name="correction_category" msgid="2236750915056607613">"Текст залруулалт"</string>
+    <string name="gesture_typing_category" msgid="497263612130532630">"Зангаагаар бичих"</string>
+    <string name="misc_category" msgid="6894192814868233453">"Бусад сонголтууд"</string>
+    <string name="advanced_settings" msgid="362895144495591463">"Дэлгэрэнгүй тохиргоо"</string>
+    <string name="advanced_settings_summary" msgid="4487980456152830271">"Экспертүүдэд зориулсан тохиргоо"</string>
+    <string name="include_other_imes_in_language_switch_list" msgid="4533689960308565519">"Оруулах өөр арга руу шилжүүлэх"</string>
+    <string name="include_other_imes_in_language_switch_list_summary" msgid="840637129103317635">"Хэл солих түлхүүрт өөр оруулах аргууд мөн багтсан байгаа"</string>
+    <string name="show_language_switch_key" msgid="5915478828318774384">"Хэл солих товч"</string>
+    <string name="show_language_switch_key_summary" msgid="7343403647474265713">"Оруулах хэл олныг идэвхжүүлсэн үед харуулах"</string>
+    <string name="sliding_key_input_preview" msgid="6604262359510068370">"Гулсалт заагчийг харуулах"</string>
+    <string name="sliding_key_input_preview_summary" msgid="6340524345729093886">"Сэлгэх буюу Симбол товчуудаас гулсах үед нүдэнд харагдуулах"</string>
+    <string name="key_preview_popup_dismiss_delay" msgid="6213164897443068248">"Товчны попап арилах хугацаа"</string>
+    <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Хүлээхгүй"</string>
+    <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Үндсэн"</string>
+    <string name="abbreviation_unit_milliseconds" msgid="8700286094028323363">"<xliff:g id="MILLISECONDS">%s</xliff:g>мс"</string>
+    <string name="settings_system_default" msgid="6268225104743331821">"Системийн үндсэн утга"</string>
+    <string name="use_contacts_dict" msgid="4435317977804180815">"Харилцагчдын нэрс санал болгох"</string>
+    <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Санал болгох, залруулахда Харилцагчдын нэрсээс ашиглах"</string>
+    <string name="use_double_space_period" msgid="8781529969425082860">"Давхар зайтай цэг"</string>
+    <string name="use_double_space_period_summary" msgid="6532892187247952799">"Ардаа зайтай цэг оруулахын тулд Зай авах дээр давхар товшино уу"</string>
+    <string name="auto_cap" msgid="1719746674854628252">"Автоматаар томруулах"</string>
+    <string name="auto_cap_summary" msgid="7934452761022946874">"Өгүүлбэр бүрийн эхний үгийн эхний үсгийг томруулах"</string>
+    <string name="edit_personal_dictionary" msgid="3996910038952940420">"Хувийн толь бичиг"</string>
+    <string name="configure_dictionaries_title" msgid="4238652338556902049">"Нэмэлт толь бичгүүд"</string>
+    <string name="main_dictionary" msgid="4798763781818361168">"Үндсэн толь бичиг"</string>
+    <string name="prefs_show_suggestions" msgid="8026799663445531637">"Залруулах санал болголтуудыг харуулах"</string>
+    <string name="prefs_show_suggestions_summary" msgid="1583132279498502825">"Бичих явцад санал болгосон үгсийг харуулах"</string>
+    <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Байнга харуулах"</string>
+    <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3859783767435239118">"Босоо горимд харуулах"</string>
+    <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Байнга нуух"</string>
+    <string name="prefs_block_potentially_offensive_title" msgid="5078480071057408934">"Доромжилсон үгсийг хаах"</string>
+    <string name="prefs_block_potentially_offensive_summary" msgid="2371835479734991364">"Доромжилсон үгсийг санал болгохгүй байх"</string>
+    <string name="auto_correction" msgid="7630720885194996950">"Авто-залруулга"</string>
+    <string name="auto_correction_summary" msgid="5625751551134658006">"Хоосон зай болон цэг таслал нь буруу бичсэн үгсийг автоматаар залруулна"</string>
+    <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Идэвхгүй"</string>
+    <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Хүлээцтэй"</string>
+    <string name="auto_correction_threshold_mode_aggressive" msgid="7319007299148899623">"Хүчтэй"</string>
+    <string name="auto_correction_threshold_mode_very_aggressive" msgid="1853309024129480416">"Маш хүчтэй"</string>
+    <string name="bigram_prediction" msgid="1084449187723948550">"Дараагийн-үг санал болгох"</string>
+    <string name="bigram_prediction_summary" msgid="3896362682751109677">"Өмнөх үгийг үг санал болгоход ашиглах"</string>
+    <string name="gesture_input" msgid="826951152254563827">"Зангаагаар бичихийг идэвхжүүлэх"</string>
+    <string name="gesture_input_summary" msgid="9180350639305731231">"Үсгүүд дээр гулсуулах замаар үг оруулах"</string>
+    <string name="gesture_preview_trail" msgid="3802333369335722221">"Зангасан мөрийг харуулах"</string>
+    <string name="gesture_floating_preview_text" msgid="4443240334739381053">"Динамик хөвөгчөөр урьдчилан харах"</string>
+    <string name="gesture_floating_preview_text_summary" msgid="4472696213996203533">"Зангах явцад санал болгож буй үгийг харах"</string>
+    <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : Хадгалагдсан"</string>
+    <string name="label_go_key" msgid="1635148082137219148">"Явах"</string>
+    <string name="label_next_key" msgid="362972844525672568">"Дараах"</string>
+    <string name="label_previous_key" msgid="1211868118071386787">"Өмнөх"</string>
+    <string name="label_done_key" msgid="2441578748772529288">"Дууссан"</string>
+    <string name="label_send_key" msgid="2815056534433717444">"Илгээх"</string>
+    <string name="label_pause_key" msgid="181098308428035340">"Пауз"</string>
+    <string name="label_wait_key" msgid="6402152600878093134">"Хүлээх"</string>
+    <string name="spoken_use_headphones" msgid="896961781287283493">"Нууц үгний товчнуудыг чангаар уншихыг сонсохын тулд чихэвчээ залгана уу."</string>
+    <string name="spoken_current_text_is" msgid="2485723011272583845">"Одоогийн текст %s"</string>
+    <string name="spoken_no_text_entered" msgid="7479685225597344496">"Текст оруулаагүй"</string>
+    <string name="spoken_description_unknown" msgid="3197434010402179157">"Товчийн код %d"</string>
+    <string name="spoken_description_shift" msgid="244197883292549308">"Сэлгэх"</string>
+    <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Сэлгэхийг идэвхжүүлсэн (товшиж идэвхгүйжүүлнэ үү)"</string>
+    <string name="spoken_description_caps_lock" msgid="3276478269526304432">"Томоор бичихийг асаасан (товшиж идэвхгүйжүүлнэ үү)"</string>
+    <string name="spoken_description_delete" msgid="8740376944276199801">"Устгах"</string>
+    <string name="spoken_description_to_symbol" msgid="5486340107500448969">"Симбол"</string>
+    <string name="spoken_description_to_alpha" msgid="23129338819771807">"Үсэгнүүд"</string>
+    <string name="spoken_description_to_numeric" msgid="591752092685161732">"Тоонууд"</string>
+    <string name="spoken_description_settings" msgid="4627462689603838099">"Тохиргоо"</string>
+    <string name="spoken_description_tab" msgid="2667716002663482248">"Таб"</string>
+    <string name="spoken_description_space" msgid="2582521050049860859">"Хоосон зай"</string>
+    <string name="spoken_description_mic" msgid="615536748882611950">"Дуугаар оруулах"</string>
+    <string name="spoken_description_smiley" msgid="2256309826200113918">"Инээсэн царай"</string>
+    <string name="spoken_description_return" msgid="8178083177238315647">"Буцах"</string>
+    <string name="spoken_description_search" msgid="1247236163755920808">"Хайх"</string>
+    <string name="spoken_description_dot" msgid="40711082435231673">"Цэг"</string>
+    <string name="spoken_description_language_switch" msgid="5507091328222331316">"Хэл солих"</string>
+    <string name="spoken_description_action_next" msgid="8636078276664150324">"Дараах"</string>
+    <string name="spoken_description_action_previous" msgid="800872415009336208">"Өмнөх"</string>
+    <string name="spoken_description_shiftmode_on" msgid="5700440798609574589">"Сэлгэхийг идэвхжүүлсэн"</string>
+    <string name="spoken_description_shiftmode_locked" msgid="593175803181701830">"Томоор бичихийг идэвхжүүлсэн"</string>
+    <string name="spoken_description_shiftmode_off" msgid="657219998449174808">"Сэлгэхийг идэвхжүүлээгүй"</string>
+    <string name="spoken_description_mode_symbol" msgid="7183343879909747642">"Симбол төлөв"</string>
+    <string name="spoken_description_mode_alpha" msgid="3528307674390156956">"Үсэгнүүд төлөв"</string>
+    <string name="spoken_description_mode_phone" msgid="6520207943132026264">"Утасны төлөв"</string>
+    <string name="spoken_description_mode_phone_shift" msgid="5499629753962641227">"Утасны символ төлөв"</string>
+    <string name="announce_keyboard_hidden" msgid="8718927835531429807">"Гарыг нуусан"</string>
+    <string name="announce_keyboard_mode" msgid="4729081055438508321">"<xliff:g id="MODE">%s</xliff:g> гарыг харуулж байна"</string>
+    <string name="keyboard_mode_date" msgid="3137520166817128102">"огноо"</string>
+    <string name="keyboard_mode_date_time" msgid="339593358488851072">"огноо болон цаг"</string>
+    <string name="keyboard_mode_email" msgid="6216248078128294262">"и"</string>
+    <string name="keyboard_mode_im" msgid="1137405089766557048">"зурвас"</string>
+    <string name="keyboard_mode_number" msgid="7991623440699957069">"дугаар"</string>
+    <string name="keyboard_mode_phone" msgid="6851627527401433229">"утас"</string>
+    <string name="keyboard_mode_text" msgid="6479436687899701619">"текст"</string>
+    <string name="keyboard_mode_time" msgid="4381856885582143277">"цаг"</string>
+    <string name="keyboard_mode_url" msgid="1519819835514911218">"URL"</string>
+    <string name="voice_input" msgid="3583258583521397548">"Дуун оруулгын товч"</string>
+    <string name="voice_input_modes_main_keyboard" msgid="3360660341121083174">"Үндсэн гар дээр"</string>
+    <string name="voice_input_modes_symbols_keyboard" msgid="7203213240786084067">"Симбол гар дээр"</string>
+    <string name="voice_input_modes_off" msgid="3745699748218082014">"Идэвхгүй"</string>
+    <string name="voice_input_modes_summary_main_keyboard" msgid="6586544292900314339">"Мик үндсэн гар дээр"</string>
+    <string name="voice_input_modes_summary_symbols_keyboard" msgid="5233725927281932391">"Мик симбол гар дээр"</string>
+    <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Дуун оруулах идэвхгүйжсэн"</string>
+    <string name="configure_input_method" msgid="373356270290742459">"Оруулах аргуудын тохиргоо"</string>
+    <string name="language_selection_title" msgid="1651299598555326750">"Оруулах хэл"</string>
+    <string name="send_feedback" msgid="1780431884109392046">"Санал хүсэлт илгээх"</string>
+    <string name="select_language" msgid="3693815588777926848">"Оруулах хэл"</string>
+    <string name="hint_add_to_dictionary" msgid="573678656946085380">"Хадгалахын тулд дахин хүрнэ үү"</string>
+    <string name="has_dictionary" msgid="6071847973466625007">"Толь бичиг байна"</string>
+    <string name="prefs_enable_log" msgid="6620424505072963557">"Хэрэглэгчийн санал хүсэлтийг идэвхжүүлэх"</string>
+    <string name="prefs_description_log" msgid="7525225584555429211">"Ашиглалтын статистик болон гацалтын репортуудыг автоматаар илгээснээр энэ оруулах арга засагчийг сайжруулахад туслаарай"</string>
+    <string name="keyboard_layout" msgid="8451164783510487501">"Гарын загвар"</string>
+    <string name="subtype_en_GB" msgid="88170601942311355">"Англи (ИБ)"</string>
+    <string name="subtype_en_US" msgid="6160452336634534239">"Англи (АНУ)"</string>
+    <string name="subtype_es_US" msgid="5583145191430180200">"Испани (АНУ)"</string>
+    <string name="subtype_with_layout_en_GB" msgid="2179097748724725906">"Англи (ИБ) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
+    <string name="subtype_with_layout_en_US" msgid="1362581347576714579">"Англи (АНУ) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
+    <string name="subtype_with_layout_es_US" msgid="6261791057007890189">"Испани (АНУ) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
+    <string name="subtype_nepali_traditional" msgid="9032247506728040447">"<xliff:g id="LANGUAGE">%s</xliff:g> (Уламжлалт)"</string>
+    <string name="subtype_no_language" msgid="7137390094240139495">"Хэл байхгүй (Цагаан толгой)"</string>
+    <string name="subtype_no_language_qwerty" msgid="244337630616742604">"Цагаан толгой (QWERTY)"</string>
+    <string name="subtype_no_language_qwertz" msgid="443066912507547976">"Цагаан толгой (QWERTZ)"</string>
+    <string name="subtype_no_language_azerty" msgid="8144348527575640087">"Цагаан толгой (AZERTY)"</string>
+    <string name="subtype_no_language_dvorak" msgid="1564494667584718094">"Цагаан толгой (Dvorak)"</string>
+    <string name="subtype_no_language_colemak" msgid="5837418400010302623">"Цагаан толгой (Colemak)"</string>
+    <string name="subtype_no_language_pcqwerty" msgid="5354918232046200018">"Цагаан толгой (PC)"</string>
+    <string name="subtype_emoji" msgid="7483586578074549196">"Инээмсэглэл"</string>
+    <string name="custom_input_styles_title" msgid="8429952441821251512">"Өөрийн оруулах загвар"</string>
+    <string name="add_style" msgid="6163126614514489951">"Загвар нэмэх"</string>
+    <string name="add" msgid="8299699805688017798">"Нэмэх"</string>
+    <string name="remove" msgid="4486081658752944606">"Устгах"</string>
+    <string name="save" msgid="7646738597196767214">"Хадгалах"</string>
+    <string name="subtype_locale" msgid="8576443440738143764">"Хэл"</string>
+    <string name="keyboard_layout_set" msgid="4309233698194565609">"Байршил"</string>
+    <string name="custom_input_style_note_message" msgid="8826731320846363423">"Та өөрийн оруулах загварыг ашиглахаас өмнө идэвхжүүлэх шаардлагатай. Одоо идэвхжүүлэх үү?"</string>
+    <string name="enable" msgid="5031294444630523247">"Идэвхжүүлэх"</string>
+    <string name="not_now" msgid="6172462888202790482">"Одоо биш"</string>
+    <string name="custom_input_style_already_exists" msgid="8008728952215449707">"Ижилхэн оруулах загвар байна: <xliff:g id="INPUT_STYLE_NAME">%s</xliff:g>"</string>
+    <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Ашиглалтын судалгааны горим"</string>
+    <string name="prefs_key_longpress_timeout_settings" msgid="6102240298932897873">"Товч удаан дарах хугацааны тохиргоо"</string>
+    <string name="prefs_keypress_vibration_duration_settings" msgid="7918341459947439226">"Товч дарах чичиргээний хугацаа"</string>
+    <string name="prefs_keypress_sound_volume_settings" msgid="6027007337036891623">"Товчны дууны хэмжээ"</string>
+    <string name="prefs_read_external_dictionary" msgid="2588931418575013067">"Толь бичгийн гадны файлыг унших"</string>
+    <string name="read_external_dictionary_no_files_message" msgid="4947420942224623792">"Татаж авсан фолдерт толь бичгийн файл байхгүй байна"</string>
+    <string name="read_external_dictionary_multiple_files_title" msgid="7637749044265808628">"Суулгах толь бичгийн файлыг сонгоно уу"</string>
+    <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"<xliff:g id="LOCALE_NAME">%s</xliff:g>-д зориулсан энэ файлыг үнэхээр суулгах уу?"</string>
+    <string name="error" msgid="8940763624668513648">"Алдаа гарсан"</string>
+    <string name="button_default" msgid="3988017840431881491">"Үндсэн"</string>
+    <string name="setup_welcome_title" msgid="6112821709832031715">"Та <xliff:g id="APPLICATION_NAME">%s</xliff:g>-д тавтай морилно уу"</string>
+    <string name="setup_welcome_additional_description" msgid="8150252008545768953">"Зангаагаар бичихээр"</string>
+    <string name="setup_start_action" msgid="8936036460897347708">"Эхлэх"</string>
+    <string name="setup_next_action" msgid="371821437915144603">"Дараагийн алхам"</string>
+    <string name="setup_steps_title" msgid="6400373034871816182">"<xliff:g id="APPLICATION_NAME">%s</xliff:g>-г тохируулж байна"</string>
+    <string name="setup_step1_title" msgid="3147967630253462315">"<xliff:g id="APPLICATION_NAME">%s</xliff:g>-г идэвхжүүлэх"</string>
+    <string name="setup_step1_instruction" msgid="2578631936624637241">"Өөрийн Хэл &amp; оруулах тохиргоон дотроос \"<xliff:g id="APPLICATION_NAME">%s</xliff:g>\"-г сонгоно уу. Ингэснээр таны төхөөрөмж дээр ажиллах зөвшөөрлийг өгөх болно."</string>
+    <string name="setup_step1_finished_instruction" msgid="10761482004957994">"<xliff:g id="APPLICATION_NAME">%s</xliff:g> таны Хэл &amp;amp оруулах тохиргоонд аль хэдийн идэвхжүүлсэн байгаа учир энэ алхам хийгдсэн. Дараагийн алхам руу!"</string>
+    <string name="setup_step1_action" msgid="4366513534999901728">"Тохиргоо дотроос идэвхжүүлэх"</string>
+    <string name="setup_step2_title" msgid="6860725447906690594">"<xliff:g id="APPLICATION_NAME">%s</xliff:g> рүү шилжих"</string>
+    <string name="setup_step2_instruction" msgid="9141481964870023336">"Дараа нь \"<xliff:g id="APPLICATION_NAME">%s</xliff:g>\"-г өөрийн идэвхтэй текст-оруулах аргаар сонгоно уу."</string>
+    <string name="setup_step2_action" msgid="1660330307159824337">"Оруулах аргыг солих"</string>
+    <string name="setup_step3_title" msgid="3154757183631490281">"Баяр хүргэе, та бүгдийг нь тохируулчихлаа!"</string>
+    <string name="setup_step3_instruction" msgid="8025981829605426000">"Та одоо өөрийн дуртай апп-ууд дотроо <xliff:g id="APPLICATION_NAME">%s</xliff:g> ашиглан бичих болохоор боллоо."</string>
+    <string name="setup_step3_action" msgid="600879797256942259">"Нэмэлт хэлнүүдийг тохируулах"</string>
+    <string name="setup_finish_action" msgid="276559243409465389">"Дууссан"</string>
+    <string name="show_setup_wizard_icon" msgid="5008028590593710830">"Апп дүрсийг харуулах"</string>
+    <string name="show_setup_wizard_icon_summary" msgid="4119998322536880213">"Эхлүүлэгч дээр аппликешний дүрсийг харуулах"</string>
+    <string name="app_name" msgid="6320102637491234792">"Толь бичгээр хангагч"</string>
+    <string name="dictionary_provider_name" msgid="3027315045397363079">"Толь бичгээг хангагч"</string>
+    <string name="dictionary_service_name" msgid="6237472350693511448">"Толь бичгийн үйлчилгээ"</string>
+    <string name="download_description" msgid="6014835283119198591">"Толь бичгийн шинэчлэлтийн мэдээлэл"</string>
+    <string name="dictionary_settings_title" msgid="8091417676045693313">"Нэмэлт толь бичгүүд"</string>
+    <string name="dictionary_install_over_metered_network_prompt" msgid="3587517870006332980">"Толь бичиг байна"</string>
+    <string name="dictionary_settings_summary" msgid="5305694987799824349">"Толь бичгийн тохиргоо"</string>
+    <string name="user_dictionaries" msgid="3582332055892252845">"Хэрэглэгчийн толь бичиг"</string>
+    <string name="default_user_dict_pref_name" msgid="1625055720489280530">"Хэрэглэгчийн толь"</string>
+    <string name="dictionary_available" msgid="4728975345815214218">"Толь бичиг байна"</string>
+    <string name="dictionary_downloading" msgid="2982650524622620983">"Одоо татаж байна"</string>
+    <string name="dictionary_installed" msgid="8081558343559342962">"Суулгасан"</string>
+    <string name="dictionary_disabled" msgid="8950383219564621762">"Суулгасан, идэвхгүйжүүлсэн"</string>
+    <string name="cannot_connect_to_dict_service" msgid="9216933695765732398">"Толь бичгийн үйлчилгээнд холбогдоход алдаа гарлаа"</string>
+    <string name="no_dictionaries_available" msgid="8039920716566132611">"Толь бичиг байхгүй"</string>
+    <string name="check_for_updates_now" msgid="8087688440916388581">"Дахин шинэчлэх"</string>
+    <string name="last_update" msgid="730467549913588780">"Сүүлд шинэчлэгдсэн"</string>
+    <string name="message_updating" msgid="4457761393932375219">"Шинэчлэлтийг шалгаж байна"</string>
+    <string name="message_loading" msgid="8689096636874758814">"Ачаалж байна..."</string>
+    <string name="main_dict_description" msgid="3072821352793492143">"Үндсэн толь бичиг"</string>
+    <string name="cancel" msgid="6830980399865683324">"Цуцлах"</string>
+    <string name="install_dict" msgid="180852772562189365">"Суулгах"</string>
+    <string name="cancel_download_dict" msgid="7843340278507019303">"Цуцлах"</string>
+    <string name="delete_dict" msgid="756853268088330054">"Устгах"</string>
+    <string name="should_download_over_metered_prompt" msgid="2878629598667658845">"Таны мобайль төхөөрөмж дээр сонгосон хэлэнд толь бичиг байна.&lt;br/&gt; Тус  <xliff:g id="LANGUAGE">%1$s</xliff:g> толь бичгийг &lt;b&gt;татаж аван&lt;/b&gt; зөв бичилтээ сайжруулахыг бид зөвлөж байна.&lt;br/&gt; &lt;br/&gt; Татаж авахад 3G сүлжээгээр нэг хоёр минут болно. Танд &lt;b&gt;хязгааргүй дата эрх&lt;/b&gt; байхгүй бол нэмэлт төлбөр гарч болно.&lt;br/&gt; Та дата эрхийнхээ талаар сайн мэдэхгүй байгаа бол Wi-Fi холболттой газар очин автоматаар татаж авахыг зөвлөж байна.&lt;br/&gt; &lt;br/&gt; Зөвлөмж: Та өөрийн мобайль төхөөрөмжийн &lt;b&gt;Тохиргоо&lt;/b&gt; цэсний &lt;b&gt;Хэл &amp; оруулах&lt;/b&gt; руу очиж толь бичиг татаж авах буюу устгаж болно."</string>
+    <string name="download_over_metered" msgid="1643065851159409546">"Одоо татах (<xliff:g id="SIZE_IN_MEGABYTES">%1$.1f</xliff:g>MB)"</string>
+    <string name="do_not_download_over_metered" msgid="2176209579313941583">"Wi-Fi-р татаж авах"</string>
+    <string name="dict_available_notification_title" msgid="6514288591959117288">"<xliff:g id="LANGUAGE">%1$s</xliff:g> хэлний толь бичигтэй"</string>
+    <string name="dict_available_notification_description" msgid="1075194169443163487">"Шалгах болон татаж авахын тулд дарна уу"</string>
+    <string name="toast_downloading_suggestions" msgid="1313027353588566660">"Татаж байна: <xliff:g id="LANGUAGE">%1$s</xliff:g> хэлний санал болгох үгс удахгүй бэлэн болно."</string>
+    <string name="version_text" msgid="2715354215568469385">"Хувилбар <xliff:g id="VERSION_NUMBER">%1$s</xliff:g>"</string>
+    <string name="user_dict_settings_add_menu_title" msgid="1254195365689387076">"Нэмэх"</string>
+    <string name="user_dict_settings_add_dialog_title" msgid="4096700390211748168">"Толь бичигт нэмэх"</string>
+    <string name="user_dict_settings_add_screen_title" msgid="5818914331629278758">"Хэллэг"</string>
+    <string name="user_dict_settings_add_dialog_more_options" msgid="5671682004887093112">"Нэмэлт сонголтууд"</string>
+    <string name="user_dict_settings_add_dialog_less_options" msgid="2716586567241724126">"Цөөн сонголт"</string>
+    <string name="user_dict_settings_add_dialog_confirm" msgid="4703129507388332950">"Тийм"</string>
+    <string name="user_dict_settings_add_word_option_name" msgid="6665558053408962865">"Үг:"</string>
+    <string name="user_dict_settings_add_shortcut_option_name" msgid="3094731590655523777">"Товчилбор:"</string>
+    <string name="user_dict_settings_add_locale_option_name" msgid="4738643440987277705">"Хэл:"</string>
+    <string name="user_dict_settings_add_word_hint" msgid="4902434148985906707">"Үг оруулна уу"</string>
+    <string name="user_dict_settings_add_shortcut_hint" msgid="2265453012555060178">"Зайлшгүй биш товчилбор"</string>
+    <string name="user_dict_settings_edit_dialog_title" msgid="3765774633869590352">"Үг засах"</string>
+    <string name="user_dict_settings_context_menu_edit_title" msgid="6812255903472456302">"Засах"</string>
+    <string name="user_dict_settings_context_menu_delete_title" msgid="8142932447689461181">"Устгах"</string>
+    <string name="user_dict_settings_empty_text" msgid="558499587532668203">"Таны хэрэглэгчийн толинд ямар ч үг алга байна. Нэмэх (+) товчинд хүрэн үг нэмнэ үү."</string>
+    <string name="user_dict_settings_all_languages" msgid="8276126583216298886">"Бүх хэлэнд"</string>
+    <string name="user_dict_settings_more_languages" msgid="7131268499685180461">"Өөр хэлүүд…"</string>
+    <string name="user_dict_settings_delete" msgid="110413335187193859">"Устгах"</string>
+    <string name="user_dict_fast_scroll_alphabet" msgid="5431919401558285473">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
+</resources>
diff --git a/java/res/values-mn/strings.xml b/java/res/values-mn/strings.xml
index 12a3b47..4a61204 100644
--- a/java/res/values-mn/strings.xml
+++ b/java/res/values-mn/strings.xml
@@ -64,8 +64,8 @@
     <string name="auto_correction_summary" msgid="5625751551134658006">"Хоосон зай болон цэг таслал нь буруу бичсэн үгсийг автоматаар залруулна"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Идэвхгүй"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Хүлээцтэй"</string>
-    <string name="auto_correction_threshold_mode_aggressive" msgid="3524029103734923819">"Хүчтэй"</string>
-    <string name="auto_correction_threshold_mode_very_aggressive" msgid="3386782235540547678">"Маш хүчтэй"</string>
+    <string name="auto_correction_threshold_mode_aggressive" msgid="7319007299148899623">"Хүчтэй"</string>
+    <string name="auto_correction_threshold_mode_very_aggressive" msgid="1853309024129480416">"Маш хүчтэй"</string>
     <string name="bigram_prediction" msgid="1084449187723948550">"Дараагийн-үг санал болгох"</string>
     <string name="bigram_prediction_summary" msgid="3896362682751109677">"Өмнөх үгийг үг санал болгоход ашиглах"</string>
     <string name="gesture_input" msgid="826951152254563827">"Зангаагаар бичихийг идэвхжүүлэх"</string>
@@ -143,13 +143,13 @@
     <string name="subtype_with_layout_en_GB" msgid="2179097748724725906">"Англи (ИБ) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_en_US" msgid="1362581347576714579">"Англи (АНУ) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_es_US" msgid="6261791057007890189">"Испани (АНУ) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
-    <string name="subtype_no_language" msgid="141420857808801746">"Хэл байхгүй"</string>
-    <string name="subtype_no_language_qwerty" msgid="2956121451616633133">"Хэл байхгүй (QWERTY)"</string>
-    <string name="subtype_no_language_qwertz" msgid="1177848172397202890">"Хэл байхгүй (QWERTZ)"</string>
-    <string name="subtype_no_language_azerty" msgid="8721460968141187394">"Хэл байхгүй (AZERTY)"</string>
-    <string name="subtype_no_language_dvorak" msgid="3122976737669823935">"Хэл байхгүй (Dvorak)"</string>
-    <string name="subtype_no_language_colemak" msgid="4205992994906097244">"Хэл байхгүй (Colemak)"</string>
-    <string name="subtype_no_language_pcqwerty" msgid="8840928374394180189">"Хэл байхгүй (PC)"</string>
+    <string name="subtype_no_language" msgid="7137390094240139495">"Хэл байхгүй (Цагаан толгой)"</string>
+    <string name="subtype_no_language_qwerty" msgid="244337630616742604">"Цагаан толгой (QWERTY)"</string>
+    <string name="subtype_no_language_qwertz" msgid="443066912507547976">"Цагаан толгой (QWERTZ)"</string>
+    <string name="subtype_no_language_azerty" msgid="8144348527575640087">"Цагаан толгой (AZERTY)"</string>
+    <string name="subtype_no_language_dvorak" msgid="1564494667584718094">"Цагаан толгой (Dvorak)"</string>
+    <string name="subtype_no_language_colemak" msgid="5837418400010302623">"Цагаан толгой (Colemak)"</string>
+    <string name="subtype_no_language_pcqwerty" msgid="5354918232046200018">"Цагаан толгой (PC)"</string>
     <string name="custom_input_styles_title" msgid="8429952441821251512">"Өөрийн оруулах загвар"</string>
     <string name="add_style" msgid="6163126614514489951">"Загвар нэмэх"</string>
     <string name="add" msgid="8299699805688017798">"Нэмэх"</string>
diff --git a/java/res/values-ms-rMY/strings.xml b/java/res/values-ms-rMY/strings.xml
new file mode 100644
index 0000000..49efdf1
--- /dev/null
+++ b/java/res/values-ms-rMY/strings.xml
@@ -0,0 +1,244 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+**
+** Copyright 2008, 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:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="english_ime_input_options" msgid="3909945612939668554">"Pilihan input"</string>
+    <string name="english_ime_research_log" msgid="8492602295696577851">"Arahan Log Penyelidikan"</string>
+    <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Cari nama kenalan"</string>
+    <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Penyemak ejaan menggunakan entri dari senarai kenalan anda"</string>
+    <string name="vibrate_on_keypress" msgid="5258079494276955460">"Getar pada tekanan kekunci"</string>
+    <string name="sound_on_keypress" msgid="6093592297198243644">"Bunyi pada tekanan kekunci"</string>
+    <string name="popup_on_keypress" msgid="123894815723512944">"Pop timbul pada tekanan kunci"</string>
+    <string name="general_category" msgid="1859088467017573195">"Umum"</string>
+    <string name="correction_category" msgid="2236750915056607613">"Pembetulan teks"</string>
+    <string name="gesture_typing_category" msgid="497263612130532630">"Taipan gerak isyarat"</string>
+    <string name="misc_category" msgid="6894192814868233453">"Pilihan lain"</string>
+    <string name="advanced_settings" msgid="362895144495591463">"Tetapan lanjutan"</string>
+    <string name="advanced_settings_summary" msgid="4487980456152830271">"Pilihan untuk pakar"</string>
+    <string name="include_other_imes_in_language_switch_list" msgid="4533689960308565519">"Tukar ke kaedah input lain"</string>
+    <string name="include_other_imes_in_language_switch_list_summary" msgid="840637129103317635">"Kunci pertukaran bahasa meliputi kaedah masukan lain juga"</string>
+    <string name="show_language_switch_key" msgid="5915478828318774384">"Kekunci tukar bahasa"</string>
+    <string name="show_language_switch_key_summary" msgid="7343403647474265713">"Tunjukkan apabila berbilang bahasa input didayakan"</string>
+    <string name="sliding_key_input_preview" msgid="6604262359510068370">"Tunjukkan penunjuk slaid"</string>
+    <string name="sliding_key_input_preview_summary" msgid="6340524345729093886">"Paparkan petunjuk visual semasa meluncur daripada kekunci Shift atau Simbol"</string>
+    <string name="key_preview_popup_dismiss_delay" msgid="6213164897443068248">"Pop tmbl knci ketpkn lengah"</string>
+    <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Tiada kelewatan"</string>
+    <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Lalai"</string>
+    <string name="abbreviation_unit_milliseconds" msgid="8700286094028323363">"<xliff:g id="MILLISECONDS">%s</xliff:g>ms"</string>
+    <string name="settings_system_default" msgid="6268225104743331821">"Tetapan asal sistem"</string>
+    <string name="use_contacts_dict" msgid="4435317977804180815">"Cadangkan nama Kenalan"</string>
+    <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Menggunakan nama daripada Kenalan untuk cadangan dan pembetulan"</string>
+    <string name="use_double_space_period" msgid="8781529969425082860">"Titik ruang berganda"</string>
+    <string name="use_double_space_period_summary" msgid="6532892187247952799">"Mengetik 2X pada bar ruang memasukkan titik diikuti dengan ruang"</string>
+    <string name="auto_cap" msgid="1719746674854628252">"Autopenghurufbesaran"</string>
+    <string name="auto_cap_summary" msgid="7934452761022946874">"Besarkan perkataan pertama setiap ayat"</string>
+    <string name="edit_personal_dictionary" msgid="3996910038952940420">"Kamus peribadi"</string>
+    <string name="configure_dictionaries_title" msgid="4238652338556902049">"Kamus tambahan"</string>
+    <string name="main_dictionary" msgid="4798763781818361168">"Kamus utama"</string>
+    <string name="prefs_show_suggestions" msgid="8026799663445531637">"Tunjukkan cadangan pembetulan"</string>
+    <string name="prefs_show_suggestions_summary" msgid="1583132279498502825">"Paparkan cadangan perkataan semasa menaip"</string>
+    <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Sentiasa tunjukkan"</string>
+    <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3859783767435239118">"Tunjukkan dalam mod potret"</string>
+    <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Sentiasa sembunyikan"</string>
+    <string name="prefs_block_potentially_offensive_title" msgid="5078480071057408934">"Sekat perkataan yg menyinggung"</string>
+    <string name="prefs_block_potentially_offensive_summary" msgid="2371835479734991364">"Jangan cadangkan perkataan yang boleh menyinggung"</string>
+    <string name="auto_correction" msgid="7630720885194996950">"Auto pembetulan"</string>
+    <string name="auto_correction_summary" msgid="5625751551134658006">"Bar ruang dan tanda baca secara automatik membetulkan perkataan yang ditaip salah"</string>
+    <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Dimati"</string>
+    <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Sederhana"</string>
+    <string name="auto_correction_threshold_mode_aggressive" msgid="7319007299148899623">"Agresif"</string>
+    <string name="auto_correction_threshold_mode_very_aggressive" msgid="1853309024129480416">"Sangat agresif"</string>
+    <string name="bigram_prediction" msgid="1084449187723948550">"Cadangan perkataan seterusnya"</string>
+    <string name="bigram_prediction_summary" msgid="3896362682751109677">"Gunakan perkataan sebelumnya dalam membuat cadangan"</string>
+    <string name="gesture_input" msgid="826951152254563827">"Dayakan taipan gerak isyarat"</string>
+    <string name="gesture_input_summary" msgid="9180350639305731231">"Input perkataan dengan meluncur melalui huruf"</string>
+    <string name="gesture_preview_trail" msgid="3802333369335722221">"Tunjukkan jejak gerak isyarat"</string>
+    <string name="gesture_floating_preview_text" msgid="4443240334739381053">"Pratonton terapung dinamik"</string>
+    <string name="gesture_floating_preview_text_summary" msgid="4472696213996203533">"Lihat perkataan yang dicadangkan semasa membuat gerak isyarat"</string>
+    <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : Disimpan"</string>
+    <string name="label_go_key" msgid="1635148082137219148">"Pergi"</string>
+    <string name="label_next_key" msgid="362972844525672568">"Seterusnya"</string>
+    <string name="label_previous_key" msgid="1211868118071386787">"Sblm"</string>
+    <string name="label_done_key" msgid="2441578748772529288">"Selesai"</string>
+    <string name="label_send_key" msgid="2815056534433717444">"Hantar"</string>
+    <string name="label_pause_key" msgid="181098308428035340">"Jeda"</string>
+    <string name="label_wait_key" msgid="6402152600878093134">"Tunggu"</string>
+    <string name="spoken_use_headphones" msgid="896961781287283493">"Pasangkan set kepala untuk mendengar kekunci kata laluan disebut dengan kuat."</string>
+    <string name="spoken_current_text_is" msgid="2485723011272583845">"Teks semasa adalah %s"</string>
+    <string name="spoken_no_text_entered" msgid="7479685225597344496">"Tiada teks dimasukkan"</string>
+    <string name="spoken_description_unknown" msgid="3197434010402179157">"Kod kunci %d"</string>
+    <string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
+    <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Kunci anjak dihidupkan (ketik untuk melumpuhkan)"</string>
+    <string name="spoken_description_caps_lock" msgid="3276478269526304432">"Kunci huruf besar dihidupkan (ketik untuk melumpuhkan)"</string>
+    <string name="spoken_description_delete" msgid="8740376944276199801">"Padam"</string>
+    <string name="spoken_description_to_symbol" msgid="5486340107500448969">"Simbol"</string>
+    <string name="spoken_description_to_alpha" msgid="23129338819771807">"Huruf"</string>
+    <string name="spoken_description_to_numeric" msgid="591752092685161732">"Numbers"</string>
+    <string name="spoken_description_settings" msgid="4627462689603838099">"Tetapan"</string>
+    <string name="spoken_description_tab" msgid="2667716002663482248">"Tab"</string>
+    <string name="spoken_description_space" msgid="2582521050049860859">"Ruang"</string>
+    <string name="spoken_description_mic" msgid="615536748882611950">"Input suara"</string>
+    <string name="spoken_description_smiley" msgid="2256309826200113918">"Muka senyum"</string>
+    <string name="spoken_description_return" msgid="8178083177238315647">"Kembali"</string>
+    <string name="spoken_description_search" msgid="1247236163755920808">"Cari"</string>
+    <string name="spoken_description_dot" msgid="40711082435231673">"Titik"</string>
+    <string name="spoken_description_language_switch" msgid="5507091328222331316">"Tukar bahasa"</string>
+    <string name="spoken_description_action_next" msgid="8636078276664150324">"Seterusnya"</string>
+    <string name="spoken_description_action_previous" msgid="800872415009336208">"Sebelumnya"</string>
+    <string name="spoken_description_shiftmode_on" msgid="5700440798609574589">"Shift didayakan"</string>
+    <string name="spoken_description_shiftmode_locked" msgid="593175803181701830">"Kunci huruf besar didayakan"</string>
+    <string name="spoken_description_shiftmode_off" msgid="657219998449174808">"Kunci anjak dilumpuhkan"</string>
+    <string name="spoken_description_mode_symbol" msgid="7183343879909747642">"Mod simbol"</string>
+    <string name="spoken_description_mode_alpha" msgid="3528307674390156956">"Mod huruf"</string>
+    <string name="spoken_description_mode_phone" msgid="6520207943132026264">"Mod telefon"</string>
+    <string name="spoken_description_mode_phone_shift" msgid="5499629753962641227">"Mod simbol telefon"</string>
+    <string name="announce_keyboard_hidden" msgid="8718927835531429807">"Papan kekunci tersembunyi"</string>
+    <string name="announce_keyboard_mode" msgid="4729081055438508321">"Menunjukkan <xliff:g id="MODE">%s</xliff:g> papan kekunci"</string>
+    <string name="keyboard_mode_date" msgid="3137520166817128102">"tarikh"</string>
+    <string name="keyboard_mode_date_time" msgid="339593358488851072">"tarikh dan masa"</string>
+    <string name="keyboard_mode_email" msgid="6216248078128294262">"e-mel"</string>
+    <string name="keyboard_mode_im" msgid="1137405089766557048">"pemesejan"</string>
+    <string name="keyboard_mode_number" msgid="7991623440699957069">"nombor"</string>
+    <string name="keyboard_mode_phone" msgid="6851627527401433229">"telefon"</string>
+    <string name="keyboard_mode_text" msgid="6479436687899701619">"teks"</string>
+    <string name="keyboard_mode_time" msgid="4381856885582143277">"masa"</string>
+    <string name="keyboard_mode_url" msgid="1519819835514911218">"URL"</string>
+    <string name="voice_input" msgid="3583258583521397548">"Kunci input suara"</string>
+    <string name="voice_input_modes_main_keyboard" msgid="3360660341121083174">"Pada papan kekunci utama"</string>
+    <string name="voice_input_modes_symbols_keyboard" msgid="7203213240786084067">"Pada papan kekunci simbol"</string>
+    <string name="voice_input_modes_off" msgid="3745699748218082014">"Dimati"</string>
+    <string name="voice_input_modes_summary_main_keyboard" msgid="6586544292900314339">"Mikrofon pada papan kekunci utama"</string>
+    <string name="voice_input_modes_summary_symbols_keyboard" msgid="5233725927281932391">"Mikrofon pada papan kekunci simbol"</string>
+    <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Input suara dilmphkn"</string>
+    <string name="configure_input_method" msgid="373356270290742459">"Konfigurasikan kaedah input"</string>
+    <string name="language_selection_title" msgid="1651299598555326750">"Bahasa input"</string>
+    <string name="send_feedback" msgid="1780431884109392046">"Hantar maklum balas"</string>
+    <string name="select_language" msgid="3693815588777926848">"Bahasa input"</string>
+    <string name="hint_add_to_dictionary" msgid="573678656946085380">"Sentuh lagi untuk menyimpan"</string>
+    <string name="has_dictionary" msgid="6071847973466625007">"Kamus tersedia"</string>
+    <string name="prefs_enable_log" msgid="6620424505072963557">"Dayakan maklum balas pengguna"</string>
+    <string name="prefs_description_log" msgid="7525225584555429211">"Bantu memperbaik editor kaedah input ini dengan menghantar statistik penggunaan dan laporan ranap secara automatik"</string>
+    <string name="keyboard_layout" msgid="8451164783510487501">"Tema papan kekunci"</string>
+    <string name="subtype_en_GB" msgid="88170601942311355">"Bahasa Inggeris (UK)"</string>
+    <string name="subtype_en_US" msgid="6160452336634534239">"Bahasa Inggeris (Australia)"</string>
+    <string name="subtype_es_US" msgid="5583145191430180200">"Bahasa Sepanyol (AS)"</string>
+    <string name="subtype_with_layout_en_GB" msgid="2179097748724725906">"Bahasa Inggeris (UK) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
+    <string name="subtype_with_layout_en_US" msgid="1362581347576714579">"Bahasa Inggeris (AS) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
+    <string name="subtype_with_layout_es_US" msgid="6261791057007890189">"Bahasa Sepanyol (AS) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
+    <string name="subtype_nepali_traditional" msgid="9032247506728040447">"<xliff:g id="LANGUAGE">%s</xliff:g> (Tradisional)"</string>
+    <string name="subtype_no_language" msgid="7137390094240139495">"Tiada bahasa (Abjad)"</string>
+    <string name="subtype_no_language_qwerty" msgid="244337630616742604">"Abjad (QWERTY)"</string>
+    <string name="subtype_no_language_qwertz" msgid="443066912507547976">"Abjad (QWERTZ)"</string>
+    <string name="subtype_no_language_azerty" msgid="8144348527575640087">"Abjad (AZERTY)"</string>
+    <string name="subtype_no_language_dvorak" msgid="1564494667584718094">"Abjad (Dvorak)"</string>
+    <string name="subtype_no_language_colemak" msgid="5837418400010302623">"Abjad (Colemak)"</string>
+    <string name="subtype_no_language_pcqwerty" msgid="5354918232046200018">"Abjad (PC)"</string>
+    <string name="subtype_emoji" msgid="7483586578074549196">"Emoji"</string>
+    <string name="custom_input_styles_title" msgid="8429952441821251512">"Gaya input peribadi"</string>
+    <string name="add_style" msgid="6163126614514489951">"Tambah gaya"</string>
+    <string name="add" msgid="8299699805688017798">"Tambah"</string>
+    <string name="remove" msgid="4486081658752944606">"Alih Keluar"</string>
+    <string name="save" msgid="7646738597196767214">"Simpan"</string>
+    <string name="subtype_locale" msgid="8576443440738143764">"Bahasa"</string>
+    <string name="keyboard_layout_set" msgid="4309233698194565609">"Reka letak"</string>
+    <string name="custom_input_style_note_message" msgid="8826731320846363423">"Gaya input tersuai anda perlu didayakan sebelum anda mula menggunakannya. Adakah anda ingin mendayakannya sekarang?"</string>
+    <string name="enable" msgid="5031294444630523247">"Dayakan"</string>
+    <string name="not_now" msgid="6172462888202790482">"Bukan sekarang"</string>
+    <string name="custom_input_style_already_exists" msgid="8008728952215449707">"The same input style already exists: <xliff:g id="INPUT_STYLE_NAME">%s</xliff:g>"</string>
+    <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Mod kajian kebolehgunaan"</string>
+    <string name="prefs_key_longpress_timeout_settings" msgid="6102240298932897873">"Kelewatan tekan lama kekunci"</string>
+    <string name="prefs_keypress_vibration_duration_settings" msgid="7918341459947439226">"Tempoh getaran tekan kekunci"</string>
+    <string name="prefs_keypress_sound_volume_settings" msgid="6027007337036891623">"Kelantangan bunyi tekan kekunci"</string>
+    <string name="prefs_read_external_dictionary" msgid="2588931418575013067">"Baca fail kamus luaran"</string>
+    <string name="read_external_dictionary_no_files_message" msgid="4947420942224623792">"Tiada fail kamus dalam folder Muat Turun"</string>
+    <string name="read_external_dictionary_multiple_files_title" msgid="7637749044265808628">"Pilih fail kamus untuk dipasang"</string>
+    <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"Betul-betul pasang fail ini untuk <xliff:g id="LOCALE_NAME">%s</xliff:g>?"</string>
+    <string name="error" msgid="8940763624668513648">"Berlaku ralat"</string>
+    <string name="button_default" msgid="3988017840431881491">"Lalai"</string>
+    <string name="setup_welcome_title" msgid="6112821709832031715">"Selamat datang ke <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_welcome_additional_description" msgid="8150252008545768953">"dengan Taipan Gerak Isyarat"</string>
+    <string name="setup_start_action" msgid="8936036460897347708">"Bermula"</string>
+    <string name="setup_next_action" msgid="371821437915144603">"Langkah seterusnya"</string>
+    <string name="setup_steps_title" msgid="6400373034871816182">"Menyediakan <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step1_title" msgid="3147967630253462315">"Dayakan <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step1_instruction" msgid="2578631936624637241">"Sila semak \"<xliff:g id="APPLICATION_NAME">%s</xliff:g>\" dlm ttpn Bhs &amp; input. Ini mbnarkn apl djlnkn pd pranti anda."</string>
+    <string name="setup_step1_finished_instruction" msgid="10761482004957994">"<xliff:g id="APPLICATION_NAME">%s</xliff:g> sudah didayakan dalam tetapan Bahasa &amp; input anda, jadi langkah ini telah selesai. Beralih ke langkah seterusnya!"</string>
+    <string name="setup_step1_action" msgid="4366513534999901728">"Dayakan dalam Tetapan"</string>
+    <string name="setup_step2_title" msgid="6860725447906690594">"Beralih ke <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step2_instruction" msgid="9141481964870023336">"Seterusnya, pilih \"<xliff:g id="APPLICATION_NAME">%s</xliff:g>\" sebagai kaedah input teks aktif anda."</string>
+    <string name="setup_step2_action" msgid="1660330307159824337">"Tukar kaedah input"</string>
+    <string name="setup_step3_title" msgid="3154757183631490281">"Tahniah, anda sudah sedia!"</string>
+    <string name="setup_step3_instruction" msgid="8025981829605426000">"Kini anda boleh menaip dalam semua apl kegemaran anda dengan <xliff:g id="APPLICATION_NAME">%s</xliff:g>."</string>
+    <string name="setup_step3_action" msgid="600879797256942259">"Konfigurasikan bahasa tambahan"</string>
+    <string name="setup_finish_action" msgid="276559243409465389">"Selesai"</string>
+    <string name="show_setup_wizard_icon" msgid="5008028590593710830">"Tunjukkan ikon apl"</string>
+    <string name="show_setup_wizard_icon_summary" msgid="4119998322536880213">"Paparkan ikon apl dalam pelancar"</string>
+    <string name="app_name" msgid="6320102637491234792">"Pembekal Kamus"</string>
+    <string name="dictionary_provider_name" msgid="3027315045397363079">"Pembekal Kamus"</string>
+    <string name="dictionary_service_name" msgid="6237472350693511448">"Perkhidmatan Kamus"</string>
+    <string name="download_description" msgid="6014835283119198591">"Maklumat kemas kini kamus"</string>
+    <string name="dictionary_settings_title" msgid="8091417676045693313">"Kamus tambahan"</string>
+    <string name="dictionary_install_over_metered_network_prompt" msgid="3587517870006332980">"Kamus tersedia"</string>
+    <string name="dictionary_settings_summary" msgid="5305694987799824349">"Tetapan untuk kamus"</string>
+    <string name="user_dictionaries" msgid="3582332055892252845">"Kamus pengguna"</string>
+    <string name="default_user_dict_pref_name" msgid="1625055720489280530">"Kamus pengguna"</string>
+    <string name="dictionary_available" msgid="4728975345815214218">"Kamus tersedia"</string>
+    <string name="dictionary_downloading" msgid="2982650524622620983">"Sedang memuat turun"</string>
+    <string name="dictionary_installed" msgid="8081558343559342962">"Dipasang"</string>
+    <string name="dictionary_disabled" msgid="8950383219564621762">"Dipasang, dilumpuhkan"</string>
+    <string name="cannot_connect_to_dict_service" msgid="9216933695765732398">"Masalah menyambung kepada perkhidmatan kamus"</string>
+    <string name="no_dictionaries_available" msgid="8039920716566132611">"Tiada kamus tersedia"</string>
+    <string name="check_for_updates_now" msgid="8087688440916388581">"Muatkan semula"</string>
+    <string name="last_update" msgid="730467549913588780">"Kali terakhir dikemas kini"</string>
+    <string name="message_updating" msgid="4457761393932375219">"Menyemak kemas kini"</string>
+    <string name="message_loading" msgid="8689096636874758814">"Memuatkan..."</string>
+    <string name="main_dict_description" msgid="3072821352793492143">"Kamus utama"</string>
+    <string name="cancel" msgid="6830980399865683324">"Batal"</string>
+    <string name="install_dict" msgid="180852772562189365">"Pasang"</string>
+    <string name="cancel_download_dict" msgid="7843340278507019303">"Batal"</string>
+    <string name="delete_dict" msgid="756853268088330054">"Padam"</string>
+    <string name="should_download_over_metered_prompt" msgid="2878629598667658845">"Bahasa pilihan pada peranti mudah alih anda mempunyai kamus tersedia.&lt;br/&gt; Kami mengesyorkan &lt;b&gt;memuat turun&lt;/b&gt; kamus <xliff:g id="LANGUAGE">%1$s</xliff:g> untuk memperbaik pengalaman menaip anda.&lt;br/&gt; &lt;br/&gt; Muat turun boleh mengambil masa seminit atau dua melalui 3G. Caj mungkin dikenakan jika anda tidak mempunyai &lt;b&gt;pelan data tanpa had&lt;/b&gt;.&lt;br/&gt; Jika anda tidak pasti jenis pelan data yang anda miliki, kami mengesyorkan agar anda mencari sambungan Wi-Fi untuk mula memuat turun secara automatik.&lt;br/&gt; &lt;br/&gt; Petua: Anda boleh memuat turun dan mengalih keluar kamus dengan pergi ke menu &lt;b&gt;Bahasa &amp; input&lt;/b&gt; dalam &lt;b&gt;Tetapan&lt;/b&gt; peranti mudah alih anda."</string>
+    <string name="download_over_metered" msgid="1643065851159409546">"Muat turun sekarang (<xliff:g id="SIZE_IN_MEGABYTES">%1$.1f</xliff:g>MB)"</string>
+    <string name="do_not_download_over_metered" msgid="2176209579313941583">"Muat turun melalui Wi-Fi"</string>
+    <string name="dict_available_notification_title" msgid="6514288591959117288">"Kamus tersedia untuk <xliff:g id="LANGUAGE">%1$s</xliff:g>"</string>
+    <string name="dict_available_notification_description" msgid="1075194169443163487">"Tekan untuk mengulas dan memuat turun"</string>
+    <string name="toast_downloading_suggestions" msgid="1313027353588566660">"Memuat turun: cadangan untuk <xliff:g id="LANGUAGE">%1$s</xliff:g> akan sedia tidak lama lagi."</string>
+    <string name="version_text" msgid="2715354215568469385">"Versi <xliff:g id="VERSION_NUMBER">%1$s</xliff:g>"</string>
+    <string name="user_dict_settings_add_menu_title" msgid="1254195365689387076">"tambah"</string>
+    <string name="user_dict_settings_add_dialog_title" msgid="4096700390211748168">"Tambah ke kamus"</string>
+    <string name="user_dict_settings_add_screen_title" msgid="5818914331629278758">"Frasa"</string>
+    <string name="user_dict_settings_add_dialog_more_options" msgid="5671682004887093112">"Lagi pilihan"</string>
+    <string name="user_dict_settings_add_dialog_less_options" msgid="2716586567241724126">"Kurang pilihan"</string>
+    <string name="user_dict_settings_add_dialog_confirm" msgid="4703129507388332950">"OK"</string>
+    <string name="user_dict_settings_add_word_option_name" msgid="6665558053408962865">"Perkataan:"</string>
+    <string name="user_dict_settings_add_shortcut_option_name" msgid="3094731590655523777">"Pintasan:"</string>
+    <string name="user_dict_settings_add_locale_option_name" msgid="4738643440987277705">"Bahasa:"</string>
+    <string name="user_dict_settings_add_word_hint" msgid="4902434148985906707">"Taip perkataan"</string>
+    <string name="user_dict_settings_add_shortcut_hint" msgid="2265453012555060178">"Pintasan pilihan"</string>
+    <string name="user_dict_settings_edit_dialog_title" msgid="3765774633869590352">"Edit perkataan"</string>
+    <string name="user_dict_settings_context_menu_edit_title" msgid="6812255903472456302">"Edit"</string>
+    <string name="user_dict_settings_context_menu_delete_title" msgid="8142932447689461181">"Padam"</string>
+    <string name="user_dict_settings_empty_text" msgid="558499587532668203">"Anda tidak mempunyai sebarang perkataan dalam kamus pengguna. Tambahkan perkataan dengan menyentuh butang Tambah (+)."</string>
+    <string name="user_dict_settings_all_languages" msgid="8276126583216298886">"Untuk semua bahasa"</string>
+    <string name="user_dict_settings_more_languages" msgid="7131268499685180461">"Lebih banyak bahasa..."</string>
+    <string name="user_dict_settings_delete" msgid="110413335187193859">"Padam"</string>
+    <string name="user_dict_fast_scroll_alphabet" msgid="5431919401558285473">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
+</resources>
diff --git a/java/res/values-ms/strings.xml b/java/res/values-ms/strings.xml
index 968243b..df30627 100644
--- a/java/res/values-ms/strings.xml
+++ b/java/res/values-ms/strings.xml
@@ -64,8 +64,8 @@
     <string name="auto_correction_summary" msgid="5625751551134658006">"Bar ruang dan tanda baca secara automatik membetulkan perkataan yang ditaip salah"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Matikan"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Sederhana"</string>
-    <string name="auto_correction_threshold_mode_aggressive" msgid="3524029103734923819">"Agresif"</string>
-    <string name="auto_correction_threshold_mode_very_aggressive" msgid="3386782235540547678">"Sangat agresif"</string>
+    <string name="auto_correction_threshold_mode_aggressive" msgid="7319007299148899623">"Agresif"</string>
+    <string name="auto_correction_threshold_mode_very_aggressive" msgid="1853309024129480416">"Sangat agresif"</string>
     <string name="bigram_prediction" msgid="1084449187723948550">"Cadangan perkataan seterusnya"</string>
     <string name="bigram_prediction_summary" msgid="3896362682751109677">"Gunakan perkataan sebelumnya dalam membuat cadangan"</string>
     <string name="gesture_input" msgid="826951152254563827">"Dayakan taipan gerak isyarat"</string>
@@ -143,13 +143,13 @@
     <string name="subtype_with_layout_en_GB" msgid="2179097748724725906">"Bahasa Inggeris (UK) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_en_US" msgid="1362581347576714579">"Bahasa Inggeris (AS) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_es_US" msgid="6261791057007890189">"Bahasa Sepanyol (AS) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
-    <string name="subtype_no_language" msgid="141420857808801746">"Tiada bahasa"</string>
-    <string name="subtype_no_language_qwerty" msgid="2956121451616633133">"Tiada bahasa (QWERTY)"</string>
-    <string name="subtype_no_language_qwertz" msgid="1177848172397202890">"Tiada bahasa (QWERTZ)"</string>
-    <string name="subtype_no_language_azerty" msgid="8721460968141187394">"Tiada bahasa (AZERTY)"</string>
-    <string name="subtype_no_language_dvorak" msgid="3122976737669823935">"Tiada bahasa (Dvorak)"</string>
-    <string name="subtype_no_language_colemak" msgid="4205992994906097244">"Tiada bahasa (Colemak)"</string>
-    <string name="subtype_no_language_pcqwerty" msgid="8840928374394180189">"Tiada bahasa (PC)"</string>
+    <string name="subtype_no_language" msgid="7137390094240139495">"Tiada bahasa (Abjad)"</string>
+    <string name="subtype_no_language_qwerty" msgid="244337630616742604">"Abjad (QWERTY)"</string>
+    <string name="subtype_no_language_qwertz" msgid="443066912507547976">"Abjad (QWERTZ)"</string>
+    <string name="subtype_no_language_azerty" msgid="8144348527575640087">"Abjad (AZERTY)"</string>
+    <string name="subtype_no_language_dvorak" msgid="1564494667584718094">"Abjad (Dvorak)"</string>
+    <string name="subtype_no_language_colemak" msgid="5837418400010302623">"Abjad (Colemak)"</string>
+    <string name="subtype_no_language_pcqwerty" msgid="5354918232046200018">"Abjad (PC)"</string>
     <string name="custom_input_styles_title" msgid="8429952441821251512">"Gaya input peribadi"</string>
     <string name="add_style" msgid="6163126614514489951">"Tambah gaya"</string>
     <string name="add" msgid="8299699805688017798">"Tambah"</string>
diff --git a/java/res/values-nb/strings.xml b/java/res/values-nb/strings.xml
index 33da13e..9b30ea3 100644
--- a/java/res/values-nb/strings.xml
+++ b/java/res/values-nb/strings.xml
@@ -64,8 +64,8 @@
     <string name="auto_correction_summary" msgid="5625751551134658006">"Mellomromstast og skilletegn retter automat. feilstavede ord"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Av"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Moderat"</string>
-    <string name="auto_correction_threshold_mode_aggressive" msgid="3524029103734923819">"Omfattende"</string>
-    <string name="auto_correction_threshold_mode_very_aggressive" msgid="3386782235540547678">"Veldig aggressiv"</string>
+    <string name="auto_correction_threshold_mode_aggressive" msgid="7319007299148899623">"Omfattende"</string>
+    <string name="auto_correction_threshold_mode_very_aggressive" msgid="1853309024129480416">"Veldig omfattende"</string>
     <string name="bigram_prediction" msgid="1084449187723948550">"Forslag til neste ord"</string>
     <string name="bigram_prediction_summary" msgid="3896362682751109677">"Bruk forrige ord til å lage forslag"</string>
     <string name="gesture_input" msgid="826951152254563827">"Aktiver ordføring"</string>
@@ -143,13 +143,15 @@
     <string name="subtype_with_layout_en_GB" msgid="2179097748724725906">"Engelsk (Storbritannia) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_en_US" msgid="1362581347576714579">"Engelsk (USA) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_es_US" msgid="6261791057007890189">"Spansk (USA) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
-    <string name="subtype_no_language" msgid="141420857808801746">"Ingen språk"</string>
-    <string name="subtype_no_language_qwerty" msgid="2956121451616633133">"Ingen språk (QWERTY)"</string>
-    <string name="subtype_no_language_qwertz" msgid="1177848172397202890">"Ingen språk (QWERTZ)"</string>
-    <string name="subtype_no_language_azerty" msgid="8721460968141187394">"Ingen språk (AZERTY)"</string>
-    <string name="subtype_no_language_dvorak" msgid="3122976737669823935">"Ingen språk (Dvorak)"</string>
-    <string name="subtype_no_language_colemak" msgid="4205992994906097244">"Ingen språk (Colemak)"</string>
-    <string name="subtype_no_language_pcqwerty" msgid="8840928374394180189">"Ingen språk (PC)"</string>
+    <string name="subtype_nepali_traditional" msgid="9032247506728040447">"<xliff:g id="LANGUAGE">%s</xliff:g> (tradisjonell)"</string>
+    <string name="subtype_no_language" msgid="7137390094240139495">"Ingen språk (alfabet)"</string>
+    <string name="subtype_no_language_qwerty" msgid="244337630616742604">"Alfabet (QWERTY)"</string>
+    <string name="subtype_no_language_qwertz" msgid="443066912507547976">"Alfabet (QWERTZ)"</string>
+    <string name="subtype_no_language_azerty" msgid="8144348527575640087">"Alfabet (AZERTY)"</string>
+    <string name="subtype_no_language_dvorak" msgid="1564494667584718094">"Alfabet (Dvorak)"</string>
+    <string name="subtype_no_language_colemak" msgid="5837418400010302623">"Alfabet (Colemak)"</string>
+    <string name="subtype_no_language_pcqwerty" msgid="5354918232046200018">"Alfabet (PC)"</string>
+    <string name="subtype_emoji" msgid="7483586578074549196">"Emoji"</string>
     <string name="custom_input_styles_title" msgid="8429952441821251512">"Egendefinerte inndata"</string>
     <string name="add_style" msgid="6163126614514489951">"Legg til stil"</string>
     <string name="add" msgid="8299699805688017798">"Legg til"</string>
diff --git a/java/res/values-nl/strings.xml b/java/res/values-nl/strings.xml
index 5224f81..c61b090 100644
--- a/java/res/values-nl/strings.xml
+++ b/java/res/values-nl/strings.xml
@@ -64,8 +64,8 @@
     <string name="auto_correction_summary" msgid="5625751551134658006">"Met spatiebalk en interpunctie worden verkeerd gespelde woorden automatisch gecorrigeerd"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Uitgeschakeld"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Normaal"</string>
-    <string name="auto_correction_threshold_mode_aggressive" msgid="3524029103734923819">"Agressief"</string>
-    <string name="auto_correction_threshold_mode_very_aggressive" msgid="3386782235540547678">"Zeer agressief"</string>
+    <string name="auto_correction_threshold_mode_aggressive" msgid="7319007299148899623">"Agressief"</string>
+    <string name="auto_correction_threshold_mode_very_aggressive" msgid="1853309024129480416">"Zeer agressief"</string>
     <string name="bigram_prediction" msgid="1084449187723948550">"Suggesties voor volgend woord"</string>
     <string name="bigram_prediction_summary" msgid="3896362682751109677">"Het vorige woord gebruiken bij het doen van suggesties"</string>
     <string name="gesture_input" msgid="826951152254563827">"Typen via tekenen inschakelen"</string>
@@ -143,13 +143,15 @@
     <string name="subtype_with_layout_en_GB" msgid="2179097748724725906">"Engels (VK) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_en_US" msgid="1362581347576714579">"Engels (VS) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_es_US" msgid="6261791057007890189">"Spaans (VS) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
-    <string name="subtype_no_language" msgid="141420857808801746">"Geen taal"</string>
-    <string name="subtype_no_language_qwerty" msgid="2956121451616633133">"Geen taal (QWERTY)"</string>
-    <string name="subtype_no_language_qwertz" msgid="1177848172397202890">"Geen taal (QWERTZ)"</string>
-    <string name="subtype_no_language_azerty" msgid="8721460968141187394">"Geen taal (AZERTY)"</string>
-    <string name="subtype_no_language_dvorak" msgid="3122976737669823935">"Geen taal (Dvorak)"</string>
-    <string name="subtype_no_language_colemak" msgid="4205992994906097244">"Geen taal (Colemak)"</string>
-    <string name="subtype_no_language_pcqwerty" msgid="8840928374394180189">"Geen taal (PC)"</string>
+    <string name="subtype_nepali_traditional" msgid="9032247506728040447">"<xliff:g id="LANGUAGE">%s</xliff:g> (traditioneel)"</string>
+    <string name="subtype_no_language" msgid="7137390094240139495">"Geen taal (alfabet)"</string>
+    <string name="subtype_no_language_qwerty" msgid="244337630616742604">"Alfabet (QWERTY)"</string>
+    <string name="subtype_no_language_qwertz" msgid="443066912507547976">"Alfabet (QWERTZ)"</string>
+    <string name="subtype_no_language_azerty" msgid="8144348527575640087">"Alfabet (AZERTY)"</string>
+    <string name="subtype_no_language_dvorak" msgid="1564494667584718094">"Alfabet (Dvorak)"</string>
+    <string name="subtype_no_language_colemak" msgid="5837418400010302623">"Alfabet (Colemak)"</string>
+    <string name="subtype_no_language_pcqwerty" msgid="5354918232046200018">"Alfabet (pc)"</string>
+    <string name="subtype_emoji" msgid="7483586578074549196">"Emoji"</string>
     <string name="custom_input_styles_title" msgid="8429952441821251512">"Aangep. invoerstijlen"</string>
     <string name="add_style" msgid="6163126614514489951">"Stijl toev."</string>
     <string name="add" msgid="8299699805688017798">"Toevoegen"</string>
diff --git a/java/res/values-pl/strings.xml b/java/res/values-pl/strings.xml
index e128c0e..31eace5 100644
--- a/java/res/values-pl/strings.xml
+++ b/java/res/values-pl/strings.xml
@@ -64,8 +64,8 @@
     <string name="auto_correction_summary" msgid="5625751551134658006">"Spacja i znaki przestankowe poprawiają błędnie wpisane słowa"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Wyłącz"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Umiarkowana"</string>
-    <string name="auto_correction_threshold_mode_aggressive" msgid="3524029103734923819">"Agresywna"</string>
-    <string name="auto_correction_threshold_mode_very_aggressive" msgid="3386782235540547678">"Bardzo agresywna"</string>
+    <string name="auto_correction_threshold_mode_aggressive" msgid="7319007299148899623">"Agresywna"</string>
+    <string name="auto_correction_threshold_mode_very_aggressive" msgid="1853309024129480416">"Bardzo agresywna"</string>
     <string name="bigram_prediction" msgid="1084449187723948550">"Podpowiadanie kolejnego słowa"</string>
     <string name="bigram_prediction_summary" msgid="3896362682751109677">"Pokazuj podpowiedzi na podstawie poprzedniego słowa"</string>
     <string name="gesture_input" msgid="826951152254563827">"Włącz pisanie gestami"</string>
@@ -143,13 +143,15 @@
     <string name="subtype_with_layout_en_GB" msgid="2179097748724725906">"angielski (UK) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_en_US" msgid="1362581347576714579">"angielski (USA) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_es_US" msgid="6261791057007890189">"hiszpański (USA) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
-    <string name="subtype_no_language" msgid="141420857808801746">"Brak języka"</string>
-    <string name="subtype_no_language_qwerty" msgid="2956121451616633133">"Brak języka (QWERTY)"</string>
-    <string name="subtype_no_language_qwertz" msgid="1177848172397202890">"Brak języka (QWERTZ)"</string>
-    <string name="subtype_no_language_azerty" msgid="8721460968141187394">"Brak języka (AZERTY)"</string>
-    <string name="subtype_no_language_dvorak" msgid="3122976737669823935">"Brak języka (Dvorak)"</string>
-    <string name="subtype_no_language_colemak" msgid="4205992994906097244">"Brak języka (Colemak)"</string>
-    <string name="subtype_no_language_pcqwerty" msgid="8840928374394180189">"Brak języka (PC)"</string>
+    <string name="subtype_nepali_traditional" msgid="9032247506728040447">"<xliff:g id="LANGUAGE">%s</xliff:g> (tradycyjny)"</string>
+    <string name="subtype_no_language" msgid="7137390094240139495">"Bez języka (alfabet)"</string>
+    <string name="subtype_no_language_qwerty" msgid="244337630616742604">"Alfabet (QWERTY)"</string>
+    <string name="subtype_no_language_qwertz" msgid="443066912507547976">"Alfabet (QWERTZ)"</string>
+    <string name="subtype_no_language_azerty" msgid="8144348527575640087">"Alfabet (AZERTY)"</string>
+    <string name="subtype_no_language_dvorak" msgid="1564494667584718094">"Alfabet (Dvorak)"</string>
+    <string name="subtype_no_language_colemak" msgid="5837418400010302623">"Alfabet (Colemak)"</string>
+    <string name="subtype_no_language_pcqwerty" msgid="5354918232046200018">"Alfabet (PC)"</string>
+    <string name="subtype_emoji" msgid="7483586578074549196">"Emotikony"</string>
     <string name="custom_input_styles_title" msgid="8429952441821251512">"Style niestandardowe"</string>
     <string name="add_style" msgid="6163126614514489951">"Dodaj styl"</string>
     <string name="add" msgid="8299699805688017798">"Dodaj"</string>
diff --git a/java/res/values-pt-rPT/strings.xml b/java/res/values-pt-rPT/strings.xml
index fb06f92..cd89a77 100644
--- a/java/res/values-pt-rPT/strings.xml
+++ b/java/res/values-pt-rPT/strings.xml
@@ -64,8 +64,8 @@
     <string name="auto_correction_summary" msgid="5625751551134658006">"Correcção automática de palavras mal escritas c/ barra de espaços e pontuação"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Desligar"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Moderada"</string>
-    <string name="auto_correction_threshold_mode_aggressive" msgid="3524029103734923819">"Agressiva"</string>
-    <string name="auto_correction_threshold_mode_very_aggressive" msgid="3386782235540547678">"Muito agressivo"</string>
+    <string name="auto_correction_threshold_mode_aggressive" msgid="7319007299148899623">"Agressiva"</string>
+    <string name="auto_correction_threshold_mode_very_aggressive" msgid="1853309024129480416">"Muito agressiva"</string>
     <string name="bigram_prediction" msgid="1084449187723948550">"Sugestões da palavra seguinte"</string>
     <string name="bigram_prediction_summary" msgid="3896362682751109677">"Utilizar palavra anterior para fazer sugestões"</string>
     <string name="gesture_input" msgid="826951152254563827">"Ativar escrita por toque"</string>
@@ -134,7 +134,7 @@
     <string name="select_language" msgid="3693815588777926848">"Idiomas de introdução"</string>
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"Toque novamente para guardar"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Dicionário disponível"</string>
-    <string name="prefs_enable_log" msgid="6620424505072963557">"Activar comentários do utilizador"</string>
+    <string name="prefs_enable_log" msgid="6620424505072963557">"Ativar comentários do utilizador"</string>
     <string name="prefs_description_log" msgid="7525225584555429211">"Envie automaticamente estatísticas de utilização e relatórios de falhas e ajude-nos a melhorar este editor do método de introdução."</string>
     <string name="keyboard_layout" msgid="8451164783510487501">"Tema do teclado"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"Inglês (RU)"</string>
@@ -143,13 +143,15 @@
     <string name="subtype_with_layout_en_GB" msgid="2179097748724725906">"Inglês (RU) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_en_US" msgid="1362581347576714579">"Inglês (EUA) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_es_US" msgid="6261791057007890189">"Espanhol (EUA) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
-    <string name="subtype_no_language" msgid="141420857808801746">"Nenhum idioma"</string>
-    <string name="subtype_no_language_qwerty" msgid="2956121451616633133">"Nenhum idioma (QWERTY)"</string>
-    <string name="subtype_no_language_qwertz" msgid="1177848172397202890">"Nenhum idioma (QWERTZ)"</string>
-    <string name="subtype_no_language_azerty" msgid="8721460968141187394">"Nenhum idioma (AZERTY)"</string>
-    <string name="subtype_no_language_dvorak" msgid="3122976737669823935">"Nenhum idioma (Dvorak)"</string>
-    <string name="subtype_no_language_colemak" msgid="4205992994906097244">"Nenh. idioma (Colemak)"</string>
-    <string name="subtype_no_language_pcqwerty" msgid="8840928374394180189">"Nenhum idioma (PC)"</string>
+    <string name="subtype_nepali_traditional" msgid="9032247506728040447">"<xliff:g id="LANGUAGE">%s</xliff:g> (Tradicional)"</string>
+    <string name="subtype_no_language" msgid="7137390094240139495">"Sem idioma (alfabeto)"</string>
+    <string name="subtype_no_language_qwerty" msgid="244337630616742604">"Alfabeto (QWERTY)"</string>
+    <string name="subtype_no_language_qwertz" msgid="443066912507547976">"Alfabeto (QWERTZ)"</string>
+    <string name="subtype_no_language_azerty" msgid="8144348527575640087">"Alfabeto (AZERTY)"</string>
+    <string name="subtype_no_language_dvorak" msgid="1564494667584718094">"Alfabeto (Dvorak)"</string>
+    <string name="subtype_no_language_colemak" msgid="5837418400010302623">"Alfabeto (Colemak)"</string>
+    <string name="subtype_no_language_pcqwerty" msgid="5354918232046200018">"Alfabeto (PC)"</string>
+    <string name="subtype_emoji" msgid="7483586578074549196">"Emoji"</string>
     <string name="custom_input_styles_title" msgid="8429952441821251512">"Estilos entrada pers."</string>
     <string name="add_style" msgid="6163126614514489951">"Adic. estilo"</string>
     <string name="add" msgid="8299699805688017798">"Adicionar"</string>
diff --git a/java/res/values-pt/strings.xml b/java/res/values-pt/strings.xml
index 8cbc41e..820f577 100644
--- a/java/res/values-pt/strings.xml
+++ b/java/res/values-pt/strings.xml
@@ -56,7 +56,7 @@
     <string name="prefs_show_suggestions" msgid="8026799663445531637">"Exibir sugestões de correção"</string>
     <string name="prefs_show_suggestions_summary" msgid="1583132279498502825">"Exibir sugestões de palavras durante a digitação"</string>
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Mostrar sempre"</string>
-    <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3859783767435239118">"Mostrar em modo de retrato"</string>
+    <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3859783767435239118">"Mostrar em modo retrato"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Sempre ocultar"</string>
     <string name="prefs_block_potentially_offensive_title" msgid="5078480071057408934">"Bloquear palavras ofensivas"</string>
     <string name="prefs_block_potentially_offensive_summary" msgid="2371835479734991364">"Não sugerir palavras potencialmente ofensivas"</string>
@@ -64,8 +64,8 @@
     <string name="auto_correction_summary" msgid="5625751551134658006">"A barra de espaço e a pontuação corrigem automaticamente palavras com erro de digitação"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Desativado"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Moderado"</string>
-    <string name="auto_correction_threshold_mode_aggressive" msgid="3524029103734923819">"Agressivo"</string>
-    <string name="auto_correction_threshold_mode_very_aggressive" msgid="3386782235540547678">"Muito agressivo"</string>
+    <string name="auto_correction_threshold_mode_aggressive" msgid="7319007299148899623">"Agressivo"</string>
+    <string name="auto_correction_threshold_mode_very_aggressive" msgid="1853309024129480416">"Muito agressivo"</string>
     <string name="bigram_prediction" msgid="1084449187723948550">"Sugestões para a palavra seguinte"</string>
     <string name="bigram_prediction_summary" msgid="3896362682751109677">"Usar a palavra anterior ao fazer sugestões"</string>
     <string name="gesture_input" msgid="826951152254563827">"Ativar a escrita com gestos"</string>
@@ -143,13 +143,15 @@
     <string name="subtype_with_layout_en_GB" msgid="2179097748724725906">"Inglês (Reino Unido) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_en_US" msgid="1362581347576714579">"Inglês (EUA) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_es_US" msgid="6261791057007890189">"espanhol (EUA) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
-    <string name="subtype_no_language" msgid="141420857808801746">"Sem idioma"</string>
-    <string name="subtype_no_language_qwerty" msgid="2956121451616633133">"nenhum idioma (QWERTY)"</string>
-    <string name="subtype_no_language_qwertz" msgid="1177848172397202890">"Nenhum idioma (QWERTZ)"</string>
-    <string name="subtype_no_language_azerty" msgid="8721460968141187394">"Nenhum idioma (AZERTY)"</string>
-    <string name="subtype_no_language_dvorak" msgid="3122976737669823935">"Nenhum idioma (Dvorak)"</string>
-    <string name="subtype_no_language_colemak" msgid="4205992994906097244">"Nenhum idioma (Colemak)"</string>
-    <string name="subtype_no_language_pcqwerty" msgid="8840928374394180189">"Nenhum idioma (PC)"</string>
+    <string name="subtype_nepali_traditional" msgid="9032247506728040447">"<xliff:g id="LANGUAGE">%s</xliff:g> (tradicional)"</string>
+    <string name="subtype_no_language" msgid="7137390094240139495">"Nenhum idioma (alfabeto)"</string>
+    <string name="subtype_no_language_qwerty" msgid="244337630616742604">"Alfabeto (QWERTY)"</string>
+    <string name="subtype_no_language_qwertz" msgid="443066912507547976">"Alfabeto (QWERTZ)"</string>
+    <string name="subtype_no_language_azerty" msgid="8144348527575640087">"Alfabeto (AZERTY)"</string>
+    <string name="subtype_no_language_dvorak" msgid="1564494667584718094">"Alfabeto (Dvorak)"</string>
+    <string name="subtype_no_language_colemak" msgid="5837418400010302623">"Alfabeto (Colemak)"</string>
+    <string name="subtype_no_language_pcqwerty" msgid="5354918232046200018">"Alfabeto (PC)"</string>
+    <string name="subtype_emoji" msgid="7483586578074549196">"Emoji"</string>
     <string name="custom_input_styles_title" msgid="8429952441821251512">"Estilos personalizados"</string>
     <string name="add_style" msgid="6163126614514489951">"Adic. estilo"</string>
     <string name="add" msgid="8299699805688017798">"Adicionar"</string>
diff --git a/java/res/values-rm/strings.xml b/java/res/values-rm/strings.xml
index 71c9deb..1c509ad 100644
--- a/java/res/values-rm/strings.xml
+++ b/java/res/values-rm/strings.xml
@@ -104,9 +104,9 @@
     <skip />
     <!-- no translation found for auto_correction_threshold_mode_modest (8788366690620799097) -->
     <skip />
-    <!-- no translation found for auto_correction_threshold_mode_aggressive (3524029103734923819) -->
+    <!-- no translation found for auto_correction_threshold_mode_aggressive (7319007299148899623) -->
     <skip />
-    <!-- no translation found for auto_correction_threshold_mode_very_aggressive (3386782235540547678) -->
+    <!-- no translation found for auto_correction_threshold_mode_very_aggressive (1853309024129480416) -->
     <skip />
     <!-- no translation found for bigram_prediction (1084449187723948550) -->
     <skip />
@@ -254,19 +254,23 @@
     <skip />
     <!-- no translation found for subtype_with_layout_es_US (6261791057007890189) -->
     <skip />
-    <!-- no translation found for subtype_no_language (141420857808801746) -->
+    <!-- no translation found for subtype_nepali_traditional (9032247506728040447) -->
     <skip />
-    <!-- no translation found for subtype_no_language_qwerty (2956121451616633133) -->
+    <!-- no translation found for subtype_no_language (7137390094240139495) -->
     <skip />
-    <!-- no translation found for subtype_no_language_qwertz (1177848172397202890) -->
+    <!-- no translation found for subtype_no_language_qwerty (244337630616742604) -->
     <skip />
-    <!-- no translation found for subtype_no_language_azerty (8721460968141187394) -->
+    <!-- no translation found for subtype_no_language_qwertz (443066912507547976) -->
     <skip />
-    <!-- no translation found for subtype_no_language_dvorak (3122976737669823935) -->
+    <!-- no translation found for subtype_no_language_azerty (8144348527575640087) -->
     <skip />
-    <!-- no translation found for subtype_no_language_colemak (4205992994906097244) -->
+    <!-- no translation found for subtype_no_language_dvorak (1564494667584718094) -->
     <skip />
-    <!-- no translation found for subtype_no_language_pcqwerty (8840928374394180189) -->
+    <!-- no translation found for subtype_no_language_colemak (5837418400010302623) -->
+    <skip />
+    <!-- no translation found for subtype_no_language_pcqwerty (5354918232046200018) -->
+    <skip />
+    <!-- no translation found for subtype_emoji (7483586578074549196) -->
     <skip />
     <!-- no translation found for custom_input_styles_title (8429952441821251512) -->
     <skip />
diff --git a/java/res/values-ro/strings.xml b/java/res/values-ro/strings.xml
index cbba8a3..3caff13 100644
--- a/java/res/values-ro/strings.xml
+++ b/java/res/values-ro/strings.xml
@@ -39,7 +39,7 @@
     <string name="show_language_switch_key_summary" msgid="7343403647474265713">"Afişaţi când sunt activate mai multe limbi de intrare"</string>
     <string name="sliding_key_input_preview" msgid="6604262359510068370">"Afișați indicator glisare"</string>
     <string name="sliding_key_input_preview_summary" msgid="6340524345729093886">"Afișați un indicator în timpul glisării de la Shift sau tasta de simboluri"</string>
-    <string name="key_preview_popup_dismiss_delay" msgid="6213164897443068248">"Înt. înch. pop-up esenţ."</string>
+    <string name="key_preview_popup_dismiss_delay" msgid="6213164897443068248">"Închidere pop-up taste"</string>
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Fără întârziere"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Prestabilit"</string>
     <string name="abbreviation_unit_milliseconds" msgid="8700286094028323363">"<xliff:g id="MILLISECONDS">%s</xliff:g> msec."</string>
@@ -48,7 +48,7 @@
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Utilizaţi numele din Agendă pentru sugestii şi corecţii"</string>
     <string name="use_double_space_period" msgid="8781529969425082860">"Inserează punct spațiu"</string>
     <string name="use_double_space_period_summary" msgid="6532892187247952799">"Dubla atingere a barei de spațiu inserează punct urmat de spațiu"</string>
-    <string name="auto_cap" msgid="1719746674854628252">"Auto-capitalizare"</string>
+    <string name="auto_cap" msgid="1719746674854628252">"Scriere automată cu majuscule"</string>
     <string name="auto_cap_summary" msgid="7934452761022946874">"Scrie cu majusculă primul cuvânt din fiecare propoziţie"</string>
     <string name="edit_personal_dictionary" msgid="3996910038952940420">"Dicționar personal"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"Dicţionare suplimentare"</string>
@@ -64,8 +64,8 @@
     <string name="auto_correction_summary" msgid="5625751551134658006">"Corectare automată cuvinte prin bară spaţiu/semne punctuaţie"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Dezactivată"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Moderată"</string>
-    <string name="auto_correction_threshold_mode_aggressive" msgid="3524029103734923819">"Agresivă"</string>
-    <string name="auto_correction_threshold_mode_very_aggressive" msgid="3386782235540547678">"Foarte exigentă"</string>
+    <string name="auto_correction_threshold_mode_aggressive" msgid="7319007299148899623">"Exigentă"</string>
+    <string name="auto_correction_threshold_mode_very_aggressive" msgid="1853309024129480416">"Foarte agresivă"</string>
     <string name="bigram_prediction" msgid="1084449187723948550">"Sugestii pentru cuvântul următor"</string>
     <string name="bigram_prediction_summary" msgid="3896362682751109677">"Utilizează cuvântul anterior pentru sugestii"</string>
     <string name="gesture_input" msgid="826951152254563827">"Activați tastarea gestuală"</string>
@@ -131,25 +131,27 @@
     <string name="configure_input_method" msgid="373356270290742459">"Configuraţi metodele de intrare"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Selectaţi limba"</string>
     <string name="send_feedback" msgid="1780431884109392046">"Trimiteți feedback"</string>
-    <string name="select_language" msgid="3693815588777926848">"Limbi de intrare"</string>
+    <string name="select_language" msgid="3693815588777926848">"Limbi de introducere de text"</string>
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"Atingeţi din nou pentru a salva"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Dicţionar disponibil"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"Activaţi feedback de la utilizatori"</string>
     <string name="prefs_description_log" msgid="7525225584555429211">"Ajutați la îmbunătățirea acestui instrument de editare a metodelor de introducere a textului trimițând în mod automat statistici de utilizare și rapoarte de blocare."</string>
     <string name="keyboard_layout" msgid="8451164783510487501">"Temă pentru tastatură"</string>
-    <string name="subtype_en_GB" msgid="88170601942311355">"Engleză (Marea Britanie)"</string>
-    <string name="subtype_en_US" msgid="6160452336634534239">"Engleză (S.U.A.)"</string>
-    <string name="subtype_es_US" msgid="5583145191430180200">"Spaniolă (S.U.A.)"</string>
+    <string name="subtype_en_GB" msgid="88170601942311355">"engleză (Regatul Unit)"</string>
+    <string name="subtype_en_US" msgid="6160452336634534239">"engleză (S.U.A.)"</string>
+    <string name="subtype_es_US" msgid="5583145191430180200">"spaniolă (S.U.A.)"</string>
     <string name="subtype_with_layout_en_GB" msgid="2179097748724725906">"Engleză (Regatul Unit) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_en_US" msgid="1362581347576714579">"Engleză (S.U.A.) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_es_US" msgid="6261791057007890189">"Spaniolă (S.U.A.) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
-    <string name="subtype_no_language" msgid="141420857808801746">"Nicio limbă"</string>
-    <string name="subtype_no_language_qwerty" msgid="2956121451616633133">"Nicio limbă (QWERTY)"</string>
-    <string name="subtype_no_language_qwertz" msgid="1177848172397202890">"Nicio limbă (QWERTZ)"</string>
-    <string name="subtype_no_language_azerty" msgid="8721460968141187394">"Nicio limbă (AZERTY)"</string>
-    <string name="subtype_no_language_dvorak" msgid="3122976737669823935">"Nicio limbă (Dvorak)"</string>
-    <string name="subtype_no_language_colemak" msgid="4205992994906097244">"Nicio limbă (Colemak)"</string>
-    <string name="subtype_no_language_pcqwerty" msgid="8840928374394180189">"Nicio limbă (PC)"</string>
+    <string name="subtype_nepali_traditional" msgid="9032247506728040447">"<xliff:g id="LANGUAGE">%s</xliff:g> (tradițional)"</string>
+    <string name="subtype_no_language" msgid="7137390094240139495">"Nicio limbă (alfabet)"</string>
+    <string name="subtype_no_language_qwerty" msgid="244337630616742604">"Alfabet (QWERTY)"</string>
+    <string name="subtype_no_language_qwertz" msgid="443066912507547976">"Alfabet (QWERTZ)"</string>
+    <string name="subtype_no_language_azerty" msgid="8144348527575640087">"Alfabet (AZERTY)"</string>
+    <string name="subtype_no_language_dvorak" msgid="1564494667584718094">"Alfabet (Dvorak)"</string>
+    <string name="subtype_no_language_colemak" msgid="5837418400010302623">"Alfabet (Colemak)"</string>
+    <string name="subtype_no_language_pcqwerty" msgid="5354918232046200018">"Alfabet (PC)"</string>
+    <string name="subtype_emoji" msgid="7483586578074549196">"Emoji"</string>
     <string name="custom_input_styles_title" msgid="8429952441821251512">"Stiluri personalizate"</string>
     <string name="add_style" msgid="6163126614514489951">"Stil"</string>
     <string name="add" msgid="8299699805688017798">"Adăugaţi"</string>
@@ -164,7 +166,7 @@
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Modul Studiu privind utilizarea"</string>
     <string name="prefs_key_longpress_timeout_settings" msgid="6102240298932897873">"Timpul apăsării lungi a tastei"</string>
     <string name="prefs_keypress_vibration_duration_settings" msgid="7918341459947439226">"Vibrare după apăsarea tastei"</string>
-    <string name="prefs_keypress_sound_volume_settings" msgid="6027007337036891623">"Volum prin apăsarea tastei"</string>
+    <string name="prefs_keypress_sound_volume_settings" msgid="6027007337036891623">"Sunet la apăsarea tastelor"</string>
     <string name="prefs_read_external_dictionary" msgid="2588931418575013067">"Citiți fișierul de dicționar extern"</string>
     <string name="read_external_dictionary_no_files_message" msgid="4947420942224623792">"Nu există fișiere dicționar în dosarul Descărcări"</string>
     <string name="read_external_dictionary_multiple_files_title" msgid="7637749044265808628">"Selectați un fișier dicționar de instalat"</string>
diff --git a/java/res/values-ru/strings.xml b/java/res/values-ru/strings.xml
index 13f34ee..affe88f 100644
--- a/java/res/values-ru/strings.xml
+++ b/java/res/values-ru/strings.xml
@@ -64,8 +64,8 @@
     <string name="auto_correction_summary" msgid="5625751551134658006">"Автоматическое исправление опечаток при вводе знака препинания или пробела"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Откл."</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Умеренное"</string>
-    <string name="auto_correction_threshold_mode_aggressive" msgid="3524029103734923819">"Активное"</string>
-    <string name="auto_correction_threshold_mode_very_aggressive" msgid="3386782235540547678">"Очень активно"</string>
+    <string name="auto_correction_threshold_mode_aggressive" msgid="7319007299148899623">"Активно"</string>
+    <string name="auto_correction_threshold_mode_very_aggressive" msgid="1853309024129480416">"Очень активно"</string>
     <string name="bigram_prediction" msgid="1084449187723948550">"Подсказывать слова"</string>
     <string name="bigram_prediction_summary" msgid="3896362682751109677">"Предлагать подсказки на основе предыдущего слова"</string>
     <string name="gesture_input" msgid="826951152254563827">"Включить функцию"</string>
@@ -143,13 +143,15 @@
     <string name="subtype_with_layout_en_GB" msgid="2179097748724725906">"Английская (Великобр.) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_en_US" msgid="1362581347576714579">"Английская (США) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_es_US" msgid="6261791057007890189">"Испанский (США): <xliff:g id="LAYOUT">%s</xliff:g>"</string>
-    <string name="subtype_no_language" msgid="141420857808801746">"Язык не указан"</string>
-    <string name="subtype_no_language_qwerty" msgid="2956121451616633133">"QWERTY-клавиатура"</string>
-    <string name="subtype_no_language_qwertz" msgid="1177848172397202890">"Язык не задан (QWERTZ)"</string>
-    <string name="subtype_no_language_azerty" msgid="8721460968141187394">"Язык не задан (AZERTY)"</string>
-    <string name="subtype_no_language_dvorak" msgid="3122976737669823935">"Язык не задан (Dvorak)"</string>
-    <string name="subtype_no_language_colemak" msgid="4205992994906097244">"Яз. не задан (Colemak)"</string>
-    <string name="subtype_no_language_pcqwerty" msgid="8840928374394180189">"Язык не задан (PC)"</string>
+    <string name="subtype_nepali_traditional" msgid="9032247506728040447">"<xliff:g id="LANGUAGE">%s</xliff:g> (традиционный)"</string>
+    <string name="subtype_no_language" msgid="7137390094240139495">"Язык не определен (латиница)"</string>
+    <string name="subtype_no_language_qwerty" msgid="244337630616742604">"Латиница (QWERTY)"</string>
+    <string name="subtype_no_language_qwertz" msgid="443066912507547976">"Латиница (QWERTZ)"</string>
+    <string name="subtype_no_language_azerty" msgid="8144348527575640087">"Латиница (AZERTY)"</string>
+    <string name="subtype_no_language_dvorak" msgid="1564494667584718094">"Латиница (Dvorak)"</string>
+    <string name="subtype_no_language_colemak" msgid="5837418400010302623">"Латиница (Colemak)"</string>
+    <string name="subtype_no_language_pcqwerty" msgid="5354918232046200018">"Латиница (ПК)"</string>
+    <string name="subtype_emoji" msgid="7483586578074549196">"Эмодзи"</string>
     <string name="custom_input_styles_title" msgid="8429952441821251512">"Персонализированные стили"</string>
     <string name="add_style" msgid="6163126614514489951">"Добавить стиль"</string>
     <string name="add" msgid="8299699805688017798">"Добавить"</string>
diff --git a/java/res/values-sk/strings.xml b/java/res/values-sk/strings.xml
index 9d315a8..85d64c6 100644
--- a/java/res/values-sk/strings.xml
+++ b/java/res/values-sk/strings.xml
@@ -64,8 +64,8 @@
     <string name="auto_correction_summary" msgid="5625751551134658006">"Stlačením medzerníka a interpunkcie sa aut. opravia chybné slová"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Vypnuté"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Mierne"</string>
-    <string name="auto_correction_threshold_mode_aggressive" msgid="3524029103734923819">"Agresívne"</string>
-    <string name="auto_correction_threshold_mode_very_aggressive" msgid="3386782235540547678">"Veľmi agresívne"</string>
+    <string name="auto_correction_threshold_mode_aggressive" msgid="7319007299148899623">"Agresívne"</string>
+    <string name="auto_correction_threshold_mode_very_aggressive" msgid="1853309024129480416">"Veľmi agresívne"</string>
     <string name="bigram_prediction" msgid="1084449187723948550">"Návrhy ďalšieho slova"</string>
     <string name="bigram_prediction_summary" msgid="3896362682751109677">"Návrhy podľa predchádzajúceho slova"</string>
     <string name="gesture_input" msgid="826951152254563827">"Povoliť písanie gestami"</string>
@@ -143,13 +143,15 @@
     <string name="subtype_with_layout_en_GB" msgid="2179097748724725906">"angličtina (UK) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_en_US" msgid="1362581347576714579">"angličtina (USA) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_es_US" msgid="6261791057007890189">"španielčina (USA) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
-    <string name="subtype_no_language" msgid="141420857808801746">"Žiadny jazyk"</string>
-    <string name="subtype_no_language_qwerty" msgid="2956121451616633133">"Žiadny jazyk (QWERTY)"</string>
-    <string name="subtype_no_language_qwertz" msgid="1177848172397202890">"Žiadny jazyk (QWERTZ)"</string>
-    <string name="subtype_no_language_azerty" msgid="8721460968141187394">"Žiadny jazyk (AZERTY)"</string>
-    <string name="subtype_no_language_dvorak" msgid="3122976737669823935">"Žiadny jazyk (Dvorak)"</string>
-    <string name="subtype_no_language_colemak" msgid="4205992994906097244">"Žiadny jazyk (Colemak)"</string>
-    <string name="subtype_no_language_pcqwerty" msgid="8840928374394180189">"Žiadny jazyk (PC)"</string>
+    <string name="subtype_nepali_traditional" msgid="9032247506728040447">"<xliff:g id="LANGUAGE">%s</xliff:g> (tradičná)"</string>
+    <string name="subtype_no_language" msgid="7137390094240139495">"Žiadny jazyk (latinka)"</string>
+    <string name="subtype_no_language_qwerty" msgid="244337630616742604">"Latinka (QWERTY)"</string>
+    <string name="subtype_no_language_qwertz" msgid="443066912507547976">"Latinka (QWERTZ)"</string>
+    <string name="subtype_no_language_azerty" msgid="8144348527575640087">"Latinka (AZERTY)"</string>
+    <string name="subtype_no_language_dvorak" msgid="1564494667584718094">"Latinka (Dvorak)"</string>
+    <string name="subtype_no_language_colemak" msgid="5837418400010302623">"Latinka (Colemak)"</string>
+    <string name="subtype_no_language_pcqwerty" msgid="5354918232046200018">"Latinka (PC)"</string>
+    <string name="subtype_emoji" msgid="7483586578074549196">"Emoji"</string>
     <string name="custom_input_styles_title" msgid="8429952441821251512">"Vlastné štýly vstupu"</string>
     <string name="add_style" msgid="6163126614514489951">"Pridať štýl"</string>
     <string name="add" msgid="8299699805688017798">"Pridať"</string>
diff --git a/java/res/values-sl/strings.xml b/java/res/values-sl/strings.xml
index 760cbde..7648d73 100644
--- a/java/res/values-sl/strings.xml
+++ b/java/res/values-sl/strings.xml
@@ -64,8 +64,8 @@
     <string name="auto_correction_summary" msgid="5625751551134658006">"Preslednica in ločila samodejno popravijo napačno vtipkane besede"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Izklopljeno"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Zmerno"</string>
-    <string name="auto_correction_threshold_mode_aggressive" msgid="3524029103734923819">"Strogo"</string>
-    <string name="auto_correction_threshold_mode_very_aggressive" msgid="3386782235540547678">"Zelo strogo"</string>
+    <string name="auto_correction_threshold_mode_aggressive" msgid="7319007299148899623">"Agresivno"</string>
+    <string name="auto_correction_threshold_mode_very_aggressive" msgid="1853309024129480416">"Zelo agresivno"</string>
     <string name="bigram_prediction" msgid="1084449187723948550">"Predlogi za naslednjo besedo"</string>
     <string name="bigram_prediction_summary" msgid="3896362682751109677">"Pri predlogu upoštevaj prejšnjo besedo"</string>
     <string name="gesture_input" msgid="826951152254563827">"Omogoči vnos besedila s potezo"</string>
@@ -143,13 +143,15 @@
     <string name="subtype_with_layout_en_GB" msgid="2179097748724725906">"Angleška (Zdr. kralj.) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_en_US" msgid="1362581347576714579">"Angleška (ZDA) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_es_US" msgid="6261791057007890189">"španščina (ZDA) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
-    <string name="subtype_no_language" msgid="141420857808801746">"Ni jezika"</string>
-    <string name="subtype_no_language_qwerty" msgid="2956121451616633133">"Ni jezika (QWERTY)"</string>
-    <string name="subtype_no_language_qwertz" msgid="1177848172397202890">"Ni jezika (QWERTZ)"</string>
-    <string name="subtype_no_language_azerty" msgid="8721460968141187394">"Ni jezika (AZERTY)"</string>
-    <string name="subtype_no_language_dvorak" msgid="3122976737669823935">"Ni jezika (Dvorak)"</string>
-    <string name="subtype_no_language_colemak" msgid="4205992994906097244">"Ni jezika (Colemak)"</string>
-    <string name="subtype_no_language_pcqwerty" msgid="8840928374394180189">"Ni jezika (PC)"</string>
+    <string name="subtype_nepali_traditional" msgid="9032247506728040447">"<xliff:g id="LANGUAGE">%s</xliff:g> (tradicionalna)"</string>
+    <string name="subtype_no_language" msgid="7137390094240139495">"Brez jezika (latinice)"</string>
+    <string name="subtype_no_language_qwerty" msgid="244337630616742604">"Latinica (QWERTY)"</string>
+    <string name="subtype_no_language_qwertz" msgid="443066912507547976">"Latinica (QWERTZ)"</string>
+    <string name="subtype_no_language_azerty" msgid="8144348527575640087">"Latinica (AZERTY)"</string>
+    <string name="subtype_no_language_dvorak" msgid="1564494667584718094">"Latinica (Dvorak)"</string>
+    <string name="subtype_no_language_colemak" msgid="5837418400010302623">"Latinica (Colemak)"</string>
+    <string name="subtype_no_language_pcqwerty" msgid="5354918232046200018">"Latinica (PC)"</string>
+    <string name="subtype_emoji" msgid="7483586578074549196">"Znaki »emoji«"</string>
     <string name="custom_input_styles_title" msgid="8429952441821251512">"Slogi vnosa po meri"</string>
     <string name="add_style" msgid="6163126614514489951">"Dodaj slog"</string>
     <string name="add" msgid="8299699805688017798">"Dodaj"</string>
diff --git a/java/res/values-sr/strings.xml b/java/res/values-sr/strings.xml
index 6bddc5c..db06bce 100644
--- a/java/res/values-sr/strings.xml
+++ b/java/res/values-sr/strings.xml
@@ -64,8 +64,8 @@
     <string name="auto_correction_summary" msgid="5625751551134658006">"Размак и интерпункција аутоматски исправљају грешке у куцању"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Искључи"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Умерено"</string>
-    <string name="auto_correction_threshold_mode_aggressive" msgid="3524029103734923819">"Агресивно"</string>
-    <string name="auto_correction_threshold_mode_very_aggressive" msgid="3386782235540547678">"Веома агресивно"</string>
+    <string name="auto_correction_threshold_mode_aggressive" msgid="7319007299148899623">"Агресивно"</string>
+    <string name="auto_correction_threshold_mode_very_aggressive" msgid="1853309024129480416">"Веома агресивно"</string>
     <string name="bigram_prediction" msgid="1084449187723948550">"Предлози за следећу реч"</string>
     <string name="bigram_prediction_summary" msgid="3896362682751109677">"Користи претходну реч при давању предлога"</string>
     <string name="gesture_input" msgid="826951152254563827">"Омогући унос покретом"</string>
@@ -143,13 +143,15 @@
     <string name="subtype_with_layout_en_GB" msgid="2179097748724725906">"енглески (УК) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_en_US" msgid="1362581347576714579">"енглески (САД) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_es_US" msgid="6261791057007890189">"шпански (САД) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
-    <string name="subtype_no_language" msgid="141420857808801746">"Без језика"</string>
-    <string name="subtype_no_language_qwerty" msgid="2956121451616633133">"Нема језика (QWERTY)"</string>
-    <string name="subtype_no_language_qwertz" msgid="1177848172397202890">"Без језика (QWERTZ)"</string>
-    <string name="subtype_no_language_azerty" msgid="8721460968141187394">"Без језика (AZERTY)"</string>
-    <string name="subtype_no_language_dvorak" msgid="3122976737669823935">"Без језика (Dvorak)"</string>
-    <string name="subtype_no_language_colemak" msgid="4205992994906097244">"Без језика (Colemak)"</string>
-    <string name="subtype_no_language_pcqwerty" msgid="8840928374394180189">"Без језика (PC)"</string>
+    <string name="subtype_nepali_traditional" msgid="9032247506728040447">"<xliff:g id="LANGUAGE">%s</xliff:g> (традиционални)"</string>
+    <string name="subtype_no_language" msgid="7137390094240139495">"Нема језика (абецеда)"</string>
+    <string name="subtype_no_language_qwerty" msgid="244337630616742604">"Абецеда (QWERTY)"</string>
+    <string name="subtype_no_language_qwertz" msgid="443066912507547976">"Абецеда (QWERTZ)"</string>
+    <string name="subtype_no_language_azerty" msgid="8144348527575640087">"Абецеда (AZERTY)"</string>
+    <string name="subtype_no_language_dvorak" msgid="1564494667584718094">"Абецеда (Dvorak)"</string>
+    <string name="subtype_no_language_colemak" msgid="5837418400010302623">"Абецеда (Colemak)"</string>
+    <string name="subtype_no_language_pcqwerty" msgid="5354918232046200018">"Абецеда (PC)"</string>
+    <string name="subtype_emoji" msgid="7483586578074549196">"Емотикони"</string>
     <string name="custom_input_styles_title" msgid="8429952441821251512">"Прилаг. стилови уноса"</string>
     <string name="add_style" msgid="6163126614514489951">"Додав. стила"</string>
     <string name="add" msgid="8299699805688017798">"Додај"</string>
diff --git a/java/res/values-sv/strings.xml b/java/res/values-sv/strings.xml
index d1567c3..c663516 100644
--- a/java/res/values-sv/strings.xml
+++ b/java/res/values-sv/strings.xml
@@ -64,8 +64,8 @@
     <string name="auto_correction_summary" msgid="5625751551134658006">"Blanksteg/skiljetecken rättar felstavning"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Av"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Måttlig"</string>
-    <string name="auto_correction_threshold_mode_aggressive" msgid="3524029103734923819">"Aggressiv"</string>
-    <string name="auto_correction_threshold_mode_very_aggressive" msgid="3386782235540547678">"Mycket aggressivt"</string>
+    <string name="auto_correction_threshold_mode_aggressive" msgid="7319007299148899623">"Aggressivt"</string>
+    <string name="auto_correction_threshold_mode_very_aggressive" msgid="1853309024129480416">"Mycket aggressivt"</string>
     <string name="bigram_prediction" msgid="1084449187723948550">"Föreslå nästa ord"</string>
     <string name="bigram_prediction_summary" msgid="3896362682751109677">"Ge förslag utifrån föregående ord"</string>
     <string name="gesture_input" msgid="826951152254563827">"Aktivera svepskrivning"</string>
@@ -143,13 +143,15 @@
     <string name="subtype_with_layout_en_GB" msgid="2179097748724725906">"Engelskt (brittiskt) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_en_US" msgid="1362581347576714579">"Engelskt (amerikanskt) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_es_US" msgid="6261791057007890189">"spanska (USA (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
-    <string name="subtype_no_language" msgid="141420857808801746">"Inget språk"</string>
-    <string name="subtype_no_language_qwerty" msgid="2956121451616633133">"Inget språk (QWERTY)"</string>
-    <string name="subtype_no_language_qwertz" msgid="1177848172397202890">"Inget språk (QWERTZ)"</string>
-    <string name="subtype_no_language_azerty" msgid="8721460968141187394">"Inget språk (AZERTY)"</string>
-    <string name="subtype_no_language_dvorak" msgid="3122976737669823935">"Inget språk (Dvorak)"</string>
-    <string name="subtype_no_language_colemak" msgid="4205992994906097244">"Inget språk (Colemak)"</string>
-    <string name="subtype_no_language_pcqwerty" msgid="8840928374394180189">"Inget språk (PC)"</string>
+    <string name="subtype_nepali_traditional" msgid="9032247506728040447">"<xliff:g id="LANGUAGE">%s</xliff:g> (traditionell)"</string>
+    <string name="subtype_no_language" msgid="7137390094240139495">"Inget språk (alfabet)"</string>
+    <string name="subtype_no_language_qwerty" msgid="244337630616742604">"Alfabet (QWERTY)"</string>
+    <string name="subtype_no_language_qwertz" msgid="443066912507547976">"Alfabet (QWERTZ)"</string>
+    <string name="subtype_no_language_azerty" msgid="8144348527575640087">"Alfabet (AZERTY)"</string>
+    <string name="subtype_no_language_dvorak" msgid="1564494667584718094">"Alfabet (Dvorak)"</string>
+    <string name="subtype_no_language_colemak" msgid="5837418400010302623">"Alfabet (Colemak)"</string>
+    <string name="subtype_no_language_pcqwerty" msgid="5354918232046200018">"Alfabet (PC)"</string>
+    <string name="subtype_emoji" msgid="7483586578074549196">"Humörsymbol"</string>
     <string name="custom_input_styles_title" msgid="8429952441821251512">"Anpassade indatastilar"</string>
     <string name="add_style" msgid="6163126614514489951">"Ny stil"</string>
     <string name="add" msgid="8299699805688017798">"Lägg till"</string>
diff --git a/java/res/values-sw/strings.xml b/java/res/values-sw/strings.xml
index 3891cf2..188bbca 100644
--- a/java/res/values-sw/strings.xml
+++ b/java/res/values-sw/strings.xml
@@ -64,8 +64,8 @@
     <string name="auto_correction_summary" msgid="5625751551134658006">"Kiaamba na kiakifishi hurekebisha maneno ambayo yamechapishwa vibaya"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Zima"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Ya wastani"</string>
-    <string name="auto_correction_threshold_mode_aggressive" msgid="3524029103734923819">"Ya hima"</string>
-    <string name="auto_correction_threshold_mode_very_aggressive" msgid="3386782235540547678">"Changamfu zaidi"</string>
+    <string name="auto_correction_threshold_mode_aggressive" msgid="7319007299148899623">"Linalokaribia"</string>
+    <string name="auto_correction_threshold_mode_very_aggressive" msgid="1853309024129480416">"Linalokaribia sana"</string>
     <string name="bigram_prediction" msgid="1084449187723948550">"Mapendekezo ya neno lifuatalo"</string>
     <string name="bigram_prediction_summary" msgid="3896362682751109677">"Tumia nelo la awali katika kufanya mapendekezo"</string>
     <string name="gesture_input" msgid="826951152254563827">"Washa kuandika kwa ishara"</string>
@@ -143,13 +143,15 @@
     <string name="subtype_with_layout_en_GB" msgid="2179097748724725906">"Kiingereza (Uingereza) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_en_US" msgid="1362581347576714579">"Kiingereza (Marekani) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_es_US" msgid="6261791057007890189">"Kihispania (Marekani) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
-    <string name="subtype_no_language" msgid="141420857808801746">"Hakuna lugha"</string>
-    <string name="subtype_no_language_qwerty" msgid="2956121451616633133">"Hakuna lugha (QWERTY)"</string>
-    <string name="subtype_no_language_qwertz" msgid="1177848172397202890">"Hakuna lugha (QWERTZ)"</string>
-    <string name="subtype_no_language_azerty" msgid="8721460968141187394">"Hakuna lugha (AZERTY)"</string>
-    <string name="subtype_no_language_dvorak" msgid="3122976737669823935">"Hakuna lugha (Dvorak)"</string>
-    <string name="subtype_no_language_colemak" msgid="4205992994906097244">"Hakuna lugha (Colemak)"</string>
-    <string name="subtype_no_language_pcqwerty" msgid="8840928374394180189">"Hakuna lugha (PC)"</string>
+    <string name="subtype_nepali_traditional" msgid="9032247506728040447">"<xliff:g id="LANGUAGE">%s</xliff:g> (Asili)"</string>
+    <string name="subtype_no_language" msgid="7137390094240139495">"Hakuna lugha (Alfabeti)"</string>
+    <string name="subtype_no_language_qwerty" msgid="244337630616742604">"Alfabeti (QWERTY)"</string>
+    <string name="subtype_no_language_qwertz" msgid="443066912507547976">"Alfabeti (QWERTZ)"</string>
+    <string name="subtype_no_language_azerty" msgid="8144348527575640087">"Alfabeti (AZERTY)"</string>
+    <string name="subtype_no_language_dvorak" msgid="1564494667584718094">"Alfabeti (Dvorak)"</string>
+    <string name="subtype_no_language_colemak" msgid="5837418400010302623">"Alfabeti (Colemak)"</string>
+    <string name="subtype_no_language_pcqwerty" msgid="5354918232046200018">"Alfabeti (PC)"</string>
+    <string name="subtype_emoji" msgid="7483586578074549196">"Emoji"</string>
     <string name="custom_input_styles_title" msgid="8429952441821251512">"Mitindo maalum ya ingizo"</string>
     <string name="add_style" msgid="6163126614514489951">"Ongeza mtindo"</string>
     <string name="add" msgid="8299699805688017798">"Ongeza"</string>
diff --git a/java/res/values-sw600dp-land/dimens.xml b/java/res/values-sw600dp-land/dimens.xml
index 51c710f..730b7d8 100644
--- a/java/res/values-sw600dp-land/dimens.xml
+++ b/java/res/values-sw600dp-land/dimens.xml
@@ -24,20 +24,15 @@
     <dimen name="keyboardHeight">283.5dp</dimen>
     <fraction name="minKeyboardHeight">45%p</fraction>
 
-    <fraction name="keyboard_top_padding">2.444%p</fraction>
-    <fraction name="keyboard_bottom_padding">0.0%p</fraction>
-    <fraction name="key_bottom_gap">4.911%p</fraction>
-    <fraction name="key_horizontal_gap">1.284%p</fraction>
-
-    <fraction name="key_bottom_gap_stone">4.355%p</fraction>
-    <fraction name="key_horizontal_gap_stone">1.505%p</fraction>
-
+    <fraction name="keyboard_top_padding_gb">2.444%p</fraction>
+    <fraction name="keyboard_bottom_padding_gb">0.0%p</fraction>
     <fraction name="key_bottom_gap_gb">5.200%p</fraction>
     <fraction name="key_horizontal_gap_gb">1.447%p</fraction>
 
+    <fraction name="keyboard_top_padding_ics">2.727%p</fraction>
+    <fraction name="keyboard_bottom_padding_ics">0.0%p</fraction>
     <fraction name="key_bottom_gap_ics">4.5%p</fraction>
     <fraction name="key_horizontal_gap_ics">0.9%p</fraction>
-    <fraction name="keyboard_bottom_padding_ics">0.0%p</fraction>
 
     <dimen name="popup_key_height">81.9dp</dimen>
 
@@ -67,4 +62,11 @@
     <dimen name="gesture_floating_preview_text_offset">76dp</dimen>
     <dimen name="gesture_floating_preview_horizontal_padding">26dp</dimen>
     <dimen name="gesture_floating_preview_vertical_padding">17dp</dimen>
+
+    <!-- Emoji keyboard -->
+    <fraction name="emoji_keyboard_key_width">10%p</fraction>
+    <fraction name="emoji_keyboard_row_height">33%p</fraction>
+    <fraction name="emoji_keyboard_key_letter_size">95%p</fraction>
+    <integer name="emoji_keyboard_max_key_count">30</integer>
+
 </resources>
diff --git a/java/res/values-sw600dp/config.xml b/java/res/values-sw600dp/config.xml
index 8265651..93862a7 100644
--- a/java/res/values-sw600dp/config.xml
+++ b/java/res/values-sw600dp/config.xml
@@ -28,8 +28,6 @@
     <bool name="config_auto_correction_spacebar_led_enabled">false</bool>
     <!-- The language is never displayed if == 0, always displayed if < 0 -->
     <integer name="config_delay_before_fadeout_language_on_spacebar">1200</integer>
-    <!-- This configuration is the index of the array {@link KeyboardSwitcher.KEYBOARD_THEMES}. -->
-    <string name="config_default_keyboard_theme_index" translatable="false">5</string>
     <integer name="config_max_more_keys_column">5</integer>
     <!--
         Configuration for MainKeyboardView
diff --git a/java/res/values-sw600dp/dimens.xml b/java/res/values-sw600dp/dimens.xml
index 75b476c..2bcf2fa 100644
--- a/java/res/values-sw600dp/dimens.xml
+++ b/java/res/values-sw600dp/dimens.xml
@@ -27,27 +27,22 @@
 
     <dimen name="popup_key_height">63.0dp</dimen>
 
-    <fraction name="keyboard_top_padding">2.291%p</fraction>
-    <fraction name="keyboard_bottom_padding">0.0%p</fraction>
-    <fraction name="key_bottom_gap">3.750%p</fraction>
-    <fraction name="key_horizontal_gap">1.857%p</fraction>
-
-    <fraction name="key_bottom_gap_stone">3.75%p</fraction>
-    <fraction name="key_horizontal_gap_stone">1.602%p</fraction>
-
+    <fraction name="keyboard_top_padding_gb">2.291%p</fraction>
+    <fraction name="keyboard_bottom_padding_gb">0.0%p</fraction>
     <fraction name="key_bottom_gap_gb">4.625%p</fraction>
     <fraction name="key_horizontal_gap_gb">2.113%p</fraction>
 
+    <fraction name="keyboard_top_padding_ics">2.335%p</fraction>
+    <fraction name="keyboard_bottom_padding_ics">4.0%p</fraction>
     <fraction name="key_bottom_gap_ics">4.5%p</fraction>
     <fraction name="key_horizontal_gap_ics">1.565%p</fraction>
-    <fraction name="keyboard_bottom_padding_ics">4.0%p</fraction>
 
     <dimen name="more_keys_keyboard_key_horizontal_padding">6dp</dimen>
     <!-- Amount of allowance for selecting keys in a mini popup keyboard by sliding finger. -->
     <!-- popup_key_height x 1.2 -->
     <dimen name="more_keys_keyboard_slide_allowance">98.3dp</dimen>
     <!-- popup_key_height x -1.0 -->
-    <dimen name="more_keys_keyboard_vertical_correction">-81.9dp</dimen>
+    <dimen name="more_keys_keyboard_vertical_correction_gb">-81.9dp</dimen>
 
     <!-- left or right padding of label alignment -->
     <dimen name="key_label_horizontal_padding">6dp</dimen>
@@ -64,7 +59,7 @@
     <fraction name="key_preview_text_ratio">50%</fraction>
     <fraction name="spacebar_text_ratio">28.0%</fraction>
     <dimen name="key_preview_height">94.5dp</dimen>
-    <dimen name="key_preview_offset">16.0dp</dimen>
+    <dimen name="key_preview_offset_gb">16.0dp</dimen>
 
     <!-- For 5-row keyboard -->
     <fraction name="key_bottom_gap_5row">3.20%p</fraction>
@@ -93,4 +88,11 @@
     <dimen name="gesture_floating_preview_horizontal_padding">28dp</dimen>
     <dimen name="gesture_floating_preview_vertical_padding">19dp</dimen>
     <dimen name="gesture_floating_preview_round_radius">3dp</dimen>
+
+    <!-- Emoji keyboard -->
+    <fraction name="emoji_keyboard_key_width">12.5%p</fraction>
+    <fraction name="emoji_keyboard_row_height">33%p</fraction>
+    <fraction name="emoji_keyboard_key_letter_size">85%p</fraction>
+    <integer name="emoji_keyboard_max_key_count">24</integer>
+
 </resources>
diff --git a/java/res/values-sw600dp/touch-position-correction.xml b/java/res/values-sw600dp/touch-position-correction.xml
index f77d3ae..df07c12 100644
--- a/java/res/values-sw600dp/touch-position-correction.xml
+++ b/java/res/values-sw600dp/touch-position-correction.xml
@@ -37,7 +37,7 @@
     </string-array>
 
     <string-array
-        name="touch_position_correction_data_gingerbread"
+        name="touch_position_correction_data_gb"
         translatable="false"
     >
         <!-- The default touch position data (See com.android.inputmethod.keyboard.ProximityInfo)
@@ -48,7 +48,7 @@
     </string-array>
 
     <string-array
-        name="touch_position_correction_data_ice_cream_sandwich"
+        name="touch_position_correction_data_ics"
         translatable="false"
     >
         <!-- The default touch position data (See com.android.inputmethod.keyboard.ProximityInfo)
diff --git a/java/res/values-sw768dp-land/dimens.xml b/java/res/values-sw768dp-land/dimens.xml
index f4a57ff..1e2e1c6 100644
--- a/java/res/values-sw768dp-land/dimens.xml
+++ b/java/res/values-sw768dp-land/dimens.xml
@@ -24,15 +24,8 @@
     <dimen name="keyboardHeight">365.4dp</dimen>
     <fraction name="minKeyboardHeight">45%p</fraction>
 
-    <fraction name="keyboard_top_padding">1.896%p</fraction>
-    <fraction name="keyboard_bottom_padding">0.0%p</fraction>
-
-    <fraction name="key_bottom_gap">4.103%p</fraction>
-    <fraction name="key_horizontal_gap">1.034%p</fraction>
-
-    <fraction name="key_bottom_gap_stone">3.379%p</fraction>
-    <fraction name="key_horizontal_gap_stone">1.062%p</fraction>
-
+    <fraction name="keyboard_top_padding_gb">1.896%p</fraction>
+    <fraction name="keyboard_bottom_padding_gb">0.0%p</fraction>
     <fraction name="key_bottom_gap_gb">3.896%p</fraction>
     <fraction name="key_horizontal_gap_gb">1.195%p</fraction>
 
@@ -70,4 +63,11 @@
     <dimen name="gesture_floating_preview_text_offset">100dp</dimen>
     <dimen name="gesture_floating_preview_horizontal_padding">32dp</dimen>
     <dimen name="gesture_floating_preview_vertical_padding">21dp</dimen>
+
+    <!-- Emoji keyboard -->
+    <fraction name="emoji_keyboard_key_width">7.69%p</fraction>
+    <fraction name="emoji_keyboard_row_height">33%p</fraction>
+    <fraction name="emoji_keyboard_key_letter_size">75%p</fraction>
+    <integer name="emoji_keyboard_max_key_count">39</integer>
+
 </resources>
diff --git a/java/res/values-sw768dp/config.xml b/java/res/values-sw768dp/config.xml
index 97f11cb..e1c07d6 100644
--- a/java/res/values-sw768dp/config.xml
+++ b/java/res/values-sw768dp/config.xml
@@ -26,8 +26,6 @@
     <bool name="config_default_key_preview_popup">false</bool>
     <bool name="config_default_sound_enabled">true</bool>
     <bool name="config_auto_correction_spacebar_led_enabled">false</bool>
-    <!-- This configuration is the index of the array {@link KeyboardSwitcher.KEYBOARD_THEMES}. -->
-    <string name="config_default_keyboard_theme_index" translatable="false">5</string>
     <integer name="config_max_more_keys_column">5</integer>
     <!--
         Configuration for MainKeyboardView
diff --git a/java/res/values-sw768dp/dimens.xml b/java/res/values-sw768dp/dimens.xml
index 91251f5..f62a536 100644
--- a/java/res/values-sw768dp/dimens.xml
+++ b/java/res/values-sw768dp/dimens.xml
@@ -25,18 +25,12 @@
     <fraction name="maxKeyboardHeight">46%p</fraction>
     <fraction name="minKeyboardHeight">-35.0%p</fraction>
 
-    <fraction name="keyboard_top_padding">2.291%p</fraction>
-    <fraction name="keyboard_bottom_padding">0.0%p</fraction>
-
-    <fraction name="key_bottom_gap">4.270%p</fraction>
-    <fraction name="key_horizontal_gap">1.551%p</fraction>
-
-    <fraction name="key_bottom_gap_stone">3.75%p</fraction>
-    <fraction name="key_horizontal_gap_stone">1.059%p</fraction>
-
+    <fraction name="keyboard_top_padding_gb">2.291%p</fraction>
+    <fraction name="keyboard_bottom_padding_gb">0.0%p</fraction>
     <fraction name="key_bottom_gap_gb">4.687%p</fraction>
     <fraction name="key_horizontal_gap_gb">1.272%p</fraction>
 
+    <fraction name="keyboard_top_padding_ics">2.335%p</fraction>
     <fraction name="keyboard_bottom_padding_ics">0.0%p</fraction>
     <fraction name="key_bottom_gap_ics">3.312%p</fraction>
     <fraction name="key_horizontal_gap_ics">1.066%p</fraction>
@@ -48,7 +42,7 @@
     <!-- popup_key_height x 1.2 -->
     <dimen name="more_keys_keyboard_slide_allowance">98.3dp</dimen>
     <!-- popup_key_height x -1.0 -->
-    <dimen name="more_keys_keyboard_vertical_correction">-81.9dp</dimen>
+    <dimen name="more_keys_keyboard_vertical_correction_gb">-81.9dp</dimen>
 
     <!-- left or right padding of label alignment -->
     <dimen name="key_label_horizontal_padding">6dp</dimen>
@@ -65,7 +59,7 @@
     <fraction name="key_preview_text_ratio">50%</fraction>
     <fraction name="spacebar_text_ratio">29.03%</fraction>
     <dimen name="key_preview_height">94.5dp</dimen>
-    <dimen name="key_preview_offset">16.0dp</dimen>
+    <dimen name="key_preview_offset_gb">16.0dp</dimen>
 
     <!-- For 5-row keyboard -->
     <fraction name="key_bottom_gap_5row">2.95%p</fraction>
@@ -94,4 +88,11 @@
     <dimen name="gesture_floating_preview_horizontal_padding">26dp</dimen>
     <dimen name="gesture_floating_preview_vertical_padding">17dp</dimen>
     <dimen name="gesture_floating_preview_round_radius">3dp</dimen>
+
+    <!-- Emoji keyboard -->
+    <fraction name="emoji_keyboard_key_width">10%p</fraction>
+    <fraction name="emoji_keyboard_row_height">33%p</fraction>
+    <fraction name="emoji_keyboard_key_letter_size">85%p</fraction>
+    <integer name="emoji_keyboard_max_key_count">30</integer>
+
 </resources>
diff --git a/java/res/xml-sw768dp/kbd_more_keys_keyboard_template.xml b/java/res/values-th/donottranslate.xml
similarity index 68%
copy from java/res/xml-sw768dp/kbd_more_keys_keyboard_template.xml
copy to java/res/values-th/donottranslate.xml
index f89a0a6..a9893fe 100644
--- a/java/res/xml-sw768dp/kbd_more_keys_keyboard_template.xml
+++ b/java/res/values-th/donottranslate.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2008, The Android Open Source Project
+** 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.
@@ -17,10 +17,7 @@
 ** limitations under the License.
 */
 -->
-
-<Keyboard xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyWidth="5.0%p"
-    latin:rowHeight="@dimen/popup_key_height"
-    style="?attr/moreKeysKeyboardStyle"
-    >
-</Keyboard>
+<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-th/strings.xml b/java/res/values-th/strings.xml
index 9d407dd..632f39a 100644
--- a/java/res/values-th/strings.xml
+++ b/java/res/values-th/strings.xml
@@ -64,8 +64,8 @@
     <string name="auto_correction_summary" msgid="5625751551134658006">"กดเว้นวรรคและเครื่องหมายจะแก้คำผิดอัตโนมัติ"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"ปิด"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"ปานกลาง"</string>
-    <string name="auto_correction_threshold_mode_aggressive" msgid="3524029103734923819">"เข้มงวด"</string>
-    <string name="auto_correction_threshold_mode_very_aggressive" msgid="3386782235540547678">"เข้มงวดมาก"</string>
+    <string name="auto_correction_threshold_mode_aggressive" msgid="7319007299148899623">"เข้มงวด"</string>
+    <string name="auto_correction_threshold_mode_very_aggressive" msgid="1853309024129480416">"เข้มงวดมาก"</string>
     <string name="bigram_prediction" msgid="1084449187723948550">"คำแนะนำสำหรับคำถัดไป"</string>
     <string name="bigram_prediction_summary" msgid="3896362682751109677">"ใช้คำก่อนหน้าในการสร้างข้อเสนอแนะ"</string>
     <string name="gesture_input" msgid="826951152254563827">"เปิดการพิมพ์ด้วยท่าทางสัมผัส"</string>
@@ -143,13 +143,15 @@
     <string name="subtype_with_layout_en_GB" msgid="2179097748724725906">"อังกฤษ (สหราชอาณาจักร) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_en_US" msgid="1362581347576714579">"อังกฤษ (สหรัฐอเมริกา) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_es_US" msgid="6261791057007890189">"สเปน (สหรัฐอเมริกา) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
-    <string name="subtype_no_language" msgid="141420857808801746">"ไม่มีภาษา"</string>
-    <string name="subtype_no_language_qwerty" msgid="2956121451616633133">"ไม่มีภาษา (QWERTY)"</string>
-    <string name="subtype_no_language_qwertz" msgid="1177848172397202890">"ไม่มีภาษา (QWERTZ)"</string>
-    <string name="subtype_no_language_azerty" msgid="8721460968141187394">"ไม่มีภาษา (AZERTY)"</string>
-    <string name="subtype_no_language_dvorak" msgid="3122976737669823935">"ไม่มีภาษา (Dvorak)"</string>
-    <string name="subtype_no_language_colemak" msgid="4205992994906097244">"ไม่มีภาษา (Colemak)"</string>
-    <string name="subtype_no_language_pcqwerty" msgid="8840928374394180189">"ไม่มีภาษา (PC)"</string>
+    <string name="subtype_nepali_traditional" msgid="9032247506728040447">"<xliff:g id="LANGUAGE">%s</xliff:g> (ดั้งเดิม)"</string>
+    <string name="subtype_no_language" msgid="7137390094240139495">"ไม่มีภาษา (ตัวอักษรละติน)"</string>
+    <string name="subtype_no_language_qwerty" msgid="244337630616742604">"ตัวอักษร (QWERTY)"</string>
+    <string name="subtype_no_language_qwertz" msgid="443066912507547976">"ตัวอักษร (QWERTZ)"</string>
+    <string name="subtype_no_language_azerty" msgid="8144348527575640087">"ตัวอักษร (AZERTY)"</string>
+    <string name="subtype_no_language_dvorak" msgid="1564494667584718094">"ตัวอักษร (Dvorak)"</string>
+    <string name="subtype_no_language_colemak" msgid="5837418400010302623">"ตัวอักษร (Colemak)"</string>
+    <string name="subtype_no_language_pcqwerty" msgid="5354918232046200018">"ตัวอักษร (PC)"</string>
+    <string name="subtype_emoji" msgid="7483586578074549196">"อีโมจิ"</string>
     <string name="custom_input_styles_title" msgid="8429952441821251512">"รูปแบบอินพุตกำหนดเอง"</string>
     <string name="add_style" msgid="6163126614514489951">"เพิ่มสไตล์"</string>
     <string name="add" msgid="8299699805688017798">"เพิ่ม"</string>
diff --git a/java/res/values-tl/strings.xml b/java/res/values-tl/strings.xml
index ee5d886..3ec962b 100644
--- a/java/res/values-tl/strings.xml
+++ b/java/res/values-tl/strings.xml
@@ -64,8 +64,8 @@
     <string name="auto_correction_summary" msgid="5625751551134658006">"Awto tinatama ng spacebar at bantas ang maling na-type"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Naka-off"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Modest"</string>
-    <string name="auto_correction_threshold_mode_aggressive" msgid="3524029103734923819">"Agresibo"</string>
-    <string name="auto_correction_threshold_mode_very_aggressive" msgid="3386782235540547678">"Napaka-agresibo"</string>
+    <string name="auto_correction_threshold_mode_aggressive" msgid="7319007299148899623">"Agresibo"</string>
+    <string name="auto_correction_threshold_mode_very_aggressive" msgid="1853309024129480416">"Napaka-agresibo"</string>
     <string name="bigram_prediction" msgid="1084449187723948550">"Mga suhestiyon sa susunod na salita"</string>
     <string name="bigram_prediction_summary" msgid="3896362682751109677">"Gamitin ang nakaraang salita sa paggawa ng mga suhestiyon"</string>
     <string name="gesture_input" msgid="826951152254563827">"Paganahin ang gesture na pag-type"</string>
@@ -143,13 +143,15 @@
     <string name="subtype_with_layout_en_GB" msgid="2179097748724725906">"Ingles (UK) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_en_US" msgid="1362581347576714579">"Ingles (US) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_es_US" msgid="6261791057007890189">"Spanish (US) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
-    <string name="subtype_no_language" msgid="141420857808801746">"Walang wika"</string>
-    <string name="subtype_no_language_qwerty" msgid="2956121451616633133">"Walang wika (QWERTY)"</string>
-    <string name="subtype_no_language_qwertz" msgid="1177848172397202890">"Walang wika (QWERTZ)"</string>
-    <string name="subtype_no_language_azerty" msgid="8721460968141187394">"Walang wika (AZERTY)"</string>
-    <string name="subtype_no_language_dvorak" msgid="3122976737669823935">"Walang wika (Dvorak)"</string>
-    <string name="subtype_no_language_colemak" msgid="4205992994906097244">"Walang wika (Colemak)"</string>
-    <string name="subtype_no_language_pcqwerty" msgid="8840928374394180189">"Walang wika (PC)"</string>
+    <string name="subtype_nepali_traditional" msgid="9032247506728040447">"<xliff:g id="LANGUAGE">%s</xliff:g> (Traditional)"</string>
+    <string name="subtype_no_language" msgid="7137390094240139495">"Walang wika (Alpabeto)"</string>
+    <string name="subtype_no_language_qwerty" msgid="244337630616742604">"Alpabeto (QWERTY)"</string>
+    <string name="subtype_no_language_qwertz" msgid="443066912507547976">"Alpabeto (QWERTZ)"</string>
+    <string name="subtype_no_language_azerty" msgid="8144348527575640087">"Alpabeto (AZERTY)"</string>
+    <string name="subtype_no_language_dvorak" msgid="1564494667584718094">"Alpabeto (Dvorak)"</string>
+    <string name="subtype_no_language_colemak" msgid="5837418400010302623">"Alpabeto (Colemak)"</string>
+    <string name="subtype_no_language_pcqwerty" msgid="5354918232046200018">"Alpabeto (PC)"</string>
+    <string name="subtype_emoji" msgid="7483586578074549196">"Emoji"</string>
     <string name="custom_input_styles_title" msgid="8429952441821251512">"Custom style ng input"</string>
     <string name="add_style" msgid="6163126614514489951">"Dagdag style"</string>
     <string name="add" msgid="8299699805688017798">"Idagdag"</string>
diff --git a/java/res/values-tr/strings.xml b/java/res/values-tr/strings.xml
index 2e7fa43..4d0a617 100644
--- a/java/res/values-tr/strings.xml
+++ b/java/res/values-tr/strings.xml
@@ -64,8 +64,8 @@
     <string name="auto_correction_summary" msgid="5625751551134658006">"Boşluk tuşu ve noktalama işaretleri yanlış yazılan kelimeleri otomatikman düzeltir"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Kapalı"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Ölçülü"</string>
-    <string name="auto_correction_threshold_mode_aggressive" msgid="3524029103734923819">"Agresif"</string>
-    <string name="auto_correction_threshold_mode_very_aggressive" msgid="3386782235540547678">"Çok geniş ölçekte"</string>
+    <string name="auto_correction_threshold_mode_aggressive" msgid="7319007299148899623">"Geniş ölçekte"</string>
+    <string name="auto_correction_threshold_mode_very_aggressive" msgid="1853309024129480416">"Çok geniş ölçekte"</string>
     <string name="bigram_prediction" msgid="1084449187723948550">"Sonraki kelime önerileri"</string>
     <string name="bigram_prediction_summary" msgid="3896362682751109677">"Önerilerde bulunurken önceki kelimeyi kullan"</string>
     <string name="gesture_input" msgid="826951152254563827">"Hareketle yazmayı etkinleştir"</string>
@@ -143,13 +143,15 @@
     <string name="subtype_with_layout_en_GB" msgid="2179097748724725906">"İngilizce (İngiltere) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_en_US" msgid="1362581347576714579">"İngilizce (ABD) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_es_US" msgid="6261791057007890189">"İspanyolca (ABD) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
-    <string name="subtype_no_language" msgid="141420857808801746">"Dil yok"</string>
-    <string name="subtype_no_language_qwerty" msgid="2956121451616633133">"Dil yok (QWERTY)"</string>
-    <string name="subtype_no_language_qwertz" msgid="1177848172397202890">"Dil yok (QWERTZ)"</string>
-    <string name="subtype_no_language_azerty" msgid="8721460968141187394">"Dil yok (AZERTY)"</string>
-    <string name="subtype_no_language_dvorak" msgid="3122976737669823935">"Dil yok (Dvorak)"</string>
-    <string name="subtype_no_language_colemak" msgid="4205992994906097244">"Dil yok (Colemak)"</string>
-    <string name="subtype_no_language_pcqwerty" msgid="8840928374394180189">"Dil yok (PC)"</string>
+    <string name="subtype_nepali_traditional" msgid="9032247506728040447">"<xliff:g id="LANGUAGE">%s</xliff:g> (Geleneksel)"</string>
+    <string name="subtype_no_language" msgid="7137390094240139495">"Dil yok (Alfabe)"</string>
+    <string name="subtype_no_language_qwerty" msgid="244337630616742604">"Alfabe (QWERTY)"</string>
+    <string name="subtype_no_language_qwertz" msgid="443066912507547976">"Alfabe (QWERTZ)"</string>
+    <string name="subtype_no_language_azerty" msgid="8144348527575640087">"Alfabe (AZERTY)"</string>
+    <string name="subtype_no_language_dvorak" msgid="1564494667584718094">"Alfabe (Dvorak)"</string>
+    <string name="subtype_no_language_colemak" msgid="5837418400010302623">"Alfabe (Colemak)"</string>
+    <string name="subtype_no_language_pcqwerty" msgid="5354918232046200018">"Alfabe (PC)"</string>
+    <string name="subtype_emoji" msgid="7483586578074549196">"Emoji"</string>
     <string name="custom_input_styles_title" msgid="8429952441821251512">"Özel giriş stilleri"</string>
     <string name="add_style" msgid="6163126614514489951">"Stil ekle"</string>
     <string name="add" msgid="8299699805688017798">"Ekle"</string>
diff --git a/java/res/values-uk/strings.xml b/java/res/values-uk/strings.xml
index fdc7cb9..df62f32 100644
--- a/java/res/values-uk/strings.xml
+++ b/java/res/values-uk/strings.xml
@@ -64,8 +64,8 @@
     <string name="auto_correction_summary" msgid="5625751551134658006">"Пробіл і пунктуація автоматично виправляють слова з помилками"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Вимк."</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Помірне"</string>
-    <string name="auto_correction_threshold_mode_aggressive" msgid="3524029103734923819">"Активне"</string>
-    <string name="auto_correction_threshold_mode_very_aggressive" msgid="3386782235540547678">"Дуже активне"</string>
+    <string name="auto_correction_threshold_mode_aggressive" msgid="7319007299148899623">"Активне"</string>
+    <string name="auto_correction_threshold_mode_very_aggressive" msgid="1853309024129480416">"Дуже активне"</string>
     <string name="bigram_prediction" msgid="1084449187723948550">"Пропозиції наступного слова"</string>
     <string name="bigram_prediction_summary" msgid="3896362682751109677">"Використовувати попереднє слово, щоб надавати пропозиції"</string>
     <string name="gesture_input" msgid="826951152254563827">"Увімкнути ввід жестами"</string>
@@ -143,13 +143,15 @@
     <string name="subtype_with_layout_en_GB" msgid="2179097748724725906">"Англійська (Великобр.) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_en_US" msgid="1362581347576714579">"Англійська (США) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_es_US" msgid="6261791057007890189">"іспанська (США) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
-    <string name="subtype_no_language" msgid="141420857808801746">"Мову не вибрано"</string>
-    <string name="subtype_no_language_qwerty" msgid="2956121451616633133">"QWERTY-клавіатура"</string>
-    <string name="subtype_no_language_qwertz" msgid="1177848172397202890">"Без мови (QWERTZ)"</string>
-    <string name="subtype_no_language_azerty" msgid="8721460968141187394">"Без мови (AZERTY)"</string>
-    <string name="subtype_no_language_dvorak" msgid="3122976737669823935">"Без мови (Dvorak)"</string>
-    <string name="subtype_no_language_colemak" msgid="4205992994906097244">"Без мови (Colemak)"</string>
-    <string name="subtype_no_language_pcqwerty" msgid="8840928374394180189">"Без мови (ПК)"</string>
+    <string name="subtype_nepali_traditional" msgid="9032247506728040447">"<xliff:g id="LANGUAGE">%s</xliff:g> (традиційна)"</string>
+    <string name="subtype_no_language" msgid="7137390094240139495">"Стандартна (латиниця)"</string>
+    <string name="subtype_no_language_qwerty" msgid="244337630616742604">"Латиниця (QWERTY)"</string>
+    <string name="subtype_no_language_qwertz" msgid="443066912507547976">"Латиниця (QWERTZ)"</string>
+    <string name="subtype_no_language_azerty" msgid="8144348527575640087">"Латиниця (AZERTY)"</string>
+    <string name="subtype_no_language_dvorak" msgid="1564494667584718094">"Латиниця (Dvorak)"</string>
+    <string name="subtype_no_language_colemak" msgid="5837418400010302623">"Латиниця (Colemak)"</string>
+    <string name="subtype_no_language_pcqwerty" msgid="5354918232046200018">"Латиниця (ПК)"</string>
+    <string name="subtype_emoji" msgid="7483586578074549196">"Cмайли Emoji"</string>
     <string name="custom_input_styles_title" msgid="8429952441821251512">"Персональні стилі введення"</string>
     <string name="add_style" msgid="6163126614514489951">"Додати стиль"</string>
     <string name="add" msgid="8299699805688017798">"Додати"</string>
diff --git a/java/res/values-v18/emoji-categories.xml b/java/res/values-v18/emoji-categories.xml
new file mode 100644
index 0000000..2ea0815
--- /dev/null
+++ b/java/res/values-v18/emoji-categories.xml
@@ -0,0 +1,909 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<!-- Note: This emoji code point list is valid on JB-MR2 (API == 18).
+     There is another emoji code point list for KLP and later under res/xml/values-v19. -->
+<resources>
+    <array
+        name="emoji_nature"
+        format="string"
+    >
+        <!-- <item>1f415</item> -->
+        <item>1f436</item>
+        <item>1f429</item>
+        <!-- <item>1f408</item> -->
+        <item>1f431</item>
+        <!-- <item>1f400</item> -->
+        <!-- <item>1f401</item> -->
+        <item>1f42d</item>
+        <item>1f439</item>
+        <item>1f422</item>
+        <!-- <item>1f407</item> -->
+        <item>1f430</item>
+        <!-- <item>1f413</item> -->
+        <item>1f414</item>
+        <item>1f423</item>
+        <item>1f424</item>
+        <item>1f425</item>
+        <item>1f426</item>
+        <!-- <item>1f40f</item> -->
+        <item>1f411</item>
+        <!-- <item>1f410</item> -->
+        <item>1f43a</item>
+        <!-- <item>1f403</item> -->
+        <!-- <item>1f402</item> -->
+        <!-- <item>1f404</item> -->
+        <item>1f42e</item>
+        <item>1f434</item>
+        <item>1f417</item>
+        <!-- <item>1f416</item> -->
+        <item>1f437</item>
+        <item>1f43d</item>
+        <item>1f438</item>
+        <item>1f40d</item>
+        <item>1f43c</item>
+        <item>1f427</item>
+        <item>1f418</item>
+        <item>1f428</item>
+        <item>1f412</item>
+        <item>1f435</item>
+        <!-- <item>1f406</item> -->
+        <item>1f42f</item>
+        <item>1f43b</item>
+        <item>1f42b</item>
+        <!-- <item>1f42a</item> -->
+        <!-- <item>1f40a</item> -->
+        <item>1f433</item>
+        <!-- <item>1f40b</item> -->
+        <item>1f41f</item>
+        <item>1f420</item>
+        <item>1f421</item>
+        <item>1f419</item>
+        <item>1f41a</item>
+        <item>1f42c</item>
+        <item>1f40c</item>
+        <item>1f41b</item>
+        <item>1f41c</item>
+        <item>1f41d</item>
+        <item>1f41e</item>
+        <item>1f432</item>
+        <!-- <item>1f409</item> -->
+        <item>1f43e</item>
+        <item>1f378</item>
+        <item>1f37a</item>
+        <item>1f37b</item>
+        <item>1f377</item>
+        <item>1f379</item>
+        <item>1f376</item>
+        <!-- <item>2615</item> -->
+        <item>1f375</item>
+        <!-- <item>1f37c</item> -->
+        <item>1f374</item>
+        <item>1f368</item>
+        <item>1f367</item>
+        <item>1f366</item>
+        <item>1f369</item>
+        <item>1f370</item>
+        <item>1f36a</item>
+        <item>1f36b</item>
+        <item>1f36c</item>
+        <item>1f36d</item>
+        <item>1f36e</item>
+        <item>1f36f</item>
+        <item>1f373</item>
+        <item>1f354</item>
+        <item>1f35f</item>
+        <item>1f35d</item>
+        <item>1f355</item>
+        <item>1f356</item>
+        <item>1f357</item>
+        <item>1f364</item>
+        <item>1f363</item>
+        <item>1f371</item>
+        <item>1f35e</item>
+        <item>1f35c</item>
+        <item>1f359</item>
+        <item>1f35a</item>
+        <item>1f35b</item>
+        <item>1f372</item>
+        <item>1f365</item>
+        <item>1f362</item>
+        <item>1f361</item>
+        <item>1f358</item>
+        <item>1f360</item>
+        <item>1f34c</item>
+        <item>1f34e</item>
+        <item>1f34f</item>
+        <item>1f34a</item>
+        <!-- <item>1f34b</item> -->
+        <item>1f344</item>
+        <item>1f345</item>
+        <item>1f346</item>
+        <item>1f347</item>
+        <item>1f348</item>
+        <item>1f349</item>
+        <!-- <item>1f350</item> -->
+        <item>1f351</item>
+        <item>1f352</item>
+        <item>1f353</item>
+        <item>1f34d</item>
+        <item>1f330</item>
+        <item>1f331</item>
+        <!-- <item>1f332</item> -->
+        <!-- <item>1f333</item> -->
+        <item>1f334</item>
+        <item>1f335</item>
+        <item>1f337</item>
+        <item>1f338</item>
+        <item>1f339</item>
+        <item>1f340</item>
+        <item>1f341</item>
+        <item>1f342</item>
+        <item>1f343</item>
+        <item>1f33a</item>
+        <item>1f33b</item>
+        <item>1f33c</item>
+        <item>1f33d</item>
+        <item>1f33e</item>
+        <item>1f33f</item>
+        <item>2600</item>
+        <item>1f308</item>
+        <item>26c5</item>
+        <item>2601</item>
+        <item>1f301</item>
+        <item>1f302</item>
+        <!-- <item>2614</item> -->
+        <item>1f4a7</item>
+        <item>26a1</item>
+        <item>1f300</item>
+        <item>2744</item>
+        <item>26c4</item>
+        <item>1f319</item>
+        <!-- <item>1f31e</item> -->
+        <!-- <item>1f31d</item> -->
+        <!-- <item>1f31a</item> -->
+        <item>1f31b</item>
+        <!-- <item>1f31c</item> -->
+        <item>1f311</item>
+        <!-- <item>1f312</item> -->
+        <item>1f313</item>
+        <item>1f314</item>
+        <item>1f315</item>
+        <!-- <item>1f316</item> -->
+        <!-- <item>1f317</item> -->
+        <!-- <item>1f318</item> -->
+        <item>1f391</item>
+        <item>1f304</item>
+        <item>1f305</item>
+        <item>1f307</item>
+        <item>1f306</item>
+        <item>1f303</item>
+        <item>1f30c</item>
+        <item>1f309</item>
+        <item>1f30a</item>
+        <item>1f30b</item>
+        <!-- <item>1f30e</item> -->
+        <item>1f30f</item>
+        <!-- <item>1f30d</item> -->
+        <!-- <item>1f310</item> -->
+    </array>
+    <array
+        name="emoji_symbols"
+        format="string"
+    >
+        <!-- <item>fe82e|0031,20e3</item> -->
+        <!-- <item>fe82f|0032,20e3</item> -->
+        <!-- <item>fe830|0033,20e3</item> -->
+        <!-- <item>fe831|0034,20e3</item> -->
+        <!-- <item>fe832|0035,20e3</item> -->
+        <!-- <item>fe833|0036,20e3</item> -->
+        <!-- <item>fe834|0037,20e3</item> -->
+        <!-- <item>fe835|0038,20e3</item> -->
+        <!-- <item>fe836|0039,20e3</item> -->
+        <!-- <item>fe837|0030,20e3</item> -->
+        <!-- <item>1f51f</item> -->
+        <!-- <item>fe82c|0023,20e3</item> -->
+        <item>1f51d</item>
+        <item>1f519</item>
+        <item>1f51b</item>
+        <item>1f51c</item>
+        <item>1f51a</item>
+        <item>23f3</item>
+        <item>231b</item>
+        <item>23f0</item>
+        <item>2648</item>
+        <item>2649</item>
+        <item>264a</item>
+        <item>264b</item>
+        <item>264c</item>
+        <item>264d</item>
+        <item>264e</item>
+        <item>264f</item>
+        <item>2650</item>
+        <item>2651</item>
+        <item>2652</item>
+        <item>2653</item>
+        <item>26ce</item>
+        <item>1f531</item>
+        <item>1f52f</item>
+        <item>1f6bb</item>
+        <!-- <item>1f6ae</item> -->
+        <!-- <item>1f6af</item> -->
+        <!-- <item>1f6b0</item> -->
+        <!-- <item>1f6b1</item> -->
+        <item>1f170</item>
+        <item>1f171</item>
+        <item>1f18e</item>
+        <item>1f17e</item>
+        <item>1f4ae</item>
+        <item>1f4af</item>
+        <item>1f520</item>
+        <item>1f521</item>
+        <item>1f522</item>
+        <item>1f523</item>
+        <item>1f524</item>
+        <item>27bf</item>
+        <item>1f4f6</item>
+        <item>1f4f3</item>
+        <item>1f4f4</item>
+        <!-- <item>1f4f5</item> -->
+        <item>1f6b9</item>
+        <item>1f6ba</item>
+        <item>1f6bc</item>
+        <item>267f</item>
+        <item>267b</item>
+        <item>1f6ad</item>
+        <item>1f6a9</item>
+        <item>26a0</item>
+        <item>1f201</item>
+        <item>1f51e</item>
+        <item>26d4</item>
+        <item>1f192</item>
+        <item>1f197</item>
+        <item>1f195</item>
+        <item>1f198</item>
+        <item>1f199</item>
+        <item>1f193</item>
+        <item>1f196</item>
+        <item>1f19a</item>
+        <item>1f232</item>
+        <item>1f233</item>
+        <item>1f234</item>
+        <item>1f235</item>
+        <item>1f236</item>
+        <item>1f237</item>
+        <item>1f238</item>
+        <item>1f239</item>
+        <item>1f202</item>
+        <item>1f23a</item>
+        <item>1f250</item>
+        <item>1f251</item>
+        <item>3299</item>
+        <item>00ae</item>
+        <item>00a9</item>
+        <item>2122</item>
+        <item>1f21a</item>
+        <item>1f22f</item>
+        <item>3297</item>
+        <item>2b55</item>
+        <item>274c</item>
+        <item>274e</item>
+        <item>2139</item>
+        <item>1f6ab</item>
+        <item>2705</item>
+        <item>2714</item>
+        <item>1f517</item>
+        <item>2734</item>
+        <item>2733</item>
+        <item>2795</item>
+        <item>2796</item>
+        <item>2716</item>
+        <item>2797</item>
+        <item>1f4a0</item>
+        <item>1f4a1</item>
+        <item>1f4a4</item>
+        <item>1f4a2</item>
+        <item>1f525</item>
+        <item>1f4a5</item>
+        <item>1f4a8</item>
+        <item>1f4a6</item>
+        <item>1f4ab</item>
+        <item>1f55b</item>
+        <!-- <item>1f567</item> -->
+        <item>1f550</item>
+        <!-- <item>1f55c</item> -->
+        <item>1f551</item>
+        <!-- <item>1f55d</item> -->
+        <item>1f552</item>
+        <!-- <item>1f55e</item> -->
+        <item>1f553</item>
+        <!-- <item>1f55f</item> -->
+        <item>1f554</item>
+        <!-- <item>1f560</item> -->
+        <item>1f555</item>
+        <!-- <item>1f561</item> -->
+        <item>1f556</item>
+        <!-- <item>1f562</item> -->
+        <item>1f557</item>
+        <!-- <item>1f563</item> -->
+        <item>1f558</item>
+        <!-- <item>1f564</item> -->
+        <item>1f559</item>
+        <!-- <item>1f565</item> -->
+        <item>1f55a</item>
+        <!-- <item>1f566</item> -->
+        <item>2195</item>
+        <item>2b06</item>
+        <item>2197</item>
+        <item>27a1</item>
+        <item>2198</item>
+        <item>2b07</item>
+        <item>2199</item>
+        <item>2b05</item>
+        <item>2196</item>
+        <item>2194</item>
+        <item>2934</item>
+        <item>2935</item>
+        <item>23ea</item>
+        <item>23eb</item>
+        <item>23ec</item>
+        <item>23e9</item>
+        <item>25c0</item>
+        <item>25b6</item>
+        <item>1f53d</item>
+        <item>1f53c</item>
+        <item>2747</item>
+        <item>2728</item>
+        <item>1f534</item>
+        <item>1f535</item>
+        <item>26aa</item>
+        <item>26ab</item>
+        <item>1f533</item>
+        <item>1f532</item>
+        <item>2b50</item>
+        <item>1f31f</item>
+        <item>1f320</item>
+        <item>25ab</item>
+        <item>25aa</item>
+        <item>25fd</item>
+        <item>25fe</item>
+        <item>25fb</item>
+        <item>25fc</item>
+        <item>2b1c</item>
+        <item>2b1b</item>
+        <item>1f538</item>
+        <item>1f539</item>
+        <item>1f536</item>
+        <item>1f537</item>
+        <item>1f53a</item>
+        <item>1f53b</item>
+        <item>2754</item>
+        <item>2753</item>
+        <item>2755</item>
+        <item>2757</item>
+        <item>203c</item>
+        <item>2049</item>
+        <item>3030</item>
+        <item>27b0</item>
+        <item>2660</item>
+        <item>2665</item>
+        <item>2663</item>
+        <item>2666</item>
+        <item>1f194</item>
+        <item>1f511</item>
+        <item>21a9</item>
+        <item>1f191</item>
+        <item>1f50d</item>
+        <item>1f512</item>
+        <item>1f513</item>
+        <item>21aa</item>
+        <item>1f510</item>
+        <!-- <item>2611</item> -->
+        <item>1f518</item>
+        <item>1f50e</item>
+        <item>1f516</item>
+        <item>1f50f</item>
+        <item>1f503</item>
+        <!-- <item>1f500</item> -->
+        <!-- <item>1f501</item> -->
+        <!-- <item>1f502</item> -->
+        <!-- <item>1f504</item> -->
+        <item>1f4e7</item>
+        <!-- <item>1f505</item> -->
+        <!-- <item>1f506</item> -->
+        <!-- <item>1f507</item> -->
+        <!-- <item>1f508</item> -->
+        <!-- <item>1f509</item> -->
+        <item>1f50a</item>
+    </array>
+    <array
+        name="emoji_faces"
+        format="string"
+    >
+        <item>263a</item>
+        <item>1f60a</item>
+        <!-- <item>1f600</item> -->
+        <item>1f601</item>
+        <item>1f602</item>
+        <item>1f603</item>
+        <item>1f604</item>
+        <item>1f605</item>
+        <item>1f606</item>
+        <!-- <item>1f607</item> -->
+        <!-- <item>1f608</item> -->
+        <item>1f609</item>
+        <!-- <item>1f62f</item> -->
+        <!-- <item>1f610</item> -->
+        <!-- <item>1f611</item> -->
+        <!-- <item>1f615</item> -->
+        <item>1f620</item>
+        <!-- <item>1f62c</item> -->
+        <item>1f621</item>
+        <item>1f622</item>
+        <!-- <item>1f634</item> -->
+        <!-- <item>1f62e</item> -->
+        <item>1f623</item>
+        <item>1f624</item>
+        <item>1f625</item>
+        <!-- <item>1f626</item> -->
+        <!-- <item>1f627</item> -->
+        <item>1f628</item>
+        <item>1f629</item>
+        <item>1f630</item>
+        <!-- <item>1f61f</item> -->
+        <item>1f631</item>
+        <item>1f632</item>
+        <item>1f633</item>
+        <item>1f635</item>
+        <!-- <item>1f636</item> -->
+        <item>1f637</item>
+        <item>1f61e</item>
+        <item>1f612</item>
+        <item>1f60d</item>
+        <!-- <item>1f61b</item> -->
+        <item>1f61c</item>
+        <item>1f61d</item>
+        <item>1f60b</item>
+        <!-- <item>1f617</item> -->
+        <!-- <item>1f619</item> -->
+        <item>1f618</item>
+        <item>1f61a</item>
+        <!-- <item>1f60e</item> -->
+        <item>1f62d</item>
+        <item>1f60c</item>
+        <item>1f616</item>
+        <item>1f614</item>
+        <item>1f62a</item>
+        <item>1f60f</item>
+        <item>1f613</item>
+        <item>1f62b</item>
+        <item>1f64b</item>
+        <item>1f64c</item>
+        <item>1f64d</item>
+        <item>1f645</item>
+        <item>1f646</item>
+        <item>1f647</item>
+        <item>1f64e</item>
+        <item>1f64f</item>
+        <item>1f63a</item>
+        <item>1f63c</item>
+        <item>1f638</item>
+        <item>1f639</item>
+        <item>1f63b</item>
+        <item>1f63d</item>
+        <item>1f63f</item>
+        <item>1f63e</item>
+        <item>1f640</item>
+        <item>1f648</item>
+        <item>1f649</item>
+        <item>1f64a</item>
+        <item>1f4a9</item>
+        <item>1f476</item>
+        <item>1f466</item>
+        <item>1f467</item>
+        <item>1f468</item>
+        <item>1f469</item>
+        <item>1f474</item>
+        <item>1f475</item>
+        <item>1f48f</item>
+        <item>1f491</item>
+        <item>1f46a</item>
+        <item>1f46b</item>
+        <!-- <item>1f46c</item> -->
+        <!-- <item>1f46d</item> -->
+        <item>1f464</item>
+        <!-- <item>1f465</item> -->
+        <item>1f46e</item>
+        <item>1f477</item>
+        <item>1f481</item>
+        <item>1f482</item>
+        <item>1f46f</item>
+        <item>1f470</item>
+        <item>1f478</item>
+        <item>1f385</item>
+        <item>1f47c</item>
+        <!-- <item>1f471</item> -->
+        <!-- <item>1f472</item> -->
+        <!-- <item>1f473</item> -->
+        <item>1f483</item>
+        <item>1f486</item>
+        <item>1f487</item>
+        <item>1f485</item>
+        <item>1f47b</item>
+        <item>1f479</item>
+        <item>1f47a</item>
+        <item>1f47d</item>
+        <item>1f47e</item>
+        <item>1f47f</item>
+        <item>1f480</item>
+        <item>1f4aa</item>
+        <item>1f440</item>
+        <item>1f442</item>
+        <item>1f443</item>
+        <item>1f463</item>
+        <item>1f444</item>
+        <item>1f445</item>
+        <item>1f48b</item>
+        <item>2764</item>
+        <item>1f499</item>
+        <item>1f49a</item>
+        <item>1f49b</item>
+        <item>1f49c</item>
+        <item>1f493</item>
+        <item>1f494</item>
+        <item>1f495</item>
+        <item>1f496</item>
+        <item>1f497</item>
+        <item>1f498</item>
+        <item>1f49d</item>
+        <item>1f49e</item>
+        <item>1f49f</item>
+        <item>1f44d</item>
+        <item>1f44e</item>
+        <item>1f44c</item>
+        <item>270a</item>
+        <item>270c</item>
+        <item>270b</item>
+        <item>1f44a</item>
+        <!-- <item>261d</item> -->
+        <item>1f446</item>
+        <item>1f447</item>
+        <item>1f448</item>
+        <item>1f449</item>
+        <item>1f44b</item>
+        <item>1f44f</item>
+        <!-- <item>1f450</item> -->
+    </array>
+    <array
+        name="emoji_objects"
+        format="string"
+    >
+        <item>1f530</item>
+        <item>1f484</item>
+        <item>1f45e</item>
+        <item>1f45f</item>
+        <item>1f451</item>
+        <item>1f452</item>
+        <item>1f3a9</item>
+        <item>1f393</item>
+        <item>1f453</item>
+        <item>231a</item>
+        <item>1f454</item>
+        <item>1f455</item>
+        <item>1f456</item>
+        <item>1f457</item>
+        <item>1f458</item>
+        <item>1f459</item>
+        <item>1f460</item>
+        <item>1f461</item>
+        <item>1f462</item>
+        <item>1f45a</item>
+        <item>1f45c</item>
+        <item>1f4bc</item>
+        <item>1f392</item>
+        <item>1f45d</item>
+        <item>1f45b</item>
+        <item>1f4b0</item>
+        <item>1f4b3</item>
+        <item>1f4b2</item>
+        <item>1f4b5</item>
+        <item>1f4b4</item>
+        <!-- <item>1f4b6</item> -->
+        <!-- <item>1f4b7</item> -->
+        <item>1f4b8</item>
+        <item>1f4b1</item>
+        <item>1f4b9</item>
+        <item>1f52b</item>
+        <item>1f52a</item>
+        <item>1f4a3</item>
+        <item>1f489</item>
+        <item>1f48a</item>
+        <item>1f6ac</item>
+        <item>1f514</item>
+        <!-- <item>1f515</item> -->
+        <item>1f6aa</item>
+        <!-- <item>1f52c</item> -->
+        <!-- <item>1f52d</item> -->
+        <item>1f52e</item>
+        <item>1f526</item>
+        <item>1f50b</item>
+        <item>1f50c</item>
+        <item>1f4dc</item>
+        <item>1f4d7</item>
+        <item>1f4d8</item>
+        <item>1f4d9</item>
+        <item>1f4da</item>
+        <item>1f4d4</item>
+        <item>1f4d2</item>
+        <item>1f4d1</item>
+        <item>1f4d3</item>
+        <item>1f4d5</item>
+        <item>1f4d6</item>
+        <item>1f4f0</item>
+        <item>1f4db</item>
+        <item>1f383</item>
+        <item>1f384</item>
+        <item>1f380</item>
+        <item>1f381</item>
+        <item>1f382</item>
+        <item>1f388</item>
+        <item>1f386</item>
+        <item>1f387</item>
+        <item>1f389</item>
+        <item>1f38a</item>
+        <item>1f38d</item>
+        <item>1f38f</item>
+        <item>1f38c</item>
+        <item>1f390</item>
+        <item>1f38b</item>
+        <item>1f38e</item>
+        <item>1f4f1</item>
+        <item>1f4f2</item>
+        <item>1f4df</item>
+        <item>260e</item>
+        <item>1f4de</item>
+        <item>1f4e0</item>
+        <item>1f4e6</item>
+        <item>2709</item>
+        <item>1f4e8</item>
+        <item>1f4e9</item>
+        <item>1f4ea</item>
+        <item>1f4eb</item>
+        <!-- <item>1f4ed</item> -->
+        <!-- <item>1f4ec</item> -->
+        <item>1f4ee</item>
+        <item>1f4e4</item>
+        <item>1f4e5</item>
+        <!-- <item>1f4ef</item> -->
+        <item>1f4e2</item>
+        <item>1f4e3</item>
+        <item>1f4e1</item>
+        <item>1f4ac</item>
+        <!-- <item>1f4ad</item> -->
+        <item>2712</item>
+        <item>270f</item>
+        <item>1f4dd</item>
+        <item>1f4cf</item>
+        <item>1f4d0</item>
+        <item>1f4cd</item>
+        <item>1f4cc</item>
+        <item>1f4ce</item>
+        <item>2702</item>
+        <item>1f4ba</item>
+        <item>1f4bb</item>
+        <item>1f4bd</item>
+        <item>1f4be</item>
+        <item>1f4bf</item>
+        <item>1f4c6</item>
+        <item>1f4c5</item>
+        <item>1f4c7</item>
+        <item>1f4cb</item>
+        <item>1f4c1</item>
+        <item>1f4c2</item>
+        <item>1f4c3</item>
+        <item>1f4c4</item>
+        <item>1f4ca</item>
+        <item>1f4c8</item>
+        <item>1f4c9</item>
+        <item>26fa</item>
+        <item>1f3a1</item>
+        <item>1f3a2</item>
+        <item>1f3a0</item>
+        <item>1f3aa</item>
+        <item>1f3a8</item>
+        <item>1f3ac</item>
+        <item>1f3a5</item>
+        <item>1f4f7</item>
+        <item>1f4f9</item>
+        <item>1f3a6</item>
+        <item>1f3ad</item>
+        <item>1f3ab</item>
+        <item>1f3ae</item>
+        <item>1f3b2</item>
+        <item>1f3b0</item>
+        <item>1f0cf</item>
+        <item>1f3b4</item>
+        <item>1f004</item>
+        <item>1f3af</item>
+        <item>1f4fa</item>
+        <item>1f4fb</item>
+        <item>1f4c0</item>
+        <item>1f4fc</item>
+        <item>1f3a7</item>
+        <item>1f3a4</item>
+        <item>1f3b5</item>
+        <item>1f3b6</item>
+        <item>1f3bc</item>
+        <item>1f3bb</item>
+        <item>1f3b9</item>
+        <item>1f3b7</item>
+        <item>1f3ba</item>
+        <item>1f3b8</item>
+        <item>303d</item>
+    </array>
+    <array
+        name="emoji_places"
+        format="string"
+    >
+        <item>1f3e0</item>
+        <item>1f3e1</item>
+        <item>1f3e2</item>
+        <item>1f3e3</item>
+        <!-- <item>1f3e4</item> -->
+        <item>1f3e5</item>
+        <item>1f3e6</item>
+        <item>1f3e7</item>
+        <item>1f3e8</item>
+        <item>1f3e9</item>
+        <item>1f3ea</item>
+        <item>1f3eb</item>
+        <item>26ea</item>
+        <item>26f2</item>
+        <item>1f3ec</item>
+        <item>1f3ef</item>
+        <item>1f3f0</item>
+        <item>1f3ed</item>
+        <item>1f5fb</item>
+        <item>1f5fc</item>
+        <item>1f5fd</item>
+        <item>1f5fe</item>
+        <item>1f5ff</item>
+        <item>2693</item>
+        <item>1f3ee</item>
+        <item>1f488</item>
+        <item>1f527</item>
+        <item>1f528</item>
+        <item>1f529</item>
+        <!-- <item>1f6bf</item> -->
+        <!-- <item>1f6c1</item> -->
+        <item>1f6c0</item>
+        <item>1f6bd</item>
+        <item>1f6be</item>
+        <item>1f3bd</item>
+        <item>1f3a3</item>
+        <item>1f3b1</item>
+        <item>1f3b3</item>
+        <item>26be</item>
+        <item>26f3</item>
+        <item>1f3be</item>
+        <item>26bd</item>
+        <item>1f3bf</item>
+        <item>1f3c0</item>
+        <item>1f3c1</item>
+        <item>1f3c2</item>
+        <item>1f3c3</item>
+        <item>1f3c4</item>
+        <item>1f3c6</item>
+        <!-- <item>1f3c7</item> -->
+        <item>1f40e</item>
+        <item>1f3c8</item>
+        <!-- <item>1f3c9</item> -->
+        <item>1f3ca</item>
+        <!-- <item>1f682</item> -->
+        <item>1f683</item>
+        <item>1f684</item>
+        <item>1f685</item>
+        <!-- <item>1f686</item> -->
+        <item>1f687</item>
+        <item>24c2</item>
+        <!-- <item>1f688</item> -->
+        <!-- <item>1f68a</item> -->
+        <!-- <item>1f68b</item> -->
+        <item>1f68c</item>
+        <!-- <item>1f68d</item> -->
+        <!-- <item>1f68e</item> -->
+        <item>1f68f</item>
+        <!-- <item>1f690</item> -->
+        <item>1f691</item>
+        <item>1f692</item>
+        <item>1f693</item>
+        <!-- <item>1f694</item> -->
+        <item>1f695</item>
+        <!-- <item>1f696</item> -->
+        <item>1f697</item>
+        <!-- <item>1f698</item> -->
+        <item>1f699</item>
+        <!-- <item>1f69a</item> -->
+        <!-- <item>1f69b</item> -->
+        <!-- <item>1f69c</item> -->
+        <!-- <item>1f69d</item> -->
+        <!-- <item>1f69e</item> -->
+        <!-- <item>1f69f</item> -->
+        <!-- <item>1f6a0</item> -->
+        <!-- <item>1f6a1</item> -->
+        <item>1f6a2</item>
+        <!-- <item>1f6a3</item> -->
+        <!-- <item>1f681</item> -->
+        <item>2708</item>
+        <!-- <item>1f6c2</item> -->
+        <!-- <item>1f6c3</item> -->
+        <!-- <item>1f6c4</item> -->
+        <!-- <item>1f6c5</item> -->
+        <item>26f5</item>
+        <item>1f6b2</item>
+        <!-- <item>1f6b3</item> -->
+        <!-- <item>1f6b4</item> -->
+        <!-- <item>1f6b5</item> -->
+        <!-- <item>1f6b7</item> -->
+        <!-- <item>1f6b8</item> -->
+        <item>1f689</item>
+        <item>1f680</item>
+        <item>1f6a4</item>
+        <item>1f6b6</item>
+        <item>26fd</item>
+        <item>1f17f</item>
+        <item>1f6a5</item>
+        <!-- <item>1f6a6</item> -->
+        <item>1f6a7</item>
+        <item>1f6a8</item>
+        <item>2668</item>
+        <item>1f48c</item>
+        <item>1f48d</item>
+        <item>1f48e</item>
+        <item>1f490</item>
+        <item>1f492</item>
+        <item>fe4e5|1f1ef,1f1f5</item>
+        <item>fe4e6|1f1fa,1f1f8</item>
+        <item>fe4e7|1f1eb,1f1f7</item>
+        <item>fe4e8|1f1e9,1f1ea</item>
+        <item>fe4e9|1f1ee,1f1f9</item>
+        <item>fe4ea|1f1ec,1f1e7</item>
+        <item>fe4eb|1f1ea,1f1f8</item>
+        <item>fe4ec|1f1f7,1f1fa</item>
+        <item>fe4ed|1f1e8,1f1f3</item>
+        <item>fe4ee|1f1f0,1f1f7</item>
+    </array>
+    <array
+        name="emoji_emoticons"
+        format="string"
+    >
+        <item>=-O</item>
+        <item>:-P</item>
+        <item>;-)</item>
+        <item>:-(</item>
+        <item>:-)</item>
+        <item>:-!</item>
+        <item>:-$</item>
+        <item>B-)</item>
+        <item>:O</item>
+        <item>:-*</item>
+        <item>:-D</item>
+        <item>:\'(</item>
+        <item>:-\\</item>
+        <item>O:-)</item>
+        <item>:-[</item>
+    </array>
+</resources>
diff --git a/java/res/values-v19/emoji-categories.xml b/java/res/values-v19/emoji-categories.xml
new file mode 100644
index 0000000..658bbfa
--- /dev/null
+++ b/java/res/values-v19/emoji-categories.xml
@@ -0,0 +1,909 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<!-- Note: This emoji code point list is valid on KLP and later (API >= 19).
+     There is another emoji code point list for JB-MR2 under res/xml/values and values-v18.-->
+<resources>
+    <array
+        name="emoji_nature"
+        format="string"
+    >
+        <item>1f415</item>
+        <item>1f436</item>
+        <item>1f429</item>
+        <item>1f408</item>
+        <item>1f431</item>
+        <item>1f400</item>
+        <item>1f401</item>
+        <item>1f42d</item>
+        <item>1f439</item>
+        <item>1f422</item>
+        <item>1f407</item>
+        <item>1f430</item>
+        <item>1f413</item>
+        <item>1f414</item>
+        <item>1f423</item>
+        <item>1f424</item>
+        <item>1f425</item>
+        <item>1f426</item>
+        <item>1f40f</item>
+        <item>1f411</item>
+        <item>1f410</item>
+        <item>1f43a</item>
+        <item>1f403</item>
+        <item>1f402</item>
+        <item>1f404</item>
+        <item>1f42e</item>
+        <item>1f434</item>
+        <item>1f417</item>
+        <item>1f416</item>
+        <item>1f437</item>
+        <item>1f43d</item>
+        <item>1f438</item>
+        <item>1f40d</item>
+        <item>1f43c</item>
+        <item>1f427</item>
+        <item>1f418</item>
+        <item>1f428</item>
+        <item>1f412</item>
+        <item>1f435</item>
+        <item>1f406</item>
+        <item>1f42f</item>
+        <item>1f43b</item>
+        <item>1f42b</item>
+        <item>1f42a</item>
+        <item>1f40a</item>
+        <item>1f433</item>
+        <item>1f40b</item>
+        <item>1f41f</item>
+        <item>1f420</item>
+        <item>1f421</item>
+        <item>1f419</item>
+        <item>1f41a</item>
+        <item>1f42c</item>
+        <item>1f40c</item>
+        <item>1f41b</item>
+        <item>1f41c</item>
+        <item>1f41d</item>
+        <item>1f41e</item>
+        <item>1f432</item>
+        <item>1f409</item>
+        <item>1f43e</item>
+        <item>1f378</item>
+        <item>1f37a</item>
+        <item>1f37b</item>
+        <item>1f377</item>
+        <item>1f379</item>
+        <item>1f376</item>
+        <item>2615</item>
+        <item>1f375</item>
+        <item>1f37c</item>
+        <item>1f374</item>
+        <item>1f368</item>
+        <item>1f367</item>
+        <item>1f366</item>
+        <item>1f369</item>
+        <item>1f370</item>
+        <item>1f36a</item>
+        <item>1f36b</item>
+        <item>1f36c</item>
+        <item>1f36d</item>
+        <item>1f36e</item>
+        <item>1f36f</item>
+        <item>1f373</item>
+        <item>1f354</item>
+        <item>1f35f</item>
+        <item>1f35d</item>
+        <item>1f355</item>
+        <item>1f356</item>
+        <item>1f357</item>
+        <item>1f364</item>
+        <item>1f363</item>
+        <item>1f371</item>
+        <item>1f35e</item>
+        <item>1f35c</item>
+        <item>1f359</item>
+        <item>1f35a</item>
+        <item>1f35b</item>
+        <item>1f372</item>
+        <item>1f365</item>
+        <item>1f362</item>
+        <item>1f361</item>
+        <item>1f358</item>
+        <item>1f360</item>
+        <item>1f34c</item>
+        <item>1f34e</item>
+        <item>1f34f</item>
+        <item>1f34a</item>
+        <item>1f34b</item>
+        <item>1f344</item>
+        <item>1f345</item>
+        <item>1f346</item>
+        <item>1f347</item>
+        <item>1f348</item>
+        <item>1f349</item>
+        <item>1f350</item>
+        <item>1f351</item>
+        <item>1f352</item>
+        <item>1f353</item>
+        <item>1f34d</item>
+        <item>1f330</item>
+        <item>1f331</item>
+        <item>1f332</item>
+        <item>1f333</item>
+        <item>1f334</item>
+        <item>1f335</item>
+        <item>1f337</item>
+        <item>1f338</item>
+        <item>1f339</item>
+        <item>1f340</item>
+        <item>1f341</item>
+        <item>1f342</item>
+        <item>1f343</item>
+        <item>1f33a</item>
+        <item>1f33b</item>
+        <item>1f33c</item>
+        <item>1f33d</item>
+        <item>1f33e</item>
+        <item>1f33f</item>
+        <item>2600</item>
+        <item>1f308</item>
+        <item>26c5</item>
+        <item>2601</item>
+        <item>1f301</item>
+        <item>1f302</item>
+        <item>2614</item>
+        <item>1f4a7</item>
+        <item>26a1</item>
+        <item>1f300</item>
+        <item>2744</item>
+        <item>26c4</item>
+        <item>1f319</item>
+        <item>1f31e</item>
+        <item>1f31d</item>
+        <item>1f31a</item>
+        <item>1f31b</item>
+        <item>1f31c</item>
+        <item>1f311</item>
+        <item>1f312</item>
+        <item>1f313</item>
+        <item>1f314</item>
+        <item>1f315</item>
+        <item>1f316</item>
+        <item>1f317</item>
+        <item>1f318</item>
+        <item>1f391</item>
+        <item>1f304</item>
+        <item>1f305</item>
+        <item>1f307</item>
+        <item>1f306</item>
+        <item>1f303</item>
+        <item>1f30c</item>
+        <item>1f309</item>
+        <item>1f30a</item>
+        <item>1f30b</item>
+        <item>1f30e</item>
+        <item>1f30f</item>
+        <item>1f30d</item>
+        <item>1f310</item>
+    </array>
+    <array
+        name="emoji_symbols"
+        format="string"
+    >
+        <item>fe82e|0031,20e3</item>
+        <item>fe82f|0032,20e3</item>
+        <item>fe830|0033,20e3</item>
+        <item>fe831|0034,20e3</item>
+        <item>fe832|0035,20e3</item>
+        <item>fe833|0036,20e3</item>
+        <item>fe834|0037,20e3</item>
+        <item>fe835|0038,20e3</item>
+        <item>fe836|0039,20e3</item>
+        <item>fe837|0030,20e3</item>
+        <item>1f51f</item>
+        <item>fe82c|0023,20e3</item>
+        <item>1f51d</item>
+        <item>1f519</item>
+        <item>1f51b</item>
+        <item>1f51c</item>
+        <item>1f51a</item>
+        <item>23f3</item>
+        <item>231b</item>
+        <item>23f0</item>
+        <item>2648</item>
+        <item>2649</item>
+        <item>264a</item>
+        <item>264b</item>
+        <item>264c</item>
+        <item>264d</item>
+        <item>264e</item>
+        <item>264f</item>
+        <item>2650</item>
+        <item>2651</item>
+        <item>2652</item>
+        <item>2653</item>
+        <item>26ce</item>
+        <item>1f531</item>
+        <item>1f52f</item>
+        <item>1f6bb</item>
+        <item>1f6ae</item>
+        <item>1f6af</item>
+        <item>1f6b0</item>
+        <item>1f6b1</item>
+        <item>1f170</item>
+        <item>1f171</item>
+        <item>1f18e</item>
+        <item>1f17e</item>
+        <item>1f4ae</item>
+        <item>1f4af</item>
+        <item>1f520</item>
+        <item>1f521</item>
+        <item>1f522</item>
+        <item>1f523</item>
+        <item>1f524</item>
+        <item>27bf</item>
+        <item>1f4f6</item>
+        <item>1f4f3</item>
+        <item>1f4f4</item>
+        <item>1f4f5</item>
+        <item>1f6b9</item>
+        <item>1f6ba</item>
+        <item>1f6bc</item>
+        <item>267f</item>
+        <item>267b</item>
+        <item>1f6ad</item>
+        <item>1f6a9</item>
+        <item>26a0</item>
+        <item>1f201</item>
+        <item>1f51e</item>
+        <item>26d4</item>
+        <item>1f192</item>
+        <item>1f197</item>
+        <item>1f195</item>
+        <item>1f198</item>
+        <item>1f199</item>
+        <item>1f193</item>
+        <item>1f196</item>
+        <item>1f19a</item>
+        <item>1f232</item>
+        <item>1f233</item>
+        <item>1f234</item>
+        <item>1f235</item>
+        <item>1f236</item>
+        <item>1f237</item>
+        <item>1f238</item>
+        <item>1f239</item>
+        <item>1f202</item>
+        <item>1f23a</item>
+        <item>1f250</item>
+        <item>1f251</item>
+        <item>3299</item>
+        <item>00ae</item>
+        <item>00a9</item>
+        <item>2122</item>
+        <item>1f21a</item>
+        <item>1f22f</item>
+        <item>3297</item>
+        <item>2b55</item>
+        <item>274c</item>
+        <item>274e</item>
+        <item>2139</item>
+        <item>1f6ab</item>
+        <item>2705</item>
+        <item>2714</item>
+        <item>1f517</item>
+        <item>2734</item>
+        <item>2733</item>
+        <item>2795</item>
+        <item>2796</item>
+        <item>2716</item>
+        <item>2797</item>
+        <item>1f4a0</item>
+        <item>1f4a1</item>
+        <item>1f4a4</item>
+        <item>1f4a2</item>
+        <item>1f525</item>
+        <item>1f4a5</item>
+        <item>1f4a8</item>
+        <item>1f4a6</item>
+        <item>1f4ab</item>
+        <item>1f55b</item>
+        <item>1f567</item>
+        <item>1f550</item>
+        <item>1f55c</item>
+        <item>1f551</item>
+        <item>1f55d</item>
+        <item>1f552</item>
+        <item>1f55e</item>
+        <item>1f553</item>
+        <item>1f55f</item>
+        <item>1f554</item>
+        <item>1f560</item>
+        <item>1f555</item>
+        <item>1f561</item>
+        <item>1f556</item>
+        <item>1f562</item>
+        <item>1f557</item>
+        <item>1f563</item>
+        <item>1f558</item>
+        <item>1f564</item>
+        <item>1f559</item>
+        <item>1f565</item>
+        <item>1f55a</item>
+        <item>1f566</item>
+        <item>2195</item>
+        <item>2b06</item>
+        <item>2197</item>
+        <item>27a1</item>
+        <item>2198</item>
+        <item>2b07</item>
+        <item>2199</item>
+        <item>2b05</item>
+        <item>2196</item>
+        <item>2194</item>
+        <item>2934</item>
+        <item>2935</item>
+        <item>23ea</item>
+        <item>23eb</item>
+        <item>23ec</item>
+        <item>23e9</item>
+        <item>25c0</item>
+        <item>25b6</item>
+        <item>1f53d</item>
+        <item>1f53c</item>
+        <item>2747</item>
+        <item>2728</item>
+        <item>1f534</item>
+        <item>1f535</item>
+        <item>26aa</item>
+        <item>26ab</item>
+        <item>1f533</item>
+        <item>1f532</item>
+        <item>2b50</item>
+        <item>1f31f</item>
+        <item>1f320</item>
+        <item>25ab</item>
+        <item>25aa</item>
+        <item>25fd</item>
+        <item>25fe</item>
+        <item>25fb</item>
+        <item>25fc</item>
+        <item>2b1c</item>
+        <item>2b1b</item>
+        <item>1f538</item>
+        <item>1f539</item>
+        <item>1f536</item>
+        <item>1f537</item>
+        <item>1f53a</item>
+        <item>1f53b</item>
+        <item>2754</item>
+        <item>2753</item>
+        <item>2755</item>
+        <item>2757</item>
+        <item>203c</item>
+        <item>2049</item>
+        <item>3030</item>
+        <item>27b0</item>
+        <item>2660</item>
+        <item>2665</item>
+        <item>2663</item>
+        <item>2666</item>
+        <item>1f194</item>
+        <item>1f511</item>
+        <item>21a9</item>
+        <item>1f191</item>
+        <item>1f50d</item>
+        <item>1f512</item>
+        <item>1f513</item>
+        <item>21aa</item>
+        <item>1f510</item>
+        <item>2611</item>
+        <item>1f518</item>
+        <item>1f50e</item>
+        <item>1f516</item>
+        <item>1f50f</item>
+        <item>1f503</item>
+        <item>1f500</item>
+        <item>1f501</item>
+        <item>1f502</item>
+        <item>1f504</item>
+        <item>1f4e7</item>
+        <item>1f505</item>
+        <item>1f506</item>
+        <item>1f507</item>
+        <item>1f508</item>
+        <item>1f509</item>
+        <item>1f50a</item>
+    </array>
+    <array
+        name="emoji_faces"
+        format="string"
+    >
+        <item>263a</item>
+        <item>1f60a</item>
+        <item>1f600</item>
+        <item>1f601</item>
+        <item>1f602</item>
+        <item>1f603</item>
+        <item>1f604</item>
+        <item>1f605</item>
+        <item>1f606</item>
+        <item>1f607</item>
+        <item>1f608</item>
+        <item>1f609</item>
+        <item>1f62f</item>
+        <item>1f610</item>
+        <item>1f611</item>
+        <item>1f615</item>
+        <item>1f620</item>
+        <item>1f62c</item>
+        <item>1f621</item>
+        <item>1f622</item>
+        <item>1f634</item>
+        <item>1f62e</item>
+        <item>1f623</item>
+        <item>1f624</item>
+        <item>1f625</item>
+        <item>1f626</item>
+        <item>1f627</item>
+        <item>1f628</item>
+        <item>1f629</item>
+        <item>1f630</item>
+        <item>1f61f</item>
+        <item>1f631</item>
+        <item>1f632</item>
+        <item>1f633</item>
+        <item>1f635</item>
+        <item>1f636</item>
+        <item>1f637</item>
+        <item>1f61e</item>
+        <item>1f612</item>
+        <item>1f60d</item>
+        <item>1f61b</item>
+        <item>1f61c</item>
+        <item>1f61d</item>
+        <item>1f60b</item>
+        <item>1f617</item>
+        <item>1f619</item>
+        <item>1f618</item>
+        <item>1f61a</item>
+        <item>1f60e</item>
+        <item>1f62d</item>
+        <item>1f60c</item>
+        <item>1f616</item>
+        <item>1f614</item>
+        <item>1f62a</item>
+        <item>1f60f</item>
+        <item>1f613</item>
+        <item>1f62b</item>
+        <item>1f64b</item>
+        <item>1f64c</item>
+        <item>1f64d</item>
+        <item>1f645</item>
+        <item>1f646</item>
+        <item>1f647</item>
+        <item>1f64e</item>
+        <item>1f64f</item>
+        <item>1f63a</item>
+        <item>1f63c</item>
+        <item>1f638</item>
+        <item>1f639</item>
+        <item>1f63b</item>
+        <item>1f63d</item>
+        <item>1f63f</item>
+        <item>1f63e</item>
+        <item>1f640</item>
+        <item>1f648</item>
+        <item>1f649</item>
+        <item>1f64a</item>
+        <item>1f4a9</item>
+        <item>1f476</item>
+        <item>1f466</item>
+        <item>1f467</item>
+        <item>1f468</item>
+        <item>1f469</item>
+        <item>1f474</item>
+        <item>1f475</item>
+        <item>1f48f</item>
+        <item>1f491</item>
+        <item>1f46a</item>
+        <item>1f46b</item>
+        <item>1f46c</item>
+        <item>1f46d</item>
+        <item>1f464</item>
+        <item>1f465</item>
+        <item>1f46e</item>
+        <item>1f477</item>
+        <item>1f481</item>
+        <item>1f482</item>
+        <item>1f46f</item>
+        <item>1f470</item>
+        <item>1f478</item>
+        <item>1f385</item>
+        <item>1f47c</item>
+        <item>1f471</item>
+        <item>1f472</item>
+        <item>1f473</item>
+        <item>1f483</item>
+        <item>1f486</item>
+        <item>1f487</item>
+        <item>1f485</item>
+        <item>1f47b</item>
+        <item>1f479</item>
+        <item>1f47a</item>
+        <item>1f47d</item>
+        <item>1f47e</item>
+        <item>1f47f</item>
+        <item>1f480</item>
+        <item>1f4aa</item>
+        <item>1f440</item>
+        <item>1f442</item>
+        <item>1f443</item>
+        <item>1f463</item>
+        <item>1f444</item>
+        <item>1f445</item>
+        <item>1f48b</item>
+        <item>2764</item>
+        <item>1f499</item>
+        <item>1f49a</item>
+        <item>1f49b</item>
+        <item>1f49c</item>
+        <item>1f493</item>
+        <item>1f494</item>
+        <item>1f495</item>
+        <item>1f496</item>
+        <item>1f497</item>
+        <item>1f498</item>
+        <item>1f49d</item>
+        <item>1f49e</item>
+        <item>1f49f</item>
+        <item>1f44d</item>
+        <item>1f44e</item>
+        <item>1f44c</item>
+        <item>270a</item>
+        <item>270c</item>
+        <item>270b</item>
+        <item>1f44a</item>
+        <item>261d</item>
+        <item>1f446</item>
+        <item>1f447</item>
+        <item>1f448</item>
+        <item>1f449</item>
+        <item>1f44b</item>
+        <item>1f44f</item>
+        <item>1f450</item>
+    </array>
+    <array
+        name="emoji_objects"
+        format="string"
+    >
+        <item>1f530</item>
+        <item>1f484</item>
+        <item>1f45e</item>
+        <item>1f45f</item>
+        <item>1f451</item>
+        <item>1f452</item>
+        <item>1f3a9</item>
+        <item>1f393</item>
+        <item>1f453</item>
+        <item>231a</item>
+        <item>1f454</item>
+        <item>1f455</item>
+        <item>1f456</item>
+        <item>1f457</item>
+        <item>1f458</item>
+        <item>1f459</item>
+        <item>1f460</item>
+        <item>1f461</item>
+        <item>1f462</item>
+        <item>1f45a</item>
+        <item>1f45c</item>
+        <item>1f4bc</item>
+        <item>1f392</item>
+        <item>1f45d</item>
+        <item>1f45b</item>
+        <item>1f4b0</item>
+        <item>1f4b3</item>
+        <item>1f4b2</item>
+        <item>1f4b5</item>
+        <item>1f4b4</item>
+        <item>1f4b6</item>
+        <item>1f4b7</item>
+        <item>1f4b8</item>
+        <item>1f4b1</item>
+        <item>1f4b9</item>
+        <item>1f52b</item>
+        <item>1f52a</item>
+        <item>1f4a3</item>
+        <item>1f489</item>
+        <item>1f48a</item>
+        <item>1f6ac</item>
+        <item>1f514</item>
+        <item>1f515</item>
+        <item>1f6aa</item>
+        <item>1f52c</item>
+        <item>1f52d</item>
+        <item>1f52e</item>
+        <item>1f526</item>
+        <item>1f50b</item>
+        <item>1f50c</item>
+        <item>1f4dc</item>
+        <item>1f4d7</item>
+        <item>1f4d8</item>
+        <item>1f4d9</item>
+        <item>1f4da</item>
+        <item>1f4d4</item>
+        <item>1f4d2</item>
+        <item>1f4d1</item>
+        <item>1f4d3</item>
+        <item>1f4d5</item>
+        <item>1f4d6</item>
+        <item>1f4f0</item>
+        <item>1f4db</item>
+        <item>1f383</item>
+        <item>1f384</item>
+        <item>1f380</item>
+        <item>1f381</item>
+        <item>1f382</item>
+        <item>1f388</item>
+        <item>1f386</item>
+        <item>1f387</item>
+        <item>1f389</item>
+        <item>1f38a</item>
+        <item>1f38d</item>
+        <item>1f38f</item>
+        <item>1f38c</item>
+        <item>1f390</item>
+        <item>1f38b</item>
+        <item>1f38e</item>
+        <item>1f4f1</item>
+        <item>1f4f2</item>
+        <item>1f4df</item>
+        <item>260e</item>
+        <item>1f4de</item>
+        <item>1f4e0</item>
+        <item>1f4e6</item>
+        <item>2709</item>
+        <item>1f4e8</item>
+        <item>1f4e9</item>
+        <item>1f4ea</item>
+        <item>1f4eb</item>
+        <item>1f4ed</item>
+        <item>1f4ec</item>
+        <item>1f4ee</item>
+        <item>1f4e4</item>
+        <item>1f4e5</item>
+        <item>1f4ef</item>
+        <item>1f4e2</item>
+        <item>1f4e3</item>
+        <item>1f4e1</item>
+        <item>1f4ac</item>
+        <item>1f4ad</item>
+        <item>2712</item>
+        <item>270f</item>
+        <item>1f4dd</item>
+        <item>1f4cf</item>
+        <item>1f4d0</item>
+        <item>1f4cd</item>
+        <item>1f4cc</item>
+        <item>1f4ce</item>
+        <item>2702</item>
+        <item>1f4ba</item>
+        <item>1f4bb</item>
+        <item>1f4bd</item>
+        <item>1f4be</item>
+        <item>1f4bf</item>
+        <item>1f4c6</item>
+        <item>1f4c5</item>
+        <item>1f4c7</item>
+        <item>1f4cb</item>
+        <item>1f4c1</item>
+        <item>1f4c2</item>
+        <item>1f4c3</item>
+        <item>1f4c4</item>
+        <item>1f4ca</item>
+        <item>1f4c8</item>
+        <item>1f4c9</item>
+        <item>26fa</item>
+        <item>1f3a1</item>
+        <item>1f3a2</item>
+        <item>1f3a0</item>
+        <item>1f3aa</item>
+        <item>1f3a8</item>
+        <item>1f3ac</item>
+        <item>1f3a5</item>
+        <item>1f4f7</item>
+        <item>1f4f9</item>
+        <item>1f3a6</item>
+        <item>1f3ad</item>
+        <item>1f3ab</item>
+        <item>1f3ae</item>
+        <item>1f3b2</item>
+        <item>1f3b0</item>
+        <item>1f0cf</item>
+        <item>1f3b4</item>
+        <item>1f004</item>
+        <item>1f3af</item>
+        <item>1f4fa</item>
+        <item>1f4fb</item>
+        <item>1f4c0</item>
+        <item>1f4fc</item>
+        <item>1f3a7</item>
+        <item>1f3a4</item>
+        <item>1f3b5</item>
+        <item>1f3b6</item>
+        <item>1f3bc</item>
+        <item>1f3bb</item>
+        <item>1f3b9</item>
+        <item>1f3b7</item>
+        <item>1f3ba</item>
+        <item>1f3b8</item>
+        <item>303d</item>
+    </array>
+    <array
+        name="emoji_places"
+        format="string"
+    >
+        <item>1f3e0</item>
+        <item>1f3e1</item>
+        <item>1f3e2</item>
+        <item>1f3e3</item>
+        <item>1f3e4</item>
+        <item>1f3e5</item>
+        <item>1f3e6</item>
+        <item>1f3e7</item>
+        <item>1f3e8</item>
+        <item>1f3e9</item>
+        <item>1f3ea</item>
+        <item>1f3eb</item>
+        <item>26ea</item>
+        <item>26f2</item>
+        <item>1f3ec</item>
+        <item>1f3ef</item>
+        <item>1f3f0</item>
+        <item>1f3ed</item>
+        <item>1f5fb</item>
+        <item>1f5fc</item>
+        <item>1f5fd</item>
+        <item>1f5fe</item>
+        <item>1f5ff</item>
+        <item>2693</item>
+        <item>1f3ee</item>
+        <item>1f488</item>
+        <item>1f527</item>
+        <item>1f528</item>
+        <item>1f529</item>
+        <item>1f6bf</item>
+        <item>1f6c1</item>
+        <item>1f6c0</item>
+        <item>1f6bd</item>
+        <item>1f6be</item>
+        <item>1f3bd</item>
+        <item>1f3a3</item>
+        <item>1f3b1</item>
+        <item>1f3b3</item>
+        <item>26be</item>
+        <item>26f3</item>
+        <item>1f3be</item>
+        <item>26bd</item>
+        <item>1f3bf</item>
+        <item>1f3c0</item>
+        <item>1f3c1</item>
+        <item>1f3c2</item>
+        <item>1f3c3</item>
+        <item>1f3c4</item>
+        <item>1f3c6</item>
+        <item>1f3c7</item>
+        <item>1f40e</item>
+        <item>1f3c8</item>
+        <item>1f3c9</item>
+        <item>1f3ca</item>
+        <item>1f682</item>
+        <item>1f683</item>
+        <item>1f684</item>
+        <item>1f685</item>
+        <item>1f686</item>
+        <item>1f687</item>
+        <item>24c2</item>
+        <item>1f688</item>
+        <item>1f68a</item>
+        <item>1f68b</item>
+        <item>1f68c</item>
+        <item>1f68d</item>
+        <item>1f68e</item>
+        <item>1f68f</item>
+        <item>1f690</item>
+        <item>1f691</item>
+        <item>1f692</item>
+        <item>1f693</item>
+        <item>1f694</item>
+        <item>1f695</item>
+        <item>1f696</item>
+        <item>1f697</item>
+        <item>1f698</item>
+        <item>1f699</item>
+        <item>1f69a</item>
+        <item>1f69b</item>
+        <item>1f69c</item>
+        <item>1f69d</item>
+        <item>1f69e</item>
+        <item>1f69f</item>
+        <item>1f6a0</item>
+        <item>1f6a1</item>
+        <item>1f6a2</item>
+        <item>1f6a3</item>
+        <item>1f681</item>
+        <item>2708</item>
+        <item>1f6c2</item>
+        <item>1f6c3</item>
+        <item>1f6c4</item>
+        <item>1f6c5</item>
+        <item>26f5</item>
+        <item>1f6b2</item>
+        <item>1f6b3</item>
+        <item>1f6b4</item>
+        <item>1f6b5</item>
+        <item>1f6b7</item>
+        <item>1f6b8</item>
+        <item>1f689</item>
+        <item>1f680</item>
+        <item>1f6a4</item>
+        <item>1f6b6</item>
+        <item>26fd</item>
+        <item>1f17f</item>
+        <item>1f6a5</item>
+        <item>1f6a6</item>
+        <item>1f6a7</item>
+        <item>1f6a8</item>
+        <item>2668</item>
+        <item>1f48c</item>
+        <item>1f48d</item>
+        <item>1f48e</item>
+        <item>1f490</item>
+        <item>1f492</item>
+        <item>fe4e5|1f1ef,1f1f5</item>
+        <item>fe4e6|1f1fa,1f1f8</item>
+        <item>fe4e7|1f1eb,1f1f7</item>
+        <item>fe4e8|1f1e9,1f1ea</item>
+        <item>fe4e9|1f1ee,1f1f9</item>
+        <item>fe4ea|1f1ec,1f1e7</item>
+        <item>fe4eb|1f1ea,1f1f8</item>
+        <item>fe4ec|1f1f7,1f1fa</item>
+        <item>fe4ed|1f1e8,1f1f3</item>
+        <item>fe4ee|1f1f0,1f1f7</item>
+    </array>
+    <array
+        name="emoji_emoticons"
+        format="string"
+    >
+        <item>=-O</item>
+        <item>:-P</item>
+        <item>;-)</item>
+        <item>:-(</item>
+        <item>:-)</item>
+        <item>:-!</item>
+        <item>:-$</item>
+        <item>B-)</item>
+        <item>:O</item>
+        <item>:-*</item>
+        <item>:-D</item>
+        <item>:\'(</item>
+        <item>:-\\</item>
+        <item>O:-)</item>
+        <item>:-[</item>
+    </array>
+</resources>
diff --git a/java/res/values-vi/strings.xml b/java/res/values-vi/strings.xml
index b489f57..e08d1c6 100644
--- a/java/res/values-vi/strings.xml
+++ b/java/res/values-vi/strings.xml
@@ -64,8 +64,8 @@
     <string name="auto_correction_summary" msgid="5625751551134658006">"Phím cách và dấu câu tự động sửa từ nhập sai"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Tắt"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Đơn giản"</string>
-    <string name="auto_correction_threshold_mode_aggressive" msgid="3524029103734923819">"Linh hoạt"</string>
-    <string name="auto_correction_threshold_mode_very_aggressive" msgid="3386782235540547678">"Rất linh hoạt"</string>
+    <string name="auto_correction_threshold_mode_aggressive" msgid="7319007299148899623">"Linh hoạt"</string>
+    <string name="auto_correction_threshold_mode_very_aggressive" msgid="1853309024129480416">"Rất linh hoạt"</string>
     <string name="bigram_prediction" msgid="1084449187723948550">"Đề xuất từ tiếp theo"</string>
     <string name="bigram_prediction_summary" msgid="3896362682751109677">"Sử dụng từ trước đó khi đưa ra đề xuất"</string>
     <string name="gesture_input" msgid="826951152254563827">"Bật nhập bằng cử chỉ"</string>
@@ -143,13 +143,15 @@
     <string name="subtype_with_layout_en_GB" msgid="2179097748724725906">"Tiếng Anh (Anh) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_en_US" msgid="1362581347576714579">"Tiếng Anh (Mỹ) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_es_US" msgid="6261791057007890189">"Tiếng Tây Ban Nha (Mỹ) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
-    <string name="subtype_no_language" msgid="141420857808801746">"Không có ngôn ngữ nào"</string>
-    <string name="subtype_no_language_qwerty" msgid="2956121451616633133">"Không có ngôn ngữ (QWERTY)"</string>
-    <string name="subtype_no_language_qwertz" msgid="1177848172397202890">"0 ngôn ngữ (QWERTZ)"</string>
-    <string name="subtype_no_language_azerty" msgid="8721460968141187394">"0 ngôn ngữ (AZERTY)"</string>
-    <string name="subtype_no_language_dvorak" msgid="3122976737669823935">"0 ngôn ngữ (Dvorak)"</string>
-    <string name="subtype_no_language_colemak" msgid="4205992994906097244">"0 ngôn ngữ (Colemak)"</string>
-    <string name="subtype_no_language_pcqwerty" msgid="8840928374394180189">"0 ngôn ngữ (PC)"</string>
+    <string name="subtype_nepali_traditional" msgid="9032247506728040447">"<xliff:g id="LANGUAGE">%s</xliff:g> (Truyền thống)"</string>
+    <string name="subtype_no_language" msgid="7137390094240139495">"Không ngôn ngữ nào (Bảng chữ cái)"</string>
+    <string name="subtype_no_language_qwerty" msgid="244337630616742604">"Bảng chữ cái (QWERTY)"</string>
+    <string name="subtype_no_language_qwertz" msgid="443066912507547976">"Bảng chữ cái (QWERTZ)"</string>
+    <string name="subtype_no_language_azerty" msgid="8144348527575640087">"Bảng chữ cái (AZERTY)"</string>
+    <string name="subtype_no_language_dvorak" msgid="1564494667584718094">"Bảng chữ cái (Dvorak)"</string>
+    <string name="subtype_no_language_colemak" msgid="5837418400010302623">"Bảng chữ cái (Colemak)"</string>
+    <string name="subtype_no_language_pcqwerty" msgid="5354918232046200018">"Bảng chữ cái (PC)"</string>
+    <string name="subtype_emoji" msgid="7483586578074549196">"Biểu tượng cảm xúc"</string>
     <string name="custom_input_styles_title" msgid="8429952441821251512">"Kiểu nhập tùy chỉnh"</string>
     <string name="add_style" msgid="6163126614514489951">"Thêm kiểu"</string>
     <string name="add" msgid="8299699805688017798">"Thêm"</string>
diff --git a/java/res/values-zh-rCN/strings.xml b/java/res/values-zh-rCN/strings.xml
index 1177c01..5980cbf 100644
--- a/java/res/values-zh-rCN/strings.xml
+++ b/java/res/values-zh-rCN/strings.xml
@@ -26,7 +26,7 @@
     <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"拼写检查工具会使用您的联系人列表中的条目"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"按键振动"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"按键音效"</string>
-    <string name="popup_on_keypress" msgid="123894815723512944">"按键时显示所输字符"</string>
+    <string name="popup_on_keypress" msgid="123894815723512944">"按键时弹出显示字符"</string>
     <string name="general_category" msgid="1859088467017573195">"常规"</string>
     <string name="correction_category" msgid="2236750915056607613">"文本更正"</string>
     <string name="gesture_typing_category" msgid="497263612130532630">"滑行输入"</string>
@@ -64,8 +64,8 @@
     <string name="auto_correction_summary" msgid="5625751551134658006">"按空格键和标点可自动更正错别字"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"关闭"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"小改"</string>
-    <string name="auto_correction_threshold_mode_aggressive" msgid="3524029103734923819">"大改"</string>
-    <string name="auto_correction_threshold_mode_very_aggressive" msgid="3386782235540547678">"改动极大"</string>
+    <string name="auto_correction_threshold_mode_aggressive" msgid="7319007299148899623">"大幅改动"</string>
+    <string name="auto_correction_threshold_mode_very_aggressive" msgid="1853309024129480416">"极大幅度改动"</string>
     <string name="bigram_prediction" msgid="1084449187723948550">"后续字词建议"</string>
     <string name="bigram_prediction_summary" msgid="3896362682751109677">"根据上一个字词提供建议"</string>
     <string name="gesture_input" msgid="826951152254563827">"启用滑行输入"</string>
@@ -143,13 +143,15 @@
     <string name="subtype_with_layout_en_GB" msgid="2179097748724725906">"英语(英国)(<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_en_US" msgid="1362581347576714579">"英语(美国)(<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_es_US" msgid="6261791057007890189">"西班牙语(美国)(<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
-    <string name="subtype_no_language" msgid="141420857808801746">"无语言"</string>
-    <string name="subtype_no_language_qwerty" msgid="2956121451616633133">"无语言(QWERTY 键盘)"</string>
-    <string name="subtype_no_language_qwertz" msgid="1177848172397202890">"无语言(QWERTZ 键盘)"</string>
-    <string name="subtype_no_language_azerty" msgid="8721460968141187394">"无语言(AZERTY 键盘)"</string>
-    <string name="subtype_no_language_dvorak" msgid="3122976737669823935">"无语言(Dvorak 键盘)"</string>
-    <string name="subtype_no_language_colemak" msgid="4205992994906097244">"无语言(Colemak 键盘)"</string>
-    <string name="subtype_no_language_pcqwerty" msgid="8840928374394180189">"无语言(PC 键盘)"</string>
+    <string name="subtype_nepali_traditional" msgid="9032247506728040447">"<xliff:g id="LANGUAGE">%s</xliff:g>(传统)"</string>
+    <string name="subtype_no_language" msgid="7137390094240139495">"无语言(字母)"</string>
+    <string name="subtype_no_language_qwerty" msgid="244337630616742604">"字母 (QWERTY)"</string>
+    <string name="subtype_no_language_qwertz" msgid="443066912507547976">"字母 (QWERTZ)"</string>
+    <string name="subtype_no_language_azerty" msgid="8144348527575640087">"字母 (AZERTY)"</string>
+    <string name="subtype_no_language_dvorak" msgid="1564494667584718094">"字母 (Dvorak)"</string>
+    <string name="subtype_no_language_colemak" msgid="5837418400010302623">"字母 (Colemak)"</string>
+    <string name="subtype_no_language_pcqwerty" msgid="5354918232046200018">"字母 (PC)"</string>
+    <string name="subtype_emoji" msgid="7483586578074549196">"表情符号"</string>
     <string name="custom_input_styles_title" msgid="8429952441821251512">"自定义输入风格"</string>
     <string name="add_style" msgid="6163126614514489951">"添加样式"</string>
     <string name="add" msgid="8299699805688017798">"添加"</string>
diff --git a/java/res/values-zh-rTW/strings.xml b/java/res/values-zh-rTW/strings.xml
index 351907a..b14a01a 100644
--- a/java/res/values-zh-rTW/strings.xml
+++ b/java/res/values-zh-rTW/strings.xml
@@ -25,15 +25,15 @@
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"查詢聯絡人姓名"</string>
     <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"拼字檢查程式使用您的聯絡人清單項目"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"按鍵時震動"</string>
-    <string name="sound_on_keypress" msgid="6093592297198243644">"按鍵時播放音效"</string>
-    <string name="popup_on_keypress" msgid="123894815723512944">"按鍵時顯示彈出式視窗"</string>
-    <string name="general_category" msgid="1859088467017573195">"一般設定"</string>
+    <string name="sound_on_keypress" msgid="6093592297198243644">"按鍵聲音"</string>
+    <string name="popup_on_keypress" msgid="123894815723512944">"按鍵時彈出"</string>
+    <string name="general_category" msgid="1859088467017573195">"一般"</string>
     <string name="correction_category" msgid="2236750915056607613">"文字修正"</string>
     <string name="gesture_typing_category" msgid="497263612130532630">"手勢輸入"</string>
     <string name="misc_category" msgid="6894192814868233453">"其他選項"</string>
     <string name="advanced_settings" msgid="362895144495591463">"進階設定"</string>
     <string name="advanced_settings_summary" msgid="4487980456152830271">"進階選項"</string>
-    <string name="include_other_imes_in_language_switch_list" msgid="4533689960308565519">"切換至其他輸入法"</string>
+    <string name="include_other_imes_in_language_switch_list" msgid="4533689960308565519">"切換到其他輸入法"</string>
     <string name="include_other_imes_in_language_switch_list_summary" msgid="840637129103317635">"使語言切換鍵包含其他輸入法"</string>
     <string name="show_language_switch_key" msgid="5915478828318774384">"語言切換鍵"</string>
     <string name="show_language_switch_key_summary" msgid="7343403647474265713">"有多種輸入語言可選用時顯示切換鍵"</string>
@@ -44,7 +44,7 @@
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"預設"</string>
     <string name="abbreviation_unit_milliseconds" msgid="8700286094028323363">"<xliff:g id="MILLISECONDS">%s</xliff:g> 毫秒"</string>
     <string name="settings_system_default" msgid="6268225104743331821">"系統預設"</string>
-    <string name="use_contacts_dict" msgid="4435317977804180815">"建議聯絡人名稱"</string>
+    <string name="use_contacts_dict" msgid="4435317977804180815">"建議聯絡人姓名"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"根據「聯絡人」名稱提供建議與修正"</string>
     <string name="use_double_space_period" msgid="8781529969425082860">"輕按兩下空格鍵即插入句號"</string>
     <string name="use_double_space_period_summary" msgid="6532892187247952799">"輕按兩下空格鍵可插入句號另加一個空格"</string>
@@ -57,15 +57,15 @@
     <string name="prefs_show_suggestions_summary" msgid="1583132279498502825">"輸入時顯示建議字詞"</string>
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"一律顯示"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3859783767435239118">"在垂直模式中顯示"</string>
-    <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"永遠隱藏"</string>
+    <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"一律隱藏"</string>
     <string name="prefs_block_potentially_offensive_title" msgid="5078480071057408934">"封鎖令人反感的字詞"</string>
     <string name="prefs_block_potentially_offensive_summary" msgid="2371835479734991364">"不建議可能令人反感的字詞"</string>
     <string name="auto_correction" msgid="7630720885194996950">"自動修正"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"按空白鍵或標點符號時,自動修正前面的錯字"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"關閉"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"更正範圍小"</string>
-    <string name="auto_correction_threshold_mode_aggressive" msgid="3524029103734923819">"更正範圍大"</string>
-    <string name="auto_correction_threshold_mode_very_aggressive" msgid="3386782235540547678">"更正範圍極大"</string>
+    <string name="auto_correction_threshold_mode_aggressive" msgid="7319007299148899623">"大幅更正"</string>
+    <string name="auto_correction_threshold_mode_very_aggressive" msgid="1853309024129480416">"極大幅度更正"</string>
     <string name="bigram_prediction" msgid="1084449187723948550">"建議下一個字詞"</string>
     <string name="bigram_prediction_summary" msgid="3896362682751109677">"根據前一個字詞提供建議"</string>
     <string name="gesture_input" msgid="826951152254563827">"啟用手勢輸入"</string>
@@ -74,13 +74,13 @@
     <string name="gesture_floating_preview_text" msgid="4443240334739381053">"動態浮動預覽"</string>
     <string name="gesture_floating_preview_text_summary" msgid="4472696213996203533">"使用手勢輸入時顯示建議字詞"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g>:已儲存"</string>
-    <string name="label_go_key" msgid="1635148082137219148">"開始"</string>
-    <string name="label_next_key" msgid="362972844525672568">"繼續"</string>
-    <string name="label_previous_key" msgid="1211868118071386787">"上一步"</string>
+    <string name="label_go_key" msgid="1635148082137219148">"前往"</string>
+    <string name="label_next_key" msgid="362972844525672568">"下一頁"</string>
+    <string name="label_previous_key" msgid="1211868118071386787">"上一頁"</string>
     <string name="label_done_key" msgid="2441578748772529288">"完成"</string>
     <string name="label_send_key" msgid="2815056534433717444">"傳送"</string>
     <string name="label_pause_key" msgid="181098308428035340">"暫停"</string>
-    <string name="label_wait_key" msgid="6402152600878093134">"等候"</string>
+    <string name="label_wait_key" msgid="6402152600878093134">"等待"</string>
     <string name="spoken_use_headphones" msgid="896961781287283493">"連接耳機即可聽取系統朗讀密碼按鍵。"</string>
     <string name="spoken_current_text_is" msgid="2485723011272583845">"目前文字為 %s"</string>
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"未輸入文字"</string>
@@ -101,8 +101,8 @@
     <string name="spoken_description_search" msgid="1247236163755920808">"搜尋"</string>
     <string name="spoken_description_dot" msgid="40711082435231673">"點"</string>
     <string name="spoken_description_language_switch" msgid="5507091328222331316">"切換語言"</string>
-    <string name="spoken_description_action_next" msgid="8636078276664150324">"下一步"</string>
-    <string name="spoken_description_action_previous" msgid="800872415009336208">"上一步"</string>
+    <string name="spoken_description_action_next" msgid="8636078276664150324">"下一頁"</string>
+    <string name="spoken_description_action_previous" msgid="800872415009336208">"上一頁"</string>
     <string name="spoken_description_shiftmode_on" msgid="5700440798609574589">"Shift 鍵已啟用"</string>
     <string name="spoken_description_shiftmode_locked" msgid="593175803181701830">"大寫鎖定已啟用"</string>
     <string name="spoken_description_shiftmode_off" msgid="657219998449174808">"Shift 鍵已停用"</string>
@@ -117,7 +117,7 @@
     <string name="keyboard_mode_email" msgid="6216248078128294262">"電子郵件"</string>
     <string name="keyboard_mode_im" msgid="1137405089766557048">"簡訊"</string>
     <string name="keyboard_mode_number" msgid="7991623440699957069">"數字"</string>
-    <string name="keyboard_mode_phone" msgid="6851627527401433229">"電話"</string>
+    <string name="keyboard_mode_phone" msgid="6851627527401433229">"電話號碼"</string>
     <string name="keyboard_mode_text" msgid="6479436687899701619">"文字"</string>
     <string name="keyboard_mode_time" msgid="4381856885582143277">"時間"</string>
     <string name="keyboard_mode_url" msgid="1519819835514911218">"網址"</string>
@@ -133,23 +133,25 @@
     <string name="send_feedback" msgid="1780431884109392046">"提供意見"</string>
     <string name="select_language" msgid="3693815588777926848">"輸入語言"</string>
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"再次輕觸即可儲存"</string>
-    <string name="has_dictionary" msgid="6071847973466625007">"可使用字典"</string>
+    <string name="has_dictionary" msgid="6071847973466625007">"可用的字典"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"啟用使用者意見回饋"</string>
     <string name="prefs_description_log" msgid="7525225584555429211">"自動傳送使用統計資料和當機報告,協助改善此輸入法編輯器"</string>
     <string name="keyboard_layout" msgid="8451164783510487501">"鍵盤主題"</string>
-    <string name="subtype_en_GB" msgid="88170601942311355">"英文 (英式)"</string>
-    <string name="subtype_en_US" msgid="6160452336634534239">"英文 (美式)"</string>
+    <string name="subtype_en_GB" msgid="88170601942311355">"英文 (英國)"</string>
+    <string name="subtype_en_US" msgid="6160452336634534239">"英文 (美國)"</string>
     <string name="subtype_es_US" msgid="5583145191430180200">"西班牙文 (美國)"</string>
     <string name="subtype_with_layout_en_GB" msgid="2179097748724725906">"英文 (英國) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_en_US" msgid="1362581347576714579">"英文 (美國) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_es_US" msgid="6261791057007890189">"西班牙文 (美國) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
-    <string name="subtype_no_language" msgid="141420857808801746">"無語言"</string>
-    <string name="subtype_no_language_qwerty" msgid="2956121451616633133">"無語言 (QWERTY)"</string>
-    <string name="subtype_no_language_qwertz" msgid="1177848172397202890">"無語言 (QWERTZ)"</string>
-    <string name="subtype_no_language_azerty" msgid="8721460968141187394">"無語言 (AZERTY)"</string>
-    <string name="subtype_no_language_dvorak" msgid="3122976737669823935">"無語言 (Dvorak)"</string>
-    <string name="subtype_no_language_colemak" msgid="4205992994906097244">"無語言 (Colemak)"</string>
-    <string name="subtype_no_language_pcqwerty" msgid="8840928374394180189">"無語言 (PC)"</string>
+    <string name="subtype_nepali_traditional" msgid="9032247506728040447">"<xliff:g id="LANGUAGE">%s</xliff:g> (傳統)"</string>
+    <string name="subtype_no_language" msgid="7137390094240139495">"無語言 (字母)"</string>
+    <string name="subtype_no_language_qwerty" msgid="244337630616742604">"字母 (QWERTY)"</string>
+    <string name="subtype_no_language_qwertz" msgid="443066912507547976">"字母 (QWERTZ)"</string>
+    <string name="subtype_no_language_azerty" msgid="8144348527575640087">"字母 (AZERTY)"</string>
+    <string name="subtype_no_language_dvorak" msgid="1564494667584718094">"字母 (Dvorak)"</string>
+    <string name="subtype_no_language_colemak" msgid="5837418400010302623">"字母 (Colemak)"</string>
+    <string name="subtype_no_language_pcqwerty" msgid="5354918232046200018">"字母 (PC)"</string>
+    <string name="subtype_emoji" msgid="7483586578074549196">"表情符號"</string>
     <string name="custom_input_styles_title" msgid="8429952441821251512">"自訂輸入樣式"</string>
     <string name="add_style" msgid="6163126614514489951">"新增樣式"</string>
     <string name="add" msgid="8299699805688017798">"新增"</string>
@@ -171,7 +173,7 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"準備為<xliff:g id="LOCALE_NAME">%s</xliff:g>版本安裝這個檔案嗎?"</string>
     <string name="error" msgid="8940763624668513648">"發生錯誤"</string>
     <string name="button_default" msgid="3988017840431881491">"預設"</string>
-    <string name="setup_welcome_title" msgid="6112821709832031715">"歡迎使用「<xliff:g id="APPLICATION_NAME">%s</xliff:g>」"</string>
+    <string name="setup_welcome_title" msgid="6112821709832031715">"歡迎使用 <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
     <string name="setup_welcome_additional_description" msgid="8150252008545768953">"含手勢輸入功能"</string>
     <string name="setup_start_action" msgid="8936036460897347708">"開始設定"</string>
     <string name="setup_next_action" msgid="371821437915144603">"下一步"</string>
diff --git a/java/res/values-zu/strings.xml b/java/res/values-zu/strings.xml
index 9c44c6f..9ed88a0 100644
--- a/java/res/values-zu/strings.xml
+++ b/java/res/values-zu/strings.xml
@@ -64,8 +64,8 @@
     <string name="auto_correction_summary" msgid="5625751551134658006">"Ibha yesikhala nokubhala ngamagama amakhulu kulungisa amaphutha amagama athayiphwe kabi"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Valiwe"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Thobekile"</string>
-    <string name="auto_correction_threshold_mode_aggressive" msgid="3524029103734923819">"Bukhali"</string>
-    <string name="auto_correction_threshold_mode_very_aggressive" msgid="3386782235540547678">"Nobudlova kakhulu"</string>
+    <string name="auto_correction_threshold_mode_aggressive" msgid="7319007299148899623">"Bukhali"</string>
+    <string name="auto_correction_threshold_mode_very_aggressive" msgid="1853309024129480416">"Bukhali kakhulu"</string>
     <string name="bigram_prediction" msgid="1084449187723948550">"Iziphakamiso zegama elilandelayo"</string>
     <string name="bigram_prediction_summary" msgid="3896362682751109677">"Sebenzisa igama langaphambilini ekwenzeni iziphakamiso"</string>
     <string name="gesture_input" msgid="826951152254563827">"Nika amandla okuthayipha ngokuthinta"</string>
@@ -143,13 +143,15 @@
     <string name="subtype_with_layout_en_GB" msgid="2179097748724725906">"I-English (UK) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_en_US" msgid="1362581347576714579">"I-English (US) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_es_US" msgid="6261791057007890189">"I-Spanish (US) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
-    <string name="subtype_no_language" msgid="141420857808801746">"Akunalimi"</string>
-    <string name="subtype_no_language_qwerty" msgid="2956121451616633133">"Akunalimi (QWERTY)"</string>
-    <string name="subtype_no_language_qwertz" msgid="1177848172397202890">"Alukho ulimi (QWERTZ)"</string>
-    <string name="subtype_no_language_azerty" msgid="8721460968141187394">"Alukho ulimi (AZERTY)"</string>
-    <string name="subtype_no_language_dvorak" msgid="3122976737669823935">"Alukho ulimi (Dvorak)"</string>
-    <string name="subtype_no_language_colemak" msgid="4205992994906097244">"Alukho ulimi (Colemak)"</string>
-    <string name="subtype_no_language_pcqwerty" msgid="8840928374394180189">"Alukho ulimi (PC)"</string>
+    <string name="subtype_nepali_traditional" msgid="9032247506728040447">"<xliff:g id="LANGUAGE">%s</xliff:g> (Ezosiko)"</string>
+    <string name="subtype_no_language" msgid="7137390094240139495">"Alikho ulimi (Alfabhethi)"</string>
+    <string name="subtype_no_language_qwerty" msgid="244337630616742604">"Alfabhethi (QWERTY)"</string>
+    <string name="subtype_no_language_qwertz" msgid="443066912507547976">"Alfabhethi (QWERTZ)"</string>
+    <string name="subtype_no_language_azerty" msgid="8144348527575640087">"Alfabhethi (I-AZERTY)"</string>
+    <string name="subtype_no_language_dvorak" msgid="1564494667584718094">"Alfabhethi (Dvorak)"</string>
+    <string name="subtype_no_language_colemak" msgid="5837418400010302623">"Alfabhethi (Colemak)"</string>
+    <string name="subtype_no_language_pcqwerty" msgid="5354918232046200018">"Alfabhethi (PC)"</string>
+    <string name="subtype_emoji" msgid="7483586578074549196">"I-Emoji"</string>
     <string name="custom_input_styles_title" msgid="8429952441821251512">"Izitayela zokufaka ngokwezifiso"</string>
     <string name="add_style" msgid="6163126614514489951">"Engeza isitayela"</string>
     <string name="add" msgid="8299699805688017798">"Engeza"</string>
diff --git a/java/res/values/attrs.xml b/java/res/values/attrs.xml
index eef9116..0978214 100644
--- a/java/res/values/attrs.xml
+++ b/java/res/values/attrs.xml
@@ -1,17 +1,21 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2010 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.
+<!--
+/*
+**
+** Copyright 2010, 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>
@@ -22,15 +26,18 @@
         <attr name="keyboardViewStyle" format="reference" />
         <!-- MainKeyboardView style -->
         <attr name="mainKeyboardViewStyle" format="reference" />
+        <!-- EmojiKeyboardView style -->
+        <attr name="emojiKeyboardViewStyle" format="reference" />
         <!-- MoreKeysKeyboard style -->
         <attr name="moreKeysKeyboardStyle" format="reference" />
         <!-- MoreKeysKeyboardView style -->
         <attr name="moreKeysKeyboardViewStyle" format="reference" />
-        <attr name="moreKeysKeyboardPanelStyle" format="reference" />
+        <!-- MoreKeysKeyboardView container style -->
+        <attr name="moreKeysKeyboardContainerStyle" format="reference" />
         <!-- Suggestions strip style -->
         <attr name="suggestionStripViewStyle" format="reference" />
-        <attr name="moreSuggestionsViewStyle" format="reference" />
-        <attr name="suggestionBackgroundStyle" format="reference" />
+        <!-- Suggestion word style -->
+        <attr name="suggestionWordStyle" format="reference" />
     </declare-styleable>
 
     <declare-styleable name="KeyboardView">
@@ -38,6 +45,8 @@
              possible states: normal, pressed, checkable, checkable+pressed, checkable+checked,
              checkable+checked+pressed. -->
         <attr name="keyBackground" format="reference" />
+        <!-- Image for the functional key used in Emoji layout. -->
+        <attr name="keyBackgroundEmojiFunctional" format="reference" />
 
         <!-- Horizontal padding of left/right aligned key label to the edge of the key. -->
         <attr name="keyLabelHorizontalPadding" format="dimension" />
@@ -158,6 +167,10 @@
         <attr name="suppressKeyPreviewAfterBatchInputDuration" format="integer" />
     </declare-styleable>
 
+    <declare-styleable name="EmojiKeyboardView">
+        <attr name="emojiTabLabelColor" format="reference" />
+    </declare-styleable>
+
     <declare-styleable name="SuggestionStripView">
         <attr name="suggestionStripOption" format="integer">
             <!-- This should be aligned with SuggestionStripLayoutHelper.AUTO_CORRECT_* and etc. -->
@@ -169,10 +182,6 @@
         <attr name="colorTypedWord" format="color" />
         <attr name="colorAutoCorrect" format="color" />
         <attr name="colorSuggested" format="color" />
-        <attr name="alphaValidTypedWord" format="fraction" />
-        <attr name="alphaTypedWord" format="fraction" />
-        <attr name="alphaAutoCorrect" format="fraction" />
-        <attr name="alphaSuggested" format="fraction" />
         <attr name="alphaObsoleted" format="fraction" />
         <attr name="suggestionsCountInStrip" format="integer" />
         <attr name="centerSuggestionPercentile" format="fraction" />
@@ -216,9 +225,15 @@
         <attr name="iconLanguageSwitchKey" format="reference" />
         <attr name="iconZwnjKey" format="reference" />
         <attr name="iconZwjKey" format="reference" />
+        <attr name="iconImeKey" format="reference" />
         <attr name="iconEmojiKey" format="reference" />
     </declare-styleable>
 
+    <declare-styleable name="Keyboard_GridRows">
+        <attr name="codesArray" format="reference" />
+        <attr name="textsArray" format="reference" />
+    </declare-styleable>
+
     <declare-styleable name="Keyboard_Key">
         <!-- The unicode value that this key outputs.
              Code value represented in hexadecimal prefixed with "0x" or code value reference using
@@ -240,11 +255,12 @@
         <attr name="maxMoreKeysColumn" format="integer" />
         <attr name="backgroundType" format="enum">
             <!-- This should be aligned with Key.BACKGROUND_TYPE_* -->
-            <enum name="normal" value="0" />
-            <enum name="functional" value="1" />
-            <enum name="action" value="2" />
-            <enum name="stickyOff" value="3" />
-            <enum name="stickyOn" value="4" />
+            <enum name="empty" value="0" />
+            <enum name="normal" value="1" />
+            <enum name="functional" value="2" />
+            <enum name="action" value="3" />
+            <enum name="stickyOff" value="4" />
+            <enum name="stickyOn" value="5" />
         </attr>
         <!-- The key action flags. -->
         <attr name="keyActionFlags" format="integer">
@@ -365,6 +381,7 @@
     </declare-styleable>
 
     <declare-styleable name="Keyboard_Case">
+        <attr name="keyboardLayoutSet" format="string" />
         <!-- This should be aligned with KeyboardLayoutSet_Element's elementName. -->
         <attr name="keyboardLayoutSetElement" format="enum|string">
             <enum name="alphabet" value="0" />
@@ -377,6 +394,13 @@
             <enum name="phone" value="7"  />
             <enum name="phoneSymbols" value="8"  />
             <enum name="number" value="9"  />
+            <enum name="emojiRecents" value="10" />
+            <enum name="emojiCategory1" value="11" />
+            <enum name="emojiCategory2" value="12" />
+            <enum name="emojiCategory3" value="13" />
+            <enum name="emojiCategory4" value="14" />
+            <enum name="emojiCategory5" value="15" />
+            <enum name="emojiCategory6" value="16" />
         </attr>
         <!-- This should be aligned with KeyboardId.MODE_* -->
         <attr name="mode" format="enum|string">
@@ -432,6 +456,13 @@
             <enum name="phone" value="7"  />
             <enum name="phoneSymbols" value="8"  />
             <enum name="number" value="9"  />
+            <enum name="emojiRecents" value="10" />
+            <enum name="emojiCategory1" value="11" />
+            <enum name="emojiCategory2" value="12" />
+            <enum name="emojiCategory3" value="13" />
+            <enum name="emojiCategory4" value="14" />
+            <enum name="emojiCategory5" value="15" />
+            <enum name="emojiCategory6" value="16" />
         </attr>
         <attr name="elementKeyboard" format="reference"/>
         <!-- Enable proximity characters correction. Disabled by default. -->
diff --git a/java/res/values/colors.xml b/java/res/values/colors.xml
index daa167c..3803cb7 100644
--- a/java/res/values/colors.xml
+++ b/java/res/values/colors.xml
@@ -1,47 +1,43 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2012 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
 -->
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android">
-    <!-- Color resources for default, and Gingerbread theme. -->
-    <color name="highlight_color_default">#FFFCAE00</color>
-    <color name="highlight_translucent_color_default">#99FCAE00</color>
-    <color name="key_text_color_default">@android:color/white</color>
-    <color name="key_text_shadow_color_default">#BB000000</color>
-    <color name="key_text_inactivated_color_default">@android:color/white</color>
-    <color name="key_hint_letter_color_default">#80000000</color>
-    <color name="key_hint_label_color_default">#E0E0E4E5</color>
-    <color name="key_shifted_letter_hint_inactivated_color_default">#66E0E4E5</color>
-    <color name="key_shifted_letter_hint_activated_color_default">#CCE0E4E5</color>
-    <color name="spacebar_text_color_default">#FFC0C0C0</color>
-    <color name="spacebar_text_shadow_color_default">#80000000</color>
-    <color name="typed_word_color_default">@android:color/white</color>
-    <color name="gesture_floating_preview_color_default">#C0000000</color>
-    <!-- Color resources for Stone theme. -->
-    <color name="key_text_color_stone">@android:color/black</color>
-    <color name="key_text_shadow_color_stone">@android:color/white</color>
-    <color name="key_text_inactivated_color_stone">#FF808080</color>
-    <color name="key_hint_letter_color_stone">#80000000</color>
-    <color name="key_hint_label_color_stone">#E0000000</color>
-    <color name="key_shifted_letter_hint_inactivated_color_stone">#66000000</color>
-    <color name="key_shifted_letter_hint_activated_color_stone">#CC000000</color>
-    <color name="spacebar_text_color_stone">@android:color/black</color>
-    <color name="spacebar_text_shadow_color_stone">#D0FFFFFF</color>
-    <!-- Color resources for IceCreamSandwich theme. -->
+    <!-- Color resources for Gingerbread theme. -->
+    <color name="highlight_color_gb">#FFFCAE00</color>
+    <color name="typed_word_color_gb">@android:color/white</color>
+    <color name="highlight_translucent_color_gb">#99FCAE00</color>
+    <color name="key_text_color_gb">@android:color/white</color>
+    <color name="key_text_shadow_color_gb">#BB000000</color>
+    <color name="key_text_inactivated_color_gb">#66E0E4E5</color>
+    <color name="key_hint_letter_color_gb">#80000000</color>
+    <color name="key_hint_label_color_gb">#E0E0E4E5</color>
+    <color name="key_shifted_letter_hint_inactivated_color_gb">#66E0E4E5</color>
+    <color name="key_shifted_letter_hint_activated_color_gb">#CCE0E4E5</color>
+    <color name="spacebar_text_color_gb">#FFC0C0C0</color>
+    <color name="spacebar_text_shadow_color_gb">#80000000</color>
+    <color name="gesture_floating_preview_color_gb">#C0000000</color>
+    <!-- Color resources for IceCreamSandwich theme. Base color = 33B5E5 -->
     <!-- android:color/holo_blue_light value is #FF33B5E5 -->
-    <color name="highlight_color_ics">@android:color/holo_blue_light</color>
+    <color name="highlight_color_ics">#FF33B5E5</color>
+    <color name="typed_word_color_ics">#D833B5E5</color>
+    <color name="suggested_word_color_ics">#B233B5E5</color>
     <color name="highlight_translucent_color_ics">#9933B5E5</color>
     <color name="key_text_color_ics">@android:color/white</color>
     <color name="key_text_shadow_color_ics">@android:color/transparent</color>
@@ -52,11 +48,22 @@
     <color name="key_shifted_letter_hint_activated_color_ics">@android:color/white</color>
     <color name="spacebar_text_color_ics">#FFC0C0C0</color>
     <color name="spacebar_text_shadow_color_ics">#80000000</color>
-    <color name="typed_word_color_ics">@color/highlight_color_ics</color>
+    <color name="gesture_floating_preview_color_ics">#C0000000</color>
+    <!-- Color resources for KLP theme. Base color = F0F0F0 -->
+    <color name="highlight_color_holo">#FFF0F0F0</color>
+    <color name="typed_word_color_holo">#D8F0F0F0</color>
+    <color name="suggested_word_color_holo">#B2F0F0F0</color>
+    <color name="highlight_translucent_color_holo">#99E0E0E0</color>
     <!-- Color resources for setup wizard and tutorial -->
     <color name="setup_background">#FFEBEBEB</color>
     <color name="setup_text_dark">#FF707070</color>
     <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>
+
+    <!-- TODO: Color which should be included in the theme -->
+    <color name="emoji_key_background_color">#00000000</color>
+    <color name="emoji_key_pressed_background_color">#30FFFFFF</color>
 </resources>
diff --git a/java/res/xml-sw768dp/kbd_more_keys_keyboard_template.xml b/java/res/values/config-additional-features.xml
similarity index 68%
rename from java/res/xml-sw768dp/kbd_more_keys_keyboard_template.xml
rename to java/res/values/config-additional-features.xml
index f89a0a6..47eb772 100644
--- a/java/res/xml-sw768dp/kbd_more_keys_keyboard_template.xml
+++ b/java/res/values/config-additional-features.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2008, The Android Open Source Project
+** 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.
@@ -18,9 +18,7 @@
 */
 -->
 
-<Keyboard xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyWidth="5.0%p"
-    latin:rowHeight="@dimen/popup_key_height"
-    style="?attr/moreKeysKeyboardStyle"
-    >
-</Keyboard>
+<resources>
+    <!-- Whether phrase gestures are enabled by default -->
+    <bool name="config_default_phrase_gesture_enabled">false</bool>
+</resources>
diff --git a/java/res/values/config.xml b/java/res/values/config.xml
index d3a21f2..465d52c 100644
--- a/java/res/values/config.xml
+++ b/java/res/values/config.xml
@@ -42,7 +42,7 @@
     <integer name="config_keyboard_grid_height">16</integer>
     <integer name="config_double_space_period_timeout">1100</integer>
     <!-- This configuration is the index of the array {@link KeyboardSwitcher.KEYBOARD_THEMES}. -->
-    <string name="config_default_keyboard_theme_index" translatable="false">5</string>
+    <string name="config_default_keyboard_theme_index" translatable="false">0</string>
     <integer name="config_max_more_keys_column">5</integer>
 
     <!--
diff --git a/java/res/values/dimens.xml b/java/res/values/dimens.xml
index 98ae76c..4e3b2f5 100644
--- a/java/res/values/dimens.xml
+++ b/java/res/values/dimens.xml
@@ -29,18 +29,11 @@
 
     <dimen name="more_keys_keyboard_key_horizontal_padding">8dp</dimen>
 
-    <fraction name="keyboard_top_padding">1.556%p</fraction>
-    <fraction name="keyboard_bottom_padding">4.669%p</fraction>
     <fraction name="keyboard_left_padding">0%p</fraction>
     <fraction name="keyboard_right_padding">0%p</fraction>
-    <fraction name="key_bottom_gap">6.250%p</fraction>
-    <fraction name="key_horizontal_gap">1.352%p</fraction>
 
-    <fraction name="keyboard_top_padding_stone">1.556%p</fraction>
-    <fraction name="keyboard_bottom_padding_stone">0.778%p</fraction>
-    <fraction name="key_bottom_gap_stone">7.506%p</fraction>
-    <fraction name="key_horizontal_gap_stone">1.739%p</fraction>
-
+    <fraction name="keyboard_top_padding_gb">1.556%p</fraction>
+    <fraction name="keyboard_bottom_padding_gb">4.669%p</fraction>
     <fraction name="key_bottom_gap_gb">6.495%p</fraction>
     <fraction name="key_horizontal_gap_gb">1.971%p</fraction>
 
@@ -48,13 +41,12 @@
     <fraction name="keyboard_bottom_padding_ics">4.669%p</fraction>
     <fraction name="key_bottom_gap_ics">6.127%p</fraction>
     <fraction name="key_horizontal_gap_ics">1.739%p</fraction>
-    <dimen name="more_keys_keyboard_horizontal_edges_padding_ics">4dp</dimen>
 
     <!-- Amount of allowance for selecting keys in a mini popup keyboard by sliding finger. -->
     <!-- popup_key_height x 1.2 -->
     <dimen name="more_keys_keyboard_slide_allowance">63.36dp</dimen>
     <!-- popup_key_height x -1.0 -->
-    <dimen name="more_keys_keyboard_vertical_correction">-52.8dp</dimen>
+    <dimen name="more_keys_keyboard_vertical_correction_gb">-52.8dp</dimen>
     <dimen name="keyboard_vertical_correction">0.0dp</dimen>
 
     <fraction name="key_letter_ratio">55%</fraction>
@@ -67,7 +59,7 @@
     <fraction name="key_preview_text_ratio">82%</fraction>
     <fraction name="spacebar_text_ratio">33.735%</fraction>
     <dimen name="key_preview_height">80dp</dimen>
-    <dimen name="key_preview_offset">-8.0dp</dimen>
+    <dimen name="key_preview_offset_gb">-8.0dp</dimen>
 
     <dimen name="key_label_horizontal_padding">4dp</dimen>
     <dimen name="key_hint_letter_padding">1dp</dimen>
@@ -121,10 +113,20 @@
     <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>
 
     <integer name="user_dictionary_max_word_length" translatable="false">48</integer>
+
+    <dimen name="language_on_spacebar_horizontal_margin">1dp</dimen>
+
 </resources>
diff --git a/java/res/values/donottranslate.xml b/java/res/values/donottranslate.xml
index e352f08..42e692d 100644
--- a/java/res/values/donottranslate.xml
+++ b/java/res/values/donottranslate.xml
@@ -18,6 +18,8 @@
 */
 -->
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- TODO: these settings depend on the language. They should be put either in the dictionary
+         header, or in the subtype maybe? -->
     <!-- Symbols that are suggested between words -->
     <string name="suggested_punctuations">!,?,\\,,:,;,\",(,),\',-,/,@,_</string>
     <!-- Symbols that are normally preceded by a space (used to add an auto-space before these) -->
@@ -29,6 +31,8 @@
     <string name="symbols_word_separators">"&#x0009;&#x0020;\n"()[]{}*&amp;&lt;&gt;+=|.,;:!?/_\"</string>
     <!-- Word connectors -->
     <string name="symbols_word_connectors">\'-</string>
+    <!-- Whether this language uses spaces between words -->
+    <bool name="current_language_has_spaces">true</bool>
 
     <!--  Always show the suggestion strip -->
     <string name="prefs_suggestion_visibility_show_value">0</string>
@@ -94,29 +98,17 @@
     <string name="prefs_force_non_distinct_multitouch">Force non-distinct multitouch</string>
 
     <!-- Keyboard theme names -->
-    <string name="layout_basic">Basic</string>
-    <string name="layout_high_contrast">Basic (High Contrast)</string>
-    <string name="layout_stone_bold">Stone (bold)</string>
-    <string name="layout_stone_normal">Stone (normal)</string>
     <string name="layout_gingerbread">Gingerbread</string>
     <string name="layout_ics">IceCreamSandwich</string>
 
     <!-- For keyboard theme switcher dialog -->
     <string-array name="keyboard_layout_modes">
-        <item>@string/layout_basic</item>
-        <item>@string/layout_high_contrast</item>
-        <item>@string/layout_stone_normal</item>
-        <item>@string/layout_stone_bold</item>
-        <item>@string/layout_gingerbread</item>
         <item>@string/layout_ics</item>
+        <item>@string/layout_gingerbread</item>
     </string-array>
     <string-array name="keyboard_layout_modes_values">
         <item>0</item>
         <item>1</item>
-        <item>2</item>
-        <item>3</item>
-        <item>4</item>
-        <item>5</item>
     </string-array>
 
     <!-- Subtype locale display name exceptions.
@@ -166,47 +158,47 @@
 
     <!-- Compatibility map from subtypeLocale:subtypeExtraValue to keyboardLayoutSet -->
     <string-array name="locale_and_extra_value_to_keyboard_layout_set_map">
-        <item>en_US:TrySuppressingImeSwitcher,AsciiCapable,SupportTouchPositionCorrection</item>
+        <item>en_US:TrySuppressingImeSwitcher,AsciiCapable,SupportTouchPositionCorrection,EmojiCapable</item>
         <item>qwerty</item>
-        <item>en_GB:TrySuppressingImeSwitcher,AsciiCapable,SupportTouchPositionCorrection</item>
+        <item>en_GB:TrySuppressingImeSwitcher,AsciiCapable,SupportTouchPositionCorrection,EmojiCapable</item>
         <item>qwerty</item>
-        <item>ar:SupportTouchPositionCorrection</item>
+        <item>ar:SupportTouchPositionCorrection,EmojiCapable</item>
         <item>arabic</item>
-        <item>cs:AsciiCapable,SupportTouchPositionCorrection</item>
+        <item>cs:AsciiCapable,SupportTouchPositionCorrection,EmojiCapable</item>
         <item>qwertz</item>
-        <item>da:AsciiCapable,SupportTouchPositionCorrection</item>
+        <item>da:AsciiCapable,SupportTouchPositionCorrection,EmojiCapable</item>
         <item>nordic</item>
-        <item>de:AsciiCapable,SupportTouchPositionCorrection</item>
+        <item>de:AsciiCapable,SupportTouchPositionCorrection,EmojiCapable</item>
         <item>qwertz</item>
-        <item>es:AsciiCapable,SupportTouchPositionCorrection</item>
+        <item>es:AsciiCapable,SupportTouchPositionCorrection,EmojiCapable</item>
         <item>spanish</item>
-        <item>fi:AsciiCapable,SupportTouchPositionCorrection</item>
+        <item>fi:AsciiCapable,SupportTouchPositionCorrection,EmojiCapable</item>
         <item>nordic</item>
-        <item>fr:AsciiCapable,SupportTouchPositionCorrection</item>
+        <item>fr:AsciiCapable,SupportTouchPositionCorrection,EmojiCapable</item>
         <item>azerty</item>
-        <item>fr_CA:AsciiCapable,SupportTouchPositionCorrection</item>
+        <item>fr_CA:AsciiCapable,SupportTouchPositionCorrection,EmojiCapable</item>
         <item>qwerty</item>
-        <item>hr:AsciiCapable,SupportTouchPositionCorrection</item>
+        <item>hr:AsciiCapable,SupportTouchPositionCorrection,EmojiCapable</item>
         <item>qwertz</item>
-        <item>hu:AsciiCapable,SupportTouchPositionCorrection</item>
+        <item>hu:AsciiCapable,SupportTouchPositionCorrection,EmojiCapable</item>
         <item>qwertz</item>
-        <item>it:AsciiCapable,SupportTouchPositionCorrection</item>
+        <item>it:AsciiCapable,SupportTouchPositionCorrection,EmojiCapable</item>
         <item>qwerty</item>
-        <item>iw:SupportTouchPositionCorrection</item>
+        <item>iw:SupportTouchPositionCorrection,EmojiCapable</item>
         <item>hebrew</item>
-        <item>nb:AsciiCapable,SupportTouchPositionCorrection</item>
+        <item>nb:AsciiCapable,SupportTouchPositionCorrection,EmojiCapable</item>
         <item>nordic</item>
-        <item>nl:AsciiCapable,SupportTouchPositionCorrection</item>
+        <item>nl:AsciiCapable,SupportTouchPositionCorrection,EmojiCapable</item>
         <item>qwerty</item>
-        <item>pl:AsciiCapable,SupportTouchPositionCorrection</item>
+        <item>pl:AsciiCapable,SupportTouchPositionCorrection,EmojiCapable</item>
         <item>qwerty</item>
-        <item>ru:SupportTouchPositionCorrection</item>
+        <item>ru:SupportTouchPositionCorrection,EmojiCapable</item>
         <item>east_slavic</item>
-        <item>sr:SupportTouchPositionCorrection</item>
+        <item>sr:SupportTouchPositionCorrection,EmojiCapable</item>
         <item>south_slavic</item>
-        <item>sv:AsciiCapable,SupportTouchPositionCorrection</item>
+        <item>sv:AsciiCapable,SupportTouchPositionCorrection,EmojiCapable</item>
         <item>nordic</item>
-        <item>tr:AsciiCapable,SupportTouchPositionCorrection</item>
+        <item>tr:AsciiCapable,SupportTouchPositionCorrection,EmojiCapable</item>
         <item>qwerty</item>
     </string-array>
 
diff --git a/java/res/values/emoji-categories.xml b/java/res/values/emoji-categories.xml
new file mode 100644
index 0000000..ce82a8b
--- /dev/null
+++ b/java/res/values/emoji-categories.xml
@@ -0,0 +1,127 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2013, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<!-- Note: This emoji code point list is valid prior to JB-MR2 (API < 18).
+     There is another emoji code point list for JB-MR2 and KLP and later under
+     res/xml/values-v1[89].-->
+<resources>
+    <!-- Dummy codeArrays for recents emoji keyboard.
+         Do not remove these keys, because they are used as a template. -->
+    <array
+        name="emoji_recents"
+        format="string"
+    >
+        <!-- These code point should be aligned with {@link RecentsKeyboard#TEMPLATE_KEY_CODE_*. -->
+        <item>30</item>
+        <item>31</item>
+    </array>
+    <array
+        name="emoji_nature"
+        format="string"
+    >
+        <item>2744</item> <!-- SNOWFLAKE -->
+    </array>
+    <array
+        name="emoji_symbols"
+        format="string"
+    >
+        <item>2460</item> <!-- CIRCLED DIGIT ONE -->
+        <item>2461</item> <!-- CIRCLED DIGIT TWO -->
+        <item>2462</item> <!-- CIRCLED DIGIT THREE -->
+        <item>2463</item> <!-- CIRCLED DIGIT FOUR -->
+        <item>2464</item> <!-- CIRCLED DIGIT FIVE -->
+        <item>2465</item> <!-- CIRCLED DIGIT SIX -->
+        <item>2466</item> <!-- CIRCLED DIGIT SEVEN -->
+        <item>2467</item> <!-- CIRCLED DIGIT EIGHT -->
+        <item>2468</item> <!-- CIRCLED DIGIT NINE -->
+        <item>2469</item> <!-- CIRCLED DIGIT TEN -->
+        <item>00ae</item> <!-- REGISTERED SIGN -->
+        <item>00a9</item> <!-- COPYRIGHT SIGN -->
+        <item>2122</item> <!-- TRADE MARK SIGN -->
+        <item>2734</item> <!-- EIGHT POINTED BLACK STAR -->
+        <item>2733</item> <!-- EIGHT POINTED PINWHEEL STAR -->
+        <item>2716</item> <!-- HEAVY MULTIPLICATION MARK -->
+        <item>2195</item> <!-- UP DOWN ARROW -->
+        <item>2197</item> <!-- NORTH EAST ARROW -->
+        <item>27a1</item> <!-- BLACK RIGHTWARDS ARROW -->
+        <item>2198</item> <!-- SOUTH EAST ARROW -->
+        <item>2199</item> <!-- SOUTH WEST ARROW -->
+        <item>2196</item> <!-- NORTH EAST ARROW -->
+        <item>2194</item> <!-- LEFT RIGHT ARROW -->
+        <item>25c0</item> <!-- BLACK LEFT-POINTING TRIANGLE -->
+        <item>25b6</item> <!-- BLACK ROGHT-POINTING TRIANGLE -->
+        <item>2747</item> <!-- SPARKLE -->
+        <item>25aa</item> <!-- BLACK SMALL SQUARE -->
+        <item>203c</item> <!-- DOUBLE EXCLAMATION MARK -->
+        <item>2660</item> <!-- BLACK SPADE SUIT -->
+        <item>2665</item> <!-- BLACK HEART SUIT -->
+        <item>2663</item> <!-- BLACK CLUB SUIT -->
+        <item>2666</item> <!-- BLACK DIAMOND SUIT -->
+        <item>21a9</item> <!-- LEFTWARDS ARROW WITH HOOK -->
+        <item>21aa</item> <!-- RIGHTWARDS ARROW WITH HOOK -->
+    </array>
+    <array
+        name="emoji_faces"
+        format="string"
+    >
+        <item>270C</item> <!-- VICTORY HAND -->
+        <item>2764</item> <!-- HEAVY BLACK HEART -->
+    </array>
+    <array
+        name="emoji_objects"
+        format="string"
+    >
+        <item>260e</item> <!-- BLACK TELEPHONE -->
+        <item>2709</item> <!-- ENVELOPE -->
+        <item>2712</item> <!-- BLACK NIB -->
+        <item>270f</item> <!-- PENCIL -->
+        <item>2702</item> <!-- BLACK SCISSORS -->
+        <item>2669</item> <!-- QUARTER NOTE -->
+        <item>266a</item> <!-- EIGHTH NOTE -->
+        <item>266c</item> <!-- BEAMED SIXTEENTH NOTES -->
+    </array>
+    <array
+        name="emoji_places"
+        format="string"
+    >
+        <item>2708</item> <!-- AIRPLANE -->
+        <item>2668</item> <!-- HOT SPRINGS -->
+    </array>
+    <array
+        name="emoji_emoticons"
+        format="string"
+    >
+        <item>=-O</item>
+        <item>:-P</item>
+        <item>;-)</item>
+        <item>:-(</item>
+        <item>:-)</item>
+        <item>:-!</item>
+        <item>:-$</item>
+        <item>B-)</item>
+        <item>:O</item>
+        <item>:-*</item>
+        <item>:-D</item>
+        <item>:\'(</item>
+        <item>:-\\</item>
+        <item>O:-)</item>
+        <item>:-[</item>
+    </array>
+</resources>
diff --git a/java/res/values/keyboard-icons-black.xml b/java/res/values/keyboard-icons-black.xml
deleted file mode 100644
index c1b1b65..0000000
--- a/java/res/values/keyboard-icons-black.xml
+++ /dev/null
@@ -1,44 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2011 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<resources>
-    <style name="KeyboardIcons.Black">
-        <!-- Keyboard icons -->
-        <!-- TODO: The following holo icon for phone (drawable-hdpi and drawable-xhdpi) are too
-             ambiguous.
-             sym_bkeyboard_voice_off
-          -->
-        <item name="iconShiftKey">@drawable/sym_bkeyboard_shift</item>
-        <item name="iconDeleteKey">@drawable/sym_bkeyboard_delete</item>
-        <item name="iconSettingsKey">@drawable/sym_bkeyboard_settings</item>
-        <item name="iconSpaceKey">@drawable/sym_bkeyboard_space</item>
-        <item name="iconEnterKey">@drawable/sym_bkeyboard_return</item>
-        <item name="iconSearchKey">@drawable/sym_bkeyboard_search</item>
-        <item name="iconTabKey">@drawable/sym_bkeyboard_tab</item>
-        <item name="iconShortcutKey">@drawable/sym_bkeyboard_mic</item>
-        <item name="iconShortcutForLabel">@drawable/sym_bkeyboard_label_mic</item>
-        <item name="iconSpaceKeyForNumberLayout">@drawable/sym_bkeyboard_space</item>
-        <item name="iconShiftKeyShifted">@drawable/sym_bkeyboard_shift_locked</item>
-        <item name="iconShortcutKeyDisabled">@drawable/sym_bkeyboard_voice_off</item>
-        <item name="iconTabKeyPreview">@drawable/sym_keyboard_feedback_tab</item>
-        <!-- TODO: Needs dedicated black theme globe icon -->
-        <item name="iconLanguageSwitchKey">@drawable/sym_keyboard_language_switch</item>
-        <!-- TODO: Needs dedicated black theme ZWNJ and ZWJ icons -->
-        <item name="iconZwnjKey">@drawable/sym_keyboard_zwnj_holo</item>
-        <item name="iconZwjKey">@drawable/sym_keyboard_zwj_holo</item>
-        <item name="iconEmojiKey">@drawable/ic_emoji_light</item>
-    </style>
-</resources>
diff --git a/java/res/values/keyboard-icons-ics.xml b/java/res/values/keyboard-icons-ics.xml
deleted file mode 100644
index 5ada27a..0000000
--- a/java/res/values/keyboard-icons-ics.xml
+++ /dev/null
@@ -1,41 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2011 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<resources>
-    <style name="KeyboardIcons.IceCreamSandwich">
-        <!-- Keyboard icons -->
-        <!-- TODO: The following holo icon for phone (drawable-hdpi and drawable-xhdpi) are missing.
-             sym_keyboard_123_mic_holo
-             -->
-        <item name="iconShiftKey">@drawable/sym_keyboard_shift_holo</item>
-        <item name="iconDeleteKey">@drawable/sym_keyboard_delete_holo</item>
-        <item name="iconSettingsKey">@drawable/sym_keyboard_settings_holo</item>
-        <item name="iconSpaceKey">@null</item>
-        <item name="iconEnterKey">@drawable/sym_keyboard_return_holo</item>
-        <item name="iconSearchKey">@drawable/sym_keyboard_search_holo</item>
-        <item name="iconTabKey">@drawable/sym_keyboard_tab_holo</item>
-        <item name="iconShortcutKey">@drawable/sym_keyboard_voice_holo</item>
-        <item name="iconShortcutForLabel">@drawable/sym_keyboard_label_mic_holo</item>
-        <item name="iconSpaceKeyForNumberLayout">@drawable/sym_keyboard_space_holo</item>
-        <item name="iconShiftKeyShifted">@drawable/sym_keyboard_shift_locked_holo</item>
-        <item name="iconShortcutKeyDisabled">@drawable/sym_keyboard_voice_off_holo</item>
-        <item name="iconTabKeyPreview">@drawable/sym_keyboard_feedback_tab</item>
-        <item name="iconLanguageSwitchKey">@drawable/sym_keyboard_language_switch</item>
-        <item name="iconZwnjKey">@drawable/sym_keyboard_zwnj_holo</item>
-        <item name="iconZwjKey">@drawable/sym_keyboard_zwj_holo</item>
-        <item name="iconEmojiKey">@drawable/ic_emoji_light</item>
-    </style>
-</resources>
diff --git a/java/res/values/keyboard-icons-white.xml b/java/res/values/keyboard-icons-white.xml
deleted file mode 100644
index 7c6de42..0000000
--- a/java/res/values/keyboard-icons-white.xml
+++ /dev/null
@@ -1,40 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2011 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<resources>
-    <style name="KeyboardIcons">
-        <!-- Keyboard icons -->
-        <item name="iconShiftKey">@drawable/sym_keyboard_shift</item>
-        <item name="iconDeleteKey">@drawable/sym_keyboard_delete</item>
-        <item name="iconSettingsKey">@drawable/sym_keyboard_settings</item>
-        <item name="iconSpaceKey">@drawable/sym_keyboard_space</item>
-        <item name="iconEnterKey">@drawable/sym_keyboard_return</item>ZZ
-        <item name="iconSearchKey">@drawable/sym_keyboard_search</item>
-        <item name="iconTabKey">@drawable/sym_keyboard_tab</item>
-        <item name="iconShortcutKey">@drawable/sym_keyboard_mic</item>
-        <item name="iconShortcutForLabel">@drawable/sym_keyboard_label_mic</item>
-        <item name="iconSpaceKeyForNumberLayout">@drawable/sym_keyboard_space</item>
-        <item name="iconShiftKeyShifted">@drawable/sym_keyboard_shift_locked</item>
-        <!-- TODO: Needs non-holo disabled shortcut icon drawable -->
-        <item name="iconShortcutKeyDisabled">@drawable/sym_keyboard_voice_off_holo</item>
-        <item name="iconTabKeyPreview">@drawable/sym_keyboard_feedback_tab</item>
-        <item name="iconLanguageSwitchKey">@drawable/sym_keyboard_language_switch</item>
-        <!-- TODO: Needs dedicated black theme ZWNJ and ZWJ icons -->
-        <item name="iconZwnjKey">@drawable/sym_keyboard_zwnj_holo</item>
-        <item name="iconZwjKey">@drawable/sym_keyboard_zwj_holo</item>
-        <item name="iconEmojiKey">@drawable/ic_emoji_dark</item>
-    </style>
-</resources>
diff --git a/java/res/values/keypress-vibration-durations.xml b/java/res/values/keypress-vibration-durations.xml
index 53448c3..ee0ac00 100644
--- a/java/res/values/keypress-vibration-durations.xml
+++ b/java/res/values/keypress-vibration-durations.xml
@@ -55,8 +55,8 @@
         <item>MODEL=HTL22:MANUFACTURER=HTC,15</item>
         <!-- Motorola Razor M -->
         <item>MODEL=XT907:MANUFACTURER=motorola,30</item>
-        <!-- Sony Xperia Z -->
-        <item>MODEL=C6603:MANUFACTURER=Sony,35</item>
+        <!-- Sony Xperia Z, Z Ultra -->
+        <item>MODEL=C6603|C6806:MANUFACTURER=Sony,35</item>
         <!-- Default value for unknown device. The negative value means system default. -->
         <item>,-1</item>
     </string-array>
diff --git a/java/res/values/setup-styles.xml b/java/res/values/setup-styles.xml
index 1ffe8ca..c968b2f 100644
--- a/java/res/values/setup-styles.xml
+++ b/java/res/values/setup-styles.xml
@@ -1,17 +1,21 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
+<!--
+/*
+**
+** 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:android="http://schemas.android.com/apk/res/android">
diff --git a/java/res/values/strings.xml b/java/res/values/strings.xml
index aae5b0b..69da1e8 100644
--- a/java/res/values/strings.xml
+++ b/java/res/values/strings.xml
@@ -371,6 +371,8 @@
     <!-- Description for Spanish (United States) keyboard subtype with explicit keyboard layout [CHAR LIMIT=25]
          This should be identical to subtype_es_US aside from the trailing (%s). -->
     <string name="subtype_with_layout_es_US">Spanish (US) (<xliff:g id="layout">%s</xliff:g>)</string>
+    <!-- Description for Nepali (Traditional) keyboard subtype [CHAR LIMIT=25] -->
+    <string name="subtype_nepali_traditional"><xliff:g id="language">%s</xliff:g> (Traditional)</string>
     <!-- TODO: Uncomment once we can handle IETF language tag with script name specified.
          Description for Serbian Cyrillic keyboard subtype [CHAR LIMIT=25]
     <string name="subtype_serbian_cyrillic">Serbian (Cyrillic)</string>
@@ -457,6 +459,8 @@
 disposition that offers additional keys, but smaller keys compared to other common dispositions for
 mobile devices. [CHAR LIMIT=25] -->
     <string name="subtype_no_language_pcqwerty">Alphabet (PC)</string>
+    <!-- Description for Emoji keyboard subtype [CHAR LIMIT=25] -->
+    <string name="subtype_emoji">Emoji</string>
 
     <!-- Title of the preference settings for custom input styles (language and keyboard layout pairs) [CHAR LIMIT=35]-->
     <string name="custom_input_styles_title">Custom input styles</string>
@@ -493,6 +497,8 @@
     <string name="prefs_read_external_dictionary">Read external dictionary file</string>
     <!-- Title of the settings for using only personalization dictionary -->
     <string name="prefs_use_only_personalization_dictionary" translatable="false">Use only personalization dictionary</string>
+    <!-- Title of the settings for boosting personalization dictionary -->
+    <string name="prefs_boost_personalization_dictionary" translatable="false">Boost personalization dictionary</string>
     <!-- Message to show when there are no files to install as an external dictionary [CHAR LIMIT=100] -->
     <string name="read_external_dictionary_no_files_message">No dictionary files in the Downloads folder</string>
     <!-- Title of the dialog that selects a file to install as an external dictionary [CHAR LIMIT=50] -->
diff --git a/java/res/values/styles.xml b/java/res/values/styles.xml
deleted file mode 100644
index 37c6a95..0000000
--- a/java/res/values/styles.xml
+++ /dev/null
@@ -1,415 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2010 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:android="http://schemas.android.com/apk/res/android">
-    <!-- Theme "Basic" -->
-    <style name="Keyboard">
-        <!-- This should be aligned with KeyboardSwitcher.KEYBOARD_THEMES[] -->
-        <item name="themeId">0</item>
-        <item name="touchPositionCorrectionData">@array/touch_position_correction_data_default</item>
-        <item name="rowHeight">25%p</item>
-        <item name="moreKeysTemplate">@xml/kbd_more_keys_keyboard_template</item>
-        <item name="keyboardTopPadding">@fraction/keyboard_top_padding</item>
-        <item name="keyboardBottomPadding">@fraction/keyboard_bottom_padding</item>
-        <item name="keyboardLeftPadding">@fraction/keyboard_left_padding</item>
-        <item name="keyboardRightPadding">@fraction/keyboard_right_padding</item>
-        <item name="horizontalGap">@fraction/key_horizontal_gap</item>
-        <item name="verticalGap">@fraction/key_bottom_gap</item>
-        <item name="maxMoreKeysColumn">@integer/config_max_more_keys_column</item>
-    </style>
-    <style name="KeyboardView">
-        <item name="android:background">@drawable/keyboard_background</item>
-        <item name="keyBackground">@drawable/btn_keyboard_key</item>
-        <item name="keyLetterSize">@fraction/key_letter_ratio</item>
-        <item name="keyLargeLetterRatio">@fraction/key_large_letter_ratio</item>
-        <item name="keyLabelSize">@fraction/key_label_ratio</item>
-        <item name="keyLargeLabelRatio">@fraction/key_large_label_ratio</item>
-        <item name="keyHintLetterRatio">@fraction/key_hint_letter_ratio</item>
-        <item name="keyHintLabelRatio">@fraction/key_hint_label_ratio</item>
-        <item name="keyShiftedLetterHintRatio">@fraction/key_uppercase_letter_ratio</item>
-        <item name="keyTypeface">normal</item>
-        <item name="keyTextColor">@color/key_text_color_default</item>
-        <item name="keyTextInactivatedColor">@color/key_text_color_default</item>
-        <item name="keyHintLetterColor">@color/key_hint_letter_color_default</item>
-        <item name="keyHintLabelColor">@color/key_hint_label_color_default</item>
-        <item name="keyShiftedLetterHintInactivatedColor">@color/key_shifted_letter_hint_inactivated_color_default</item>
-        <item name="keyShiftedLetterHintActivatedColor">@color/key_shifted_letter_hint_activated_color_default</item>
-        <item name="keyLabelHorizontalPadding">@dimen/key_label_horizontal_padding</item>
-        <item name="keyHintLetterPadding">@dimen/key_hint_letter_padding</item>
-        <item name="keyPopupHintLetterPadding">@dimen/key_popup_hint_letter_padding</item>
-        <item name="keyShiftedLetterHintPadding">@dimen/key_uppercase_letter_padding</item>
-        <item name="keyPreviewTextColor">@color/key_text_color_default</item>
-        <item name="keyPreviewTextRatio">@fraction/key_preview_text_ratio</item>
-        <item name="verticalCorrection">@dimen/keyboard_vertical_correction</item>
-        <item name="keyTextShadowColor">@color/key_text_shadow_color_default</item>
-        <item name="keyTextShadowRadius">2.75</item>
-        <item name="backgroundDimAlpha">128</item>
-        <item name="gestureFloatingPreviewTextSize">@dimen/gesture_floating_preview_text_size</item>
-        <item name="gestureFloatingPreviewTextColor">@color/highlight_color_default</item>
-        <item name="gestureFloatingPreviewTextOffset">@dimen/gesture_floating_preview_text_offset</item>
-        <item name="gestureFloatingPreviewColor">@color/gesture_floating_preview_color_default</item>
-        <item name="gestureFloatingPreviewHorizontalPadding">@dimen/gesture_floating_preview_horizontal_padding</item>
-        <item name="gestureFloatingPreviewVerticalPadding">@dimen/gesture_floating_preview_vertical_padding</item>
-        <item name="gestureFloatingPreviewRoundRadius">@dimen/gesture_floating_preview_round_radius</item>
-        <item name="gestureTrailMinSamplingDistance">@dimen/gesture_trail_min_sampling_distance</item>
-        <item name="gestureTrailMaxInterpolationAngularThreshold">@integer/gesture_trail_max_interpolation_angular_threshold</item>
-        <item name="gestureTrailMaxInterpolationDistanceThreshold">@dimen/gesture_trail_max_interpolation_distance_threshold</item>
-        <item name="gestureTrailMaxInterpolationSegments">@integer/gesture_trail_max_interpolation_segments</item>
-        <item name="gestureTrailFadeoutStartDelay">@integer/config_gesture_trail_fadeout_start_delay</item>
-        <item name="gestureTrailFadeoutDuration">@integer/config_gesture_trail_fadeout_duration</item>
-        <item name="gestureTrailUpdateInterval">@integer/config_gesture_trail_update_interval</item>
-        <item name="gestureTrailColor">@color/highlight_color_default</item>
-        <item name="gestureTrailStartWidth">@dimen/gesture_trail_start_width</item>
-        <item name="gestureTrailEndWidth">@dimen/gesture_trail_end_width</item>
-        <item name="gestureTrailBodyRatio">@integer/gesture_trail_body_ratio</item>
-        <item name="gestureTrailShadowRatio">@integer/gesture_trail_shadow_ratio</item>
-        <!-- Common attributes of MainKeyboardView -->
-        <item name="keyHysteresisDistance">@dimen/config_key_hysteresis_distance</item>
-        <item name="keyHysteresisDistanceForSlidingModifier">@dimen/config_key_hysteresis_distance_for_sliding_modifier</item>
-        <item name="touchNoiseThresholdTime">@integer/config_touch_noise_threshold_time</item>
-        <item name="touchNoiseThresholdDistance">@dimen/config_touch_noise_threshold_distance</item>
-        <item name="slidingKeyInputEnable">@bool/config_sliding_key_input_enabled</item>
-        <item name="slidingKeyInputPreviewColor">@color/highlight_translucent_color_default</item>
-        <item name="slidingKeyInputPreviewWidth">@dimen/config_sliding_key_input_preview_width</item>
-        <item name="slidingKeyInputPreviewBodyRatio">@integer/config_sliding_key_input_preview_body_ratio</item>
-        <item name="slidingKeyInputPreviewShadowRatio">@integer/config_sliding_key_input_preview_shadow_ratio</item>
-        <item name="keyRepeatStartTimeout">@integer/config_key_repeat_start_timeout</item>
-        <item name="keyRepeatInterval">@integer/config_key_repeat_interval</item>
-        <item name="longPressShiftLockTimeout">@integer/config_longpress_shift_lock_timeout</item>
-        <item name="ignoreAltCodeKeyTimeout">@integer/config_ignore_alt_code_key_timeout</item>
-        <item name="keyPreviewLayout">@layout/key_preview</item>
-        <item name="keyPreviewOffset">@dimen/key_preview_offset</item>
-        <item name="keyPreviewHeight">@dimen/key_preview_height</item>
-        <item name="keyPreviewLingerTimeout">@integer/config_key_preview_linger_timeout</item>
-        <item name="moreKeysKeyboardLayout">@layout/more_keys_keyboard</item>
-        <item name="showMoreKeysKeyboardAtTouchedPoint">@bool/config_show_more_keys_keyboard_at_touched_point</item>
-        <item name="languageOnSpacebarFinalAlpha">@integer/config_language_on_spacebar_final_alpha</item>
-        <item name="languageOnSpacebarFadeoutAnimator">@anim/language_on_spacebar_fadeout</item>
-        <!-- Remove animations for now because it could drain a non-negligible amount of battery while typing.
-        <item name="altCodeKeyWhileTypingFadeoutAnimator">@anim/alt_code_key_while_typing_fadeout</item>
-        <item name="altCodeKeyWhileTypingFadeinAnimator">@anim/alt_code_key_while_typing_fadein</item>
-        -->
-        <!-- Common attributes of MainKeyboardView for gesture typing detection and recognition -->
-        <item name="gestureFloatingPreviewTextLingerTimeout">@integer/config_gesture_floating_preview_text_linger_timeout</item>
-        <item name="gestureStaticTimeThresholdAfterFastTyping">@integer/config_gesture_static_time_threshold_after_fast_typing</item>
-        <item name="gestureDetectFastMoveSpeedThreshold">@fraction/config_gesture_detect_fast_move_speed_threshold</item>
-        <item name="gestureDynamicThresholdDecayDuration">@integer/config_gesture_dynamic_threshold_decay_duration</item>
-        <item name="gestureDynamicTimeThresholdFrom">@integer/config_gesture_dynamic_time_threshold_from</item>
-        <item name="gestureDynamicTimeThresholdTo">@integer/config_gesture_dynamic_time_threshold_to</item>
-        <item name="gestureDynamicDistanceThresholdFrom">@fraction/config_gesture_dynamic_distance_threshold_from</item>
-        <item name="gestureDynamicDistanceThresholdTo">@fraction/config_gesture_dynamic_distance_threshold_to</item>
-        <item name="gestureSamplingMinimumDistance">@fraction/config_gesture_sampling_minimum_distance</item>
-        <item name="gestureRecognitionMinimumTime">@integer/config_gesture_recognition_minimum_time</item>
-        <item name="gestureRecognitionUpdateTime">@integer/config_gesture_recognition_update_time</item>
-        <item name="gestureRecognitionSpeedThreshold">@fraction/config_gesture_recognition_speed_threshold</item>
-        <item name="suppressKeyPreviewAfterBatchInputDuration">@integer/config_suppress_key_preview_after_batch_input_duration</item>
-    </style>
-    <style
-        name="MainKeyboardView"
-        parent="KeyboardView">
-        <item name="autoCorrectionSpacebarLedEnabled">true</item>
-        <item name="autoCorrectionSpacebarLedIcon">@drawable/sym_keyboard_space_led</item>
-        <item name="spacebarTextRatio">@fraction/spacebar_text_ratio</item>
-        <item name="spacebarTextColor">@color/spacebar_text_color_default</item>
-        <item name="spacebarTextShadowColor">@color/spacebar_text_shadow_color_default</item>
-    </style>
-    <style
-        name="MoreKeysKeyboard"
-        parent="Keyboard"
-    >
-        <item name="keyboardTopPadding">0%p</item>
-        <item name="keyboardBottomPadding">0%p</item>
-        <item name="horizontalGap">0%p</item>
-        <item name="touchPositionCorrectionData">@null</item>
-    </style>
-    <style
-        name="MoreKeysKeyboardView"
-        parent="KeyboardView"
-    >
-        <item name="keyBackground">@drawable/btn_keyboard_key_popup</item>
-        <item name="verticalCorrection">@dimen/more_keys_keyboard_vertical_correction</item>
-    </style>
-    <style name="MoreKeysKeyboardPanelStyle">
-        <item name="android:background">@drawable/keyboard_popup_panel_background</item>
-    </style>
-    <style
-        name="SuggestionStripViewStyle"
-    >
-        <item name="android:background">@drawable/keyboard_suggest_strip</item>
-        <item name="suggestionStripOption">autoCorrectBold|validTypedWordBold</item>
-        <item name="colorValidTypedWord">@color/highlight_color_default</item>
-        <item name="colorTypedWord">@color/typed_word_color_default</item>
-        <item name="colorAutoCorrect">@color/highlight_color_default</item>
-        <item name="colorSuggested">@color/highlight_color_default</item>
-        <item name="alphaObsoleted">50%</item>
-        <item name="suggestionsCountInStrip">@integer/suggestions_count_in_strip</item>
-        <item name="centerSuggestionPercentile">@fraction/center_suggestion_percentile</item>
-        <item name="maxMoreSuggestionsRow">@integer/max_more_suggestions_row</item>
-        <item name="minMoreSuggestionsWidth">@fraction/min_more_suggestions_width</item>
-    </style>
-    <style
-        name="MoreSuggestionsViewStyle"
-        parent="MoreKeysKeyboardView"
-    >
-    </style>
-    <style name="SuggestionBackgroundStyle">
-        <item name="android:background">@drawable/btn_suggestion</item>
-    </style>
-    <!-- Theme "Basic high contrast" -->
-    <style
-        name="Keyboard.HighContrast"
-        parent="Keyboard"
-    >
-        <!-- This should be aligned with KeyboardSwitcher.KEYBOARD_THEMES[] -->
-        <item name="themeId">1</item>
-    </style>
-    <style
-        name="KeyboardView.HighContrast"
-        parent="KeyboardView"
-    >
-        <item name="android:background">@android:color/black</item>
-        <item name="keyBackground">@drawable/btn_keyboard_key3</item>
-    </style>
-    <style
-        name="MainKeyboardView.HighContrast"
-        parent="KeyboardView.HighContrast"
-    >
-        <item name="autoCorrectionSpacebarLedEnabled">true</item>
-        <item name="autoCorrectionSpacebarLedIcon">@drawable/sym_keyboard_space_led</item>
-        <item name="spacebarTextRatio">@fraction/spacebar_text_ratio</item>
-        <item name="spacebarTextColor">@color/spacebar_text_color_default</item>
-        <item name="spacebarTextShadowColor">@color/spacebar_text_shadow_color_default</item>
-    </style>
-    <!-- Theme "Stone" -->
-    <style
-        name="Keyboard.Stone"
-        parent="Keyboard"
-    >
-        <!-- This should be aligned with KeyboardSwitcher.KEYBOARD_THEMES[] -->
-        <item name="themeId">6</item>
-        <item name="keyboardTopPadding">@fraction/keyboard_top_padding_stone</item>
-        <item name="keyboardBottomPadding">@fraction/keyboard_bottom_padding_stone</item>
-        <item name="horizontalGap">@fraction/key_horizontal_gap_stone</item>
-        <item name="verticalGap">@fraction/key_bottom_gap_stone</item>
-    </style>
-    <style
-        name="KeyboardView.Stone"
-        parent="KeyboardView"
-    >
-        <item name="keyBackground">@drawable/btn_keyboard_key_stone</item>
-        <item name="keyTextColor">@color/key_text_color_stone</item>
-        <item name="keyTextInactivatedColor">@color/key_text_inactivated_color_stone</item>
-        <item name="keyHintLetterColor">@color/key_hint_letter_color_stone</item>
-        <item name="keyHintLabelColor">@color/key_hint_label_color_stone</item>
-        <item name="keyShiftedLetterHintInactivatedColor">@color/key_shifted_letter_hint_inactivated_color_stone</item>
-        <item name="keyShiftedLetterHintActivatedColor">@color/key_shifted_letter_hint_activated_color_stone</item>
-        <item name="keyTextShadowColor">@color/key_text_shadow_color_stone</item>
-    </style>
-    <style
-        name="MainKeyboardView.Stone"
-        parent="KeyboardView.Stone"
-    >
-        <item name="autoCorrectionSpacebarLedEnabled">true</item>
-        <item name="autoCorrectionSpacebarLedIcon">@drawable/sym_keyboard_space_led</item>
-        <item name="spacebarTextRatio">@fraction/spacebar_text_ratio</item>
-        <item name="spacebarTextColor">@color/spacebar_text_color_stone</item>
-        <item name="spacebarTextShadowColor">@color/spacebar_text_shadow_color_stone</item>
-    </style>
-    <style
-        name="MoreKeysKeyboard.Stone"
-        parent="Keyboard.Stone"
-    >
-        <item name="keyboardTopPadding">0%p</item>
-        <item name="keyboardBottomPadding">0%p</item>
-        <item name="horizontalGap">0%p</item>
-        <item name="touchPositionCorrectionData">@null</item>
-    </style>
-    <style
-        name="MoreKeysKeyboardView.Stone"
-        parent="MoreKeysKeyboardView"
-    >
-        <item name="keyBackground">@drawable/btn_keyboard_key_stone</item>
-        <item name="keyTextColor">@color/key_text_color_stone</item>
-        <item name="keyTextShadowColor">@color/key_text_shadow_color_stone</item>
-    </style>
-    <!-- Theme "Stone bold" -->
-    <style
-        name="Keyboard.Stone.Bold"
-        parent="Keyboard.Stone"
-    >
-        <!-- This should be aligned with KeyboardSwitcher.KEYBOARD_THEMES[] -->
-        <item name="themeId">7</item>
-    </style>
-    <style
-        name="KeyboardView.Stone.Bold"
-        parent="KeyboardView.Stone"
-    >
-        <item name="keyTypeface">bold</item>
-    </style>
-    <style
-        name="MainKeyboardView.Stone.Bold"
-        parent="KeyboardView.Stone.Bold"
-    >
-        <item name="autoCorrectionSpacebarLedEnabled">true</item>
-        <item name="autoCorrectionSpacebarLedIcon">@drawable/sym_keyboard_space_led</item>
-        <item name="spacebarTextRatio">@fraction/spacebar_text_ratio</item>
-        <item name="spacebarTextColor">@color/spacebar_text_color_stone</item>
-        <item name="spacebarTextShadowColor">@color/spacebar_text_shadow_color_stone</item>
-    </style>
-    <!-- Theme "Gingerbread" -->
-    <style
-        name="Keyboard.Gingerbread"
-        parent="Keyboard"
-    >
-        <!-- This should be aligned with KeyboardSwitcher.KEYBOARD_THEMES[] -->
-        <item name="themeId">8</item>
-        <item name="touchPositionCorrectionData">@array/touch_position_correction_data_gingerbread</item>
-        <item name="horizontalGap">@fraction/key_horizontal_gap_gb</item>
-        <item name="verticalGap">@fraction/key_bottom_gap_gb</item>
-    </style>
-    <style
-        name="KeyboardView.Gingerbread"
-        parent="KeyboardView"
-    >
-        <item name="android:background">@drawable/keyboard_dark_background</item>
-        <item name="keyBackground">@drawable/btn_keyboard_key_gingerbread</item>
-        <item name="keyTypeface">bold</item>
-    </style>
-    <style
-        name="MainKeyboardView.Gingerbread"
-        parent="KeyboardView.Gingerbread"
-    >
-        <item name="autoCorrectionSpacebarLedEnabled">true</item>
-        <item name="autoCorrectionSpacebarLedIcon">@drawable/sym_keyboard_space_led</item>
-        <item name="spacebarTextRatio">@fraction/spacebar_text_ratio</item>
-        <item name="spacebarTextColor">@color/spacebar_text_color_default</item>
-        <item name="spacebarTextShadowColor">@color/spacebar_text_shadow_color_default</item>
-    </style>
-    <style
-        name="MoreKeysKeyboard.Gingerbread"
-        parent="Keyboard.Gingerbread"
-    >
-        <item name="keyboardTopPadding">0%p</item>
-        <item name="keyboardBottomPadding">0%p</item>
-        <item name="horizontalGap">0%p</item>
-        <item name="touchPositionCorrectionData">@null</item>
-    </style>
-    <style
-        name="MoreKeysKeyboardView.Gingerbread"
-        parent="MoreKeysKeyboardView"
-    >
-        <item name="android:background">@null</item>
-    </style>
-    <!-- Theme "IceCreamSandwich" -->
-    <style
-        name="Keyboard.IceCreamSandwich"
-        parent="Keyboard"
-    >
-        <!-- This should be aligned with KeyboardSwitcher.KEYBOARD_THEMES[] -->
-        <item name="themeId">5</item>
-        <item name="keyboardTopPadding">@fraction/keyboard_top_padding_ics</item>
-        <item name="keyboardBottomPadding">@fraction/keyboard_bottom_padding_ics</item>
-        <item name="horizontalGap">@fraction/key_horizontal_gap_ics</item>
-        <item name="verticalGap">@fraction/key_bottom_gap_ics</item>
-        <item name="touchPositionCorrectionData">@array/touch_position_correction_data_ice_cream_sandwich</item>
-    </style>
-    <style
-        name="KeyboardView.IceCreamSandwich"
-        parent="KeyboardView"
-    >
-        <item name="android:background">@drawable/keyboard_background_holo</item>
-        <item name="keyBackground">@drawable/btn_keyboard_key_ics</item>
-        <item name="keyTypeface">bold</item>
-        <item name="keyTextInactivatedColor">@color/key_text_inactivated_color_ics</item>
-        <item name="keyHintLetterColor">@color/key_hint_letter_color_ics</item>
-        <item name="keyHintLabelColor">@color/key_hint_label_color_ics</item>
-        <item name="keyShiftedLetterHintInactivatedColor">@color/key_shifted_letter_hint_inactivated_color_ics</item>
-        <item name="keyShiftedLetterHintActivatedColor">@color/key_shifted_letter_hint_activated_color_ics</item>
-        <item name="keyPreviewLayout">@layout/key_preview_ics</item>
-        <item name="keyPreviewTextColor">@color/key_text_color_ics</item>
-        <item name="keyPreviewOffset">@dimen/key_preview_offset_ics</item>
-        <item name="keyTextShadowColor">@color/key_text_shadow_color_ics</item>
-        <item name="keyTextShadowRadius">0.0</item>
-        <item name="slidingKeyInputPreviewColor">@color/highlight_translucent_color_ics</item>
-        <item name="gestureFloatingPreviewTextColor">@color/highlight_color_ics</item>
-        <item name="gestureTrailColor">@color/highlight_color_ics</item>
-    </style>
-    <style
-        name="MainKeyboardView.IceCreamSandwich"
-        parent="KeyboardView.IceCreamSandwich"
-    >
-        <item name="autoCorrectionSpacebarLedEnabled">false</item>
-        <item name="autoCorrectionSpacebarLedIcon">@drawable/sym_keyboard_space_led_holo</item>
-        <item name="spacebarTextRatio">@fraction/spacebar_text_ratio</item>
-        <item name="spacebarTextColor">@color/spacebar_text_color_ics</item>
-        <item name="spacebarTextShadowColor">@color/spacebar_text_shadow_color_ics</item>
-    </style>
-    <style
-        name="MoreKeysKeyboard.IceCreamSandwich"
-        parent="Keyboard.IceCreamSandwich"
-    >
-        <item name="keyboardTopPadding">0%p</item>
-        <item name="keyboardBottomPadding">0%p</item>
-        <item name="horizontalGap">0%p</item>
-        <item name="touchPositionCorrectionData">@null</item>
-    </style>
-    <style
-        name="MoreKeysKeyboardView.IceCreamSandwich"
-        parent="MoreKeysKeyboardView"
-    >
-        <item name="android:background">@null</item>
-        <item name="keyBackground">@drawable/btn_keyboard_key_popup_ics</item>
-        <item name="verticalCorrection">@dimen/more_keys_keyboard_vertical_correction_ics</item>
-    </style>
-    <style name="MoreKeysKeyboardPanelStyle.IceCreamSandwich">
-        <item name="android:background">@drawable/keyboard_popup_panel_background_holo</item>
-    </style>
-    <style
-        name="SuggestionStripViewStyle.IceCreamSandwich"
-    >
-        <item name="android:background">@drawable/keyboard_suggest_strip_holo</item>
-        <item name="suggestionStripOption">autoCorrectBold|validTypedWordBold</item>
-        <item name="colorValidTypedWord">@color/highlight_color_ics</item>
-        <item name="colorTypedWord">@color/highlight_color_ics</item>
-        <item name="colorAutoCorrect">@color/highlight_color_ics</item>
-        <item name="colorSuggested">@color/highlight_color_ics</item>
-        <item name="alphaValidTypedWord">85%</item>
-        <item name="alphaTypedWord">85%</item>
-        <item name="alphaSuggested">70%</item>
-        <item name="alphaObsoleted">70%</item>
-        <item name="suggestionsCountInStrip">@integer/suggestions_count_in_strip</item>
-        <item name="centerSuggestionPercentile">@fraction/center_suggestion_percentile</item>
-        <item name="maxMoreSuggestionsRow">@integer/max_more_suggestions_row</item>
-        <item name="minMoreSuggestionsWidth">@fraction/min_more_suggestions_width</item>
-    </style>
-    <style
-        name="MoreSuggestionsViewStyle.IceCreamSandwich"
-        parent="MoreKeysKeyboardView.IceCreamSandwich"
-    >
-    </style>
-    <style name="SuggestionBackgroundStyle.IceCreamSandwich">
-        <item name="android:background">@drawable/btn_suggestion_ics</item>
-    </style>
-    <style
-        name="SuggestionPreviewBackgroundStyle.IceCreamSandwich"
-        parent="MoreKeysKeyboardPanelStyle.IceCreamSandwich"
-    >
-    </style>
-    <style name="MoreKeysKeyboardAnimation">
-        <item name="android:windowEnterAnimation">@anim/more_keys_keyboard_fadein</item>
-        <item name="android:windowExitAnimation">@anim/more_keys_keyboard_fadeout</item>
-    </style>
-</resources>
diff --git a/java/res/values/themes-basic-highcontrast.xml b/java/res/values/themes-basic-highcontrast.xml
deleted file mode 100644
index e81d473..0000000
--- a/java/res/values/themes-basic-highcontrast.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2011 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<resources>
-    <style name="KeyboardTheme.HighContrast" parent="KeyboardIcons">
-        <item name="keyboardStyle">@style/Keyboard.HighContrast</item>
-        <item name="keyboardViewStyle">@style/KeyboardView.HighContrast</item>
-        <item name="mainKeyboardViewStyle">@style/MainKeyboardView.HighContrast</item>
-        <item name="moreKeysKeyboardStyle">@style/MoreKeysKeyboard</item>
-        <item name="moreKeysKeyboardViewStyle">@style/MoreKeysKeyboardView</item>
-        <item name="moreKeysKeyboardPanelStyle">@style/MoreKeysKeyboardPanelStyle</item>
-        <item name="suggestionStripViewStyle">@style/SuggestionStripViewStyle</item>
-        <item name="moreSuggestionsViewStyle">@style/MoreSuggestionsViewStyle</item>
-        <item name="suggestionBackgroundStyle">@style/SuggestionBackgroundStyle</item>
-    </style>
-</resources>
diff --git a/java/res/values/themes-basic.xml b/java/res/values/themes-basic.xml
deleted file mode 100644
index c44f0f6..0000000
--- a/java/res/values/themes-basic.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2011 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<resources>
-    <style name="KeyboardTheme" parent="KeyboardIcons">
-        <item name="keyboardStyle">@style/Keyboard</item>
-        <item name="keyboardViewStyle">@style/KeyboardView</item>
-        <item name="mainKeyboardViewStyle">@style/MainKeyboardView</item>
-        <item name="moreKeysKeyboardStyle">@style/MoreKeysKeyboard</item>
-        <item name="moreKeysKeyboardViewStyle">@style/MoreKeysKeyboardView</item>
-        <item name="moreKeysKeyboardPanelStyle">@style/MoreKeysKeyboardPanelStyle</item>
-        <item name="suggestionStripViewStyle">@style/SuggestionStripViewStyle</item>
-        <item name="moreSuggestionsViewStyle">@style/MoreSuggestionsViewStyle</item>
-        <item name="suggestionBackgroundStyle">@style/SuggestionBackgroundStyle</item>
-    </style>
-</resources>
diff --git a/java/res/values/themes-common.xml b/java/res/values/themes-common.xml
new file mode 100644
index 0000000..8e9cfc9
--- /dev/null
+++ b/java/res/values/themes-common.xml
@@ -0,0 +1,131 @@
+<?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:android="http://schemas.android.com/apk/res/android">
+    <style name="KeyboardIcons" />
+    <!-- Default theme values -->
+    <style name="Keyboard">
+        <item name="touchPositionCorrectionData">@array/touch_position_correction_data_default</item>
+        <item name="rowHeight">25%p</item>
+        <item name="moreKeysTemplate">@xml/kbd_more_keys_keyboard_template</item>
+        <item name="keyboardLeftPadding">@fraction/keyboard_left_padding</item>
+        <item name="keyboardRightPadding">@fraction/keyboard_right_padding</item>
+        <item name="maxMoreKeysColumn">@integer/config_max_more_keys_column</item>
+    </style>
+    <style name="KeyboardView">
+        <item name="keyBackground">@drawable/btn_keyboard_key_ics</item>
+        <item name="keyLetterSize">@fraction/key_letter_ratio</item>
+        <item name="keyLargeLetterRatio">@fraction/key_large_letter_ratio</item>
+        <item name="keyLabelSize">@fraction/key_label_ratio</item>
+        <item name="keyLargeLabelRatio">@fraction/key_large_label_ratio</item>
+        <item name="keyHintLetterRatio">@fraction/key_hint_letter_ratio</item>
+        <item name="keyHintLabelRatio">@fraction/key_hint_label_ratio</item>
+        <item name="keyShiftedLetterHintRatio">@fraction/key_uppercase_letter_ratio</item>
+        <item name="keyTypeface">normal</item>
+        <item name="keyLabelHorizontalPadding">@dimen/key_label_horizontal_padding</item>
+        <item name="keyHintLetterPadding">@dimen/key_hint_letter_padding</item>
+        <item name="keyPopupHintLetterPadding">@dimen/key_popup_hint_letter_padding</item>
+        <item name="keyShiftedLetterHintPadding">@dimen/key_uppercase_letter_padding</item>
+        <item name="keyPreviewTextRatio">@fraction/key_preview_text_ratio</item>
+        <item name="verticalCorrection">@dimen/keyboard_vertical_correction</item>
+        <item name="backgroundDimAlpha">128</item>
+        <item name="gestureFloatingPreviewTextSize">@dimen/gesture_floating_preview_text_size</item>
+        <item name="gestureFloatingPreviewTextOffset">@dimen/gesture_floating_preview_text_offset</item>
+        <item name="gestureFloatingPreviewHorizontalPadding">@dimen/gesture_floating_preview_horizontal_padding</item>
+        <item name="gestureFloatingPreviewVerticalPadding">@dimen/gesture_floating_preview_vertical_padding</item>
+        <item name="gestureFloatingPreviewRoundRadius">@dimen/gesture_floating_preview_round_radius</item>
+        <item name="gestureTrailMinSamplingDistance">@dimen/gesture_trail_min_sampling_distance</item>
+        <item name="gestureTrailMaxInterpolationAngularThreshold">@integer/gesture_trail_max_interpolation_angular_threshold</item>
+        <item name="gestureTrailMaxInterpolationDistanceThreshold">@dimen/gesture_trail_max_interpolation_distance_threshold</item>
+        <item name="gestureTrailMaxInterpolationSegments">@integer/gesture_trail_max_interpolation_segments</item>
+        <item name="gestureTrailFadeoutStartDelay">@integer/config_gesture_trail_fadeout_start_delay</item>
+        <item name="gestureTrailFadeoutDuration">@integer/config_gesture_trail_fadeout_duration</item>
+        <item name="gestureTrailUpdateInterval">@integer/config_gesture_trail_update_interval</item>
+        <item name="gestureTrailStartWidth">@dimen/gesture_trail_start_width</item>
+        <item name="gestureTrailEndWidth">@dimen/gesture_trail_end_width</item>
+        <item name="gestureTrailBodyRatio">@integer/gesture_trail_body_ratio</item>
+        <item name="gestureTrailShadowRatio">@integer/gesture_trail_shadow_ratio</item>
+        <!-- Common attributes of MainKeyboardView -->
+        <item name="keyHysteresisDistance">@dimen/config_key_hysteresis_distance</item>
+        <item name="keyHysteresisDistanceForSlidingModifier">@dimen/config_key_hysteresis_distance_for_sliding_modifier</item>
+        <item name="touchNoiseThresholdTime">@integer/config_touch_noise_threshold_time</item>
+        <item name="touchNoiseThresholdDistance">@dimen/config_touch_noise_threshold_distance</item>
+        <item name="slidingKeyInputEnable">@bool/config_sliding_key_input_enabled</item>
+        <item name="slidingKeyInputPreviewWidth">@dimen/config_sliding_key_input_preview_width</item>
+        <item name="slidingKeyInputPreviewBodyRatio">@integer/config_sliding_key_input_preview_body_ratio</item>
+        <item name="slidingKeyInputPreviewShadowRatio">@integer/config_sliding_key_input_preview_shadow_ratio</item>
+        <item name="keyRepeatStartTimeout">@integer/config_key_repeat_start_timeout</item>
+        <item name="keyRepeatInterval">@integer/config_key_repeat_interval</item>
+        <item name="longPressShiftLockTimeout">@integer/config_longpress_shift_lock_timeout</item>
+        <item name="ignoreAltCodeKeyTimeout">@integer/config_ignore_alt_code_key_timeout</item>
+        <item name="keyPreviewHeight">@dimen/key_preview_height</item>
+        <item name="keyPreviewLingerTimeout">@integer/config_key_preview_linger_timeout</item>
+        <item name="moreKeysKeyboardLayout">@layout/more_keys_keyboard</item>
+        <item name="showMoreKeysKeyboardAtTouchedPoint">@bool/config_show_more_keys_keyboard_at_touched_point</item>
+        <item name="spacebarTextRatio">@fraction/spacebar_text_ratio</item>
+        <item name="languageOnSpacebarFinalAlpha">@integer/config_language_on_spacebar_final_alpha</item>
+        <item name="languageOnSpacebarFadeoutAnimator">@anim/language_on_spacebar_fadeout</item>
+        <!-- Remove animations for now because it could drain a non-negligible amount of battery while typing.
+        <item name="altCodeKeyWhileTypingFadeoutAnimator">@anim/alt_code_key_while_typing_fadeout</item>
+        <item name="altCodeKeyWhileTypingFadeinAnimator">@anim/alt_code_key_while_typing_fadein</item>
+        -->
+        <!-- Common attributes of MainKeyboardView for gesture typing detection and recognition -->
+        <item name="gestureFloatingPreviewTextLingerTimeout">@integer/config_gesture_floating_preview_text_linger_timeout</item>
+        <item name="gestureStaticTimeThresholdAfterFastTyping">@integer/config_gesture_static_time_threshold_after_fast_typing</item>
+        <item name="gestureDetectFastMoveSpeedThreshold">@fraction/config_gesture_detect_fast_move_speed_threshold</item>
+        <item name="gestureDynamicThresholdDecayDuration">@integer/config_gesture_dynamic_threshold_decay_duration</item>
+        <item name="gestureDynamicTimeThresholdFrom">@integer/config_gesture_dynamic_time_threshold_from</item>
+        <item name="gestureDynamicTimeThresholdTo">@integer/config_gesture_dynamic_time_threshold_to</item>
+        <item name="gestureDynamicDistanceThresholdFrom">@fraction/config_gesture_dynamic_distance_threshold_from</item>
+        <item name="gestureDynamicDistanceThresholdTo">@fraction/config_gesture_dynamic_distance_threshold_to</item>
+        <item name="gestureSamplingMinimumDistance">@fraction/config_gesture_sampling_minimum_distance</item>
+        <item name="gestureRecognitionMinimumTime">@integer/config_gesture_recognition_minimum_time</item>
+        <item name="gestureRecognitionUpdateTime">@integer/config_gesture_recognition_update_time</item>
+        <item name="gestureRecognitionSpeedThreshold">@fraction/config_gesture_recognition_speed_threshold</item>
+        <item name="suppressKeyPreviewAfterBatchInputDuration">@integer/config_suppress_key_preview_after_batch_input_duration</item>
+    </style>
+    <style
+        name="MainKeyboardView"
+        parent="KeyboardView" />
+    <!-- Though {@link EmojiKeyboardView} doesn't extend {@link KeyboardView}, some views inside it,
+         for instance delete button, need themed {@link KeyboardView} attributes. -->
+    <style
+        name="EmojiKeyboardView"
+        parent="KeyboardView"
+    >
+        <item name="emojiTabLabelColor">@color/emoji_tab_label_color_ics</item>
+    </style>
+    <style name="MoreKeysKeyboard" />
+    <style
+        name="MoreKeysKeyboardView"
+        parent="MainKeyboardView" />
+    <style name="MoreKeysKeyboardContainer" />
+    <style name="SuggestionStripView">
+        <item name="suggestionsCountInStrip">@integer/suggestions_count_in_strip</item>
+        <item name="centerSuggestionPercentile">@fraction/center_suggestion_percentile</item>
+        <item name="maxMoreSuggestionsRow">@integer/max_more_suggestions_row</item>
+        <item name="minMoreSuggestionsWidth">@fraction/min_more_suggestions_width</item>
+    </style>
+    <style name="SuggestionWord" />
+    <style name="MoreKeysKeyboardAnimation">
+        <item name="android:windowEnterAnimation">@anim/more_keys_keyboard_fadein</item>
+        <item name="android:windowExitAnimation">@anim/more_keys_keyboard_fadeout</item>
+    </style>
+</resources>
\ No newline at end of file
diff --git a/java/res/values/themes-gb.xml b/java/res/values/themes-gb.xml
new file mode 100644
index 0000000..d9ac4ac
--- /dev/null
+++ b/java/res/values/themes-gb.xml
@@ -0,0 +1,146 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<resources>
+    <style name="KeyboardTheme.GB" parent="KeyboardIcons.GB">
+        <item name="keyboardStyle">@style/Keyboard.GB</item>
+        <item name="keyboardViewStyle">@style/KeyboardView.GB</item>
+        <item name="mainKeyboardViewStyle">@style/MainKeyboardView.GB</item>
+        <item name="emojiKeyboardViewStyle">@style/EmojiKeyboardView.GB</item>
+        <item name="moreKeysKeyboardStyle">@style/MoreKeysKeyboard.GB</item>
+        <item name="moreKeysKeyboardViewStyle">@style/MoreKeysKeyboardView.GB</item>
+        <item name="moreKeysKeyboardContainerStyle">@style/MoreKeysKeyboardContainer.GB</item>
+        <item name="suggestionStripViewStyle">@style/SuggestionStripView.GB</item>
+        <item name="suggestionWordStyle">@style/SuggestionWord.GB</item>
+    </style>
+    <style name="KeyboardIcons.GB">
+        <!-- Keyboard icons -->
+        <item name="iconShiftKey">@drawable/sym_keyboard_shift_holo_dark</item>
+        <item name="iconDeleteKey">@drawable/sym_keyboard_delete_holo_dark</item>
+        <item name="iconSettingsKey">@drawable/sym_keyboard_settings_holo_dark</item>
+        <item name="iconSpaceKey">@drawable/sym_keyboard_space_holo_dark</item>
+        <item name="iconEnterKey">@drawable/sym_keyboard_return_holo_dark</item>
+        <item name="iconSearchKey">@drawable/sym_keyboard_search_holo_dark</item>
+        <item name="iconTabKey">@drawable/sym_keyboard_tab_holo_dark</item>
+        <item name="iconShortcutKey">@drawable/sym_keyboard_mic_holo_dark</item>
+        <item name="iconShortcutForLabel">@drawable/sym_keyboard_label_mic_holo_dark</item>
+        <item name="iconSpaceKeyForNumberLayout">@drawable/sym_keyboard_space</item>
+        <item name="iconShiftKeyShifted">@drawable/sym_keyboard_shift_locked_holo_dark</item>
+        <!-- TODO: Needs non-holo disabled shortcut icon drawable -->
+        <item name="iconShortcutKeyDisabled">@drawable/sym_keyboard_voice_off_holo_dark</item>
+        <item name="iconTabKeyPreview">@drawable/sym_keyboard_feedback_tab</item>
+        <item name="iconLanguageSwitchKey">@drawable/sym_keyboard_language_switch_dark</item>
+        <!-- TODO: Needs dedicated black theme ZWNJ and ZWJ icons -->
+        <item name="iconZwnjKey">@drawable/sym_keyboard_zwnj_holo_dark</item>
+        <item name="iconZwjKey">@drawable/sym_keyboard_zwj_holo_dark</item>
+        <item name="iconEmojiKey">@drawable/sym_keyboard_smiley_holo_dark</item>
+    </style>
+    <style
+        name="Keyboard.GB"
+        parent="Keyboard"
+    >
+        <!-- This should be aligned with KeyboardSwitcher.KEYBOARD_THEMES[] -->
+        <item name="themeId">1</item>
+        <item name="touchPositionCorrectionData">@array/touch_position_correction_data_gb</item>
+        <item name="keyboardTopPadding">@fraction/keyboard_top_padding_gb</item>
+        <item name="keyboardBottomPadding">@fraction/keyboard_bottom_padding_gb</item>
+        <item name="horizontalGap">@fraction/key_horizontal_gap_gb</item>
+        <item name="verticalGap">@fraction/key_bottom_gap_gb</item>
+    </style>
+    <style
+        name="KeyboardView.GB"
+        parent="KeyboardView"
+    >
+        <item name="android:background">@drawable/keyboard_background_gb</item>
+        <item name="keyBackground">@drawable/btn_keyboard_key_gb</item>
+        <item name="keyTypeface">bold</item>
+        <item name="keyTextColor">@color/key_text_color_gb</item>
+        <item name="keyTextInactivatedColor">@color/key_text_inactivated_color_gb</item>
+        <item name="keyHintLetterColor">@color/key_hint_letter_color_gb</item>
+        <item name="keyHintLabelColor">@color/key_hint_label_color_gb</item>
+        <item name="keyShiftedLetterHintInactivatedColor">@color/key_shifted_letter_hint_inactivated_color_gb</item>
+        <item name="keyShiftedLetterHintActivatedColor">@color/key_shifted_letter_hint_activated_color_gb</item>
+        <item name="keyPreviewTextColor">@color/key_text_color_gb</item>
+        <item name="keyTextShadowColor">@color/key_text_shadow_color_gb</item>
+        <item name="keyTextShadowRadius">2.75</item>
+    </style>
+    <style
+        name="MainKeyboardView.GB"
+        parent="KeyboardView.GB"
+    >
+        <item name="keyPreviewLayout">@layout/key_preview_gb</item>
+        <item name="keyPreviewOffset">@dimen/key_preview_offset_gb</item>
+        <item name="gestureFloatingPreviewTextColor">@color/highlight_color_gb</item>
+        <item name="gestureFloatingPreviewColor">@color/gesture_floating_preview_color_gb</item>
+        <item name="gestureTrailColor">@color/highlight_color_gb</item>
+        <item name="slidingKeyInputPreviewColor">@color/highlight_translucent_color_gb</item>
+        <item name="autoCorrectionSpacebarLedEnabled">true</item>
+        <item name="autoCorrectionSpacebarLedIcon">@drawable/sym_keyboard_space_led_gb</item>
+        <item name="spacebarTextColor">@color/spacebar_text_color_gb</item>
+        <item name="spacebarTextShadowColor">@color/spacebar_text_shadow_color_gb</item>
+    </style>
+    <!-- Though {@link EmojiKeyboardView} doesn't extend {@link KeyboardView}, some views inside it,
+         for instance delete button, need themed {@link KeyboardView} attributes. -->
+    <style
+        name="EmojiKeyboardView.GB"
+        parent="KeyboardView.GB"
+    >
+        <item name="keyBackground">@drawable/btn_keyboard_key_functional_gb</item>
+        <item name="emojiTabLabelColor">@color/emoji_tab_label_color_gb</item>
+    </style>
+    <style
+        name="MoreKeysKeyboard.GB"
+        parent="Keyboard.GB"
+    >
+        <item name="keyboardTopPadding">0%p</item>
+        <item name="keyboardBottomPadding">0%p</item>
+        <item name="horizontalGap">0%p</item>
+        <item name="touchPositionCorrectionData">@null</item>
+    </style>
+    <style
+        name="MoreKeysKeyboardView.GB"
+        parent="KeyboardView.GB"
+    >
+        <item name="android:background">@null</item>
+        <item name="keyBackground">@drawable/btn_keyboard_key_popup_gb</item>
+        <item name="keyTypeface">normal</item>
+        <item name="verticalCorrection">@dimen/more_keys_keyboard_vertical_correction_gb</item>
+    </style>
+    <style
+        name="MoreKeysKeyboardContainer.GB"
+    >
+        <item name="android:background">@drawable/keyboard_popup_panel_background_gb</item>
+    </style>
+    <style
+        name="SuggestionStripView.GB"
+        parent="SuggestionStripView"
+    >
+        <item name="android:background">@drawable/keyboard_suggest_strip_gb</item>
+        <item name="suggestionStripOption">autoCorrectBold|validTypedWordBold</item>
+        <item name="colorValidTypedWord">@color/highlight_color_gb</item>
+        <item name="colorTypedWord">@color/typed_word_color_gb</item>
+        <item name="colorAutoCorrect">@color/highlight_color_gb</item>
+        <item name="colorSuggested">@color/highlight_color_gb</item>
+        <item name="alphaObsoleted">50%</item>
+    </style>
+    <style name="SuggestionWord.GB">
+        <item name="android:background">@drawable/btn_suggestion_gb</item>
+    </style>
+</resources>
diff --git a/java/res/values/themes-gingerbread.xml b/java/res/values/themes-gingerbread.xml
deleted file mode 100644
index 129afdf..0000000
--- a/java/res/values/themes-gingerbread.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2011 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<resources>
-    <style name="KeyboardTheme.Gingerbread" parent="KeyboardIcons">
-        <item name="keyboardStyle">@style/Keyboard.Gingerbread</item>
-        <item name="keyboardViewStyle">@style/KeyboardView.Gingerbread</item>
-        <item name="mainKeyboardViewStyle">@style/MainKeyboardView.Gingerbread</item>
-        <item name="moreKeysKeyboardStyle">@style/MoreKeysKeyboard.Gingerbread</item>
-        <item name="moreKeysKeyboardViewStyle">@style/MoreKeysKeyboardView.Gingerbread</item>
-        <item name="moreKeysKeyboardPanelStyle">@style/MoreKeysKeyboardPanelStyle</item>
-        <item name="suggestionStripViewStyle">@style/SuggestionStripViewStyle</item>
-        <item name="moreSuggestionsViewStyle">@style/MoreSuggestionsViewStyle</item>
-        <item name="suggestionBackgroundStyle">@style/SuggestionBackgroundStyle</item>
-    </style>
-</resources>
diff --git a/java/res/values/themes-ics.xml b/java/res/values/themes-ics.xml
index 1264831..33dd50c 100644
--- a/java/res/values/themes-ics.xml
+++ b/java/res/values/themes-ics.xml
@@ -1,29 +1,147 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2011 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
+<!--
+/*
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
 -->
 
-<resources>
-    <style name="KeyboardTheme.IceCreamSandwich" parent="KeyboardIcons.IceCreamSandwich">
-        <item name="keyboardStyle">@style/Keyboard.IceCreamSandwich</item>
-        <item name="keyboardViewStyle">@style/KeyboardView.IceCreamSandwich</item>
-        <item name="mainKeyboardViewStyle">@style/MainKeyboardView.IceCreamSandwich</item>
-        <item name="moreKeysKeyboardStyle">@style/MoreKeysKeyboard.IceCreamSandwich</item>
-        <item name="moreKeysKeyboardViewStyle">@style/MoreKeysKeyboardView.IceCreamSandwich</item>
-        <item name="moreKeysKeyboardPanelStyle">@style/MoreKeysKeyboardPanelStyle.IceCreamSandwich</item>
-        <item name="suggestionStripViewStyle">@style/SuggestionStripViewStyle.IceCreamSandwich</item>
-        <item name="moreSuggestionsViewStyle">@style/MoreSuggestionsViewStyle.IceCreamSandwich</item>
-        <item name="suggestionBackgroundStyle">@style/SuggestionBackgroundStyle.IceCreamSandwich</item>
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+    <style name="KeyboardTheme.ICS" parent="KeyboardIcons.ICS">
+        <item name="keyboardStyle">@style/Keyboard.ICS</item>
+        <item name="keyboardViewStyle">@style/KeyboardView.ICS</item>
+        <item name="mainKeyboardViewStyle">@style/MainKeyboardView.ICS</item>
+        <item name="emojiKeyboardViewStyle">@style/EmojiKeyboardView.ICS</item>
+        <item name="moreKeysKeyboardStyle">@style/MoreKeysKeyboard.ICS</item>
+        <item name="moreKeysKeyboardViewStyle">@style/MoreKeysKeyboardView.ICS</item>
+        <item name="moreKeysKeyboardContainerStyle">@style/MoreKeysKeyboardContainer.ICS</item>
+        <item name="suggestionStripViewStyle">@style/SuggestionStripView.ICS</item>
+        <item name="suggestionWordStyle">@style/SuggestionWord.ICS</item>
+    </style>
+    <style name="KeyboardIcons.ICS">
+        <!-- Keyboard icons -->
+        <!-- TODO: The following holo icon for phone (drawable-hdpi and drawable-xhdpi) are missing.
+             sym_keyboard_123_mic_holo
+             -->
+        <item name="iconShiftKey">@drawable/sym_keyboard_shift_holo_dark</item>
+        <item name="iconDeleteKey">@drawable/sym_keyboard_delete_holo_dark</item>
+        <item name="iconSettingsKey">@drawable/sym_keyboard_settings_holo_dark</item>
+        <item name="iconSpaceKey">@null</item>
+        <item name="iconEnterKey">@drawable/sym_keyboard_return_holo_dark</item>
+        <item name="iconSearchKey">@drawable/sym_keyboard_search_holo_dark</item>
+        <item name="iconTabKey">@drawable/sym_keyboard_tab_holo_dark</item>
+        <item name="iconShortcutKey">@drawable/sym_keyboard_voice_holo_dark</item>
+        <item name="iconShortcutForLabel">@drawable/sym_keyboard_label_mic_holo_dark</item>
+        <item name="iconSpaceKeyForNumberLayout">@drawable/sym_keyboard_space_holo_dark</item>
+        <item name="iconShiftKeyShifted">@drawable/sym_keyboard_shift_locked_holo_dark</item>
+        <item name="iconShortcutKeyDisabled">@drawable/sym_keyboard_voice_off_holo_dark</item>
+        <item name="iconTabKeyPreview">@drawable/sym_keyboard_feedback_tab</item>
+        <item name="iconLanguageSwitchKey">@drawable/sym_keyboard_language_switch_dark</item>
+        <item name="iconZwnjKey">@drawable/sym_keyboard_zwnj_holo_dark</item>
+        <item name="iconZwjKey">@drawable/sym_keyboard_zwj_holo_dark</item>
+        <item name="iconEmojiKey">@drawable/sym_keyboard_smiley_holo_dark</item>
+    </style>
+    <style
+        name="Keyboard.ICS"
+        parent="Keyboard"
+    >
+        <!-- This should be aligned with KeyboardSwitcher.KEYBOARD_THEMES[] -->
+        <item name="themeId">0</item>
+        <item name="keyboardTopPadding">@fraction/keyboard_top_padding_ics</item>
+        <item name="keyboardBottomPadding">@fraction/keyboard_bottom_padding_ics</item>
+        <item name="horizontalGap">@fraction/key_horizontal_gap_ics</item>
+        <item name="verticalGap">@fraction/key_bottom_gap_ics</item>
+        <item name="touchPositionCorrectionData">@array/touch_position_correction_data_ics</item>
+    </style>
+    <style
+        name="KeyboardView.ICS"
+        parent="KeyboardView"
+    >
+        <item name="android:background">@drawable/keyboard_background_holo</item>
+        <item name="keyBackground">@drawable/btn_keyboard_key_ics</item>
+        <item name="keyTypeface">bold</item>
+        <item name="keyTextColor">@color/key_text_color_ics</item>
+        <item name="keyTextInactivatedColor">@color/key_text_inactivated_color_ics</item>
+        <item name="keyHintLetterColor">@color/key_hint_letter_color_ics</item>
+        <item name="keyHintLabelColor">@color/key_hint_label_color_ics</item>
+        <item name="keyShiftedLetterHintInactivatedColor">@color/key_shifted_letter_hint_inactivated_color_ics</item>
+        <item name="keyShiftedLetterHintActivatedColor">@color/key_shifted_letter_hint_activated_color_ics</item>
+        <item name="keyPreviewTextColor">@color/key_text_color_ics</item>
+        <item name="keyTextShadowColor">@color/key_text_shadow_color_ics</item>
+        <item name="keyTextShadowRadius">0.0</item>
+    </style>
+    <style
+        name="MainKeyboardView.ICS"
+        parent="KeyboardView.ICS"
+    >
+        <item name="keyPreviewLayout">@layout/key_preview_ics</item>
+        <item name="keyPreviewOffset">@dimen/key_preview_offset_ics</item>
+        <item name="gestureFloatingPreviewTextColor">@color/highlight_color_holo</item>
+        <item name="gestureFloatingPreviewColor">@color/gesture_floating_preview_color_ics</item>
+        <item name="gestureTrailColor">@color/highlight_color_holo</item>
+        <item name="slidingKeyInputPreviewColor">@color/highlight_translucent_color_holo</item>
+        <item name="autoCorrectionSpacebarLedEnabled">false</item>
+        <item name="autoCorrectionSpacebarLedIcon">@drawable/sym_keyboard_space_led_holo</item>
+        <item name="spacebarTextColor">@color/spacebar_text_color_ics</item>
+        <item name="spacebarTextShadowColor">@color/spacebar_text_shadow_color_ics</item>
+    </style>
+    <!-- Though {@link EmojiKeyboardView} doesn't extend {@link KeyboardView}, some views inside it,
+         for instance delete button, need themed {@link KeyboardView} attributes. -->
+    <style
+        name="EmojiKeyboardView.ICS"
+        parent="KeyboardView.ICS"
+    >
+        <item name="keyBackgroundEmojiFunctional">@drawable/btn_keyboard_key_functional_ics</item>
+        <item name="emojiTabLabelColor">@color/emoji_tab_label_color_ics</item>
+    </style>
+    <style
+        name="MoreKeysKeyboard.ICS"
+        parent="Keyboard.ICS"
+    >
+        <item name="keyboardTopPadding">0%p</item>
+        <item name="keyboardBottomPadding">0%p</item>
+        <item name="horizontalGap">0%p</item>
+        <item name="touchPositionCorrectionData">@null</item>
+    </style>
+    <style
+        name="MoreKeysKeyboardView.ICS"
+        parent="KeyboardView.ICS"
+    >
+        <item name="android:background">@null</item>
+        <item name="keyBackground">@drawable/btn_keyboard_key_popup_ics</item>
+        <item name="keyTypeface">normal</item>
+        <item name="verticalCorrection">@dimen/more_keys_keyboard_vertical_correction_ics</item>
+    </style>
+    <style
+        name="MoreKeysKeyboardContainer.ICS"
+    >
+        <item name="android:background">@drawable/keyboard_popup_panel_background_holo</item>
+    </style>
+    <style
+        name="SuggestionStripView.ICS"
+        parent="SuggestionStripView"
+    >
+        <item name="android:background">@drawable/keyboard_suggest_strip_holo</item>
+        <item name="suggestionStripOption">autoCorrectBold|validTypedWordBold</item>
+        <item name="colorValidTypedWord">@color/typed_word_color_holo</item>
+        <item name="colorTypedWord">@color/typed_word_color_holo</item>
+        <item name="colorAutoCorrect">@color/highlight_color_holo</item>
+        <item name="colorSuggested">@color/suggested_word_color_holo</item>
+        <item name="alphaObsoleted">70%</item>
+    </style>
+    <style name="SuggestionWord.ICS">
+        <item name="android:background">@drawable/btn_suggestion_ics</item>
     </style>
 </resources>
diff --git a/java/res/values/themes-stone-bold.xml b/java/res/values/themes-stone-bold.xml
deleted file mode 100644
index 196f3ef..0000000
--- a/java/res/values/themes-stone-bold.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2011 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<resources>
-    <style name="KeyboardTheme.Stone.Bold" parent="KeyboardIcons.Black">
-        <item name="keyboardStyle">@style/Keyboard.Stone.Bold</item>
-        <item name="keyboardViewStyle">@style/KeyboardView.Stone.Bold</item>
-        <item name="mainKeyboardViewStyle">@style/MainKeyboardView.Stone.Bold</item>
-        <item name="moreKeysKeyboardStyle">@style/MoreKeysKeyboard.Stone</item>
-        <item name="moreKeysKeyboardViewStyle">@style/MoreKeysKeyboardView.Stone</item>
-        <item name="moreKeysKeyboardPanelStyle">@style/MoreKeysKeyboardPanelStyle</item>
-        <item name="suggestionStripViewStyle">@style/SuggestionStripViewStyle</item>
-        <item name="moreSuggestionsViewStyle">@style/MoreSuggestionsViewStyle</item>
-        <item name="suggestionBackgroundStyle">@style/SuggestionBackgroundStyle</item>
-    </style>
-</resources>
diff --git a/java/res/values/themes-stone.xml b/java/res/values/themes-stone.xml
deleted file mode 100644
index d0d35c2..0000000
--- a/java/res/values/themes-stone.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2011 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<resources>
-    <style name="KeyboardTheme.Stone" parent="KeyboardIcons.Black">
-        <item name="keyboardStyle">@style/Keyboard.Stone</item>
-        <item name="keyboardViewStyle">@style/KeyboardView.Stone</item>
-        <item name="mainKeyboardViewStyle">@style/MainKeyboardView.Stone</item>
-        <item name="moreKeysKeyboardStyle">@style/MoreKeysKeyboard.Stone</item>
-        <item name="moreKeysKeyboardViewStyle">@style/MoreKeysKeyboardView.Stone</item>
-        <item name="moreKeysKeyboardPanelStyle">@style/MoreKeysKeyboardPanelStyle</item>
-        <item name="suggestionStripViewStyle">@style/SuggestionStripViewStyle</item>
-        <item name="moreSuggestionsViewStyle">@style/MoreSuggestionsViewStyle</item>
-        <item name="suggestionBackgroundStyle">@style/SuggestionBackgroundStyle</item>
-    </style>
-</resources>
diff --git a/java/res/values/touch-position-correction.xml b/java/res/values/touch-position-correction.xml
index 7df86f4..9df517b 100644
--- a/java/res/values/touch-position-correction.xml
+++ b/java/res/values/touch-position-correction.xml
@@ -37,7 +37,7 @@
     </string-array>
 
     <string-array
-        name="touch_position_correction_data_gingerbread"
+        name="touch_position_correction_data_gb"
         translatable="false"
     >
         <!-- First row -->
@@ -57,7 +57,7 @@
     </string-array>
 
     <string-array
-        name="touch_position_correction_data_ice_cream_sandwich"
+        name="touch_position_correction_data_ics"
         translatable="false"
     >
         <!-- First row -->
diff --git a/java/res/xml-sw600dp/kbd_10_10_7_symbols_shift.xml b/java/res/xml-sw600dp/kbd_10_10_7_symbols_shift.xml
deleted file mode 100644
index c36f009..0000000
--- a/java/res/xml-sw600dp/kbd_10_10_7_symbols_shift.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<Keyboard
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:touchPositionCorrectionData="@array/touch_position_correction_data_default"
->
-    <include
-        latin:keyboardLayout="@xml/rows_10_10_7_symbols_shift" />
-</Keyboard>
diff --git a/java/res/xml-sw600dp/key_apostrophe.xml b/java/res/xml-sw600dp/key_apostrophe.xml
deleted file mode 100644
index 2aec347..0000000
--- a/java/res/xml-sw600dp/key_apostrophe.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <switch>
-        <case
-            latin:mode="email|url"
-        >
-            <Key
-                latin:keyLabel="-" />
-        </case>
-        <case
-            latin:languageCode="fa"
-        >
-            <Key
-                latin:keyLabel="!text/keylabel_for_apostrophe"
-                latin:keyHintLabel="!text/keyhintlabel_for_apostrophe"
-                latin:keyLabelFlags="hasPopupHint"
-                latin:moreKeys="!text/more_keys_for_apostrophe"
-                latin:keyStyle="hasShiftedLetterHintStyle" />
-        </case>
-        <default>
-            <Key
-                latin:keyLabel="!text/keylabel_for_apostrophe"
-                latin:keyHintLabel="!text/keyhintlabel_for_apostrophe"
-                latin:moreKeys="!text/more_keys_for_apostrophe"
-                latin:keyStyle="hasShiftedLetterHintStyle" />
-        </default>
-    </switch>
-</merge>
diff --git a/java/res/xml-sw600dp/key_dash.xml b/java/res/xml-sw600dp/key_dash.xml
deleted file mode 100644
index b139c29..0000000
--- a/java/res/xml-sw600dp/key_dash.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <switch>
-        <case
-            latin:mode="email|url"
-        >
-            <Key
-                latin:keyLabel="_" />
-        </case>
-        <case
-            latin:languageCode="ar|fa"
-        >
-            <Key
-                latin:keyLabel="."
-                latin:keyHintLabel="!text/keyhintlabel_for_arabic_diacritics"
-                latin:keyLabelFlags="hasPopupHint"
-                latin:moreKeys="!text/more_keys_for_arabic_diacritics"
-                latin:keyStyle="hasShiftedLetterHintStyle" />
-        </case>
-        <default>
-            <Key
-                latin:keyLabel="-"
-                latin:keyHintLabel="_"
-                latin:moreKeys="_"
-                latin:keyStyle="hasShiftedLetterHintStyle" />
-        </default>
-    </switch>
-</merge>
diff --git a/java/res/xml-sw600dp/key_f1.xml b/java/res/xml-sw600dp/key_f1.xml
index 77afe4e..ac00532 100644
--- a/java/res/xml-sw600dp/key_f1.xml
+++ b/java/res/xml-sw600dp/key_f1.xml
@@ -53,10 +53,7 @@
         </case>
         <default>
             <Key
-                latin:keyLabel="/"
-                latin:keyHintLabel="\@"
-                latin:moreKeys="\@"
-                latin:keyStyle="hasShiftedLetterHintStyle" />
+                latin:keyLabel="/" />
         </default>
     </switch>
 </merge>
diff --git a/java/res/xml-sw600dp/key_space.xml b/java/res/xml-sw600dp/key_space_5kw.xml
similarity index 100%
rename from java/res/xml-sw600dp/key_space.xml
rename to java/res/xml-sw600dp/key_space_5kw.xml
diff --git a/java/res/xml/kbd_10_10_7_symbols.xml b/java/res/xml-sw600dp/key_space_symbols.xml
similarity index 82%
copy from java/res/xml/kbd_10_10_7_symbols.xml
copy to java/res/xml-sw600dp/key_space_symbols.xml
index 4d9861b..07aa7d1 100644
--- a/java/res/xml/kbd_10_10_7_symbols.xml
+++ b/java/res/xml-sw600dp/key_space_symbols.xml
@@ -18,10 +18,9 @@
 */
 -->
 
-<Keyboard
+<merge
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:touchPositionCorrectionData="@array/touch_position_correction_data_default"
 >
     <include
-        latin:keyboardLayout="@xml/rows_symbols" />
-</Keyboard>
+        latin:keyboardLayout="@xml/key_space_5kw" />
+</merge>
diff --git a/java/res/xml-sw600dp/key_styles_common.xml b/java/res/xml-sw600dp/key_styles_common.xml
index f407ba3..d817add 100644
--- a/java/res/xml-sw600dp/key_styles_common.xml
+++ b/java/res/xml-sw600dp/key_styles_common.xml
@@ -123,7 +123,8 @@
         latin:styleName="emojiKeyStyle"
         latin:code="!code/key_emoji"
         latin:keyIcon="!icon/emoji_key"
-        latin:keyActionFlags="noKeyPreview" />
+        latin:keyActionFlags="noKeyPreview"
+        latin:backgroundType="functional" />
     <key-style
         latin:styleName="settingsKeyStyle"
         latin:code="!code/key_settings"
diff --git a/java/res/xml-sw600dp/keys_comma_period.xml b/java/res/xml-sw600dp/keys_comma_period.xml
deleted file mode 100644
index 752f75b..0000000
--- a/java/res/xml-sw600dp/keys_comma_period.xml
+++ /dev/null
@@ -1,46 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <switch>
-        <case
-            latin:mode="email"
-        >
-            <Key
-                latin:keyLabel="," />
-            <Key
-                latin:keyLabel="." />
-        </case>
-        <default>
-            <Key
-                latin:keyLabel="!text/keylabel_for_tablet_comma"
-                latin:keyHintLabel="!text/keyhintlabel_for_tablet_comma"
-                latin:moreKeys="!text/more_keys_for_tablet_comma"
-                latin:keyStyle="hasShiftedLetterHintStyle" />
-            <Key
-                latin:keyLabel="."
-                latin:keyHintLabel="!text/keyhintlabel_for_tablet_period"
-                latin:moreKeys="!text/more_keys_for_tablet_period"
-                latin:keyStyle="hasShiftedLetterHintStyle" />
-        </default>
-    </switch>
-</merge>
diff --git a/java/res/xml/kbd_10_10_7_symbols.xml b/java/res/xml-sw600dp/keys_exclamation_question.xml
similarity index 75%
copy from java/res/xml/kbd_10_10_7_symbols.xml
copy to java/res/xml-sw600dp/keys_exclamation_question.xml
index 4d9861b..983ef38 100644
--- a/java/res/xml/kbd_10_10_7_symbols.xml
+++ b/java/res/xml-sw600dp/keys_exclamation_question.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2012, The Android Open Source Project
+** 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.
@@ -18,10 +18,11 @@
 */
 -->
 
-<Keyboard
+<merge
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:touchPositionCorrectionData="@array/touch_position_correction_data_default"
 >
-    <include
-        latin:keyboardLayout="@xml/rows_symbols" />
-</Keyboard>
+    <Key
+        latin:keyLabel="\?" />
+    <Key
+        latin:keyLabel="!" />
+</merge>
diff --git a/java/res/xml-sw600dp/keys_pcqwerty2_right3.xml b/java/res/xml-sw600dp/keys_pcqwerty2_right3.xml
index 0a27ca7..324e025 100644
--- a/java/res/xml-sw600dp/keys_pcqwerty2_right3.xml
+++ b/java/res/xml-sw600dp/keys_pcqwerty2_right3.xml
@@ -23,36 +23,32 @@
 >
     <switch>
         <case
-            latin:keyboardLayoutSetElement="symbols|symbolsShifted"
+            latin:keyboardLayoutSetElement="alphabet|alphabetAutomaticShifted"
         >
             <Key
                 latin:keyLabel="["
-                latin:moreKeys="{" />
-            <Key
-                latin:keyLabel="]"
-                latin:moreKeys="}" />
-            <!-- U+00A6: "¦" BROKEN BAR -->
-            <Key
-                latin:keyLabel="\\"
-                latin:moreKeys="\\|,&#x00A6;" />
-        </case>
-        <default>
-            <Key
-                latin:keyLabel="["
                 latin:keyHintLabel="{"
-                latin:moreKeys="{"
+                latin:additionalMoreKeys="{"
                 latin:keyStyle="hasShiftedLetterHintStyle" />
             <Key
                 latin:keyLabel="]"
                 latin:keyHintLabel="}"
-                latin:moreKeys="}"
+                latin:additionalMoreKeys="}"
                 latin:keyStyle="hasShiftedLetterHintStyle" />
-            <!-- U+00A6: "¦" BROKEN BAR -->
             <Key
                 latin:keyLabel="\\"
                 latin:keyHintLabel="|"
-                latin:moreKeys="\\|,&#x00A6;"
+                latin:additionalMoreKeys="\\|"
                 latin:keyStyle="hasShiftedLetterHintStyle" />
+        </case>
+        <!-- keyboardLayoutSetElement="alphabetManualShifted|alphabetShiftLocked|alphabetShiftLockShifted" -->
+        <default>
+            <Key
+                latin:keyLabel="{" />
+            <Key
+                latin:keyLabel="}" />
+            <Key
+                latin:keyLabel="|" />
         </default>
     </switch>
-</merge>
+</merge>
\ No newline at end of file
diff --git a/java/res/xml-sw600dp/keys_pcqwerty3_right2.xml b/java/res/xml-sw600dp/keys_pcqwerty3_right2.xml
index 0e3013a..254b5e5 100644
--- a/java/res/xml-sw600dp/keys_pcqwerty3_right2.xml
+++ b/java/res/xml-sw600dp/keys_pcqwerty3_right2.xml
@@ -23,26 +23,27 @@
 >
     <switch>
         <case
-            latin:keyboardLayoutSetElement="symbols|symbolsShifted"
+            latin:keyboardLayoutSetElement="alphabet|alphabetAutomaticShifted"
         >
             <Key
                 latin:keyLabel=";"
-                latin:moreKeys=":" />
-            <Key
-                latin:keyLabel="\'"
-                latin:moreKeys="!fixedColumnOrder!3,!text/double_quotes,!text/single_quotes" />
-        </case>
-        <default>
-            <Key
-                latin:keyLabel=";"
                 latin:keyHintLabel=":"
-                latin:moreKeys=":"
+                latin:additionalMoreKeys=":"
                 latin:keyStyle="hasShiftedLetterHintStyle" />
             <Key
                 latin:keyLabel="\'"
                 latin:keyHintLabel="&quot;"
-                latin:moreKeys="!fixedColumnOrder!4,!text/double_quotes,&quot;,!text/single_quotes"
-                latin:keyStyle="hasShiftedLetterHintStyle" />
+                latin:additionalMoreKeys="&quot;"
+                latin:keyStyle="hasShiftedLetterHintStyle"
+                latin:moreKeys="!fixedColumnOrder!4,!text/double_quotes,%,!text/single_quotes" />
+        </case>
+        <!-- keyboardLayoutSetElement="alphabetManualShifted|alphabetShiftLocked|alphabetShiftLockShifted" -->
+        <default>
+            <Key
+                latin:keyLabel=":" />
+            <Key
+                latin:keyLabel="&quot;"
+                latin:moreKeys="!fixedColumnOrder!3,!text/double_quotes,!text/single_quotes" />
         </default>
     </switch>
 </merge>
diff --git a/java/res/xml-sw600dp/keys_pcqwerty4_right3.xml b/java/res/xml-sw600dp/keys_pcqwerty4_right3.xml
index ee5271a..774ff8d 100644
--- a/java/res/xml-sw600dp/keys_pcqwerty4_right3.xml
+++ b/java/res/xml-sw600dp/keys_pcqwerty4_right3.xml
@@ -23,28 +23,26 @@
 >
     <switch>
         <case
-            latin:keyboardLayoutSetElement="symbols|symbolsShifted"
+            latin:keyboardLayoutSetElement="alphabet|alphabetAutomaticShifted"
         >
-            <!-- U+2039: "‹" SINGLE LEFT-POINTING ANGLE QUOTATION MARK
-                 U+203A: "›" SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
-                 U+2264: "≤" LESS-THAN OR EQUAL TO
-                 U+2265: "≥" GREATER-THAN EQUAL TO
-                 U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
-                 U+00BB: "»" RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK -->
-           <Key
+            <Key
                 latin:keyLabel=","
+                latin:keyHintLabel="&lt;"
                 latin:additionalMoreKeys="&lt;"
-                latin:moreKeys="!fixedColumnOrder!4,&#x2039;,&#x2064;,&#x00AB;" />
+                latin:keyStyle="hasShiftedLetterHintStyle" />
             <Key
                 latin:keyLabel="."
+                latin:keyHintLabel="&gt;"
                 latin:additionalMoreKeys="&gt;"
-                latin:moreKeys="!fixedColumnOrder!4,&#x203A;,&#x2065;,&#x00BB;" />
-            <!-- U+00BF: "¿" INVERTED QUESTION MARK -->
+                latin:keyStyle="hasShiftedLetterHintStyle" />
             <Key
                 latin:keyLabel="/"
+                latin:keyHintLabel="\?"
                 latin:additionalMoreKeys="\?"
-                latin:moreKeys="&#x00BF;" />
+                latin:keyStyle="hasShiftedLetterHintStyle"
+                latin:moreKeys="!text/more_keys_for_symbols_question" />
         </case>
+        <!-- keyboardLayoutSetElement="alphabetManualShifted|alphabetShiftLocked|alphabetShiftLockShifted" -->
         <default>
             <!-- U+2039: "‹" SINGLE LEFT-POINTING ANGLE QUOTATION MARK
                  U+203A: "›" SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
@@ -53,24 +51,14 @@
                  U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
                  U+00BB: "»" RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK -->
             <Key
-                latin:keyLabel=","
-                latin:keyHintLabel="&lt;"
-                latin:additionalMoreKeys="&lt;"
-                latin:moreKeys="!fixedColumnOrder!4,&#x2039;,&#x2264;,&#x00AB;"
-                latin:keyStyle="hasShiftedLetterHintStyle" />
+                latin:keyLabel="&lt;"
+                latin:moreKeys="!fixedColumnOrder!3,&#x2039;,&#x2264;,&#x00AB;" />
             <Key
-                latin:keyLabel="."
-                latin:keyHintLabel="&gt;"
-                latin:additionalMoreKeys="&gt;"
-                latin:moreKeys="!fixedColumnOrder!4,&#x203A;,&#x2265;,&#x00BB;"
-                latin:keyStyle="hasShiftedLetterHintStyle" />
-            <!-- U+00BF: "¿" INVERTED QUESTION MARK -->
+                latin:keyLabel="&gt;"
+                latin:moreKeys="!fixedColumnOrder!3,&#x203A;,&#x2265;,&#x00BB;" />
             <Key
-                latin:keyLabel="/"
-                latin:keyHintLabel="\?"
-                latin:additionalMoreKeys="\?"
-                latin:moreKeys="&#x00BF;"
-                latin:keyStyle="hasShiftedLetterHintStyle" />
+                latin:keyLabel="\?"
+                latin:moreKeys="!text/more_keys_for_symbols_question" />
         </default>
     </switch>
 </merge>
diff --git a/java/res/xml-sw600dp/row_dvorak4.xml b/java/res/xml-sw600dp/row_dvorak4.xml
index 969cc14..11b4034 100644
--- a/java/res/xml-sw600dp/row_dvorak4.xml
+++ b/java/res/xml-sw600dp/row_dvorak4.xml
@@ -34,12 +34,15 @@
             latin:keyboardLayout="@xml/key_f1" />
         <include
             latin:keyXPos="28.0%p"
-            latin:keyboardLayout="@xml/key_space"
+            latin:keyboardLayout="@xml/key_space_5kw"
             latin:backgroundType="normal" />
         <include
             latin:keyboardLayout="@xml/key_question_exclamation" />
-        <include
-            latin:keyboardLayout="@xml/key_dash" />
+        <Key
+            latin:keyLabel="-"
+            latin:keyHintLabel="_"
+            latin:moreKeys="_"
+            latin:keyStyle="hasShiftedLetterHintStyle" />
         <include
             latin:keyboardLayout="@xml/key_f2" />
     </Row>
diff --git a/java/res/xml-sw600dp/row_hebrew4.xml b/java/res/xml-sw600dp/row_hebrew4.xml
deleted file mode 100644
index f429f97..0000000
--- a/java/res/xml-sw600dp/row_hebrew4.xml
+++ /dev/null
@@ -1,44 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <Row
-        latin:keyWidth="9.0%p"
-        latin:backgroundType="functional"
-    >
-        <Key
-            latin:keyStyle="toSymbolKeyStyle"
-            latin:keyWidth="10.0%p" />
-        <include
-            latin:keyboardLayout="@xml/key_shortcut" />
-        <include
-            latin:keyboardLayout="@xml/key_f1" />
-        <include
-            latin:keyXPos="28.0%p"
-            latin:keyboardLayout="@xml/key_space"
-            latin:backgroundType="normal" />
-        <include
-            latin:keyboardLayout="@xml/keys_comma_period" />
-        <include
-            latin:keyboardLayout="@xml/key_f2" />
-    </Row>
-</merge>
diff --git a/java/res/xml-sw600dp/row_pcqwerty5.xml b/java/res/xml-sw600dp/row_pcqwerty5.xml
index 3c4a466..b854f10 100644
--- a/java/res/xml-sw600dp/row_pcqwerty5.xml
+++ b/java/res/xml-sw600dp/row_pcqwerty5.xml
@@ -38,9 +38,23 @@
             latin:keyStyle="spaceKeyStyle"
             latin:keyXPos="25.5%p"
             latin:keyWidth="49.0%p" />
-         <include
-            latin:keyXPos="-9.0%p"
-            latin:keyWidth="fillRight"
-            latin:keyboardLayout="@xml/key_shortcut" />
+        <switch>
+            <case
+                latin:keyboardLayoutSetElement="alphabet|alphabetAutomaticShifted"
+            >
+                <include
+                    latin:keyXPos="-9.0%p"
+                    latin:keyWidth="9.0%p"
+                    latin:keyboardLayout="@xml/key_shortcut" />
+            </case>
+            <!-- keyboardLayoutSetElement="alphabetManualShifted|alphabetShiftLocked|alphabetShiftLockShifted" -->
+            <default>
+                <include
+                    latin:keyXPos="-9.0%p"
+                    latin:keyWidth="9.0%p"
+                    latin:backgroundType="functional"
+                    latin:keyboardLayout="@xml/key_f2" />
+            </default>
+        </switch>
     </Row>
 </merge>
diff --git a/java/res/xml-sw600dp/row_qwerty4.xml b/java/res/xml-sw600dp/row_qwerty4.xml
index fa43363..7969dd8 100644
--- a/java/res/xml-sw600dp/row_qwerty4.xml
+++ b/java/res/xml-sw600dp/row_qwerty4.xml
@@ -34,12 +34,10 @@
             latin:keyboardLayout="@xml/key_f1" />
         <include
             latin:keyXPos="28.0%p"
-            latin:keyboardLayout="@xml/key_space"
+            latin:keyboardLayout="@xml/key_space_5kw"
             latin:backgroundType="normal" />
         <include
-            latin:keyboardLayout="@xml/key_apostrophe" />
-        <include
-            latin:keyboardLayout="@xml/key_dash" />
+            latin:keyboardLayout="@xml/keys_comma_period" />
         <include
             latin:keyboardLayout="@xml/key_f2" />
     </Row>
diff --git a/java/res/xml-sw600dp/row_symbols4.xml b/java/res/xml-sw600dp/row_symbols4.xml
deleted file mode 100644
index f138d8e..0000000
--- a/java/res/xml-sw600dp/row_symbols4.xml
+++ /dev/null
@@ -1,48 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <Row
-        latin:keyWidth="9.0%p"
-        latin:backgroundType="functional"
-    >
-        <Key
-            latin:keyStyle="toAlphaKeyStyle"
-            latin:keyWidth="10.0%p" />
-        <Key
-            latin:keyLabel="/" />
-        <include
-            latin:keyboardLayout="@xml/key_f1" />
-        <include
-            latin:keyXPos="28.0%p"
-            latin:keyboardLayout="@xml/key_space"
-            latin:backgroundType="normal" />
-        <Key
-            latin:keyLabel="&quot;"
-            latin:moreKeys="!text/more_keys_for_tablet_double_quote" />
-        <Key
-            latin:keyLabel="_" />
-        <!-- Note: This Spacer prevents the above key from being marked as a right edge key. -->
-        <Spacer
-            latin:keyWidth="fillRight" />
-    </Row>
-</merge>
diff --git a/java/res/xml-sw600dp/row_symbols_shift4.xml b/java/res/xml-sw600dp/row_symbols_shift4.xml
deleted file mode 100644
index 29befa9..0000000
--- a/java/res/xml-sw600dp/row_symbols_shift4.xml
+++ /dev/null
@@ -1,40 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <Row
-        latin:keyWidth="9.0%p"
-        latin:backgroundType="functional"
-    >
-        <Key
-            latin:keyStyle="toAlphaKeyStyle"
-            latin:keyWidth="10.0%p" />
-        <!-- Here is empty space. -->
-        <include
-            latin:keyXPos="28.0%p"
-            latin:keyboardLayout="@xml/key_space"
-            latin:backgroundType="normal" />
-        <!-- Note: This Spacer prevents the above key from being marked as a right edge key. -->
-        <Spacer
-            latin:keyWidth="fillRight" />
-    </Row>
-</merge>
diff --git a/java/res/xml-sw600dp/rowkeys_pcqwerty1.xml b/java/res/xml-sw600dp/rowkeys_pcqwerty1.xml
index b11bbba..254d3fd 100644
--- a/java/res/xml-sw600dp/rowkeys_pcqwerty1.xml
+++ b/java/res/xml-sw600dp/rowkeys_pcqwerty1.xml
@@ -21,93 +21,87 @@
 <merge
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
 >
-    <switch>
-        <case
-            latin:keyboardLayoutSetElement="symbols|symbolsShifted"
-        >
-            <include
-                latin:keyboardLayout="@xml/keys_pcqwerty_symbols1" />
-        </case>
-        <!-- keyboardLayoutSetElement="alphabet*" -->
-        <default>
-            <!-- U+00AC: "¬" NOT SIGN -->
-            <Key
-                latin:keyLabel="`"
-                latin:keyHintLabel="~"
-                latin:additionalMoreKeys="~"
-                latin:moreKeys="&#x00AC;"
-                latin:keyStyle="hasShiftedLetterHintStyle" />
-            <!-- U+00A1: "¡" NVERTED EXCLAMATION MARK -->
-            <Key
-                latin:keyLabel="1"
-                latin:keyHintLabel="!"
-                latin:additionalMoreKeys="!"
-                latin:moreKeys="&#x00A1;,!text/more_keys_for_symbols_1"
-                latin:keyStyle="hasShiftedLetterHintStyle" />
-            <Key
-                latin:keyLabel="2"
-                latin:keyHintLabel="\@"
-                latin:additionalMoreKeys="\@"
-                latin:moreKeys="!text/more_keys_for_symbols_2"
-                latin:keyStyle="hasShiftedLetterHintStyle" />
-            <Key
-                latin:keyLabel="3"
-                latin:keyHintLabel="\#"
-                latin:additionalMoreKeys="\#"
-                latin:moreKeys="!text/more_keys_for_symbols_3"
-                latin:keyStyle="hasShiftedLetterHintStyle" />
-            <Key
-                latin:keyLabel="4"
-                latin:keyHintLabel="$"
-                latin:additionalMoreKeys="$"
-                latin:moreKeys="!text/more_keys_for_symbols_4"
-                latin:keyStyle="hasShiftedLetterHintStyle" />
-            <Key
-                latin:keyLabel="5"
-                latin:keyHintLabel="%"
-                latin:additionalMoreKeys="\\%"
-                latin:moreKeys="!text/more_keys_for_symbols_5"
-                latin:keyStyle="hasShiftedLetterHintStyle" />
-            <Key
-                latin:keyLabel="6"
-                latin:keyHintLabel="^"
-                latin:additionalMoreKeys="^"
-                latin:moreKeys="!text/more_keys_for_symbols_6"
-                latin:keyStyle="hasShiftedLetterHintStyle" />
-            <Key
-                latin:keyLabel="7"
-                latin:keyHintLabel="&amp;"
-                latin:additionalMoreKeys="&amp;"
-                latin:moreKeys="!text/more_keys_for_symbols_7"
-                latin:keyStyle="hasShiftedLetterHintStyle" />
-            <Key
-                latin:keyLabel="8"
-                latin:keyHintLabel="*"
-                latin:additionalMoreKeys="*"
-                latin:moreKeys="!text/more_keys_for_symbols_8"
-                latin:keyStyle="hasShiftedLetterHintStyle" />
-            <Key
-                latin:keyLabel="9"
-                latin:keyHintLabel="("
-                latin:additionalMoreKeys="("
-                latin:moreKeys="!text/more_keys_for_symbols_9"
-                latin:keyStyle="hasShiftedLetterHintStyle" />
-            <Key
-                latin:keyLabel="0"
-                latin:keyHintLabel=")"
-                latin:additionalMoreKeys=")"
-                latin:moreKeys="!text/more_keys_for_symbols_0"
-                latin:keyStyle="hasShiftedLetterHintStyle" />
-            <Key
-                latin:keyLabel="-"
-                latin:keyHintLabel="_"
-                latin:moreKeys="_"
-                latin:keyStyle="hasShiftedLetterHintStyle" />
-            <Key
-                latin:keyLabel="="
-                latin:keyHintLabel="+"
-                latin:moreKeys="+"
-                latin:keyStyle="hasShiftedLetterHintStyle" />
-        </default>
-    </switch>
+    <Key
+        latin:keyLabel="`"
+        latin:keyHintLabel="~"
+        latin:additionalMoreKeys="~"
+        latin:keyStyle="hasShiftedLetterHintStyle" />
+    <Key
+        latin:keyLabel="1"
+        latin:keyHintLabel="!"
+        latin:additionalMoreKeys="!"
+        latin:keyStyle="hasShiftedLetterHintStyle"
+        latin:moreKeys="!text/more_keys_for_symbols_exclamation,!text/more_keys_for_symbols_1" />
+    <Key
+        latin:keyLabel="2"
+        latin:keyHintLabel="\@"
+        latin:additionalMoreKeys="\@"
+        latin:keyStyle="hasShiftedLetterHintStyle"
+        latin:moreKeys="!text/more_keys_for_symbols_2" />
+    <Key
+        latin:keyLabel="3"
+        latin:keyHintLabel="\#"
+        latin:additionalMoreKeys="\#"
+        latin:keyStyle="hasShiftedLetterHintStyle"
+        latin:moreKeys="!text/more_keys_for_symbols_3" />
+    <Key
+        latin:keyLabel="4"
+        latin:keyHintLabel="$"
+        latin:additionalMoreKeys="$"
+        latin:keyStyle="hasShiftedLetterHintStyle"
+        latin:moreKeys="!text/more_keys_for_symbols_4" />
+    <Key
+        latin:keyLabel="5"
+        latin:keyHintLabel="%"
+        latin:additionalMoreKeys="\\%"
+        latin:keyStyle="hasShiftedLetterHintStyle"
+        latin:moreKeys="!text/more_keys_for_symbols_5" />
+    <Key
+        latin:keyLabel="6"
+        latin:keyHintLabel="^"
+        latin:additionalMoreKeys="^"
+        latin:keyStyle="hasShiftedLetterHintStyle"
+        latin:moreKeys="!text/more_keys_for_symbols_6" />
+    <Key
+        latin:keyLabel="7"
+        latin:keyHintLabel="&amp;"
+        latin:additionalMoreKeys="&amp;"
+        latin:keyStyle="hasShiftedLetterHintStyle"
+        latin:moreKeys="!text/more_keys_for_symbols_7" />
+    <Key
+        latin:keyLabel="8"
+        latin:keyHintLabel="*"
+        latin:additionalMoreKeys="*"
+        latin:keyStyle="hasShiftedLetterHintStyle"
+        latin:moreKeys="!text/more_keys_for_symbols_8" />
+    <Key
+        latin:keyLabel="9"
+        latin:keyHintLabel="("
+        latin:additionalMoreKeys="("
+        latin:keyStyle="hasShiftedLetterHintStyle"
+        latin:moreKeys="!text/more_keys_for_symbols_9" />
+    <Key
+        latin:keyLabel="0"
+        latin:keyHintLabel=")"
+        latin:additionalMoreKeys=")"
+        latin:keyStyle="hasShiftedLetterHintStyle"
+        latin:moreKeys="!text/more_keys_for_symbols_0" />
+    <!-- U+2013: "–" EN DASH
+         U+2014: "—" EM DASH
+         U+00B7: "·" MIDDLE DOT -->
+    <Key
+        latin:keyLabel="-"
+        latin:keyHintLabel="_"
+        latin:additionalMoreKeys="_"
+        latin:keyStyle="hasShiftedLetterHintStyle"
+        latin:moreKeys="&#x2013;,&#x2014;,&#x00B7;" />
+    <!-- U+221E: "∞" INFINITY
+         U+2260: "≠" NOT EQUAL TO
+         U+2248: "≈" ALMOST EQUAL TO -->
+    <Key
+        latin:keyLabel="="
+        latin:keyHintLabel="+"
+        latin:additionalMoreKeys="+"
+        latin:keyStyle="hasShiftedLetterHintStyle"
+        latin:moreKeys="&#x221E;,&#x2260;,&#x2248;" />
 </merge>
diff --git a/java/res/xml-sw600dp/rowkeys_symbols2.xml b/java/res/xml-sw600dp/rowkeys_symbols2.xml
deleted file mode 100644
index 14abb42..0000000
--- a/java/res/xml-sw600dp/rowkeys_symbols2.xml
+++ /dev/null
@@ -1,75 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <switch>
-        <case
-            latin:languageCode="fa"
-        >
-            <!-- U+066C: "٬" ARABIC THOUSANDS SEPARATOR -->
-            <Key
-                latin:keyLabel="&#x066C;"
-                latin:keyHintLabel="&amp;"
-                latin:keyLabelFlags="hasPopupHint|hasShiftedLetterHint"
-                latin:moreKeys="&amp;" />
-        </case>
-        <default>
-            <Key
-                latin:keyLabel="\#" />
-        </default>
-    </switch>
-    <Key
-        latin:keyStyle="currencyKeyStyle" />
-    <Key
-        latin:keyLabel="!text/keylabel_for_symbols_percent"
-        latin:moreKeys="!text/more_keys_for_symbols_percent" />
-    <switch>
-        <case
-            latin:languageCode="fa"
-        >
-            <!-- U+066B: "٫" ARABIC DECIMAL SEPARATOR -->
-            <Key
-                latin:keyLabel="&#x066B;"
-                latin:keyHintLabel="\#"
-                latin:keyLabelFlags="hasPopupHint|hasShiftedLetterHint"
-                latin:moreKeys="\#" />
-        </case>
-        <default>
-            <Key
-                latin:keyLabel="&amp;" />
-        </default>
-    </switch>
-    <Key
-        latin:keyLabel="*"
-        latin:moreKeys="!text/more_keys_for_star" />
-    <!-- U+2013: "–" EN DASH
-         U+2014: "—" EM DASH
-         U+00B7: "·" MIDDLE DOT -->
-    <Key
-        latin:keyLabel="-"
-        latin:moreKeys="_,&#x2013;,&#x2014;,&#x00B7;" />
-    <Key
-        latin:keyLabel="+"
-        latin:moreKeys="!text/more_keys_for_plus" />
-    <include
-        latin:keyboardLayout="@xml/keys_parentheses" />
-</merge>
diff --git a/java/res/xml-sw600dp/rowkeys_symbols3.xml b/java/res/xml-sw600dp/rowkeys_symbols3.xml
deleted file mode 100644
index 30fba38..0000000
--- a/java/res/xml-sw600dp/rowkeys_symbols3.xml
+++ /dev/null
@@ -1,57 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <include
-        latin:keyboardLayout="@xml/keys_less_greater" />
-    <!-- U+2260: "≠" NOT EQUAL TO
-         U+2248: "≈" ALMOST EQUAL TO -->
-    <Key
-        latin:keyLabel="="
-        latin:moreKeys="&#x2260;,&#x2248;" />
-    <switch>
-        <case
-            latin:mode="url"
-        >
-            <Key
-                latin:keyLabel="\'" />
-        </case>
-        <default>
-            <Key
-                latin:keyLabel=":" />
-        </default>
-    </switch>
-    <Key
-        latin:keyLabel="!text/keylabel_for_symbols_semicolon"
-        latin:moreKeys="!text/more_keys_for_symbols_semicolon" />
-    <Key
-        latin:keyLabel="!text/keylabel_for_comma"
-        latin:moreKeys="!text/more_keys_for_comma" />
-    <Key
-        latin:keyLabel="." />
-    <Key
-        latin:keyLabel="!"
-        latin:moreKeys="!text/more_keys_for_symbols_exclamation" />
-    <Key
-        latin:keyLabel="!text/keylabel_for_symbols_question"
-        latin:moreKeys="!text/more_keys_for_symbols_question" />
-</merge>
diff --git a/java/res/xml-sw600dp/rowkeys_symbols_shift1.xml b/java/res/xml-sw600dp/rowkeys_symbols_shift1.xml
deleted file mode 100644
index 3549fdd..0000000
--- a/java/res/xml-sw600dp/rowkeys_symbols_shift1.xml
+++ /dev/null
@@ -1,56 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <Key
-        latin:keyLabel="~" />
-    <Key
-        latin:keyLabel="`" />
-    <Key
-        latin:keyLabel="|" />
-    <!-- U+2022: "•" BULLET -->
-    <Key
-        latin:keyLabel="&#x2022;"
-        latin:moreKeys="!text/more_keys_for_bullet" />
-    <!-- U+221A: "√" SQUARE ROOT -->
-    <Key
-        latin:keyLabel="&#x221A;" />
-    <!-- U+03C0: "π" GREEK SMALL LETTER PI
-         U+03A0: "Π" GREEK CAPITAL LETTER PI -->
-    <Key
-        latin:keyLabel="&#x03C0;"
-        latin:moreKeys="&#x03A0;" />
-    <!-- U+00F7: "÷" DIVISION SIGN -->
-    <Key
-        latin:keyLabel="&#x00F7;" />
-    <!-- U+00D7: "×" MULTIPLICATION SIGN -->
-    <Key
-        latin:keyLabel="&#x00D7;" />
-    <!-- U+00A7: "§" SECTION SIGN
-         U+00B6: "¶" PILCROW SIGN -->
-    <Key
-        latin:keyLabel="&#x00A7;"
-        latin:moreKeys="&#x00B6;" />
-    <!-- U+0394: "Δ" GREEK CAPITAL LETTER DELTA -->
-    <Key
-        latin:keyLabel="&#x0394;" />
-</merge>
diff --git a/java/res/xml-sw600dp/rowkeys_symbols_shift2.xml b/java/res/xml-sw600dp/rowkeys_symbols_shift2.xml
deleted file mode 100644
index 2048b73..0000000
--- a/java/res/xml-sw600dp/rowkeys_symbols_shift2.xml
+++ /dev/null
@@ -1,52 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <Key
-        latin:keyStyle="moreCurrency1KeyStyle" />
-    <Key
-        latin:keyStyle="moreCurrency2KeyStyle" />
-    <Key
-        latin:keyStyle="moreCurrency3KeyStyle" />
-    <Key
-        latin:keyStyle="moreCurrency4KeyStyle" />
-    <!-- U+2191: "↑" UPWARDS ARROW
-         U+2193: "↓" DOWNWARDS ARROW
-         U+2190: "←" LEFTWARDS ARROW
-         U+2192: "→" RIGHTWARDS ARROW -->
-    <Key
-        latin:keyLabel="^"
-        latin:moreKeys="&#x2191;,&#x2193;,&#x2190;,&#x2192;" />
-    <!-- U+00B0: "°" DEGREE SIGN
-         U+2032: "′" PRIME
-         U+2033: "″" DOUBLE PRIME -->
-    <Key
-        latin:keyLabel="&#x00B0;"
-        latin:moreKeys="&#x2032;,&#x2033;" />
-    <!-- U+00B1: "±" PLUS-MINUS SIGN
-         U+221E: "∞" INFINITY -->
-    <Key
-        latin:keyLabel="&#x00B1;"
-        latin:moreKeys="&#x221E;" />
-    <include
-        latin:keyboardLayout="@xml/keys_curly_brackets" />
-</merge>
diff --git a/java/res/xml-sw600dp/rowkeys_symbols_shift3.xml b/java/res/xml-sw600dp/rowkeys_symbols_shift3.xml
deleted file mode 100644
index 8bd8656..0000000
--- a/java/res/xml-sw600dp/rowkeys_symbols_shift3.xml
+++ /dev/null
@@ -1,46 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <Key
-        latin:keyLabel="\\" />
-    <!-- U+00A9: "©" COPYRIGHT SIGN -->
-    <Key
-        latin:keyLabel="&#x00A9;" />
-    <!-- U+00AE: "®" REGISTERED SIGN -->
-    <Key
-        latin:keyLabel="&#x00AE;" />
-    <!-- U+2122: "™" TRADE MARK SIGN -->
-    <Key
-        latin:keyLabel="&#x2122;" />
-    <!-- U+2105: "℅" CARE OF -->
-    <Key
-        latin:keyLabel="&#x2105;" />
-    <include
-        latin:keyboardLayout="@xml/keys_square_brackets" />
-    <!-- U+00A1: "¡" INVERTED EXCLAMATION MARK -->
-    <Key
-        latin:keyLabel="&#x00A1;" />
-    <!-- U+00BF: "¿" INVERTED QUESTION MARK -->
-    <Key
-        latin:keyLabel="&#x00BF;" />
-</merge>
diff --git a/java/res/xml-sw600dp/rows_10_10_7_symbols_shift.xml b/java/res/xml-sw600dp/rows_10_10_7_symbols_shift.xml
deleted file mode 100644
index 3d3b59f..0000000
--- a/java/res/xml-sw600dp/rows_10_10_7_symbols_shift.xml
+++ /dev/null
@@ -1,60 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <include
-        latin:keyboardLayout="@xml/key_styles_common" />
-    <include
-        latin:keyboardLayout="@xml/key_styles_currency" />
-    <Row
-        latin:keyWidth="9.0%p"
-    >
-        <include
-            latin:keyboardLayout="@xml/rowkeys_symbols_shift1" />
-        <Key
-            latin:keyStyle="deleteKeyStyle"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <Row
-        latin:keyWidth="9.0%p"
-    >
-        <include
-            latin:keyboardLayout="@xml/rowkeys_symbols_shift2" />
-        <Key
-            latin:keyStyle="enterKeyStyle"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <Row
-        latin:keyWidth="9.0%p"
-    >
-        <Key
-            latin:keyStyle="backFromMoreSymbolKeyStyle"
-            latin:keyWidth="10.0%p" />
-        <include
-            latin:keyboardLayout="@xml/rowkeys_symbols_shift3" />
-        <Key
-            latin:keyStyle="backFromMoreSymbolKeyStyle"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <include
-        latin:keyboardLayout="@xml/row_symbols_shift4" />
-</merge>
diff --git a/java/res/xml-sw768dp/rows_colemak.xml b/java/res/xml-sw600dp/rows_armenian_phonetic.xml
similarity index 63%
rename from java/res/xml-sw768dp/rows_colemak.xml
rename to java/res/xml-sw600dp/rows_armenian_phonetic.xml
index 073f812..9bc2a18 100644
--- a/java/res/xml-sw768dp/rows_colemak.xml
+++ b/java/res/xml-sw600dp/rows_armenian_phonetic.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2010, The Android Open Source Project
+** 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.
@@ -24,43 +24,43 @@
     <include
         latin:keyboardLayout="@xml/key_styles_common" />
     <Row
-        latin:keyWidth="8.282%p"
+        latin:keyWidth="9.0%p"
     >
-        <Key
-            latin:keyStyle="tabKeyStyle"
-            latin:keyLabelFlags="alignLeft"
-            latin:keyWidth="7.969%p" />
         <include
-            latin:keyboardLayout="@xml/rowkeys_colemak1" />
-        <include
-            latin:keyboardLayout="@xml/key_colemak_colon" />
+            latin:keyboardLayout="@xml/rowkeys_armenian_phonetic1" />
         <Key
             latin:keyStyle="deleteKeyStyle"
             latin:keyWidth="fillRight" />
+        </Row>
+    <Row
+        latin:keyWidth="9.0%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_armenian_phonetic2" />
+        <include
+            latin:keyboardLayout="@xml/key_armenian_xeh" />
     </Row>
     <Row
-        latin:keyWidth="8.125%p"
+        latin:keyWidth="9.0%p"
     >
-        <Key
-            latin:keyStyle="toSymbolKeyStyle"
-            latin:keyLabelFlags="alignLeft"
-            latin:keyWidth="10.167%p" />
         <include
-            latin:keyboardLayout="@xml/rowkeys_colemak2" />
+            latin:keyboardLayout="@xml/rowkeys_armenian_phonetic3" />
+        <include
+            latin:keyboardLayout="@xml/key_armenian_sha" />
         <Key
             latin:keyStyle="enterKeyStyle"
             latin:keyWidth="fillRight" />
     </Row>
     <Row
-        latin:keyWidth="8.047%p"
+        latin:keyWidth="8.8889%p"
     >
         <Key
             latin:keyStyle="shiftKeyStyle"
-            latin:keyWidth="13.829%p" />
+            latin:keyWidth="10.0%p" />
         <include
-            latin:keyboardLayout="@xml/rowkeys_colemak3" />
+            latin:keyboardLayout="@xml/rowkeys_armenian_phonetic4" />
         <include
-            latin:keyboardLayout="@xml/keys_comma_period" />
+            latin:keyboardLayout="@xml/keys_exclamation_question" />
         <Key
             latin:keyStyle="shiftKeyStyle"
             latin:keyWidth="fillRight" />
diff --git a/java/res/xml-sw600dp/rows_azerty.xml b/java/res/xml-sw600dp/rows_azerty.xml
index 5a5a7d1..cdc9185 100644
--- a/java/res/xml-sw600dp/rows_azerty.xml
+++ b/java/res/xml-sw600dp/rows_azerty.xml
@@ -50,7 +50,7 @@
         <include
             latin:keyboardLayout="@xml/rowkeys_azerty3" />
         <include
-            latin:keyboardLayout="@xml/keys_comma_period" />
+            latin:keyboardLayout="@xml/keys_exclamation_question" />
         <Key
             latin:keyStyle="shiftKeyStyle"
             latin:keyWidth="fillRight" />
diff --git a/java/res/xml-sw600dp/rows_bulgarian.xml b/java/res/xml-sw600dp/rows_bulgarian.xml
index 2635620..c73aa1c 100644
--- a/java/res/xml-sw600dp/rows_bulgarian.xml
+++ b/java/res/xml-sw600dp/rows_bulgarian.xml
@@ -50,7 +50,7 @@
         <include
             latin:keyboardLayout="@xml/rowkeys_bulgarian3" />
         <include
-            latin:keyboardLayout="@xml/keys_comma_period" />
+            latin:keyboardLayout="@xml/keys_exclamation_question" />
         <Key
             latin:keyStyle="shiftKeyStyle"
             latin:keyWidth="fillRight" />
diff --git a/java/res/xml-sw600dp/rows_bulgarian_bds.xml b/java/res/xml-sw600dp/rows_bulgarian_bds.xml
index 9439a63..bc773ee 100644
--- a/java/res/xml-sw600dp/rows_bulgarian_bds.xml
+++ b/java/res/xml-sw600dp/rows_bulgarian_bds.xml
@@ -50,7 +50,7 @@
         <include
             latin:keyboardLayout="@xml/rowkeys_bulgarian_bds3" />
         <include
-            latin:keyboardLayout="@xml/keys_comma_period" />
+            latin:keyboardLayout="@xml/keys_exclamation_question" />
     </Row>
     <include
         latin:keyboardLayout="@xml/row_qwerty4" />
diff --git a/java/res/xml-sw600dp/rows_colemak.xml b/java/res/xml-sw600dp/rows_colemak.xml
index 98a24e4..ab059da 100644
--- a/java/res/xml-sw600dp/rows_colemak.xml
+++ b/java/res/xml-sw600dp/rows_colemak.xml
@@ -52,7 +52,7 @@
         <include
             latin:keyboardLayout="@xml/rowkeys_colemak3" />
         <include
-            latin:keyboardLayout="@xml/keys_comma_period" />
+            latin:keyboardLayout="@xml/keys_exclamation_question" />
         <Key
             latin:keyStyle="shiftKeyStyle"
             latin:keyWidth="fillRight" />
diff --git a/java/res/xml-sw600dp/rows_east_slavic.xml b/java/res/xml-sw600dp/rows_east_slavic.xml
index b4160d6..c5045ff 100644
--- a/java/res/xml-sw600dp/rows_east_slavic.xml
+++ b/java/res/xml-sw600dp/rows_east_slavic.xml
@@ -50,7 +50,7 @@
         <include
             latin:keyboardLayout="@xml/rowkeys_east_slavic3" />
         <include
-            latin:keyboardLayout="@xml/keys_comma_period" />
+            latin:keyboardLayout="@xml/keys_exclamation_question" />
     </Row>
     <include
         latin:keyboardLayout="@xml/row_qwerty4" />
diff --git a/java/res/xml-sw600dp/rows_georgian.xml b/java/res/xml-sw600dp/rows_georgian.xml
index b0e9e35..891cbc1 100644
--- a/java/res/xml-sw600dp/rows_georgian.xml
+++ b/java/res/xml-sw600dp/rows_georgian.xml
@@ -51,7 +51,7 @@
         <include
             latin:keyboardLayout="@xml/rowkeys_georgian3" />
         <include
-            latin:keyboardLayout="@xml/keys_comma_period" />
+            latin:keyboardLayout="@xml/keys_exclamation_question" />
         <Key
             latin:keyStyle="shiftKeyStyle"
             latin:keyWidth="fillRight" />
diff --git a/java/res/xml-sw600dp/rows_greek.xml b/java/res/xml-sw600dp/rows_greek.xml
index de214c6..066dc47 100644
--- a/java/res/xml-sw600dp/rows_greek.xml
+++ b/java/res/xml-sw600dp/rows_greek.xml
@@ -53,7 +53,7 @@
         <include
             latin:keyboardLayout="@xml/rowkeys_greek3" />
         <include
-            latin:keyboardLayout="@xml/keys_comma_period" />
+            latin:keyboardLayout="@xml/keys_exclamation_question" />
         <Key
             latin:keyStyle="shiftKeyStyle"
             latin:keyWidth="fillRight" />
diff --git a/java/res/xml-sw600dp/rows_hebrew.xml b/java/res/xml-sw600dp/rows_hebrew.xml
index 9945dee..852e176 100644
--- a/java/res/xml-sw600dp/rows_hebrew.xml
+++ b/java/res/xml-sw600dp/rows_hebrew.xml
@@ -49,5 +49,5 @@
             latin:keyXPos="10.0%p" />
     </Row>
     <include
-        latin:keyboardLayout="@xml/row_hebrew4" />
+        latin:keyboardLayout="@xml/row_qwerty4" />
 </merge>
diff --git a/java/res/xml-sw600dp/rows_hindi.xml b/java/res/xml-sw600dp/rows_hindi.xml
index 2a9a419..ca581be 100644
--- a/java/res/xml-sw600dp/rows_hindi.xml
+++ b/java/res/xml-sw600dp/rows_hindi.xml
@@ -50,7 +50,7 @@
         <include
             latin:keyboardLayout="@xml/rowkeys_hindi3" />
         <include
-            latin:keyboardLayout="@xml/keys_comma_period" />
+            latin:keyboardLayout="@xml/keys_exclamation_question" />
     </Row>
     <include
         latin:keyboardLayout="@xml/row_qwerty4" />
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
new file mode 100644
index 0000000..446d9bd
--- /dev/null
+++ b/java/res/xml-sw600dp/rows_lao.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_lao1" />
+        <Key
+            latin:keyStyle="deleteKeyStyle"
+            latin:keyWidth="fillRight" />
+        </Row>
+    <Row
+        latin:keyWidth="7.5%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_lao2" />
+    </Row>
+    <Row
+        latin:keyWidth="7.5%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_lao3" />
+        <Key
+            latin:keyStyle="enterKeyStyle"
+            latin:keyWidth="fillRight" />
+    </Row>
+    <Row
+        latin:keyWidth="7.5%p"
+    >
+        <Key
+            latin:keyStyle="shiftKeyStyle"
+            latin:keyWidth="10.0%p" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_lao4" />
+        <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_mongolian.xml b/java/res/xml-sw600dp/rows_mongolian.xml
index dc0c1fe..8e39e62 100644
--- a/java/res/xml-sw600dp/rows_mongolian.xml
+++ b/java/res/xml-sw600dp/rows_mongolian.xml
@@ -50,7 +50,7 @@
         <include
             latin:keyboardLayout="@xml/rowkeys_mongolian3" />
         <include
-            latin:keyboardLayout="@xml/keys_comma_period" />
+            latin:keyboardLayout="@xml/keys_exclamation_question" />
     </Row>
     <include
         latin:keyboardLayout="@xml/row_qwerty4" />
diff --git a/java/res/xml-sw600dp/rows_10_10_7_symbols.xml b/java/res/xml-sw600dp/rows_nepali_romanized.xml
similarity index 66%
rename from java/res/xml-sw600dp/rows_10_10_7_symbols.xml
rename to java/res/xml-sw600dp/rows_nepali_romanized.xml
index 0e4710c..21d1dc6 100644
--- a/java/res/xml-sw600dp/rows_10_10_7_symbols.xml
+++ b/java/res/xml-sw600dp/rows_nepali_romanized.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2012, The Android Open Source Project
+** 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.
@@ -23,38 +23,35 @@
 >
     <include
         latin:keyboardLayout="@xml/key_styles_common" />
-    <include
-        latin:keyboardLayout="@xml/key_styles_currency" />
     <Row
-        latin:keyWidth="9.0%p"
+        latin:keyWidth="8.182%p"
     >
         <include
-            latin:keyboardLayout="@xml/rowkeys_symbols1" />
+            latin:keyboardLayout="@xml/rowkeys_nepali_romanized1" />
         <Key
             latin:keyStyle="deleteKeyStyle"
             latin:keyWidth="fillRight" />
     </Row>
     <Row
-        latin:keyWidth="9.0%p"
+        latin:keyWidth="8.182%p"
     >
         <include
-            latin:keyboardLayout="@xml/rowkeys_symbols2" />
+            latin:keyboardLayout="@xml/rowkeys_nepali_romanized2" />
         <Key
             latin:keyStyle="enterKeyStyle"
             latin:keyWidth="fillRight" />
     </Row>
     <Row
-        latin:keyWidth="9.0%p"
+        latin:keyWidth="8.182%p"
     >
         <Key
-            latin:keyStyle="toMoreSymbolKeyStyle"
+            latin:keyStyle="shiftKeyStyle"
             latin:keyWidth="10.0%p" />
         <include
-            latin:keyboardLayout="@xml/rowkeys_symbols3" />
-        <Key
-            latin:keyStyle="toMoreSymbolKeyStyle"
-            latin:keyWidth="fillRight" />
+            latin:keyboardLayout="@xml/rowkeys_nepali_romanized3" />
+        <include
+            latin:keyboardLayout="@xml/keys_exclamation_question" />
     </Row>
     <include
-        latin:keyboardLayout="@xml/row_symbols4" />
+        latin:keyboardLayout="@xml/row_qwerty4" />
 </merge>
diff --git a/java/res/xml-sw600dp/rows_10_10_7_symbols.xml b/java/res/xml-sw600dp/rows_nepali_traditional.xml
similarity index 65%
copy from java/res/xml-sw600dp/rows_10_10_7_symbols.xml
copy to java/res/xml-sw600dp/rows_nepali_traditional.xml
index 0e4710c..90703da 100644
--- a/java/res/xml-sw600dp/rows_10_10_7_symbols.xml
+++ b/java/res/xml-sw600dp/rows_nepali_traditional.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2012, The Android Open Source Project
+** 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.
@@ -23,38 +23,35 @@
 >
     <include
         latin:keyboardLayout="@xml/key_styles_common" />
-    <include
-        latin:keyboardLayout="@xml/key_styles_currency" />
     <Row
-        latin:keyWidth="9.0%p"
+        latin:keyWidth="8.182%p"
     >
         <include
-            latin:keyboardLayout="@xml/rowkeys_symbols1" />
+            latin:keyboardLayout="@xml/rowkeys_nepali_traditional1" />
         <Key
             latin:keyStyle="deleteKeyStyle"
             latin:keyWidth="fillRight" />
     </Row>
     <Row
-        latin:keyWidth="9.0%p"
+        latin:keyWidth="8.182%p"
     >
         <include
-            latin:keyboardLayout="@xml/rowkeys_symbols2" />
+            latin:keyboardLayout="@xml/rowkeys_nepali_traditional2" />
         <Key
             latin:keyStyle="enterKeyStyle"
             latin:keyWidth="fillRight" />
     </Row>
     <Row
-        latin:keyWidth="9.0%p"
+        latin:keyWidth="8.182%p"
     >
         <Key
-            latin:keyStyle="toMoreSymbolKeyStyle"
+            latin:keyStyle="shiftKeyStyle"
             latin:keyWidth="10.0%p" />
         <include
-            latin:keyboardLayout="@xml/rowkeys_symbols3" />
-        <Key
-            latin:keyStyle="toMoreSymbolKeyStyle"
-            latin:keyWidth="fillRight" />
-    </Row>
+            latin:keyboardLayout="@xml/rowkeys_nepali_traditional3_left6" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_nepali_traditional3_right5" />
+        </Row>
     <include
-        latin:keyboardLayout="@xml/row_symbols4" />
+        latin:keyboardLayout="@xml/row_qwerty4" />
 </merge>
diff --git a/java/res/xml-sw600dp/rows_nordic.xml b/java/res/xml-sw600dp/rows_nordic.xml
index 299bf89..56fa406 100644
--- a/java/res/xml-sw600dp/rows_nordic.xml
+++ b/java/res/xml-sw600dp/rows_nordic.xml
@@ -52,7 +52,7 @@
         <include
             latin:keyboardLayout="@xml/rowkeys_qwerty3" />
         <include
-            latin:keyboardLayout="@xml/keys_comma_period" />
+            latin:keyboardLayout="@xml/keys_exclamation_question" />
         <Key
             latin:keyStyle="shiftKeyStyle"
             latin:keyXPos="-10.0%p"
diff --git a/java/res/xml-sw600dp/rows_pcqwerty.xml b/java/res/xml-sw600dp/rows_pcqwerty.xml
index fa6080a..8714815 100644
--- a/java/res/xml-sw600dp/rows_pcqwerty.xml
+++ b/java/res/xml-sw600dp/rows_pcqwerty.xml
@@ -26,8 +26,19 @@
     <Row
         latin:keyWidth="7.0%p"
     >
-        <include
-            latin:keyboardLayout="@xml/rowkeys_pcqwerty1" />
+        <switch>
+            <case
+                latin:keyboardLayoutSetElement="alphabet|alphabetAutomaticShifted"
+            >
+                <include
+                    latin:keyboardLayout="@xml/rowkeys_pcqwerty1" />
+            </case>
+            <!-- keyboardLayoutSetElement="alphabetManualShifted|alphabetShiftLocked|alphabetShiftLockShifted" -->
+            <default>
+                <include
+                     latin:keyboardLayout="@xml/rowkeys_pcqwerty1_shift" />
+            </default>
+        </switch>
         <Key
             latin:keyStyle="deleteKeyStyle"
             latin:keyWidth="fillRight" />
@@ -44,9 +55,7 @@
     <Row
         latin:keyWidth="7.0%p"
     >
-        <Key
-            latin:keyStyle="toSymbolKeyStyle"
-            latin:keyLabel="!text/label_to_symbol_key_pcqwerty"
+        <Spacer
             latin:keyWidth="12.0%p" />
         <include
             latin:keyboardLayout="@xml/rowkeys_pcqwerty3" />
diff --git a/java/res/xml-sw600dp/rows_pcqwerty_symbols.xml b/java/res/xml-sw600dp/rows_pcqwerty_symbols.xml
deleted file mode 100644
index 5e1aa63..0000000
--- a/java/res/xml-sw600dp/rows_pcqwerty_symbols.xml
+++ /dev/null
@@ -1,67 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <include
-        latin:keyboardLayout="@xml/key_styles_common" />
-    <include
-        latin:keyboardLayout="@xml/key_styles_currency" />
-    <Row
-        latin:keyWidth="7.0%p"
-    >
-        <include
-            latin:keyboardLayout="@xml/rowkeys_pcqwerty1" />
-        <Key
-            latin:keyStyle="deleteKeyStyle"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <Row
-        latin:keyWidth="7.0%p"
-    >
-        <Key
-            latin:keyStyle="tabKeyStyle"
-            latin:keyWidth="9.0%p" />
-        <include
-            latin:keyboardLayout="@xml/rowkeys_pcqwerty2" />
-    </Row>
-    <Row
-        latin:keyWidth="7.0%p"
-    >
-        <Key
-            latin:keyStyle="toAlphaKeyStyle"
-            latin:keyWidth="12.0%p" />
-        <include
-            latin:keyboardLayout="@xml/rowkeys_pcqwerty3" />
-        <Key
-            latin:keyStyle="enterKeyStyle"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <Row
-        latin:keyWidth="7.0%p"
-    >
-        <include
-            latin:keyboardLayout="@xml/rowkeys_pcqwerty4"
-            latin:keyXPos="15.0%p" />
-    </Row>
-    <include
-        latin:keyboardLayout="@xml/row_pcqwerty5" />
-</merge>
diff --git a/java/res/xml-sw600dp/rows_qwerty.xml b/java/res/xml-sw600dp/rows_qwerty.xml
index 722f9d1..58ba1d7 100644
--- a/java/res/xml-sw600dp/rows_qwerty.xml
+++ b/java/res/xml-sw600dp/rows_qwerty.xml
@@ -51,7 +51,7 @@
         <include
             latin:keyboardLayout="@xml/rowkeys_qwerty3" />
         <include
-            latin:keyboardLayout="@xml/keys_comma_period" />
+            latin:keyboardLayout="@xml/keys_exclamation_question" />
         <Key
             latin:keyStyle="shiftKeyStyle"
             latin:keyWidth="fillRight" />
diff --git a/java/res/xml-sw600dp/rows_qwertz.xml b/java/res/xml-sw600dp/rows_qwertz.xml
index f2f832c..3b59dec 100644
--- a/java/res/xml-sw600dp/rows_qwertz.xml
+++ b/java/res/xml-sw600dp/rows_qwertz.xml
@@ -51,7 +51,7 @@
         <include
             latin:keyboardLayout="@xml/rowkeys_qwertz3" />
         <include
-            latin:keyboardLayout="@xml/keys_comma_period" />
+            latin:keyboardLayout="@xml/keys_exclamation_question" />
         <Key
             latin:keyStyle="shiftKeyStyle"
             latin:keyWidth="fillRight" />
diff --git a/java/res/xml-sw600dp/rows_south_slavic.xml b/java/res/xml-sw600dp/rows_south_slavic.xml
index 6ef6643..5053988 100644
--- a/java/res/xml-sw600dp/rows_south_slavic.xml
+++ b/java/res/xml-sw600dp/rows_south_slavic.xml
@@ -50,7 +50,7 @@
         <include
             latin:keyboardLayout="@xml/rowkeys_south_slavic3" />
         <include
-            latin:keyboardLayout="@xml/keys_comma_period" />
+            latin:keyboardLayout="@xml/keys_exclamation_question" />
     </Row>
     <include
         latin:keyboardLayout="@xml/row_qwerty4" />
diff --git a/java/res/xml-sw600dp/rows_spanish.xml b/java/res/xml-sw600dp/rows_spanish.xml
index bca9bba..1092c26 100644
--- a/java/res/xml-sw600dp/rows_spanish.xml
+++ b/java/res/xml-sw600dp/rows_spanish.xml
@@ -50,7 +50,7 @@
         <include
             latin:keyboardLayout="@xml/rowkeys_qwerty3" />
         <include
-            latin:keyboardLayout="@xml/keys_comma_period" />
+            latin:keyboardLayout="@xml/keys_exclamation_question" />
         <Key
             latin:keyStyle="shiftKeyStyle"
             latin:keyWidth="fillRight" />
diff --git a/java/res/xml-sw600dp/rows_symbols.xml b/java/res/xml-sw600dp/rows_symbols.xml
index 3d0593d..fbd8492 100644
--- a/java/res/xml-sw600dp/rows_symbols.xml
+++ b/java/res/xml-sw600dp/rows_symbols.xml
@@ -50,12 +50,23 @@
         <Key
             latin:keyStyle="toMoreSymbolKeyStyle"
             latin:keyWidth="10.0%p" />
+        <Key
+            latin:keyLabel="\\" />
+        <Key
+            latin:keyLabel="=" />
         <include
             latin:keyboardLayout="@xml/rowkeys_symbols3" />
         <Key
             latin:keyStyle="toMoreSymbolKeyStyle"
             latin:keyWidth="fillRight" />
     </Row>
-    <include
-        latin:keyboardLayout="@xml/row_symbols4" />
+    <Row
+        latin:keyWidth="9.0%p"
+    >
+        <Key
+            latin:keyStyle="toAlphaKeyStyle"
+            latin:keyWidth="10.0%p" />
+        <include
+            latin:keyboardLayout="@xml/row_symbols4" />
+    </Row>
 </merge>
diff --git a/java/res/xml-sw600dp/rows_symbols_shift.xml b/java/res/xml-sw600dp/rows_symbols_shift.xml
index 0050c0c..aad047f 100644
--- a/java/res/xml-sw600dp/rows_symbols_shift.xml
+++ b/java/res/xml-sw600dp/rows_symbols_shift.xml
@@ -52,10 +52,23 @@
             latin:keyWidth="10.0%p" />
         <include
             latin:keyboardLayout="@xml/rowkeys_symbols_shift3" />
+        <!-- U+00A1: "¡" INVERTED EXCLAMATION MARK -->
+        <Key
+            latin:keyLabel="&#x00A1;" />
+        <!-- U+00BF: "¿" INVERTED QUESTION MARK -->
+        <Key
+            latin:keyLabel="&#x00BF;" />
         <Key
             latin:keyStyle="backFromMoreSymbolKeyStyle"
             latin:keyWidth="fillRight" />
     </Row>
-    <include
-        latin:keyboardLayout="@xml/row_symbols_shift4" />
+    <Row
+        latin:keyWidth="9.0%p"
+    >
+        <Key
+            latin:keyStyle="toAlphaKeyStyle"
+            latin:keyWidth="10%p" />
+        <include
+            latin:keyboardLayout="@xml/row_symbols_shift4" />
+    </Row>
 </merge>
diff --git a/java/res/xml-sw600dp/rows_thai.xml b/java/res/xml-sw600dp/rows_thai.xml
index bc89640..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_comma_period" />
+        <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-sw768dp-land/kbd_more_keys_keyboard_template.xml b/java/res/xml-sw768dp-land/kbd_more_keys_keyboard_template.xml
deleted file mode 100644
index f593fa9..0000000
--- a/java/res/xml-sw768dp-land/kbd_more_keys_keyboard_template.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2010, 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:keyWidth="3.5%p"
-    latin:rowHeight="@dimen/popup_key_height"
-    style="?attr/moreKeysKeyboardStyle"
-    >
-</Keyboard>
diff --git a/java/res/xml-sw768dp-land/kbd_number.xml b/java/res/xml-sw768dp-land/kbd_number.xml
deleted file mode 100644
index 1cb775e..0000000
--- a/java/res/xml-sw768dp-land/kbd_number.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2011, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<Keyboard
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyboardLeftPadding="10%p"
-    latin:keyboardRightPadding="10%p"
-    latin:keyWidth="13.250%p"
-    latin:touchPositionCorrectionData="@array/touch_position_correction_data_default"
->
-    <include
-        latin:keyboardLayout="@xml/rows_number" />
-</Keyboard>
diff --git a/java/res/xml-sw768dp-land/kbd_phone.xml b/java/res/xml-sw768dp-land/kbd_phone.xml
deleted file mode 100644
index 8905182..0000000
--- a/java/res/xml-sw768dp-land/kbd_phone.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2011, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<Keyboard
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyboardLeftPadding="10%p"
-    latin:keyboardRightPadding="10%p"
-    latin:keyWidth="13.250%p"
-    latin:touchPositionCorrectionData="@array/touch_position_correction_data_default"
->
-    <include
-        latin:keyboardLayout="@xml/rows_phone" />
-</Keyboard>
diff --git a/java/res/xml-sw768dp-land/kbd_phone_symbols.xml b/java/res/xml-sw768dp-land/kbd_phone_symbols.xml
deleted file mode 100644
index 6038b1f..0000000
--- a/java/res/xml-sw768dp-land/kbd_phone_symbols.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2011, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<Keyboard
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyboardLeftPadding="10%p"
-    latin:keyboardRightPadding="10%p"
-    latin:keyWidth="13.250%p"
-    latin:touchPositionCorrectionData="@array/touch_position_correction_data_default"
->
-    <!-- Tablet doesn't have phone symbols keyboard -->
-    <include
-        latin:keyboardLayout="@xml/rows_phone" />
-</Keyboard>
diff --git a/java/res/xml-sw768dp/kbd_number.xml b/java/res/xml-sw768dp/kbd_number.xml
deleted file mode 100644
index 1b46edd..0000000
--- a/java/res/xml-sw768dp/kbd_number.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2010, 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:keyWidth="13.250%p"
-    latin:touchPositionCorrectionData="@array/touch_position_correction_data_default"
->
-    <include
-        latin:keyboardLayout="@xml/rows_number" />
-</Keyboard>
diff --git a/java/res/xml-sw768dp/kbd_phone.xml b/java/res/xml-sw768dp/kbd_phone.xml
deleted file mode 100644
index 947ede0..0000000
--- a/java/res/xml-sw768dp/kbd_phone.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2010, 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:keyWidth="13.250%p"
-    latin:touchPositionCorrectionData="@array/touch_position_correction_data_default"
->
-    <include
-        latin:keyboardLayout="@xml/rows_phone" />
-</Keyboard>
diff --git a/java/res/xml-sw768dp/kbd_phone_symbols.xml b/java/res/xml-sw768dp/kbd_phone_symbols.xml
deleted file mode 100644
index dd9a6ae..0000000
--- a/java/res/xml-sw768dp/kbd_phone_symbols.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2010, 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:keyWidth="13.250%p"
-    latin:touchPositionCorrectionData="@array/touch_position_correction_data_default"
->
-    <!-- Tablet doesn't have phone symbols keyboard -->
-    <include
-        latin:keyboardLayout="@xml/rows_phone" />
-</Keyboard>
diff --git a/java/res/xml-sw768dp/kbd_thai_symbols_shift.xml b/java/res/xml-sw768dp/kbd_thai_symbols_shift.xml
deleted file mode 100644
index 135222b..0000000
--- a/java/res/xml-sw768dp/kbd_thai_symbols_shift.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<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_thai_symbols_shift" />
-</Keyboard>
diff --git a/java/res/xml-sw768dp/key_shortcut.xml b/java/res/xml-sw768dp/key_shortcut.xml
deleted file mode 100644
index 2d09ebb..0000000
--- a/java/res/xml-sw768dp/key_shortcut.xml
+++ /dev/null
@@ -1,36 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <switch>
-        <case
-            latin:shortcutKeyEnabled="true"
-        >
-            <Key
-                latin:keyStyle="shortcutKeyStyle" />
-        </case>
-        <default>
-            <!-- The empty space instead of shortcut key. -->
-            <Spacer />
-        </default>
-    </switch>
-</merge>
diff --git a/java/res/xml-sw768dp/key_space.xml b/java/res/xml-sw768dp/key_space.xml
deleted file mode 100644
index 58e71d8..0000000
--- a/java/res/xml-sw768dp/key_space.xml
+++ /dev/null
@@ -1,63 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <switch>
-        <case
-            latin:languageCode="fa"
-            latin:languageSwitchKeyEnabled="true"
-        >
-            <Key
-                latin:keyStyle="languageSwitchKeyStyle" />
-            <Key
-                latin:keyStyle="spaceKeyStyle"
-                latin:keyWidth="24.141%p" />
-            <Key
-                latin:keyStyle="zwnjKeyStyle" />
-        </case>
-        <case
-            latin:languageCode="fa"
-            latin:languageSwitchKeyEnabled="false"
-        >
-            <Key
-                latin:keyStyle="spaceKeyStyle"
-                latin:keyWidth="32.188%p" />
-            <Key
-                latin:keyStyle="zwnjKeyStyle" />
-        </case>
-        <case
-            latin:languageSwitchKeyEnabled="true"
-        >
-            <Key
-                latin:keyStyle="languageSwitchKeyStyle" />
-            <Key
-                latin:keyStyle="spaceKeyStyle"
-                latin:keyWidth="32.188%p" />
-        </case>
-         <!-- languageSwitchKeyEnabled="false" -->
-        <default>
-            <Key
-                latin:keyStyle="spaceKeyStyle"
-                latin:keyWidth="40.235%p" />
-        </default>
-    </switch>
-</merge>
diff --git a/java/res/xml-sw768dp/key_styles_common.xml b/java/res/xml-sw768dp/key_styles_common.xml
deleted file mode 100644
index 7c0a82a..0000000
--- a/java/res/xml-sw768dp/key_styles_common.xml
+++ /dev/null
@@ -1,186 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2010, 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|alphabetShiftLockShifted"
-        >
-            <key-style
-                latin:styleName="hasShiftedLetterHintStyle"
-                latin:keyLabelFlags="hasShiftedLetterHint|shiftedLetterActivated" />
-        </case>
-        <default>
-            <key-style
-                latin:styleName="hasShiftedLetterHintStyle"
-                latin:keyLabelFlags="hasShiftedLetterHint" />
-        </default>
-    </switch>
-    <!-- Base style for shift key. A single space is used for dummy label in moreKeys. -->
-    <key-style
-        latin:styleName="baseForShiftKeyStyle"
-        latin:code="!code/key_shift"
-        latin:keyActionFlags="noKeyPreview"
-        latin:keyLabelFlags="preserveCase"
-        latin:moreKeys="!noPanelAutoMoreKey!, |!code/key_capslock" />
-    <switch>
-        <case
-            latin:keyboardLayoutSetElement="alphabetManualShifted|alphabetAutomaticShifted"
-        >
-            <key-style
-                latin:styleName="shiftKeyStyle"
-                latin:keyIcon="!icon/shift_key_shifted"
-                latin:backgroundType="stickyOff"
-                latin:parentStyle="baseForShiftKeyStyle" />
-        </case>
-        <case
-            latin:keyboardLayoutSetElement="alphabetShiftLocked|alphabetShiftLockShifted"
-        >
-            <key-style
-                latin:styleName="shiftKeyStyle"
-                latin:keyIcon="!icon/shift_key_shifted"
-                latin:backgroundType="stickyOn"
-                latin:parentStyle="baseForShiftKeyStyle" />
-        </case>
-        <default>
-            <key-style
-                latin:styleName="shiftKeyStyle"
-                latin:keyIcon="!icon/shift_key"
-                latin:backgroundType="stickyOff"
-                latin:parentStyle="baseForShiftKeyStyle" />
-        </default>
-    </switch>
-    <key-style
-        latin:styleName="deleteKeyStyle"
-        latin:code="!code/key_delete"
-        latin:keyIcon="!icon/delete_key"
-        latin:keyActionFlags="isRepeatable|noKeyPreview"
-        latin:backgroundType="functional" />
-    <include
-        latin:keyboardLayout="@xml/key_styles_enter" />
-    <key-style
-        latin:styleName="spaceKeyStyle"
-        latin:code="!code/key_space"
-        latin:keyActionFlags="noKeyPreview|enableLongPress" />
-    <!-- U+200C: ZERO WIDTH NON-JOINER
-         U+200D: ZERO WIDTH JOINER -->
-    <key-style
-        latin:styleName="zwnjKeyStyle"
-        latin:code="0x200C"
-        latin:keyIcon="!icon/zwnj_key"
-        latin:moreKeys="!icon/zwj_key|&#x200D;"
-        latin:keyLabelFlags="hasPopupHint"
-        latin:keyActionFlags="noKeyPreview" />
-    <key-style
-        latin:styleName="smileyKeyStyle"
-        latin:keyLabel=":-)"
-        latin:keyOutputText=":-) "
-        latin:keyLabelFlags="hasPopupHint|preserveCase"
-        latin:moreKeys="!text/more_keys_for_smiley" />
-    <key-style
-        latin:styleName="shortcutKeyStyle"
-        latin:code="!code/key_shortcut"
-        latin:keyIcon="!icon/shortcut_key"
-        latin:keyIconDisabled="!icon/shortcut_key_disabled"
-        latin:keyActionFlags="noKeyPreview"
-        latin:backgroundType="functional" />
-    <key-style
-        latin:styleName="languageSwitchKeyStyle"
-        latin:code="!code/key_language_switch"
-        latin:keyIcon="!icon/language_switch_key"
-        latin:keyActionFlags="noKeyPreview|altCodeWhileTyping|enableLongPress"
-        latin:altCode="!code/key_space" />
-    <key-style
-        latin:styleName="emojiKeyStyle"
-        latin:code="!code/key_emoji"
-        latin:keyIcon="!icon/emoji_key"
-        latin:keyActionFlags="noKeyPreview" />
-    <key-style
-        latin:styleName="settingsKeyStyle"
-        latin:code="!code/key_settings"
-        latin:keyIcon="!icon/settings_key"
-        latin:keyActionFlags="noKeyPreview"
-        latin:backgroundType="functional" />
-    <switch>
-        <case
-            latin:keyboardLayoutSetElement="alphabetManualShifted|alphabetShiftLockShifted"
-            latin:navigatePrevious="true"
-        >
-            <key-style
-                latin:styleName="tabKeyStyle"
-                latin:code="!code/key_action_previous"
-                latin:keyLabel="!text/label_tab_key"
-                latin:keyLabelFlags="fontNormal|preserveCase"
-                latin:backgroundType="functional" />
-        </case>
-        <case
-            latin:keyboardLayoutSetElement="alphabet|alphabetAutomaticShifted|alphabetShiftLocked"
-            latin:navigateNext="true"
-        >
-            <key-style
-                latin:styleName="tabKeyStyle"
-                latin:code="!code/key_action_next"
-                latin:keyLabel="!text/label_tab_key"
-                latin:keyLabelFlags="fontNormal|preserveCase"
-                latin:backgroundType="functional" />
-        </case>
-        <default>
-            <key-style
-                latin:styleName="tabKeyStyle"
-                latin:code="!code/key_tab"
-                latin:keyLabel="!text/label_tab_key"
-                latin:keyLabelFlags="fontNormal|preserveCase"
-                latin:backgroundType="functional" />
-        </default>
-    </switch>
-    <key-style
-        latin:styleName="baseForLayoutSwitchKeyStyle"
-        latin:keyLabelFlags="fontNormal|preserveCase"
-        latin:keyActionFlags="noKeyPreview"
-        latin:backgroundType="functional" />
-    <key-style
-        latin:styleName="toSymbolKeyStyle"
-        latin:code="!code/key_switch_alpha_symbol"
-        latin:keyLabel="!text/label_to_symbol_key"
-        latin:parentStyle="baseForLayoutSwitchKeyStyle" />
-    <key-style
-        latin:styleName="toAlphaKeyStyle"
-        latin:code="!code/key_switch_alpha_symbol"
-        latin:keyLabel="!text/label_to_alpha_key"
-        latin:parentStyle="baseForLayoutSwitchKeyStyle" />
-    <key-style
-        latin:styleName="toMoreSymbolKeyStyle"
-        latin:code="!code/key_shift"
-        latin:keyLabel="!text/label_to_more_symbol_for_tablet_key"
-        latin:parentStyle="baseForLayoutSwitchKeyStyle" />
-    <key-style
-        latin:styleName="backFromMoreSymbolKeyStyle"
-        latin:code="!code/key_shift"
-        latin:keyLabel="!text/label_to_symbol_key"
-        latin:parentStyle="baseForLayoutSwitchKeyStyle" />
-    <key-style
-        latin:styleName="comKeyStyle"
-        latin:keyLabel="!text/keylabel_for_popular_domain"
-        latin:keyLabelFlags="fontNormal|hasPopupHint|preserveCase"
-        latin:keyOutputText="!text/keylabel_for_popular_domain"
-        latin:moreKeys="!text/more_keys_for_popular_domain" />
-</merge>
diff --git a/java/res/xml-sw768dp/row_dvorak4.xml b/java/res/xml-sw768dp/row_dvorak4.xml
deleted file mode 100644
index 8f9230d..0000000
--- a/java/res/xml-sw768dp/row_dvorak4.xml
+++ /dev/null
@@ -1,51 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <Row
-        latin:keyWidth="8.047%p"
-        latin:backgroundType="functional"
-    >
-        <!-- Note: This Spacer prevents the below key from being marked as a left edge key. -->
-        <Spacer
-            latin:keyWidth="5.782%p" />
-        <include
-            latin:keyboardLayout="@xml/key_settings" />
-        <include
-            latin:keyboardLayout="@xml/key_shortcut" />
-        <include
-            latin:keyboardLayout="@xml/key_f1" />
-        <include
-            latin:keyXPos="29.923%p"
-            latin:keyboardLayout="@xml/key_space"
-            latin:backgroundType="normal" />
-        <include
-            latin:keyboardLayout="@xml/key_question_exclamation" />
-        <include
-            latin:keyboardLayout="@xml/key_dash" />
-        <include
-            latin:keyboardLayout="@xml/key_f2" />
-        <!-- Note: This Spacer prevents the above key from being marked as a right edge key. -->
-        <Spacer
-            latin:keyWidth="fillRight" />
-    </Row>
-</merge>
diff --git a/java/res/xml-sw768dp/row_hebrew4.xml b/java/res/xml-sw768dp/row_hebrew4.xml
deleted file mode 100644
index ae14f02..0000000
--- a/java/res/xml-sw768dp/row_hebrew4.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <Row
-        latin:keyWidth="8.047%p"
-        latin:backgroundType="functional"
-    >
-        <!-- Note: This Spacer prevents the below key from being marked as a left edge key. -->
-        <Spacer
-            latin:keyWidth="5.782%p" />
-        <include
-            latin:keyboardLayout="@xml/key_settings" />
-        <include
-            latin:keyboardLayout="@xml/key_shortcut" />
-        <include
-            latin:keyboardLayout="@xml/key_f1" />
-        <include
-            latin:keyXPos="29.923%p"
-            latin:keyboardLayout="@xml/key_space"
-            latin:backgroundType="normal" />
-        <include
-            latin:keyboardLayout="@xml/keys_comma_period" />
-        <include
-            latin:keyboardLayout="@xml/key_f2" />
-        <!-- Note: This Spacer prevents the above key from being marked as a right edge key. -->
-        <Spacer
-            latin:keyWidth="fillRight" />
-    </Row>
-</merge>
diff --git a/java/res/xml-sw768dp/row_pcqwerty5.xml b/java/res/xml-sw768dp/row_pcqwerty5.xml
deleted file mode 100644
index e27ec87..0000000
--- a/java/res/xml-sw768dp/row_pcqwerty5.xml
+++ /dev/null
@@ -1,59 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <Row
-        latin:keyWidth="8.047%p"
-    >
-        <include
-            latin:keyboardLayout="@xml/key_settings" />
-        <switch>
-            <case
-                latin:keyboardLayoutSetElement="symbols|symbolsShifted"
-            >
-                <Spacer
-                    latin:keyXPos="15.0%p"
-                    latin:keyWidth="10.5%p" />
-            </case>
-            <case
-                latin:mode="email|url"
-            >
-                <Key
-                    latin:keyStyle="comKeyStyle"
-                    latin:keyXPos="15.0%p"
-                    latin:keyWidth="10.5%p" />
-            </case>
-            <default>
-                <Spacer
-                    latin:keyXPos="15.0%p"
-                    latin:keyWidth="10.5%p" />
-            </default>
-        </switch>
-        <Key
-            latin:keyStyle="spaceKeyStyle"
-            latin:keyWidth="49.0%p" />
-        <include
-            latin:keyXPos="-8.047%p"
-            latin:keyWidth="fillRight"
-            latin:keyboardLayout="@xml/key_shortcut" />
-    </Row>
-</merge>
diff --git a/java/res/xml-sw768dp/row_qwerty4.xml b/java/res/xml-sw768dp/row_qwerty4.xml
deleted file mode 100644
index f1f4214..0000000
--- a/java/res/xml-sw768dp/row_qwerty4.xml
+++ /dev/null
@@ -1,51 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2010, 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"
->
-    <Row
-        latin:keyWidth="8.047%p"
-        latin:backgroundType="functional"
-    >
-        <!-- Note: This Spacer prevents the below key from being marked as a left edge key. -->
-        <Spacer
-            latin:keyWidth="5.782%p" />
-        <include
-            latin:keyboardLayout="@xml/key_settings" />
-        <include
-            latin:keyboardLayout="@xml/key_shortcut" />
-        <include
-            latin:keyboardLayout="@xml/key_f1" />
-        <include
-            latin:keyXPos="29.923%p"
-            latin:keyboardLayout="@xml/key_space"
-            latin:backgroundType="normal" />
-        <include
-            latin:keyboardLayout="@xml/key_apostrophe" />
-        <include
-            latin:keyboardLayout="@xml/key_dash" />
-        <include
-            latin:keyboardLayout="@xml/key_f2" />
-        <!-- Note: This Spacer prevents the above key from being marked as a right edge key. -->
-        <Spacer
-            latin:keyWidth="fillRight" />
-    </Row>
-</merge>
diff --git a/java/res/xml-sw768dp/row_symbols4.xml b/java/res/xml-sw768dp/row_symbols4.xml
deleted file mode 100644
index b801a12..0000000
--- a/java/res/xml-sw768dp/row_symbols4.xml
+++ /dev/null
@@ -1,48 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <Row
-        latin:keyWidth="8.047%p"
-        latin:backgroundType="functional"
-    >
-        <!-- Note: This Spacer prevents the below key from being marked as a left edge key. -->
-        <Spacer
-            latin:keyWidth="13.829%p" />
-        <Key
-            latin:keyLabel="/" />
-        <include
-            latin:keyboardLayout="@xml/key_f1" />
-        <include
-            latin:keyXPos="29.923%p"
-            latin:keyboardLayout="@xml/key_space"
-            latin:backgroundType="normal" />
-        <Key
-            latin:keyLabel="&quot;"
-            latin:moreKeys="!text/more_keys_for_tablet_double_quote" />
-        <Key
-            latin:keyLabel="_" />
-        <!-- Note: This Spacer prevents the above key from being marked as a right edge key. -->
-        <Spacer
-            latin:keyWidth="fillRight" />
-    </Row>
-</merge>
diff --git a/java/res/xml-sw768dp/row_symbols_shift4.xml b/java/res/xml-sw768dp/row_symbols_shift4.xml
deleted file mode 100644
index f71864b..0000000
--- a/java/res/xml-sw768dp/row_symbols_shift4.xml
+++ /dev/null
@@ -1,38 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <Row
-        latin:keyWidth="8.047%p"
-        latin:backgroundType="functional"
-    >
-        <!-- Note: This Spacer prevents the below key from being marked as a left edge key. -->
-        <Spacer
-            latin:keyWidth="29.923%p" />
-        <include
-            latin:keyboardLayout="@xml/key_space"
-            latin:backgroundType="normal" />
-        <!-- Note: This Spacer prevents the above key from being marked as a right edge key. -->
-        <Spacer
-            latin:keyWidth="fillRight" />
-    </Row>
-</merge>
diff --git a/java/res/xml-sw768dp/rowkeys_thai_digits.xml b/java/res/xml-sw768dp/rowkeys_thai_digits.xml
deleted file mode 100644
index 55196eb..0000000
--- a/java/res/xml-sw768dp/rowkeys_thai_digits.xml
+++ /dev/null
@@ -1,64 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <!-- U+0E51: "๑" THAI DIGIT ONE -->
-    <Key
-        latin:keyLabel="&#x0E51;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0E52: "๒" THAI DIGIT TWO -->
-    <Key
-        latin:keyLabel="&#x0E52;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0E53: "๓" THAI DIGIT THREE -->
-    <Key
-        latin:keyLabel="&#x0E53;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0E54: "๔" THAI DIGIT FOUR -->
-    <Key
-        latin:keyLabel="&#x0E54;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0E55: "๕" THAI DIGIT FIVE -->
-    <Key
-        latin:keyLabel="&#x0E55;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0E56: "๖" THAI DIGIT SIX -->
-    <Key
-        latin:keyLabel="&#x0E56;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0E57: "๗" THAI DIGIT SEVEN -->
-    <Key
-        latin:keyLabel="&#x0E57;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0E58: "๘" THAI DIGIT EIGHT -->
-    <Key
-        latin:keyLabel="&#x0E58;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0E59: "๙" THAI DIGIT NINE -->
-    <Key
-        latin:keyLabel="&#x0E59;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0E50: "๐" THAI DIGIT ZERO -->
-    <Key
-        latin:keyLabel="&#x0E50;"
-        latin:keyLabelFlags="fontNormal" />
-</merge>
diff --git a/java/res/xml-sw768dp/rows_10_10_7_symbols.xml b/java/res/xml-sw768dp/rows_10_10_7_symbols.xml
deleted file mode 100644
index d9b0d23..0000000
--- a/java/res/xml-sw768dp/rows_10_10_7_symbols.xml
+++ /dev/null
@@ -1,69 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <include
-        latin:keyboardLayout="@xml/key_styles_common" />
-    <include
-        latin:keyboardLayout="@xml/key_styles_currency" />
-    <Row
-        latin:keyWidth="8.282%p"
-    >
-        <Key
-            latin:keyStyle="tabKeyStyle"
-            latin:keyLabelFlags="alignLeft"
-            latin:keyWidth="7.969%p" />
-        <include
-            latin:keyboardLayout="@xml/rowkeys_symbols1" />
-        <Key
-            latin:keyStyle="deleteKeyStyle"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <Row
-        latin:keyWidth="8.125%p"
-    >
-        <Key
-            latin:keyStyle="toAlphaKeyStyle"
-            latin:keyLabelFlags="alignLeft"
-            latin:keyWidth="10.167%p" />
-        <include
-            latin:keyboardLayout="@xml/rowkeys_symbols2" />
-        <Key
-            latin:keyStyle="enterKeyStyle"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <Row
-        latin:keyWidth="8.047%p"
-    >
-        <Key
-            latin:keyStyle="toMoreSymbolKeyStyle"
-            latin:keyWidth="13.829%p" />
-        <include
-            latin:keyboardLayout="@xml/rowkeys_symbols3" />
-        <Key
-            latin:keyStyle="toMoreSymbolKeyStyle"
-            latin:keyXPos="-13.750%p"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <include
-        latin:keyboardLayout="@xml/row_symbols4" />
-</merge>
diff --git a/java/res/xml-sw768dp/rows_10_10_7_symbols_shift.xml b/java/res/xml-sw768dp/rows_10_10_7_symbols_shift.xml
deleted file mode 100644
index a317dbf..0000000
--- a/java/res/xml-sw768dp/rows_10_10_7_symbols_shift.xml
+++ /dev/null
@@ -1,69 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <include
-        latin:keyboardLayout="@xml/key_styles_common" />
-    <include
-        latin:keyboardLayout="@xml/key_styles_currency" />
-    <Row
-        latin:keyWidth="8.282%p"
-    >
-        <Key
-            latin:keyStyle="tabKeyStyle"
-            latin:keyLabelFlags="alignLeft"
-            latin:keyWidth="7.969%p" />
-        <include
-            latin:keyboardLayout="@xml/rowkeys_symbols_shift1" />
-        <Key
-            latin:keyStyle="deleteKeyStyle"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <Row
-        latin:keyWidth="8.125%p"
-    >
-        <Key
-            latin:keyStyle="toAlphaKeyStyle"
-            latin:keyLabelFlags="alignLeft"
-            latin:keyWidth="10.167%p" />
-        <include
-            latin:keyboardLayout="@xml/rowkeys_symbols_shift2" />
-        <Key
-            latin:keyStyle="enterKeyStyle"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <Row
-        latin:keyWidth="8.047%p"
-    >
-        <Key
-            latin:keyStyle="backFromMoreSymbolKeyStyle"
-            latin:keyWidth="13.829%p" />
-        <include
-            latin:keyboardLayout="@xml/rowkeys_symbols_shift3" />
-        <Key
-            latin:keyStyle="backFromMoreSymbolKeyStyle"
-            latin:keyXPos="-13.750%p"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <include
-        latin:keyboardLayout="@xml/row_symbols_shift4" />
-</merge>
diff --git a/java/res/xml-sw768dp/rows_arabic.xml b/java/res/xml-sw768dp/rows_arabic.xml
deleted file mode 100644
index 204f6d5..0000000
--- a/java/res/xml-sw768dp/rows_arabic.xml
+++ /dev/null
@@ -1,61 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2011, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <include
-        latin:keyboardLayout="@xml/key_styles_common" />
-    <Row
-        latin:keyWidth="7.375%p"
-    >
-        <Key
-            latin:keyStyle="tabKeyStyle"
-            latin:keyLabelFlags="alignLeft"
-            latin:keyWidth="7.969%p" />
-        <include
-            latin:keyboardLayout="@xml/rowkeys_arabic1" />
-        <Key
-            latin:keyStyle="deleteKeyStyle"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <Row
-        latin:keyWidth="7.227%p"
-    >
-        <Key
-            latin:keyStyle="toSymbolKeyStyle"
-            latin:keyLabelFlags="alignLeft"
-            latin:keyWidth="11.172%p" />
-        <include
-            latin:keyboardLayout="@xml/rowkeys_arabic2" />
-        <Key
-            latin:keyStyle="enterKeyStyle"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <Row
-        latin:keyWidth="7.227%p"
-    >
-        <include
-            latin:keyboardLayout="@xml/rowkeys_arabic3"
-            latin:keyXPos="6.602%p" />
-    </Row>
-    <include
-        latin:keyboardLayout="@xml/row_qwerty4" />
-</merge>
diff --git a/java/res/xml-sw768dp/rows_azerty.xml b/java/res/xml-sw768dp/rows_azerty.xml
deleted file mode 100644
index cf4bc92..0000000
--- a/java/res/xml-sw768dp/rows_azerty.xml
+++ /dev/null
@@ -1,68 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2010, 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.282%p"
-    >
-        <Key
-            latin:keyStyle="tabKeyStyle"
-            latin:keyLabelFlags="alignLeft"
-            latin:keyWidth="7.969%p" />
-        <include
-            latin:keyboardLayout="@xml/rowkeys_azerty1" />
-        <Key
-            latin:keyStyle="deleteKeyStyle"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <Row
-        latin:keyWidth="8.125%p"
-    >
-        <Key
-            latin:keyStyle="toSymbolKeyStyle"
-            latin:keyLabelFlags="alignLeft"
-            latin:keyWidth="10.167%p" />
-        <include
-            latin:keyboardLayout="@xml/rowkeys_azerty2" />
-        <Key
-            latin:keyStyle="enterKeyStyle"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <Row
-        latin:keyWidth="8.047%p"
-    >
-        <Key
-            latin:keyStyle="shiftKeyStyle"
-            latin:keyWidth="13.829%p" />
-        <include
-            latin:keyboardLayout="@xml/rowkeys_azerty3" />
-        <include
-            latin:keyboardLayout="@xml/keys_comma_period" />
-        <Key
-            latin:keyStyle="shiftKeyStyle"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <include
-        latin:keyboardLayout="@xml/row_qwerty4" />
-</merge>
diff --git a/java/res/xml-sw768dp/rows_bulgarian.xml b/java/res/xml-sw768dp/rows_bulgarian.xml
deleted file mode 100644
index bdc1262..0000000
--- a/java/res/xml-sw768dp/rows_bulgarian.xml
+++ /dev/null
@@ -1,68 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <include
-        latin:keyboardLayout="@xml/key_styles_common" />
-    <Row
-        latin:keyWidth="7.375%p"
-    >
-        <Key
-            latin:keyStyle="tabKeyStyle"
-            latin:keyLabelFlags="alignLeft"
-            latin:keyWidth="7.969%p" />
-        <include
-            latin:keyboardLayout="@xml/rowkeys_bulgarian1" />
-        <Key
-            latin:keyStyle="deleteKeyStyle"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <Row
-        latin:keyWidth="7.227%p"
-    >
-        <Key
-            latin:keyStyle="toSymbolKeyStyle"
-            latin:keyLabelFlags="alignLeft"
-            latin:keyWidth="11.172%p" />
-        <include
-            latin:keyboardLayout="@xml/rowkeys_bulgarian2" />
-        <Key
-            latin:keyStyle="enterKeyStyle"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <Row
-        latin:keyWidth="7.186%p"
-    >
-        <Key
-            latin:keyStyle="shiftKeyStyle"
-            latin:keyWidth="13.829%p" />
-        <include
-            latin:keyboardLayout="@xml/rowkeys_bulgarian3" />
-        <include
-            latin:keyboardLayout="@xml/keys_comma_period" />
-        <Key
-            latin:keyStyle="shiftKeyStyle"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <include
-        latin:keyboardLayout="@xml/row_qwerty4" />
-</merge>
diff --git a/java/res/xml-sw768dp/rows_bulgarian_bds.xml b/java/res/xml-sw768dp/rows_bulgarian_bds.xml
deleted file mode 100644
index 58c4611..0000000
--- a/java/res/xml-sw768dp/rows_bulgarian_bds.xml
+++ /dev/null
@@ -1,68 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <include
-        latin:keyboardLayout="@xml/key_styles_common" />
-    <Row
-        latin:keyWidth="7.375%p"
-    >
-        <Key
-            latin:keyStyle="tabKeyStyle"
-            latin:keyLabelFlags="alignLeft"
-            latin:keyWidth="7.969%p" />
-        <include
-            latin:keyboardLayout="@xml/rowkeys_bulgarian_bds1" />
-        <Key
-            latin:keyStyle="deleteKeyStyle"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <Row
-        latin:keyWidth="7.227%p"
-    >
-        <Key
-            latin:keyStyle="toSymbolKeyStyle"
-            latin:keyLabelFlags="alignLeft"
-            latin:keyWidth="11.172%p" />
-        <include
-            latin:keyboardLayout="@xml/rowkeys_bulgarian_bds2" />
-        <Key
-            latin:keyStyle="enterKeyStyle"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <Row
-        latin:keyWidth="7.000%p"
-    >
-        <Key
-            latin:keyStyle="shiftKeyStyle"
-            latin:keyWidth="13.829%p" />
-        <include
-            latin:keyboardLayout="@xml/rowkeys_bulgarian_bds3" />
-        <include
-            latin:keyboardLayout="@xml/keys_comma_period" />
-        <Key
-            latin:keyStyle="shiftKeyStyle"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <include
-        latin:keyboardLayout="@xml/row_qwerty4" />
-</merge>
diff --git a/java/res/xml-sw768dp/rows_dvorak.xml b/java/res/xml-sw768dp/rows_dvorak.xml
deleted file mode 100644
index 60d5dd6..0000000
--- a/java/res/xml-sw768dp/rows_dvorak.xml
+++ /dev/null
@@ -1,66 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <include
-        latin:keyboardLayout="@xml/key_styles_common" />
-    <Row
-        latin:keyWidth="8.282%p"
-    >
-        <Key
-            latin:keyStyle="tabKeyStyle"
-            latin:keyLabelFlags="alignLeft"
-            latin:keyWidth="7.969%p" />
-        <include
-            latin:keyboardLayout="@xml/rowkeys_dvorak1" />
-        <Key
-            latin:keyStyle="deleteKeyStyle"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <Row
-        latin:keyWidth="8.125%p"
-    >
-        <Key
-            latin:keyStyle="toSymbolKeyStyle"
-            latin:keyLabelFlags="alignLeft"
-            latin:keyWidth="10.167%p" />
-        <include
-            latin:keyboardLayout="@xml/rowkeys_dvorak2" />
-        <Key
-            latin:keyStyle="enterKeyStyle"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <Row
-        latin:keyWidth="8.047%p"
-    >
-        <Key
-            latin:keyStyle="shiftKeyStyle"
-            latin:keyWidth="13.829%p" />
-        <include
-            latin:keyboardLayout="@xml/rowkeys_dvorak3" />
-        <Key
-            latin:keyStyle="shiftKeyStyle"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <include
-        latin:keyboardLayout="@xml/row_dvorak4" />
-</merge>
diff --git a/java/res/xml-sw768dp/rows_east_slavic.xml b/java/res/xml-sw768dp/rows_east_slavic.xml
deleted file mode 100644
index 420307d..0000000
--- a/java/res/xml-sw768dp/rows_east_slavic.xml
+++ /dev/null
@@ -1,70 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2011, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <include
-        latin:keyboardLayout="@xml/key_styles_common" />
-    <Row
-        latin:keyWidth="7.000%p"
-    >
-        <Key
-            latin:keyStyle="tabKeyStyle"
-            latin:keyLabelFlags="alignLeft"
-            latin:keyWidth="7.969%p" />
-        <include
-            latin:keyboardLayout="@xml/rowkeys_east_slavic1" />
-        <Key
-            latin:keyLabel="!text/keylabel_for_east_slavic_row1_12" />
-        <Key
-            latin:keyStyle="deleteKeyStyle"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <Row
-        latin:keyWidth="7.000%p"
-    >
-        <Key
-            latin:keyStyle="toSymbolKeyStyle"
-            latin:keyLabelFlags="alignLeft"
-            latin:keyWidth="11.172%p" />
-        <include
-            latin:keyboardLayout="@xml/rowkeys_east_slavic2" />
-        <Key
-            latin:keyStyle="enterKeyStyle"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <Row
-        latin:keyWidth="7.000%p"
-    >
-        <Key
-            latin:keyStyle="shiftKeyStyle"
-            latin:keyWidth="13.829%p" />
-        <include
-            latin:keyboardLayout="@xml/rowkeys_east_slavic3" />
-        <include
-            latin:keyboardLayout="@xml/keys_comma_period" />
-        <Key
-            latin:keyStyle="shiftKeyStyle"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <include
-        latin:keyboardLayout="@xml/row_qwerty4" />
-</merge>
diff --git a/java/res/xml-sw768dp/rows_farsi.xml b/java/res/xml-sw768dp/rows_farsi.xml
deleted file mode 100644
index 8d3fb05..0000000
--- a/java/res/xml-sw768dp/rows_farsi.xml
+++ /dev/null
@@ -1,61 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <include
-        latin:keyboardLayout="@xml/key_styles_common" />
-    <Row
-        latin:keyWidth="7.375%p"
-    >
-        <Key
-            latin:keyStyle="tabKeyStyle"
-            latin:keyLabelFlags="alignLeft"
-            latin:keyWidth="7.969%p" />
-        <include
-            latin:keyboardLayout="@xml/rowkeys_farsi1" />
-        <Key
-            latin:keyStyle="deleteKeyStyle"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <Row
-        latin:keyWidth="7.227%p"
-    >
-        <Key
-            latin:keyStyle="toSymbolKeyStyle"
-            latin:keyLabelFlags="alignLeft"
-            latin:keyWidth="11.172%p" />
-        <include
-            latin:keyboardLayout="@xml/rowkeys_farsi2" />
-        <Key
-            latin:keyStyle="enterKeyStyle"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <Row
-        latin:keyWidth="7.227%p"
-    >
-        <include
-            latin:keyboardLayout="@xml/rowkeys_farsi3"
-            latin:keyXPos="13.829%p" />
-    </Row>
-    <include
-        latin:keyboardLayout="@xml/row_qwerty4" />
-</merge>
diff --git a/java/res/xml-sw768dp/rows_georgian.xml b/java/res/xml-sw768dp/rows_georgian.xml
deleted file mode 100644
index 3f8bd45..0000000
--- a/java/res/xml-sw768dp/rows_georgian.xml
+++ /dev/null
@@ -1,68 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <include
-        latin:keyboardLayout="@xml/key_styles_common" />
-    <Row
-        latin:keyWidth="8.282%p"
-    >
-        <Key
-            latin:keyStyle="tabKeyStyle"
-            latin:keyLabelFlags="alignLeft"
-            latin:keyWidth="7.969%p" />
-        <include
-            latin:keyboardLayout="@xml/rowkeys_georgian1" />
-        <Key
-            latin:keyStyle="deleteKeyStyle"
-            latin:keyWidth="fillRight"/>
-    </Row>
-    <Row
-        latin:keyWidth="8.125%p"
-    >
-        <Key
-            latin:keyStyle="toSymbolKeyStyle"
-            latin:keyLabelFlags="alignLeft"
-            latin:keyWidth="11.172%p"/>
-        <include
-            latin:keyboardLayout="@xml/rowkeys_georgian2" />
-        <Key
-            latin:keyStyle="enterKeyStyle"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <Row
-        latin:keyWidth="8.047%p"
-    >
-        <Key
-            latin:keyStyle="shiftKeyStyle"
-            latin:keyWidth="13.829%p"/>
-        <include
-            latin:keyboardLayout="@xml/rowkeys_georgian3" />
-        <include
-            latin:keyboardLayout="@xml/keys_comma_period" />
-        <Key
-            latin:keyStyle="shiftKeyStyle"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <include
-        latin:keyboardLayout="@xml/row_qwerty4" />
-</merge>
diff --git a/java/res/xml-sw768dp/rows_greek.xml b/java/res/xml-sw768dp/rows_greek.xml
deleted file mode 100644
index 9e1e00b..0000000
--- a/java/res/xml-sw768dp/rows_greek.xml
+++ /dev/null
@@ -1,70 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <include
-        latin:keyboardLayout="@xml/key_styles_common" />
-    <Row
-        latin:keyWidth="8.282%p"
-    >
-        <Key
-            latin:keyStyle="tabKeyStyle"
-            latin:keyLabelFlags="alignLeft"
-            latin:keyWidth="7.969%p" />
-        <include
-            latin:keyboardLayout="@xml/key_greek_semicolon" />
-        <include
-            latin:keyboardLayout="@xml/rowkeys_greek1" />
-        <Key
-            latin:keyStyle="deleteKeyStyle"
-            latin:keyWidth="fillRight"/>
-    </Row>
-    <Row
-        latin:keyWidth="8.125%p"
-    >
-        <Key
-            latin:keyStyle="toSymbolKeyStyle"
-            latin:keyLabelFlags="alignLeft"
-            latin:keyWidth="11.172%p"/>
-        <include
-            latin:keyboardLayout="@xml/rowkeys_greek2" />
-        <Key
-            latin:keyStyle="enterKeyStyle"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <Row
-        latin:keyWidth="8.047%p"
-    >
-        <Key
-            latin:keyStyle="shiftKeyStyle"
-            latin:keyWidth="13.829%p" />
-        <include
-            latin:keyboardLayout="@xml/rowkeys_greek3" />
-        <include
-            latin:keyboardLayout="@xml/keys_comma_period" />
-        <Key
-            latin:keyStyle="shiftKeyStyle"
-            latin:keyWidth="fillRight" />
-    </Row>
-   <include
-        latin:keyboardLayout="@xml/row_qwerty4" />
-</merge>
diff --git a/java/res/xml-sw768dp/rows_hebrew.xml b/java/res/xml-sw768dp/rows_hebrew.xml
deleted file mode 100644
index a5f6dfe..0000000
--- a/java/res/xml-sw768dp/rows_hebrew.xml
+++ /dev/null
@@ -1,61 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2011, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <include
-        latin:keyboardLayout="@xml/key_styles_common" />
-    <Row
-        latin:keyWidth="8.282%p"
-    >
-        <Key
-            latin:keyStyle="tabKeyStyle"
-            latin:keyLabelFlags="alignLeft"
-            latin:keyWidth="7.969%p" />
-        <include
-            latin:keyboardLayout="@xml/rowkeys_hebrew1" />
-        <Key
-            latin:keyStyle="deleteKeyStyle"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <Row
-        latin:keyWidth="8.125%p"
-    >
-        <Key
-            latin:keyStyle="toSymbolKeyStyle"
-            latin:keyLabelFlags="alignLeft"
-            latin:keyWidth="10.167%p" />
-        <include
-            latin:keyboardLayout="@xml/rowkeys_hebrew2" />
-        <Key
-            latin:keyStyle="enterKeyStyle"
-            latin:keyWidth="fillRight" />
-        </Row>
-    <Row
-        latin:keyWidth="8.047%p"
-    >
-        <include
-            latin:keyboardLayout="@xml/rowkeys_hebrew3"
-            latin:keyXPos="13.829%p" />
-    </Row>
-    <include
-        latin:keyboardLayout="@xml/row_hebrew4" />
-</merge>
diff --git a/java/res/xml-sw768dp/rows_hindi.xml b/java/res/xml-sw768dp/rows_hindi.xml
deleted file mode 100644
index 6baf09e..0000000
--- a/java/res/xml-sw768dp/rows_hindi.xml
+++ /dev/null
@@ -1,68 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <include
-        latin:keyboardLayout="@xml/key_styles_common" />
-    <Row
-        latin:keyWidth="7.375%p"
-    >
-        <Key
-            latin:keyStyle="tabKeyStyle"
-            latin:keyLabelFlags="alignLeft"
-            latin:keyWidth="7.969%p" />
-        <include
-            latin:keyboardLayout="@xml/rowkeys_hindi1" />
-        <Key
-            latin:keyStyle="deleteKeyStyle"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <Row
-        latin:keyWidth="7.227%p"
-    >
-        <Key
-            latin:keyStyle="toSymbolKeyStyle"
-            latin:keyLabelFlags="alignLeft"
-            latin:keyWidth="11.172%p" />
-        <include
-            latin:keyboardLayout="@xml/rowkeys_hindi2" />
-        <Key
-            latin:keyStyle="enterKeyStyle"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <Row
-        latin:keyWidth="7.000%p"
-    >
-        <Key
-            latin:keyStyle="shiftKeyStyle"
-            latin:keyWidth="13.829%p" />
-        <include
-            latin:keyboardLayout="@xml/rowkeys_hindi3" />
-        <include
-            latin:keyboardLayout="@xml/keys_comma_period" />
-        <Key
-            latin:keyStyle="shiftKeyStyle"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <include
-        latin:keyboardLayout="@xml/row_qwerty4" />
-</merge>
diff --git a/java/res/xml-sw768dp/rows_mongolian.xml b/java/res/xml-sw768dp/rows_mongolian.xml
deleted file mode 100644
index 5f37f87..0000000
--- a/java/res/xml-sw768dp/rows_mongolian.xml
+++ /dev/null
@@ -1,68 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <include
-        latin:keyboardLayout="@xml/key_styles_common" />
-    <Row
-        latin:keyWidth="7.375%p"
-    >
-        <Key
-            latin:keyStyle="tabKeyStyle"
-            latin:keyLabelFlags="alignLeft"
-            latin:keyWidth="7.969%p" />
-        <include
-            latin:keyboardLayout="@xml/rowkeys_mongolian1" />
-        <Key
-            latin:keyStyle="deleteKeyStyle"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <Row
-        latin:keyWidth="7.227%p"
-    >
-        <Key
-            latin:keyStyle="toSymbolKeyStyle"
-            latin:keyLabelFlags="alignLeft"
-            latin:keyWidth="11.172%p" />
-        <include
-            latin:keyboardLayout="@xml/rowkeys_mongolian2" />
-        <Key
-            latin:keyStyle="enterKeyStyle"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <Row
-        latin:keyWidth="7.000%p"
-    >
-        <Key
-            latin:keyStyle="shiftKeyStyle"
-            latin:keyWidth="13.829%p" />
-        <include
-            latin:keyboardLayout="@xml/rowkeys_mongolian3" />
-        <include
-            latin:keyboardLayout="@xml/keys_comma_period" />
-        <Key
-            latin:keyStyle="shiftKeyStyle"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <include
-        latin:keyboardLayout="@xml/row_qwerty4" />
-</merge>
diff --git a/java/res/xml-sw768dp/rows_nordic.xml b/java/res/xml-sw768dp/rows_nordic.xml
deleted file mode 100644
index 13d9399..0000000
--- a/java/res/xml-sw768dp/rows_nordic.xml
+++ /dev/null
@@ -1,71 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2010, 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.375%p"
-    >
-        <Key
-            latin:keyStyle="tabKeyStyle"
-            latin:keyLabelFlags="alignLeft"
-            latin:keyWidth="7.969%p" />
-        <include
-            latin:keyboardLayout="@xml/rowkeys_nordic1" />
-        <Key
-            latin:keyStyle="deleteKeyStyle"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <Row
-        latin:keyWidth="7.227%p"
-    >
-        <Key
-            latin:keyStyle="toSymbolKeyStyle"
-            latin:keyLabelFlags="alignLeft"
-            latin:keyWidth="11.172%p" />
-        <include
-            latin:keyboardLayout="@xml/rowkeys_nordic2" />
-        <Key
-            latin:keyStyle="enterKeyStyle"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <Row
-        latin:keyWidth="7.227%p"
-    >
-        <Key
-            latin:keyStyle="shiftKeyStyle"
-            latin:keyWidth="13.829%p" />
-        <Spacer
-            latin:keyWidth="3.689%p" />
-        <include
-            latin:keyboardLayout="@xml/rowkeys_qwerty3" />
-        <include
-            latin:keyboardLayout="@xml/keys_comma_period" />
-        <Key
-            latin:keyStyle="shiftKeyStyle"
-            latin:keyXPos="-13.750%p"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <include
-        latin:keyboardLayout="@xml/row_qwerty4" />
-</merge>
diff --git a/java/res/xml-sw768dp/rows_number_normal.xml b/java/res/xml-sw768dp/rows_number_normal.xml
deleted file mode 100644
index de49aba..0000000
--- a/java/res/xml-sw768dp/rows_number_normal.xml
+++ /dev/null
@@ -1,175 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <Row>
-        <Key
-            latin:keyStyle="tabKeyStyle"
-            latin:keyLabelFlags="alignLeft"
-            latin:keyWidth="11.172%p" />
-        <Key
-            latin:keyLabel="-"
-            latin:keyStyle="numKeyStyle"
-            latin:keyXPos="13.829%p"
-            latin:keyWidth="8.047%p"
-            latin:backgroundType="functional" />
-        <Key
-            latin:keyLabel="+"
-            latin:keyStyle="numKeyStyle"
-            latin:keyWidth="8.047%p"
-            latin:backgroundType="functional" />
-        <Key
-            latin:keyLabel="."
-            latin:keyStyle="numKeyStyle"
-            latin:keyWidth="8.047%p"
-            latin:backgroundType="functional" />
-        <Key
-            latin:keyLabel="1"
-            latin:keyStyle="numKeyStyle"
-            latin:keyXPos="43.125%p" />
-        <Key
-            latin:keyLabel="2"
-            latin:keyStyle="numKeyStyle" />
-        <Key
-            latin:keyLabel="3"
-            latin:keyStyle="numKeyStyle" />
-        <Key
-            latin:keyStyle="deleteKeyStyle"
-            latin:keyXPos="-11.172%p"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <Row>
-        <!-- Note: This Spacer prevents the below key from being marked as a left edge key. -->
-        <Spacer
-            latin:keyWidth="13.829%p" />
-        <Key
-            latin:keyStyle="numStarKeyStyle"
-            latin:keyWidth="8.047%p"
-            latin:backgroundType="functional" />
-        <Key
-            latin:keyLabel="/"
-            latin:keyStyle="numKeyStyle"
-            latin:keyWidth="8.047%p"
-            latin:backgroundType="functional" />
-        <switch>
-            <case
-                latin:mode="time|datetime"
-            >
-                <Key
-                    latin:keyLabel=","
-                    latin:keyLabelFlags="hasPopupHint"
-                    latin:moreKeys="!text/more_keys_for_am_pm"
-                    latin:keyStyle="numKeyStyle"
-                    latin:keyWidth="8.047%p"
-                    latin:backgroundType="functional" />
-            </case>
-            <default>
-                <Key
-                    latin:keyLabel=","
-                    latin:keyStyle="numKeyStyle"
-                    latin:keyWidth="8.047%p"
-                    latin:backgroundType="functional" />
-            </default>
-        </switch>
-        <Key
-            latin:keyLabel="4"
-            latin:keyStyle="numKeyStyle"
-            latin:keyXPos="43.125%p" />
-        <Key
-            latin:keyLabel="5"
-            latin:keyStyle="numKeyStyle" />
-        <Key
-            latin:keyLabel="6"
-            latin:keyStyle="numKeyStyle" />
-        <Key
-            latin:keyStyle="enterKeyStyle"
-            latin:keyXPos="-11.172%p"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <Row>
-        <!-- Note: This Spacer prevents the below key from being marked as a left edge key. -->
-        <Spacer
-            latin:keyWidth="13.829%p" />
-        <Key
-            latin:keyLabel="("
-            latin:keyStyle="numKeyStyle"
-            latin:keyWidth="8.047%p"
-            latin:backgroundType="functional" />
-        <Key
-            latin:keyLabel=")"
-            latin:keyStyle="numKeyStyle"
-            latin:keyWidth="8.047%p"
-            latin:backgroundType="functional" />
-        <switch>
-            <case
-                latin:mode="time|datetime"
-            >
-                <Key
-                    latin:keyLabel=":"
-                    latin:keyStyle="numKeyStyle"
-                    latin:keyWidth="8.047%p"
-                    latin:backgroundType="functional" />
-            </case>
-            <default>
-                <Key
-                    latin:keyLabel="="
-                    latin:keyStyle="numKeyStyle"
-                    latin:keyWidth="8.047%p"
-                    latin:backgroundType="functional" />
-            </default>
-        </switch>
-        <Key
-            latin:keyLabel="7"
-            latin:keyStyle="numKeyStyle"
-            latin:keyXPos="43.125%p" />
-        <Key
-            latin:keyLabel="8"
-            latin:keyStyle="numKeyStyle" />
-        <Key
-            latin:keyLabel="9"
-            latin:keyStyle="numKeyStyle" />
-        <!-- Note: This Spacer prevents the above key from being marked as a right edge key. -->
-        <Spacer />
-    </Row>
-    <Row>
-        <include
-            latin:keyboardLayout="@xml/key_settings"
-            latin:keyWidth="8.047%p" />
-        <Key
-            latin:keyStyle="spaceKeyStyle"
-            latin:keyXPos="13.829%p"
-            latin:keyWidth="24.140%p"
-            latin:backgroundType="functional" />
-        <Key
-            latin:keyStyle="numStarKeyStyle"
-            latin:keyXPos="43.125%p" />
-        <Key
-            latin:keyLabel="0"
-            latin:keyStyle="numKeyStyle" />
-        <Key
-            latin:keyLabel="\#"
-            latin:keyStyle="numKeyStyle" />
-        <!-- Note: This Spacer prevents the above key from being marked as a right edge key. -->
-        <Spacer
-            latin:keyWidth="fillRight" />
-    </Row>
-</merge>
diff --git a/java/res/xml-sw768dp/rows_number_password.xml b/java/res/xml-sw768dp/rows_number_password.xml
deleted file mode 100644
index cfa2214..0000000
--- a/java/res/xml-sw768dp/rows_number_password.xml
+++ /dev/null
@@ -1,79 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <Row>
-        <Key
-            latin:keyStyle="numTabKeyStyle"
-            latin:keyLabelFlags="alignLeft"
-            latin:keyWidth="11.172%p" />
-        <Key
-            latin:keyStyle="num1KeyStyle"
-            latin:keyXPos="32.076%p" />
-        <Key
-            latin:keyStyle="num2KeyStyle" />
-        <Key
-            latin:keyStyle="num3KeyStyle" />
-        <Key
-            latin:keyStyle="deleteKeyStyle"
-            latin:keyXPos="-11.172%p"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <Row>
-        <!-- Note: This Spacer prevents the below key from being marked as a left edge key. -->
-        <Spacer
-            latin:keyWidth="32.076%p" />
-        <Key
-            latin:keyStyle="num4KeyStyle" />
-        <Key
-            latin:keyStyle="num5KeyStyle" />
-        <Key
-            latin:keyStyle="num6KeyStyle" />
-        <Key
-            latin:keyStyle="enterKeyStyle"
-            latin:keyXPos="-11.172%p"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <Row>
-        <!-- Note: This Spacer prevents the below key from being marked as a left edge key. -->
-        <Spacer
-            latin:keyWidth="32.076%p" />
-        <Key
-            latin:keyStyle="num7KeyStyle" />
-        <Key
-            latin:keyStyle="num8KeyStyle" />
-        <Key
-            latin:keyStyle="num9KeyStyle" />
-        <!-- Note: This Spacer prevents the above key from being marked as a right edge key. -->
-        <Spacer />
-    </Row>
-    <Row>
-        <include
-            latin:keyboardLayout="@xml/key_settings"
-            latin:keyWidth="8.047%p" />
-        <Key
-            latin:keyXPos="45.326%p"
-            latin:keyStyle="num0KeyStyle" />
-        <!-- Note: This Spacer prevents the above key from being marked as a right edge key. -->
-        <Spacer />
-    </Row>
-</merge>
diff --git a/java/res/xml-sw768dp/rows_pcqwerty.xml b/java/res/xml-sw768dp/rows_pcqwerty.xml
deleted file mode 100644
index a844728..0000000
--- a/java/res/xml-sw768dp/rows_pcqwerty.xml
+++ /dev/null
@@ -1,71 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <include
-        latin:keyboardLayout="@xml/key_styles_common" />
-    <Row
-        latin:keyWidth="7.0%p"
-    >
-        <include
-            latin:keyboardLayout="@xml/rowkeys_pcqwerty1" />
-        <Key
-            latin:keyStyle="deleteKeyStyle"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <Row
-        latin:keyWidth="7.0%p"
-    >
-        <Key
-            latin:keyStyle="tabKeyStyle"
-            latin:keyWidth="9.0%p" />
-        <include
-            latin:keyboardLayout="@xml/rowkeys_pcqwerty2" />
-    </Row>
-    <Row
-        latin:keyWidth="7.0%p"
-    >
-        <Key
-            latin:keyStyle="toSymbolKeyStyle"
-            latin:keyLabel="!text/label_to_symbol_key_pcqwerty"
-            latin:keyWidth="12.0%p" />
-        <include
-            latin:keyboardLayout="@xml/rowkeys_pcqwerty3" />
-        <Key
-            latin:keyStyle="enterKeyStyle"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <Row
-        latin:keyWidth="7.0%p"
-    >
-        <Key
-            latin:keyStyle="shiftKeyStyle"
-            latin:keyWidth="15.0%p" />
-        <include
-            latin:keyboardLayout="@xml/rowkeys_pcqwerty4" />
-        <Key
-            latin:keyStyle="shiftKeyStyle"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <include
-        latin:keyboardLayout="@xml/row_pcqwerty5" />
-</merge>
diff --git a/java/res/xml-sw768dp/rows_pcqwerty_symbols.xml b/java/res/xml-sw768dp/rows_pcqwerty_symbols.xml
deleted file mode 100644
index 956da97..0000000
--- a/java/res/xml-sw768dp/rows_pcqwerty_symbols.xml
+++ /dev/null
@@ -1,67 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2011, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <include
-        latin:keyboardLayout="@xml/key_styles_common" />
-    <include
-        latin:keyboardLayout="@xml/key_styles_currency" />
-    <Row
-        latin:keyWidth="7.0%p"
-    >
-        <include
-            latin:keyboardLayout="@xml/rowkeys_pcqwerty1" />
-        <Key
-            latin:keyStyle="deleteKeyStyle"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <Row
-        latin:keyWidth="7.0%p"
-    >
-        <Key
-            latin:keyStyle="tabKeyStyle"
-            latin:keyWidth="9.0%p" />
-        <include
-            latin:keyboardLayout="@xml/rowkeys_pcqwerty2" />
-    </Row>
-    <Row
-        latin:keyWidth="7.0%p"
-    >
-        <Key
-            latin:keyStyle="toAlphaKeyStyle"
-            latin:keyWidth="12.0%p" />
-        <include
-            latin:keyboardLayout="@xml/rowkeys_pcqwerty3" />
-        <Key
-            latin:keyStyle="enterKeyStyle"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <Row
-        latin:keyWidth="7.0%p"
-    >
-        <include
-            latin:keyboardLayout="@xml/rowkeys_pcqwerty4"
-            latin:keyXPos="15.0%p" />
-    </Row>
-    <include
-        latin:keyboardLayout="@xml/row_pcqwerty5" />
-</merge>
diff --git a/java/res/xml-sw768dp/rows_phone.xml b/java/res/xml-sw768dp/rows_phone.xml
deleted file mode 100644
index d06a63b..0000000
--- a/java/res/xml-sw768dp/rows_phone.xml
+++ /dev/null
@@ -1,138 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2011, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <include
-        latin:keyboardLayout="@xml/key_styles_common" />
-    <include
-        latin:keyboardLayout="@xml/key_styles_number" />
-    <Row>
-        <Key
-            latin:keyStyle="numTabKeyStyle"
-            latin:keyLabelFlags="alignLeft"
-            latin:keyWidth="11.172%p" />
-        <Key
-            latin:keyLabel="-"
-            latin:keyStyle="numKeyStyle"
-            latin:keyXPos="13.829%p"
-            latin:keyWidth="8.047%p"
-            latin:backgroundType="functional" />
-        <Key
-            latin:keyLabel="+"
-            latin:keyStyle="numKeyStyle"
-            latin:keyWidth="8.047%p"
-            latin:backgroundType="functional" />
-        <Key
-            latin:keyStyle="numPauseKeyStyle"
-            latin:keyWidth="8.047%p"
-            latin:backgroundType="functional" />
-        <Key
-            latin:keyStyle="num1KeyStyle"
-            latin:keyXPos="43.125%p" />
-        <Key
-            latin:keyStyle="num2KeyStyle" />
-        <Key
-            latin:keyStyle="num3KeyStyle" />
-        <Key
-            latin:keyStyle="deleteKeyStyle"
-            latin:keyXPos="-11.172%p"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <Row>
-        <!-- Note: This Spacer prevents the below key from being marked as a left edge key. -->
-        <Spacer
-            latin:keyWidth="13.829%p" />
-        <Key
-            latin:keyLabel=","
-            latin:keyStyle="numKeyStyle"
-            latin:keyXPos="13.829%p"
-            latin:keyWidth="8.047%p"
-            latin:backgroundType="functional" />
-        <Key
-            latin:keyLabel="."
-            latin:keyStyle="numKeyStyle"
-            latin:keyWidth="8.047%p"
-            latin:backgroundType="functional" />
-        <Key
-            latin:keyStyle="numWaitKeyStyle"
-            latin:keyWidth="8.047%p"
-            latin:backgroundType="functional" />
-        <Key
-            latin:keyStyle="num4KeyStyle"
-            latin:keyXPos="43.125%p" />
-        <Key
-            latin:keyStyle="num5KeyStyle" />
-        <Key
-            latin:keyStyle="num6KeyStyle" />
-        <Key
-            latin:keyStyle="enterKeyStyle"
-            latin:keyXPos="-11.172%p"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <Row>
-        <!-- Note: This Spacer prevents the below key from being marked as a left edge key. -->
-        <Spacer
-            latin:keyWidth="13.829%p" />
-        <Key
-            latin:keyLabel="("
-            latin:keyStyle="numKeyStyle"
-            latin:keyWidth="8.047%p"
-            latin:backgroundType="functional" />
-        <Key
-            latin:keyLabel=")"
-            latin:keyStyle="numKeyStyle"
-            latin:keyWidth="8.047%p"
-            latin:backgroundType="functional" />
-        <Key
-            latin:keyLabel="N"
-            latin:keyStyle="numKeyStyle"
-            latin:keyWidth="8.047%p"
-            latin:backgroundType="functional" />
-        <Key
-            latin:keyStyle="num7KeyStyle"
-            latin:keyXPos="43.125%p" />
-        <Key
-            latin:keyStyle="num8KeyStyle" />
-        <Key
-            latin:keyStyle="num9KeyStyle" />
-        <!-- Note: This Spacer prevents the above key from being marked as a right edge key. -->
-        <Spacer />
-    </Row>
-    <Row>
-        <include
-            latin:keyboardLayout="@xml/key_settings"
-            latin:keyWidth="8.047%p" />
-        <Key
-            latin:keyStyle="spaceKeyStyle"
-            latin:keyXPos="13.829%p"
-            latin:keyWidth="24.140%p"
-            latin:backgroundType="functional" />
-        <Key
-            latin:keyStyle="numStarKeyStyle"
-            latin:keyXPos="43.125%p" />
-        <Key
-            latin:keyStyle="num0KeyStyle" />
-        <Key
-            latin:keyLabel="\#"
-            latin:keyStyle="numKeyStyle" />
-   </Row>
-</merge>
diff --git a/java/res/xml-sw768dp/rows_qwerty.xml b/java/res/xml-sw768dp/rows_qwerty.xml
deleted file mode 100644
index 8af18ed..0000000
--- a/java/res/xml-sw768dp/rows_qwerty.xml
+++ /dev/null
@@ -1,68 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2010, 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.282%p"
-    >
-        <Key
-            latin:keyStyle="tabKeyStyle"
-            latin:keyLabelFlags="alignLeft"
-            latin:keyWidth="7.969%p" />
-        <include
-            latin:keyboardLayout="@xml/rowkeys_qwerty1" />
-        <Key
-            latin:keyStyle="deleteKeyStyle"
-            latin:keyWidth="fillRight"/>
-    </Row>
-    <Row
-        latin:keyWidth="8.125%p"
-    >
-        <Key
-            latin:keyStyle="toSymbolKeyStyle"
-            latin:keyLabelFlags="alignLeft"
-            latin:keyWidth="11.172%p"/>
-        <include
-            latin:keyboardLayout="@xml/rowkeys_qwerty2" />
-        <Key
-            latin:keyStyle="enterKeyStyle"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <Row
-        latin:keyWidth="8.047%p"
-    >
-        <Key
-            latin:keyStyle="shiftKeyStyle"
-            latin:keyWidth="13.829%p"/>
-        <include
-            latin:keyboardLayout="@xml/rowkeys_qwerty3" />
-        <include
-            latin:keyboardLayout="@xml/keys_comma_period" />
-        <Key
-            latin:keyStyle="shiftKeyStyle"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <include
-        latin:keyboardLayout="@xml/row_qwerty4" />
-</merge>
diff --git a/java/res/xml-sw768dp/rows_qwertz.xml b/java/res/xml-sw768dp/rows_qwertz.xml
deleted file mode 100644
index 0dd206d..0000000
--- a/java/res/xml-sw768dp/rows_qwertz.xml
+++ /dev/null
@@ -1,68 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2010, 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.282%p"
-    >
-        <Key
-            latin:keyStyle="tabKeyStyle"
-            latin:keyLabelFlags="alignLeft"
-            latin:keyWidth="7.969%p" />
-        <include
-            latin:keyboardLayout="@xml/rowkeys_qwertz1" />
-        <Key
-            latin:keyStyle="deleteKeyStyle"
-            latin:keyWidth="fillRight"/>
-    </Row>
-    <Row
-        latin:keyWidth="8.125%p"
-    >
-        <Key
-            latin:keyStyle="toSymbolKeyStyle"
-            latin:keyLabelFlags="alignLeft"
-            latin:keyWidth="11.172%p"/>
-        <include
-            latin:keyboardLayout="@xml/rowkeys_qwerty2" />
-        <Key
-            latin:keyStyle="enterKeyStyle"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <Row
-        latin:keyWidth="8.047%p"
-    >
-        <Key
-            latin:keyStyle="shiftKeyStyle"
-            latin:keyWidth="13.829%p"/>
-        <include
-            latin:keyboardLayout="@xml/rowkeys_qwertz3" />
-        <include
-            latin:keyboardLayout="@xml/keys_comma_period" />
-        <Key
-            latin:keyStyle="shiftKeyStyle"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <include
-        latin:keyboardLayout="@xml/row_qwerty4" />
-</merge>
diff --git a/java/res/xml-sw768dp/rows_south_slavic.xml b/java/res/xml-sw768dp/rows_south_slavic.xml
deleted file mode 100644
index 6b44c4e..0000000
--- a/java/res/xml-sw768dp/rows_south_slavic.xml
+++ /dev/null
@@ -1,68 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2011, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <include
-        latin:keyboardLayout="@xml/key_styles_common" />
-    <Row
-        latin:keyWidth="7.375%p"
-    >
-        <Key
-            latin:keyStyle="tabKeyStyle"
-            latin:keyLabelFlags="alignLeft"
-            latin:keyWidth="7.969%p" />
-        <include
-            latin:keyboardLayout="@xml/rowkeys_south_slavic1" />
-        <Key
-            latin:keyStyle="deleteKeyStyle"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <Row
-        latin:keyWidth="7.227%p"
-    >
-        <Key
-            latin:keyStyle="toSymbolKeyStyle"
-            latin:keyLabelFlags="alignLeft"
-            latin:keyWidth="11.172%p" />
-        <include
-            latin:keyboardLayout="@xml/rowkeys_south_slavic2" />
-        <Key
-            latin:keyStyle="enterKeyStyle"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <Row
-        latin:keyWidth="7.000%p"
-    >
-        <Key
-            latin:keyStyle="shiftKeyStyle"
-            latin:keyWidth="13.829%p" />
-        <include
-            latin:keyboardLayout="@xml/rowkeys_south_slavic3" />
-        <include
-            latin:keyboardLayout="@xml/keys_comma_period" />
-        <Key
-            latin:keyStyle="shiftKeyStyle"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <include
-        latin:keyboardLayout="@xml/row_qwerty4" />
-</merge>
diff --git a/java/res/xml-sw768dp/rows_spanish.xml b/java/res/xml-sw768dp/rows_spanish.xml
deleted file mode 100644
index 4520c10..0000000
--- a/java/res/xml-sw768dp/rows_spanish.xml
+++ /dev/null
@@ -1,69 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2011, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <include
-        latin:keyboardLayout="@xml/key_styles_common" />
-    <Row
-        latin:keyWidth="8.282%p"
-    >
-        <Key
-            latin:keyStyle="tabKeyStyle"
-            latin:keyLabelFlags="alignLeft"
-            latin:keyWidth="7.969%p" />
-        <include
-            latin:keyboardLayout="@xml/rowkeys_qwerty1" />
-        <Key
-            latin:keyStyle="deleteKeyStyle"
-            latin:keyWidth="fillRight"/>
-    </Row>
-    <Row
-        latin:keyWidth="8.125%p"
-    >
-        <Key
-            latin:keyStyle="toSymbolKeyStyle"
-            latin:keyLabelFlags="alignLeft"
-            latin:keyWidth="10.167%p" />
-        <include
-            latin:keyboardLayout="@xml/rowkeys_spanish2" />
-        <Key
-            latin:keyStyle="enterKeyStyle"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <Row
-        latin:keyWidth="8.047%p"
-    >
-        <Key
-            latin:keyStyle="shiftKeyStyle"
-            latin:keyWidth="13.829%p"/>
-        <include
-            latin:keyboardLayout="@xml/rowkeys_qwerty3" />
-        <include
-            latin:keyboardLayout="@xml/keys_comma_period" />
-        <Key
-            latin:keyStyle="shiftKeyStyle"
-            latin:keyXPos="-13.750%p"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <include
-        latin:keyboardLayout="@xml/row_qwerty4" />
-</merge>
diff --git a/java/res/xml-sw768dp/rows_symbols.xml b/java/res/xml-sw768dp/rows_symbols.xml
deleted file mode 100644
index efd7735..0000000
--- a/java/res/xml-sw768dp/rows_symbols.xml
+++ /dev/null
@@ -1,68 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2011, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <include
-        latin:keyboardLayout="@xml/key_styles_common" />
-    <include
-        latin:keyboardLayout="@xml/key_styles_currency" />
-    <Row
-        latin:keyWidth="8.282%p"
-    >
-        <Key
-            latin:keyStyle="tabKeyStyle"
-            latin:keyLabelFlags="alignLeft"
-            latin:keyWidth="7.969%p" />
-        <include
-            latin:keyboardLayout="@xml/rowkeys_symbols1" />
-        <Key
-            latin:keyStyle="deleteKeyStyle"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <Row
-        latin:keyWidth="8.125%p"
-    >
-        <Key
-            latin:keyStyle="toAlphaKeyStyle"
-            latin:keyLabelFlags="alignLeft"
-            latin:keyWidth="11.172%p" />
-        <include
-            latin:keyboardLayout="@xml/rowkeys_symbols2" />
-        <Key
-            latin:keyStyle="enterKeyStyle"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <Row
-        latin:keyWidth="8.047%p"
-    >
-        <Key
-            latin:keyStyle="toMoreSymbolKeyStyle"
-            latin:keyWidth="13.829%p" />
-        <include
-            latin:keyboardLayout="@xml/rowkeys_symbols3" />
-        <Key
-            latin:keyStyle="toMoreSymbolKeyStyle"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <include
-        latin:keyboardLayout="@xml/row_symbols4" />
-</merge>
diff --git a/java/res/xml-sw768dp/rows_symbols_shift.xml b/java/res/xml-sw768dp/rows_symbols_shift.xml
deleted file mode 100644
index fd1b93d..0000000
--- a/java/res/xml-sw768dp/rows_symbols_shift.xml
+++ /dev/null
@@ -1,68 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2011, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <include
-        latin:keyboardLayout="@xml/key_styles_common" />
-    <include
-        latin:keyboardLayout="@xml/key_styles_currency" />
-    <Row
-        latin:keyWidth="8.282%p"
-    >
-        <Key
-            latin:keyStyle="tabKeyStyle"
-            latin:keyLabelFlags="alignLeft"
-            latin:keyWidth="7.969%p" />
-        <include
-            latin:keyboardLayout="@xml/rowkeys_symbols_shift1" />
-        <Key
-            latin:keyStyle="deleteKeyStyle"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <Row
-        latin:keyWidth="8.125%p"
-    >
-        <Key
-            latin:keyStyle="toAlphaKeyStyle"
-            latin:keyLabelFlags="alignLeft"
-            latin:keyWidth="11.172%p" />
-        <include
-            latin:keyboardLayout="@xml/rowkeys_symbols_shift2" />
-        <Key
-            latin:keyStyle="enterKeyStyle"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <Row
-        latin:keyWidth="8.047%p"
-    >
-        <Key
-            latin:keyStyle="backFromMoreSymbolKeyStyle"
-            latin:keyWidth="13.829%p" />
-        <include
-            latin:keyboardLayout="@xml/rowkeys_symbols_shift3" />
-        <Key
-            latin:keyStyle="backFromMoreSymbolKeyStyle"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <include
-        latin:keyboardLayout="@xml/row_symbols_shift4" />
-</merge>
diff --git a/java/res/xml-sw768dp/rows_thai.xml b/java/res/xml-sw768dp/rows_thai.xml
deleted file mode 100644
index 5f9b383..0000000
--- a/java/res/xml-sw768dp/rows_thai.xml
+++ /dev/null
@@ -1,74 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <include
-        latin:keyboardLayout="@xml/key_styles_common" />
-    <Row
-        latin:keyWidth="7.079%p"
-    >
-        <include
-            latin:keyboardLayout="@xml/rowkeys_thai1"
-            latin:keyXPos="3.799%p" />
-        <Key
-            latin:keyStyle="deleteKeyStyle"
-            latin:keyWidth="fillRight"/>
-    </Row>
-    <Row
-        latin:keyWidth="7.079%p"
-    >
-        <Key
-            latin:keyStyle="tabKeyStyle"
-            latin:keyLabelFlags="alignLeft"
-            latin:keyWidth="7.969%p" />
-        <include
-            latin:keyboardLayout="@xml/rowkeys_thai2" />
-        <include
-            latin:keyboardLayout="@xml/key_thai_kho_khuat" />
-    </Row>
-    <Row
-        latin:keyWidth="7.079%p"
-    >
-        <Key
-            latin:keyStyle="toSymbolKeyStyle"
-            latin:keyLabelFlags="alignLeft"
-            latin:keyWidth="11.172%p"/>
-        <include
-            latin:keyboardLayout="@xml/rowkeys_thai3" />
-        <Key
-            latin:keyStyle="enterKeyStyle"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <Row
-        latin:keyWidth="7.181%p"
-    >
-        <Key
-            latin:keyStyle="shiftKeyStyle"
-            latin:keyWidth="13.829%p"/>
-        <include
-            latin:keyboardLayout="@xml/rowkeys_thai4" />
-        <include
-            latin:keyboardLayout="@xml/keys_comma_period" />
-    </Row>
-    <include
-        latin:keyboardLayout="@xml/row_qwerty4" />
-</merge>
diff --git a/java/res/xml-sw768dp/rows_thai_symbols.xml b/java/res/xml-sw768dp/rows_thai_symbols.xml
deleted file mode 100644
index 5285141..0000000
--- a/java/res/xml-sw768dp/rows_thai_symbols.xml
+++ /dev/null
@@ -1,76 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <include
-        latin:keyboardLayout="@xml/key_styles_common" />
-    <include
-        latin:keyboardLayout="@xml/key_styles_currency" />
-    <Row
-        latin:keyWidth="8.282%p"
-    >
-        <include
-            latin:keyboardLayout="@xml/rowkeys_thai_digits"
-            latin:keyXPos="7.969%p" />
-        <Key
-            latin:keyStyle="deleteKeyStyle"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <Row
-        latin:keyWidth="8.282%p"
-    >
-        <Key
-            latin:keyStyle="tabKeyStyle"
-            latin:keyLabelFlags="alignLeft"
-            latin:keyWidth="7.969%p" />
-        <include
-            latin:keyboardLayout="@xml/rowkeys_symbols1" />
-    </Row>
-    <Row
-        latin:keyWidth="8.125%p"
-    >
-        <Key
-            latin:keyStyle="toAlphaKeyStyle"
-            latin:keyLabelFlags="alignLeft"
-            latin:keyWidth="11.172%p" />
-        <include
-            latin:keyboardLayout="@xml/rowkeys_symbols2" />
-        <Key
-            latin:keyStyle="enterKeyStyle"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <Row
-        latin:keyWidth="8.047%p"
-    >
-        <Key
-            latin:keyStyle="toMoreSymbolKeyStyle"
-            latin:keyWidth="13.829%p" />
-        <include
-            latin:keyboardLayout="@xml/rowkeys_symbols3" />
-        <Key
-            latin:keyStyle="toMoreSymbolKeyStyle"
-            latin:keyXPos="-13.750%p"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <include
-        latin:keyboardLayout="@xml/row_symbols4" />
-</merge>
diff --git a/java/res/xml-sw768dp/rows_thai_symbols_shift.xml b/java/res/xml-sw768dp/rows_thai_symbols_shift.xml
deleted file mode 100644
index 9d2694b..0000000
--- a/java/res/xml-sw768dp/rows_thai_symbols_shift.xml
+++ /dev/null
@@ -1,76 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <include
-        latin:keyboardLayout="@xml/key_styles_common" />
-    <include
-        latin:keyboardLayout="@xml/key_styles_currency" />
-    <Row
-        latin:keyWidth="8.282%p"
-    >
-        <include
-            latin:keyboardLayout="@xml/rowkeys_thai_digits"
-            latin:keyXPos="7.969%p" />
-        <Key
-            latin:keyStyle="deleteKeyStyle"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <Row
-        latin:keyWidth="8.282%p"
-    >
-        <Key
-            latin:keyStyle="tabKeyStyle"
-            latin:keyLabelFlags="alignLeft"
-            latin:keyWidth="7.969%p" />
-        <include
-            latin:keyboardLayout="@xml/rowkeys_symbols_shift1" />
-    </Row>
-    <Row
-        latin:keyWidth="8.125%p"
-    >
-        <Key
-            latin:keyStyle="toAlphaKeyStyle"
-            latin:keyLabelFlags="alignLeft"
-            latin:keyWidth="11.172%p" />
-        <include
-            latin:keyboardLayout="@xml/rowkeys_symbols_shift2" />
-        <Key
-            latin:keyStyle="enterKeyStyle"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <Row
-        latin:keyWidth="8.047%p"
-    >
-        <Key
-            latin:keyStyle="backFromMoreSymbolKeyStyle"
-            latin:keyWidth="13.829%p" />
-        <include
-            latin:keyboardLayout="@xml/rowkeys_symbols_shift3" />
-        <Key
-            latin:keyStyle="backFromMoreSymbolKeyStyle"
-            latin:keyXPos="-13.750%p"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <include
-        latin:keyboardLayout="@xml/row_symbols_shift4" />
-</merge>
diff --git a/java/res/xml-v16/key_hindi1_shift.xml b/java/res/xml-v16/key_devanagari_sign_anusvara.xml
similarity index 78%
copy from java/res/xml-v16/key_hindi1_shift.xml
copy to java/res/xml-v16/key_devanagari_sign_anusvara.xml
index 19b9643..27c7bff 100644
--- a/java/res/xml-v16/key_hindi1_shift.xml
+++ b/java/res/xml-v16/key_devanagari_sign_anusvara.xml
@@ -20,13 +20,13 @@
 
 <!-- The code point U+25CC for key label is needed because the font rendering system prior to
      API version 16 can't automatically render dotted circle for incomplete combining letter
-     of Hindi. The files named res/xml/{key,keys}_hindi*.xml have this U+25CC hack, although the
-     counterpart files named res/xml-v16/{key,keys}_hindi*.xml don't have this hack. -->
+     of some scripts. The files named res/xml/key_*.xml have this U+25CC hack, although the
+     counterpart files named res/xml-v16/key_*.xml don't have this hack. -->
 <merge
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
 >
-    <!-- U+0903: "ः" DEVANAGARI SIGN VISARGA -->
+    <!-- U+0902: "ं" DEVANAGARI SIGN ANUSVARA -->
     <Key
-        latin:keyLabel="&#x0903;"
+        latin:keyLabel="&#x0902;"
         latin:keyLabelFlags="fontNormal" />
 </merge>
diff --git a/java/res/xml-v16/key_devanagari_sign_candrabindu.xml b/java/res/xml-v16/key_devanagari_sign_candrabindu.xml
new file mode 100644
index 0000000..03017dd
--- /dev/null
+++ b/java/res/xml-v16/key_devanagari_sign_candrabindu.xml
@@ -0,0 +1,48 @@
+<?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.
+*/
+-->
+
+<!-- The code point U+25CC for key label is needed because the font rendering system prior to
+     API version 16 can't automatically render dotted circle for incomplete combining letter
+     of some scripts. The files named res/xml/key_*.xml have this U+25CC hack, although the
+     counterpart files named res/xml-v16/key_*.xml don't have this hack. -->
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <switch>
+        <case
+            latin:keyboardLayoutSet="hindi"
+        >
+            <!-- U+0901: "ँ" DEVANAGARI SIGN CANDRABINDU
+                 U+0945: "ॅ" DEVANAGARI VOWEL SIGN CANDRA E -->
+            <key-style
+                latin:styleName="moreKeysDevanagariSignCandrabindu"
+                latin:moreKeys="&#x0945;" />
+        </case>
+        <default>
+             <key-style
+                latin:styleName="moreKeysDevanagariSignCandrabindu" />
+        </default>
+    </switch>
+    <!-- U+0901: "ँ" DEVANAGARI SIGN CANDRABINDU -->
+    <Key
+        latin:keyStyle="moreKeysDevanagariSignCandrabindu"
+        latin:keyLabel="&#x0901;"
+        latin:keyLabelFlags="fontNormal" />
+</merge>
diff --git a/java/res/xml-v16/key_devanagari_sign_nukta.xml b/java/res/xml-v16/key_devanagari_sign_nukta.xml
new file mode 100644
index 0000000..09c3477
--- /dev/null
+++ b/java/res/xml-v16/key_devanagari_sign_nukta.xml
@@ -0,0 +1,49 @@
+<?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.
+*/
+-->
+
+<!-- The code point U+25CC for key label is needed because the font rendering system prior to
+     API version 16 can't automatically render dotted circle for incomplete combining letter
+     of some scripts. The files named res/xml/key_*.xml have this U+25CC hack, although the
+     counterpart files named res/xml-v16/key_*.xml don't have this hack. -->
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <switch>
+        <case
+            latin:keyboardLayoutSet="hindi"
+        >
+            <!-- U+097D: "ॽ" DEVANAGARI LETTER GLOTTAL STOP
+                 U+0970: "॰" DEVANAGARI ABBREVIATION SIGN
+                 U+093D: "ऽ" DEVANAGARI SIGN AVAGRAHA -->
+             <key-style
+                latin:styleName="moreKeysDevanagariSignNukta"
+                latin:moreKeys="&#x097D;,&#x0970;,&#x093D;" />
+        </case>
+        <default>
+             <key-style
+                latin:styleName="moreKeysDevanagariSignNukta" />
+        </default>
+    </switch>
+    <!-- U+093C: "़" DEVANAGARI SIGN NUKTA -->
+    <Key
+        latin:keyStyle="moreKeysDevanagariSignNukta"
+        latin:keyLabel="&#x093C;"
+        latin:keyLabelFlags="fontNormal" />
+</merge>
diff --git a/java/res/xml-v16/key_hindi1_shift.xml b/java/res/xml-v16/key_devanagari_vowel_sign_candra_o.xml
similarity index 78%
copy from java/res/xml-v16/key_hindi1_shift.xml
copy to java/res/xml-v16/key_devanagari_vowel_sign_candra_o.xml
index 19b9643..0316a7b 100644
--- a/java/res/xml-v16/key_hindi1_shift.xml
+++ b/java/res/xml-v16/key_devanagari_vowel_sign_candra_o.xml
@@ -20,13 +20,13 @@
 
 <!-- The code point U+25CC for key label is needed because the font rendering system prior to
      API version 16 can't automatically render dotted circle for incomplete combining letter
-     of Hindi. The files named res/xml/{key,keys}_hindi*.xml have this U+25CC hack, although the
-     counterpart files named res/xml-v16/{key,keys}_hindi*.xml don't have this hack. -->
+     of some scripts. The files named res/xml/key_*.xml have this U+25CC hack, although the
+     counterpart files named res/xml-v16/key_*.xml don't have this hack. -->
 <merge
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
 >
-    <!-- U+0903: "ः" DEVANAGARI SIGN VISARGA -->
+    <!-- U+0949: "ॉ" DEVANAGARI VOWEL SIGN CANDRA O -->
     <Key
-        latin:keyLabel="&#x0903;"
+        latin:keyLabel="&#x0949;"
         latin:keyLabelFlags="fontNormal" />
 </merge>
diff --git a/java/res/xml-v16/key_devanagari_vowel_sign_vocalic_r.xml b/java/res/xml-v16/key_devanagari_vowel_sign_vocalic_r.xml
new file mode 100644
index 0000000..4dd3e85
--- /dev/null
+++ b/java/res/xml-v16/key_devanagari_vowel_sign_vocalic_r.xml
@@ -0,0 +1,55 @@
+<?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.
+*/
+-->
+
+<!-- The code point U+25CC for key label is needed because the font rendering system prior to
+     API version 16 can't automatically render dotted circle for incomplete combining letter
+     of some scripts. The files named res/xml/key_*.xml have this U+25CC hack, although the
+     counterpart files named res/xml-v16/key_*.xml don't have this hack. -->
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <switch>
+        <case
+            latin:keyboardLayoutSet="hindi"
+        >
+            <!-- U+0944: "ॄ" DEVANAGARI VOWEL SIGN VOCALIC RR -->
+            <key-style
+                latin:styleName="moreKeysDevanagariVowelSignVocalicR"
+                latin:moreKeys="&#x0944;" />
+        </case>
+        <case
+            latin:keyboardLayoutSet="nepali_traditional"
+        >
+            <!-- U+0913: "ओ" DEVANAGARI LETTER O -->
+            <key-style
+                latin:styleName="moreKeysDevanagariVowelSignVocalicR"
+                latin:moreKeys="&#x0913;" />
+        </case>
+        <default>
+             <key-style
+                latin:styleName="moreKeysDevanagariVowelSignVocalicR" />
+        </default>
+    </switch>
+    <!-- U+0943: "ृ" DEVANAGARI VOWEL SIGN VOCALIC R -->
+    <Key
+        latin:keyStyle="moreKeysDevanagariVowelSignVocalicR"
+        latin:keyLabel="&#x0943;"
+        latin:keyLabelFlags="fontNormal" />
+</merge>
diff --git a/java/res/xml-v16/key_hindi3_right.xml b/java/res/xml-v16/key_hindi3_right.xml
deleted file mode 100644
index 232810f..0000000
--- a/java/res/xml-v16/key_hindi3_right.xml
+++ /dev/null
@@ -1,36 +0,0 @@
-<?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.
-*/
--->
-
-<!-- The code point U+25CC for key label is needed because the font rendering system prior to
-     API version 16 can't automatically render dotted circle for incomplete combining letter
-     of Hindi. The files named res/xml/{key,keys}_hindi*.xml have this U+25CC hack, although the
-     counterpart files named res/xml-v16/{key,keys}_hindi*.xml don't have this hack. -->
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <!-- U+093C: "़" DEVANAGARI SIGN NUKTA
-         U+097D: "ॽ" DEVANAGARI LETTER GLOTTAL STOP
-         U+0970: "॰" DEVANAGARI ABBREVIATION SIGN
-         U+093D: "ऽ" DEVANAGARI SIGN AVAGRAHA -->
-    <Key
-        latin:keyLabel="&#x093C;"
-        latin:moreKeys="&#x097D;,&#x0970;,&#x093D;"
-        latin:keyLabelFlags="fontNormal" />
-</merge>
diff --git a/java/res/xml-v16/key_hindi3_shift_left.xml b/java/res/xml-v16/key_hindi3_shift_left.xml
deleted file mode 100644
index 1eb1768..0000000
--- a/java/res/xml-v16/key_hindi3_shift_left.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<?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.
-*/
--->
-
-<!-- The code point U+25CC for key label is needed because the font rendering system prior to
-     API version 16 can't automatically render dotted circle for incomplete combining letter
-     of Hindi. The files named res/xml/{key,keys}_hindi*.xml have this U+25CC hack, although the
-     counterpart files named res/xml-v16/{key,keys}_hindi*.xml don't have this hack. -->
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <!-- U+0901: "ँ" DEVANAGARI SIGN CANDRABINDU
-         U+0945: "ॅ" DEVANAGARI VOWEL SIGN CANDRA E -->
-    <Key
-        latin:keyLabel="&#x0901;"
-        latin:moreKeys="&#x0945;"
-        latin:keyLabelFlags="fontNormal" />
-</merge>
diff --git a/java/res/xml-v16/key_hindi3_shift_right.xml b/java/res/xml-v16/key_hindi3_shift_right.xml
deleted file mode 100644
index 0f26cb5..0000000
--- a/java/res/xml-v16/key_hindi3_shift_right.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<?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.
-*/
--->
-
-<!-- The code point U+25CC for key label is needed because the font rendering system prior to
-     API version 16 can't automatically render dotted circle for incomplete combining letter
-     of Hindi. The files named res/xml/{key,keys}_hindi*.xml have this U+25CC hack, although the
-     counterpart files named res/xml-v16/{key,keys}_hindi*.xml don't have this hack. -->
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <!-- U+0943: "ृ" DEVANAGARI VOWEL SIGN VOCALIC R
-         U+0944: "ॄ" DEVANAGARI VOWEL SIGN VOCALIC RR -->
-    <Key
-        latin:keyLabel="&#x0943;"
-        latin:moreKeys="&#x0944;"
-        latin:keyLabelFlags="fontNormal" />
-</merge>
diff --git a/java/res/xml-v16/keys_hindi1_left5.xml b/java/res/xml-v16/keys_hindi1_left5.xml
deleted file mode 100644
index e3ad299..0000000
--- a/java/res/xml-v16/keys_hindi1_left5.xml
+++ /dev/null
@@ -1,75 +0,0 @@
-<?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.
-*/
--->
-
-<!-- The code point U+25CC for key label is needed because the font rendering system prior to
-     API version 16 can't automatically render dotted circle for incomplete combining letter
-     of Hindi. The files named res/xml/{key,keys}_hindi*.xml have this U+25CC hack, although the
-     counterpart files named res/xml-v16/{key,keys}_hindi*.xml don't have this hack. -->
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <!-- U+094C: "ौ" DEVANAGARI VOWEL SIGN AU
-         U+094C/U+0902: "ौं" DEVANAGARI VOWEL SIGN AU/DEVANAGARI SIGN ANUSVARA
-         U+0967: "१" DEVANAGARI DIGIT ONE -->
-    <Key
-        latin:keyLabel="&#x094C;"
-        latin:moreKeys="&#x094C;&#x0902;,%"
-        latin:keyHintLabel="1"
-        latin:additionalMoreKeys="&#x0967;,1"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0948: "ै" DEVANAGARI VOWEL SIGN AI
-         U+0948/U+0902: "ैं" DEVANAGARI VOWEL SIGN AI/DEVANAGARI SIGN ANUSVARA
-         U+0968: "२" DEVANAGARI DIGIT TWO -->
-    <Key
-        latin:keyLabel="&#x0948;"
-        latin:moreKeys="&#x0948;&#x0902;,%"
-        latin:keyHintLabel="2"
-        latin:additionalMoreKeys="&#x0968;,2"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+093E: "ा" DEVANAGARI VOWEL SIGN AA
-         U+093E/U+0902: "ां" DEVANAGARI VOWEL SIGN AA/DEVANAGARI SIGN ANUSVARA
-         U+093E/U+0901: "ाँ" DEVANAGARI VOWEL SIGN AA/DEVANAGARI SIGN CANDRABINDU
-         U+0969: "३" DEVANAGARI DIGIT THREE -->
-    <Key
-        latin:keyLabel="&#x093E;"
-        latin:moreKeys="&#x093E;&#x0902;,&#x093E;&#x0901;,%"
-        latin:keyHintLabel="3"
-        latin:additionalMoreKeys="&#x0969;,3"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0940: "ी" DEVANAGARI VOWEL SIGN II
-         U+0940/U+0902: "ीं" DEVANAGARI VOWEL SIGN II/DEVANAGARI SIGN ANUSVARA
-         U+096A: "४" DEVANAGARI DIGIT FOUR -->
-    <Key
-        latin:keyLabel="&#x0940;"
-        latin:moreKeys="&#x0940;&#x0902;,%"
-        latin:keyHintLabel="4"
-        latin:additionalMoreKeys="&#x096A;,4"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0942: "ू" DEVANAGARI VOWEL SIGN UU
-         U+0942/U+0902: "ूं" DEVANAGARI VOWEL SIGN UU/DEVANAGARI SIGN ANUSVARA
-         U+0942/U+0901: "ूँ" DEVANAGARI VOWEL SIGN UU/DEVANAGARI SIGN CANDRABINDU
-         U+096B: "५" DEVANAGARI DIGIT FIVE -->
-    <Key
-        latin:keyLabel="&#x0942;"
-        latin:moreKeys="&#x0942;&#x0902;,&#x0942;&#x0901;,%"
-        latin:keyHintLabel="5"
-        latin:additionalMoreKeys="&#x096B;,5"
-        latin:keyLabelFlags="fontNormal" />
-</merge>
diff --git a/java/res/xml-v16/keys_hindi2_left5.xml b/java/res/xml-v16/keys_hindi2_left5.xml
deleted file mode 100644
index 05c4f57..0000000
--- a/java/res/xml-v16/keys_hindi2_left5.xml
+++ /dev/null
@@ -1,59 +0,0 @@
-<?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.
-*/
--->
-
-<!-- The code point U+25CC for key label is needed because the font rendering system prior to
-     API version 16 can't automatically render dotted circle for incomplete combining letter
-     of Hindi. The files named res/xml/{key,keys}_hindi*.xml have this U+25CC hack, although the
-     counterpart files named res/xml-v16/{key,keys}_hindi*.xml don't have this hack. -->
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <!-- U+094B: "ो" DEVANAGARI VOWEL SIGN O
-         U+094B/U+0902: "қं" DEVANAGARI VOWEL SIGN O/DEVANAGARI SIGN ANUSVARA
-         U+0949: "ॉ" DEVANAGARI VOWEL SIGN CANDRA O
-         U+094A: "ॊ" DEVANAGARI VOWEL SIGN SHORT O -->
-    <Key
-        latin:keyLabel="&#x094B;"
-        latin:moreKeys="&#x094B;&#x0902;,&#x0949;,&#x094A;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0947: "े" DEVANAGARI VOWEL SIGN E
-         U+0947/U+0902: "ें" DEVANAGARI VOWEL SIGN E/DEVANAGARI SIGN ANUSVARA -->
-    <Key
-        latin:keyLabel="&#x0947;"
-        latin:moreKeys="&#x0947;&#x0902;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+094D: "्" DEVANAGARI SIGN VIRAMA -->
-    <Key
-        latin:keyLabel="&#x094D;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+093F: "ि" DEVANAGARI VOWEL SIGN I
-         U+093F/U+0902: "िं" DEVANAGARI VOWEL SIGN I/DEVANAGARI SIGN ANUSVARA -->
-    <Key
-        latin:keyLabel="&#x093F;"
-        latin:moreKeys="&#x093F;&#x0902;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0941: "ु" DEVANAGARI VOWEL SIGN U
-         U+0941/U+0902: "ुं" DEVANAGARI VOWEL SIGN U/DEVANAGARI SIGN ANUSVARA
-         U+0941/U+0901: "ुँ" DEVANAGARI VOWEL SIGN U/DEVANAGARI SIGN CANDRABINDU -->
-    <Key
-        latin:keyLabel="&#x0941;"
-        latin:moreKeys="&#x0941;&#x0902;,&#x0941;&#x0901;"
-        latin:keyLabelFlags="fontNormal" />
-</merge>
diff --git a/java/res/xml-v16/keys_hindi3_left2.xml b/java/res/xml-v16/keys_hindi3_left2.xml
deleted file mode 100644
index 9474c17..0000000
--- a/java/res/xml-v16/keys_hindi3_left2.xml
+++ /dev/null
@@ -1,36 +0,0 @@
-<?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.
-*/
--->
-
-<!-- The code point U+25CC for key label is needed because the font rendering system prior to
-     API version 16 can't automatically render dotted circle for incomplete combining letter
-     of Hindi. The files named res/xml/{key,keys}_hindi*.xml have this U+25CC hack, although the
-     counterpart files named res/xml-v16/{key,keys}_hindi*.xml don't have this hack. -->
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <!-- U+0949: "ॉ" DEVANAGARI VOWEL SIGN CANDRA O -->
-    <Key
-        latin:keyLabel="&#x0949;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0902: "ं" DEVANAGARI SIGN ANUSVARA -->
-    <Key
-        latin:keyLabel="&#x0902;"
-        latin:keyLabelFlags="fontNormal" />
-</merge>
diff --git a/java/res/xml-v16/key_hindi1_shift.xml b/java/res/xml-v16/keystyle_devanagari_sign_virama.xml
similarity index 75%
copy from java/res/xml-v16/key_hindi1_shift.xml
copy to java/res/xml-v16/keystyle_devanagari_sign_virama.xml
index 19b9643..a2fbf53 100644
--- a/java/res/xml-v16/key_hindi1_shift.xml
+++ b/java/res/xml-v16/keystyle_devanagari_sign_virama.xml
@@ -20,13 +20,14 @@
 
 <!-- The code point U+25CC for key label is needed because the font rendering system prior to
      API version 16 can't automatically render dotted circle for incomplete combining letter
-     of Hindi. The files named res/xml/{key,keys}_hindi*.xml have this U+25CC hack, although the
-     counterpart files named res/xml-v16/{key,keys}_hindi*.xml don't have this hack. -->
+     of some scripts. The files named res/xml/key_*.xml have this U+25CC hack, although the
+     counterpart files named res/xml-v16/key_*.xml don't have this hack. -->
 <merge
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
 >
-    <!-- U+0903: "ः" DEVANAGARI SIGN VISARGA -->
-    <Key
-        latin:keyLabel="&#x0903;"
+    <!-- U+094D: "्" DEVANAGARI SIGN VIRAMA -->
+    <key-style
+        latin:styleName="baseKeyDevanagariSignVirama"
+        latin:keyLabel="&#x094D;"
         latin:keyLabelFlags="fontNormal" />
 </merge>
diff --git a/java/res/xml-v16/key_hindi1_shift.xml b/java/res/xml-v16/keystyle_devanagari_sign_visarga.xml
similarity index 81%
rename from java/res/xml-v16/key_hindi1_shift.xml
rename to java/res/xml-v16/keystyle_devanagari_sign_visarga.xml
index 19b9643..ac56cb7 100644
--- a/java/res/xml-v16/key_hindi1_shift.xml
+++ b/java/res/xml-v16/keystyle_devanagari_sign_visarga.xml
@@ -20,13 +20,14 @@
 
 <!-- The code point U+25CC for key label is needed because the font rendering system prior to
      API version 16 can't automatically render dotted circle for incomplete combining letter
-     of Hindi. The files named res/xml/{key,keys}_hindi*.xml have this U+25CC hack, although the
-     counterpart files named res/xml-v16/{key,keys}_hindi*.xml don't have this hack. -->
+     of some scripts. The files named res/xml/key_*.xml have this U+25CC hack, although the
+     counterpart files named res/xml-v16/key_*.xml don't have this hack. -->
 <merge
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
 >
     <!-- U+0903: "ः" DEVANAGARI SIGN VISARGA -->
-    <Key
+    <key-style
+        latin:styleName="baseKeyDevanagariSignVisarga"
         latin:keyLabel="&#x0903;"
         latin:keyLabelFlags="fontNormal" />
 </merge>
diff --git a/java/res/xml-v16/keystyle_devanagari_vowel_sign_aa.xml b/java/res/xml-v16/keystyle_devanagari_vowel_sign_aa.xml
new file mode 100644
index 0000000..8e25603
--- /dev/null
+++ b/java/res/xml-v16/keystyle_devanagari_vowel_sign_aa.xml
@@ -0,0 +1,49 @@
+<?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.
+*/
+-->
+
+<!-- The code point U+25CC for key label is needed because the font rendering system prior to
+     API version 16 can't automatically render dotted circle for incomplete combining letter
+     of some scripts. The files named res/xml/key_*.xml have this U+25CC hack, although the
+     counterpart files named res/xml-v16/key_*.xml don't have this hack. -->
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <switch>
+        <case
+            latin:keyboardLayoutSet="hindi"
+        >
+            <!-- U+093E/U+0902: "ां" DEVANAGARI VOWEL SIGN AA/DEVANAGARI SIGN ANUSVARA
+                 U+093E/U+0901: "ाँ" DEVANAGARI VOWEL SIGN AA/DEVANAGARI SIGN CANDRABINDU -->
+            <key-style
+                latin:styleName="moreKeysDevanagariVowelSignAa"
+                latin:moreKeys="&#x093E;&#x0902;,&#x093E;&#x0901;,%" />
+        </case>
+        <default>
+            <key-style
+                latin:styleName="moreKeysDevanagariVowelSignAa" />
+        </default>
+    </switch>
+    <!-- U+093E: "ा" DEVANAGARI VOWEL SIGN AA -->
+    <key-style
+        latin:styleName="baseKeyDevanagariVowelSignAa"
+        latin:parentStyle="moreKeysDevanagariVowelSignAa"
+        latin:keyLabel="&#x093E;"
+        latin:keyLabelFlags="fontNormal" />
+</merge>
diff --git a/java/res/xml-v16/keystyle_devanagari_vowel_sign_ai.xml b/java/res/xml-v16/keystyle_devanagari_vowel_sign_ai.xml
new file mode 100644
index 0000000..e790339
--- /dev/null
+++ b/java/res/xml-v16/keystyle_devanagari_vowel_sign_ai.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.
+*/
+-->
+
+<!-- The code point U+25CC for key label is needed because the font rendering system prior to
+     API version 16 can't automatically render dotted circle for incomplete combining letter
+     of some scripts. The files named res/xml/key_*.xml have this U+25CC hack, although the
+     counterpart files named res/xml-v16/key_*.xml don't have this hack. -->
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <switch>
+        <case
+            latin:keyboardLayoutSet="hindi"
+        >
+            <!-- U+0948/U+0902: "ैं" DEVANAGARI VOWEL SIGN AI/DEVANAGARI SIGN ANUSVARA -->
+            <key-style
+                latin:styleName="moreKeysDevanagariVowelSignAi"
+                latin:moreKeys="&#x0948;&#x0902;,%" />
+        </case>
+        <case
+            latin:keyboardLayoutSet="nepali_traditional"
+        >
+            <!-- U+0936/U+094D/U+0930: "श्र" DEVANAGARI LETTER SHA/DEVANAGARI SIGN VIRAMA/DEVANAGARI LETTER RA -->
+            <key-style
+                latin:styleName="moreKeysDevanagariVowelSignAi"
+                latin:moreKeys="&#x0936;&#x094D;&#x0930;" />
+        </case>
+        <default>
+            <key-style
+                latin:styleName="moreKeysDevanagariVowelSignAi" />
+        </default>
+    </switch>
+    <!-- U+0948: "ै" DEVANAGARI VOWEL SIGN AI -->
+    <key-style
+        latin:styleName="baseKeyDevanagariVowelSignAi"
+        latin:parentStyle="moreKeysDevanagariVowelSignAi"
+        latin:keyLabel="&#x0948;"
+        latin:keyLabelFlags="fontNormal" />
+</merge>
diff --git a/java/res/xml-v16/keystyle_devanagari_vowel_sign_au.xml b/java/res/xml-v16/keystyle_devanagari_vowel_sign_au.xml
new file mode 100644
index 0000000..43387a3
--- /dev/null
+++ b/java/res/xml-v16/keystyle_devanagari_vowel_sign_au.xml
@@ -0,0 +1,48 @@
+<?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.
+*/
+-->
+
+<!-- The code point U+25CC for key label is needed because the font rendering system prior to
+     API version 16 can't automatically render dotted circle for incomplete combining letter
+     of some scripts. The files named res/xml/key_*.xml have this U+25CC hack, although the
+     counterpart files named res/xml-v16/key_*.xml don't have this hack. -->
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <switch>
+        <case
+            latin:keyboardLayoutSet="hindi"
+        >
+            <!--U+094C/U+0902: "ौं" DEVANAGARI VOWEL SIGN AU/DEVANAGARI SIGN ANUSVARA -->
+            <key-style
+                latin:styleName="moreKeysDevanagariVowelSignAu"
+                latin:moreKeys="&#x094C;&#x0902;,%" />
+        </case>
+        <default>
+             <key-style
+                latin:styleName="moreKeysDevanagariVowelSignAu" />
+        </default>
+    </switch>
+    <!-- U+094C: "ौ" DEVANAGARI VOWEL SIGN AU -->
+    <key-style
+        latin:styleName="baseKeyDevanagariVowelSignAu"
+        latin:parentStyle="moreKeysDevanagariVowelSignAu"
+        latin:keyLabel="&#x094C;"
+        latin:keyLabelFlags="fontNormal" />
+</merge>
diff --git a/java/res/xml-v16/keystyle_devanagari_vowel_sign_e.xml b/java/res/xml-v16/keystyle_devanagari_vowel_sign_e.xml
new file mode 100644
index 0000000..c70d9d9
--- /dev/null
+++ b/java/res/xml-v16/keystyle_devanagari_vowel_sign_e.xml
@@ -0,0 +1,57 @@
+<?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.
+*/
+-->
+
+<!-- The code point U+25CC for key label is needed because the font rendering system prior to
+     API version 16 can't automatically render dotted circle for incomplete combining letter
+     of some scripts. The files named res/xml/key_*.xml have this U+25CC hack, although the
+     counterpart files named res/xml-v16/key_*.xml don't have this hack. -->
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <switch>
+        <case
+            latin:keyboardLayoutSet="hindi"
+        >
+            <!-- U+0947/U+0902: "ें" DEVANAGARI VOWEL SIGN E/DEVANAGARI SIGN ANUSVARA -->
+            <key-style
+                latin:styleName="moreKeysDevanagariVowelSignE"
+                latin:moreKeys="&#x0947;&#x0902;" />
+        </case>
+        <case
+            latin:keyboardLayoutSet="nepali_traditional"
+        >
+            <!-- U+0903: "ः‍" DEVANAGARI SIGN VISARGA
+                 U+093D: "ऽ" DEVANAGARI SIGN AVAGRAHA -->
+            <key-style
+                latin:styleName="moreKeysDevanagariVowelSignE"
+                latin:moreKeys="&#x0903;,&#x093D;" />
+        </case>
+        <default>
+             <key-style
+                latin:styleName="moreKeysDevanagariVowelSignE" />
+        </default>
+    </switch>
+    <!-- U+0947: "े" DEVANAGARI VOWEL SIGN E -->
+    <key-style
+        latin:styleName="baseKeyDevanagariVowelSignE"
+        latin:parentStyle="moreKeysDevanagariVowelSignE"
+        latin:keyLabel="&#x0947;"
+        latin:keyLabelFlags="fontNormal" />
+</merge>
diff --git a/java/res/xml-v16/keystyle_devanagari_vowel_sign_i.xml b/java/res/xml-v16/keystyle_devanagari_vowel_sign_i.xml
new file mode 100644
index 0000000..845c1b0
--- /dev/null
+++ b/java/res/xml-v16/keystyle_devanagari_vowel_sign_i.xml
@@ -0,0 +1,48 @@
+<?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.
+*/
+-->
+
+<!-- The code point U+25CC for key label is needed because the font rendering system prior to
+     API version 16 can't automatically render dotted circle for incomplete combining letter
+     of some scripts. The files named res/xml/key_*.xml have this U+25CC hack, although the
+     counterpart files named res/xml-v16/key_*.xml don't have this hack. -->
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <switch>
+        <case
+            latin:keyboardLayoutSet="hindi"
+        >
+            <!-- U+093F/U+0902: "िं" DEVANAGARI VOWEL SIGN I/DEVANAGARI SIGN ANUSVARA -->
+            <key-style
+                latin:styleName="moreKeysDevanagariVowelSignI"
+                latin:moreKeys="&#x093F;&#x0902;" />
+        </case>
+        <default>
+             <key-style
+                latin:styleName="moreKeysDevanagariVowelSignI" />
+        </default>
+    </switch>
+    <!-- U+093F: "ि" DEVANAGARI VOWEL SIGN I -->
+    <key-style
+        latin:styleName="baseKeyDevanagariVowelSignI"
+        latin:parentStyle="moreKeysDevanagariVowelSignI"
+        latin:keyLabel="&#x093F;"
+        latin:keyLabelFlags="fontNormal" />
+</merge>
diff --git a/java/res/xml-v16/keystyle_devanagari_vowel_sign_ii.xml b/java/res/xml-v16/keystyle_devanagari_vowel_sign_ii.xml
new file mode 100644
index 0000000..0de9650
--- /dev/null
+++ b/java/res/xml-v16/keystyle_devanagari_vowel_sign_ii.xml
@@ -0,0 +1,48 @@
+<?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.
+*/
+-->
+
+<!-- The code point U+25CC for key label is needed because the font rendering system prior to
+     API version 16 can't automatically render dotted circle for incomplete combining letter
+     of some scripts. The files named res/xml/key_*.xml have this U+25CC hack, although the
+     counterpart files named res/xml-v16/key_*.xml don't have this hack. -->
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <switch>
+        <case
+            latin:keyboardLayoutSet="hindi"
+        >
+            <!-- U+0940/U+0902: "ीं" DEVANAGARI VOWEL SIGN II/DEVANAGARI SIGN ANUSVARA -->
+            <key-style
+                latin:styleName="moreKeysDevanagariVowelSignIi"
+                latin:moreKeys="&#x0940;&#x0902;,%" />
+        </case>
+        <default>
+             <key-style
+                latin:styleName="moreKeysDevanagariVowelSignIi" />
+        </default>
+    </switch>
+    <!-- U+0940: "ी" DEVANAGARI VOWEL SIGN II -->
+    <key-style
+        latin:styleName="baseKeyDevanagariVowelSignIi"
+        latin:parentStyle="moreKeysDevanagariVowelSignIi"
+        latin:keyLabel="&#x0940;"
+        latin:keyLabelFlags="fontNormal" />
+</merge>
diff --git a/java/res/xml-v16/keystyle_devanagari_vowel_sign_o.xml b/java/res/xml-v16/keystyle_devanagari_vowel_sign_o.xml
new file mode 100644
index 0000000..06f07fa
--- /dev/null
+++ b/java/res/xml-v16/keystyle_devanagari_vowel_sign_o.xml
@@ -0,0 +1,50 @@
+<?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.
+*/
+-->
+
+<!-- The code point U+25CC for key label is needed because the font rendering system prior to
+     API version 16 can't automatically render dotted circle for incomplete combining letter
+     of some scripts. The files named res/xml/key_*.xml have this U+25CC hack, although the
+     counterpart files named res/xml-v16/key_*.xml don't have this hack. -->
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <switch>
+        <case
+            latin:keyboardLayoutSet="hindi"
+        >
+            <!-- U+094B/U+0902: "қं" DEVANAGARI VOWEL SIGN O/DEVANAGARI SIGN ANUSVARA
+                 U+0949: "ॉ" DEVANAGARI VOWEL SIGN CANDRA O
+                 U+094A: "ॊ" DEVANAGARI VOWEL SIGN SHORT O -->
+            <key-style
+                latin:styleName="moreKeysDevanagariVowelSignO"
+                latin:moreKeys="&#x094B;&#x0902;,&#x0949;,&#x094A;" />
+        </case>
+        <default>
+             <key-style
+                latin:styleName="moreKeysDevanagariVowelSignO" />
+        </default>
+    </switch>
+    <!-- U+094B: "ो" DEVANAGARI VOWEL SIGN O -->
+    <key-style
+        latin:styleName="baseKeyDevanagariVowelSignO"
+        latin:parentStyle="moreKeysDevanagariVowelSignO"
+        latin:keyLabel="&#x094B;"
+        latin:keyLabelFlags="fontNormal" />
+</merge>
diff --git a/java/res/xml-v16/keystyle_devanagari_vowel_sign_u.xml b/java/res/xml-v16/keystyle_devanagari_vowel_sign_u.xml
new file mode 100644
index 0000000..469a27b
--- /dev/null
+++ b/java/res/xml-v16/keystyle_devanagari_vowel_sign_u.xml
@@ -0,0 +1,49 @@
+<?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.
+*/
+-->
+
+<!-- The code point U+25CC for key label is needed because the font rendering system prior to
+     API version 16 can't automatically render dotted circle for incomplete combining letter
+     of some scripts. The files named res/xml/key_*.xml have this U+25CC hack, although the
+     counterpart files named res/xml-v16/key_*.xml don't have this hack. -->
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <switch>
+        <case
+            latin:keyboardLayoutSet="hindi"
+        >
+            <!-- U+0941/U+0902: "ुं" DEVANAGARI VOWEL SIGN U/DEVANAGARI SIGN ANUSVARA
+                 U+0941/U+0901: "ुँ" DEVANAGARI VOWEL SIGN U/DEVANAGARI SIGN CANDRABINDU -->
+            <key-style
+                latin:styleName="moreKeysDevanagariVowelSignU"
+                latin:moreKeys="&#x0941;&#x0902;,&#x0941;&#x0901;" />
+        </case>
+        <default>
+             <key-style
+                latin:styleName="moreKeysDevanagariVowelSignU" />
+        </default>
+    </switch>
+    <!-- U+0941: "ु" DEVANAGARI VOWEL SIGN U -->
+    <key-style
+        latin:styleName="baseKeyDevanagariVowelSignU"
+        latin:parentStyle="moreKeysDevanagariVowelSignU"
+        latin:keyLabel="&#x0941;"
+        latin:keyLabelFlags="fontNormal" />
+</merge>
diff --git a/java/res/xml-v16/keystyle_devanagari_vowel_sign_uu.xml b/java/res/xml-v16/keystyle_devanagari_vowel_sign_uu.xml
new file mode 100644
index 0000000..25867c0
--- /dev/null
+++ b/java/res/xml-v16/keystyle_devanagari_vowel_sign_uu.xml
@@ -0,0 +1,49 @@
+<?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.
+*/
+-->
+
+<!-- The code point U+25CC for key label is needed because the font rendering system prior to
+     API version 16 can't automatically render dotted circle for incomplete combining letter
+     of some scripts. The files named res/xml/key_*.xml have this U+25CC hack, although the
+     counterpart files named res/xml-v16/key_*.xml don't have this hack. -->
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <switch>
+        <case
+            latin:keyboardLayoutSet="hindi"
+        >
+            <!-- U+0942/U+0902: "ूं" DEVANAGARI VOWEL SIGN UU/DEVANAGARI SIGN ANUSVARA
+                 U+0942/U+0901: "ूँ" DEVANAGARI VOWEL SIGN UU/DEVANAGARI SIGN CANDRABINDU -->
+            <key-style
+                latin:styleName="moreKeysDevanagariVowelSignUu"
+                latin:moreKeys="&#x0942;&#x0902;,&#x0942;&#x0901;,%" />
+        </case>
+        <default>
+             <key-style
+                latin:styleName="moreKeysDevanagariVowelSignUu" />
+        </default>
+    </switch>
+    <!-- U+0942: "ू" DEVANAGARI VOWEL SIGN UU -->
+    <key-style
+        latin:styleName="baseKeyDevanagariVowelSignUu"
+        latin:parentStyle="moreKeysDevanagariVowelSignUu"
+        latin:keyLabel="&#x0942;"
+        latin:keyLabelFlags="fontNormal" />
+</merge>
diff --git a/java/res/xml/kbd_10_10_7_symbols_shift.xml b/java/res/xml/kbd_10_10_7_symbols_shift.xml
deleted file mode 100644
index a2d67ca..0000000
--- a/java/res/xml/kbd_10_10_7_symbols_shift.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<Keyboard
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:touchPositionCorrectionData="@array/touch_position_correction_data_default"
->
-    <include
-        latin:keyboardLayout="@xml/rows_symbols_shift" />
-</Keyboard>
diff --git a/java/res/xml-sw768dp/kbd_thai_symbols.xml b/java/res/xml/kbd_armenian_phonetic.xml
similarity index 89%
copy from java/res/xml-sw768dp/kbd_thai_symbols.xml
copy to java/res/xml/kbd_armenian_phonetic.xml
index 5ddf574..1eb3c7e 100644
--- a/java/res/xml-sw768dp/kbd_thai_symbols.xml
+++ b/java/res/xml/kbd_armenian_phonetic.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2012, The Android Open Source Project
+** 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.
@@ -27,5 +27,5 @@
     latin:touchPositionCorrectionData="@array/touch_position_correction_data_default"
 >
     <include
-        latin:keyboardLayout="@xml/rows_thai_symbols" />
+        latin:keyboardLayout="@xml/rows_armenian_phonetic" />
 </Keyboard>
diff --git a/java/res/xml/kbd_10_10_7_symbols.xml b/java/res/xml/kbd_emoji_category1.xml
similarity index 68%
copy from java/res/xml/kbd_10_10_7_symbols.xml
copy to java/res/xml/kbd_emoji_category1.xml
index 4d9861b..c11a830 100644
--- a/java/res/xml/kbd_10_10_7_symbols.xml
+++ b/java/res/xml/kbd_emoji_category1.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2012, The Android Open Source Project
+** 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.
@@ -20,8 +20,12 @@
 
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:touchPositionCorrectionData="@array/touch_position_correction_data_default"
+    latin:keyWidth="@fraction/emoji_keyboard_key_width"
+    latin:keyLetterSize="90%p"
+    latin:rowHeight="@fraction/emoji_keyboard_row_height"
 >
-    <include
-        latin:keyboardLayout="@xml/rows_symbols" />
+    <GridRows
+        latin:codesArray="@array/emoji_faces"
+        latin:keyLabelFlags="fontNormal"
+        latin:backgroundType="empty" />
 </Keyboard>
diff --git a/java/res/xml-sw600dp/kbd_10_10_7_symbols.xml b/java/res/xml/kbd_emoji_category2.xml
similarity index 67%
rename from java/res/xml-sw600dp/kbd_10_10_7_symbols.xml
rename to java/res/xml/kbd_emoji_category2.xml
index dd545b5..d3e5890 100644
--- a/java/res/xml-sw600dp/kbd_10_10_7_symbols.xml
+++ b/java/res/xml/kbd_emoji_category2.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2012, The Android Open Source Project
+** 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.
@@ -20,8 +20,12 @@
 
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:touchPositionCorrectionData="@array/touch_position_correction_data_default"
+    latin:keyWidth="@fraction/emoji_keyboard_key_width"
+    latin:keyLetterSize="90%p"
+    latin:rowHeight="@fraction/emoji_keyboard_row_height"
 >
-    <include
-        latin:keyboardLayout="@xml/rows_10_10_7_symbols" />
+    <GridRows
+        latin:codesArray="@array/emoji_objects"
+        latin:keyLabelFlags="fontNormal"
+        latin:backgroundType="empty" />
 </Keyboard>
diff --git a/java/res/xml/kbd_10_10_7_symbols.xml b/java/res/xml/kbd_emoji_category3.xml
similarity index 68%
copy from java/res/xml/kbd_10_10_7_symbols.xml
copy to java/res/xml/kbd_emoji_category3.xml
index 4d9861b..0efafa8 100644
--- a/java/res/xml/kbd_10_10_7_symbols.xml
+++ b/java/res/xml/kbd_emoji_category3.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2012, The Android Open Source Project
+** 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.
@@ -20,8 +20,12 @@
 
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:touchPositionCorrectionData="@array/touch_position_correction_data_default"
+    latin:keyWidth="@fraction/emoji_keyboard_key_width"
+    latin:keyLetterSize="90%p"
+    latin:rowHeight="@fraction/emoji_keyboard_row_height"
 >
-    <include
-        latin:keyboardLayout="@xml/rows_symbols" />
+    <GridRows
+        latin:codesArray="@array/emoji_nature"
+        latin:keyLabelFlags="fontNormal"
+        latin:backgroundType="empty" />
 </Keyboard>
diff --git a/java/res/xml/kbd_10_10_7_symbols.xml b/java/res/xml/kbd_emoji_category4.xml
similarity index 68%
copy from java/res/xml/kbd_10_10_7_symbols.xml
copy to java/res/xml/kbd_emoji_category4.xml
index 4d9861b..e529120 100644
--- a/java/res/xml/kbd_10_10_7_symbols.xml
+++ b/java/res/xml/kbd_emoji_category4.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2012, The Android Open Source Project
+** 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.
@@ -20,8 +20,12 @@
 
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:touchPositionCorrectionData="@array/touch_position_correction_data_default"
+    latin:keyWidth="@fraction/emoji_keyboard_key_width"
+    latin:keyLetterSize="90%p"
+    latin:rowHeight="@fraction/emoji_keyboard_row_height"
 >
-    <include
-        latin:keyboardLayout="@xml/rows_symbols" />
+    <GridRows
+        latin:codesArray="@array/emoji_places"
+        latin:keyLabelFlags="fontNormal"
+        latin:backgroundType="empty" />
 </Keyboard>
diff --git a/java/res/xml-sw600dp/kbd_10_10_7_symbols.xml b/java/res/xml/kbd_emoji_category5.xml
similarity index 67%
copy from java/res/xml-sw600dp/kbd_10_10_7_symbols.xml
copy to java/res/xml/kbd_emoji_category5.xml
index dd545b5..1836879 100644
--- a/java/res/xml-sw600dp/kbd_10_10_7_symbols.xml
+++ b/java/res/xml/kbd_emoji_category5.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2012, The Android Open Source Project
+** 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.
@@ -20,8 +20,12 @@
 
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:touchPositionCorrectionData="@array/touch_position_correction_data_default"
+    latin:keyWidth="@fraction/emoji_keyboard_key_width"
+    latin:keyLetterSize="90%p"
+    latin:rowHeight="@fraction/emoji_keyboard_row_height"
 >
-    <include
-        latin:keyboardLayout="@xml/rows_10_10_7_symbols" />
+    <GridRows
+        latin:codesArray="@array/emoji_symbols"
+        latin:keyLabelFlags="fontNormal"
+        latin:backgroundType="empty" />
 </Keyboard>
diff --git a/java/res/xml-sw600dp/kbd_10_10_7_symbols.xml b/java/res/xml/kbd_emoji_category6.xml
similarity index 65%
copy from java/res/xml-sw600dp/kbd_10_10_7_symbols.xml
copy to java/res/xml/kbd_emoji_category6.xml
index dd545b5..b47ebfe 100644
--- a/java/res/xml-sw600dp/kbd_10_10_7_symbols.xml
+++ b/java/res/xml/kbd_emoji_category6.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2012, The Android Open Source Project
+** 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.
@@ -20,8 +20,13 @@
 
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:touchPositionCorrectionData="@array/touch_position_correction_data_default"
+    latin:keyWidth="@fraction/emoji_keyboard_key_width"
+    latin:keyLetterSize="90%p"
+    latin:keyLabelSize="60%p"
+    latin:rowHeight="@fraction/emoji_keyboard_row_height"
 >
-    <include
-        latin:keyboardLayout="@xml/rows_10_10_7_symbols" />
+    <GridRows
+        latin:textsArray="@array/emoji_emoticons"
+        latin:keyLabelFlags="fontNormal"
+        latin:backgroundType="empty" />
 </Keyboard>
diff --git a/java/res/xml/kbd_10_10_7_symbols.xml b/java/res/xml/kbd_emoji_recents.xml
similarity index 64%
copy from java/res/xml/kbd_10_10_7_symbols.xml
copy to java/res/xml/kbd_emoji_recents.xml
index 4d9861b..73926ec 100644
--- a/java/res/xml/kbd_10_10_7_symbols.xml
+++ b/java/res/xml/kbd_emoji_recents.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2012, The Android Open Source Project
+** 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.
@@ -20,8 +20,13 @@
 
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:touchPositionCorrectionData="@array/touch_position_correction_data_default"
+    latin:keyWidth="@fraction/emoji_keyboard_key_width"
+    latin:keyLetterSize="@fraction/emoji_keyboard_key_letter_size"
+    latin:keyLabelSize="60%p"
+    latin:rowHeight="@fraction/emoji_keyboard_row_height"
 >
-    <include
-        latin:keyboardLayout="@xml/rows_symbols" />
+    <GridRows
+        latin:codesArray="@array/emoji_recents"
+        latin:keyLabelFlags="fontNormal"
+        latin:backgroundType="empty" />
 </Keyboard>
diff --git a/java/res/xml-sw768dp/kbd_thai_symbols.xml b/java/res/xml/kbd_khmer.xml
similarity index 89%
copy from java/res/xml-sw768dp/kbd_thai_symbols.xml
copy to java/res/xml/kbd_khmer.xml
index 5ddf574..7a2337a 100644
--- a/java/res/xml-sw768dp/kbd_thai_symbols.xml
+++ b/java/res/xml/kbd_khmer.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2012, The Android Open Source Project
+** 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.
@@ -27,5 +27,5 @@
     latin:touchPositionCorrectionData="@array/touch_position_correction_data_default"
 >
     <include
-        latin:keyboardLayout="@xml/rows_thai_symbols" />
+        latin:keyboardLayout="@xml/rows_khmer" />
 </Keyboard>
diff --git a/java/res/xml-sw768dp/kbd_thai_symbols.xml b/java/res/xml/kbd_lao.xml
similarity index 89%
rename from java/res/xml-sw768dp/kbd_thai_symbols.xml
rename to java/res/xml/kbd_lao.xml
index 5ddf574..2bba330 100644
--- a/java/res/xml-sw768dp/kbd_thai_symbols.xml
+++ b/java/res/xml/kbd_lao.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2012, The Android Open Source Project
+** 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.
@@ -27,5 +27,5 @@
     latin:touchPositionCorrectionData="@array/touch_position_correction_data_default"
 >
     <include
-        latin:keyboardLayout="@xml/rows_thai_symbols" />
+        latin:keyboardLayout="@xml/rows_lao" />
 </Keyboard>
diff --git a/java/res/xml/kbd_10_10_7_symbols.xml b/java/res/xml/kbd_nepali_romanized.xml
similarity index 79%
copy from java/res/xml/kbd_10_10_7_symbols.xml
copy to java/res/xml/kbd_nepali_romanized.xml
index 4d9861b..9e43813 100644
--- a/java/res/xml/kbd_10_10_7_symbols.xml
+++ b/java/res/xml/kbd_nepali_romanized.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2012, The Android Open Source Project
+** 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.
@@ -20,8 +20,7 @@
 
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:touchPositionCorrectionData="@array/touch_position_correction_data_default"
 >
     <include
-        latin:keyboardLayout="@xml/rows_symbols" />
+        latin:keyboardLayout="@xml/rows_nepali_romanized" />
 </Keyboard>
diff --git a/java/res/xml/kbd_10_10_7_symbols.xml b/java/res/xml/kbd_nepali_traditional.xml
similarity index 79%
copy from java/res/xml/kbd_10_10_7_symbols.xml
copy to java/res/xml/kbd_nepali_traditional.xml
index 4d9861b..6854e32 100644
--- a/java/res/xml/kbd_10_10_7_symbols.xml
+++ b/java/res/xml/kbd_nepali_traditional.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2012, The Android Open Source Project
+** 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.
@@ -20,8 +20,7 @@
 
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:touchPositionCorrectionData="@array/touch_position_correction_data_default"
 >
     <include
-        latin:keyboardLayout="@xml/rows_symbols" />
+        latin:keyboardLayout="@xml/rows_nepali_traditional" />
 </Keyboard>
diff --git a/java/res/xml/kbd_pcqwerty_symbols.xml b/java/res/xml/kbd_pcqwerty_symbols.xml
deleted file mode 100644
index bfb39e8..0000000
--- a/java/res/xml/kbd_pcqwerty_symbols.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<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_pcqwerty_symbols" />
-</Keyboard>
diff --git a/java/res/xml/kbd_thai_symbols.xml b/java/res/xml/kbd_thai_symbols.xml
deleted file mode 100644
index 4d9861b..0000000
--- a/java/res/xml/kbd_thai_symbols.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<Keyboard
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:touchPositionCorrectionData="@array/touch_position_correction_data_default"
->
-    <include
-        latin:keyboardLayout="@xml/rows_symbols" />
-</Keyboard>
diff --git a/java/res/xml/kbd_thai_symbols_shift.xml b/java/res/xml/kbd_thai_symbols_shift.xml
deleted file mode 100644
index a2d67ca..0000000
--- a/java/res/xml/kbd_thai_symbols_shift.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<Keyboard
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:touchPositionCorrectionData="@array/touch_position_correction_data_default"
->
-    <include
-        latin:keyboardLayout="@xml/rows_symbols_shift" />
-</Keyboard>
diff --git a/java/res/xml/kbd_10_10_7_symbols.xml b/java/res/xml/key_armenian_sha.xml
similarity index 75%
copy from java/res/xml/kbd_10_10_7_symbols.xml
copy to java/res/xml/key_armenian_sha.xml
index 4d9861b..3865c19 100644
--- a/java/res/xml/kbd_10_10_7_symbols.xml
+++ b/java/res/xml/key_armenian_sha.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2012, The Android Open Source Project
+** 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.
@@ -18,10 +18,11 @@
 */
 -->
 
-<Keyboard
+<merge
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:touchPositionCorrectionData="@array/touch_position_correction_data_default"
 >
-    <include
-        latin:keyboardLayout="@xml/rows_symbols" />
-</Keyboard>
+    <!-- U+0577: "շ" ARMENIAN SMALL LETTER SHA -->
+    <Key
+        latin:keyLabel="&#x0577;"
+        latin:keyLabelFlags="fontNormal" />
+</merge>
diff --git a/java/res/xml/kbd_10_10_7_symbols.xml b/java/res/xml/key_armenian_xeh.xml
similarity index 75%
copy from java/res/xml/kbd_10_10_7_symbols.xml
copy to java/res/xml/key_armenian_xeh.xml
index 4d9861b..007a580 100644
--- a/java/res/xml/kbd_10_10_7_symbols.xml
+++ b/java/res/xml/key_armenian_xeh.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2012, The Android Open Source Project
+** 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.
@@ -18,10 +18,11 @@
 */
 -->
 
-<Keyboard
+<merge
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:touchPositionCorrectionData="@array/touch_position_correction_data_default"
 >
-    <include
-        latin:keyboardLayout="@xml/rows_symbols" />
-</Keyboard>
+    <!-- U+056D: "խ" ARMENIAN SMALL LETTER XEH -->
+    <Key
+        latin:keyLabel="&#x056D;"
+        latin:keyLabelFlags="fontNormal" />
+</merge>
diff --git a/java/res/xml/key_hindi1_shift.xml b/java/res/xml/key_devanagari_sign_anusvara.xml
similarity index 77%
copy from java/res/xml/key_hindi1_shift.xml
copy to java/res/xml/key_devanagari_sign_anusvara.xml
index 0db5ae9..0acd3bc 100644
--- a/java/res/xml/key_hindi1_shift.xml
+++ b/java/res/xml/key_devanagari_sign_anusvara.xml
@@ -20,15 +20,15 @@
 
 <!-- The code point U+25CC for key label is needed because the font rendering system prior to
      API version 16 can't automatically render dotted circle for incomplete combining letter
-     of Hindi. The files named res/xml/{key,keys}_hindi*.xml have this U+25CC hack, although the
-     counterpart files named res/xml-v16/{key,keys}_hindi*.xml don't have this hack. -->
+     of some scripts. The files named res/xml/key_*.xml have this U+25CC hack, although the
+     counterpart files named res/xml-v16/key_*.xml don't have this hack. -->
 <merge
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
 >
     <!-- U+25CC: "◌" DOTTED CIRCLE
-         U+0903: "ः" DEVANAGARI SIGN VISARGA -->
+         U+0902: "ं" DEVANAGARI SIGN ANUSVARA -->
     <Key
-        latin:keyLabel="&#x25CC;&#x0903;"
-        latin:code="0x0903"
+        latin:keyLabel="&#x25CC;&#x0902;"
+        latin:code="0x0902"
         latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
 </merge>
diff --git a/java/res/xml/key_devanagari_sign_candrabindu.xml b/java/res/xml/key_devanagari_sign_candrabindu.xml
new file mode 100644
index 0000000..df0c4e0
--- /dev/null
+++ b/java/res/xml/key_devanagari_sign_candrabindu.xml
@@ -0,0 +1,50 @@
+<?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.
+*/
+-->
+
+<!-- The code point U+25CC for key label is needed because the font rendering system prior to
+     API version 16 can't automatically render dotted circle for incomplete combining letter
+     of some scripts. The files named res/xml/key_*.xml have this U+25CC hack, although the
+     counterpart files named res/xml-v16/key_*.xml don't have this hack. -->
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <switch>
+        <case
+            latin:keyboardLayoutSet="hindi"
+        >
+            <!-- U+25CC: "◌" DOTTED CIRCLE
+                 U+0945: "ॅ" DEVANAGARI VOWEL SIGN CANDRA E -->
+            <key-style
+                latin:styleName="moreKeysDevanagariSignCandrabindu"
+                latin:moreKeys="&#x25CC;&#x0945;|&#x0945;" />
+        </case>
+        <default>
+             <key-style
+                latin:styleName="moreKeysDevanagariSignCandrabindu" />
+        </default>
+    </switch>
+    <!-- U+25CC: "◌" DOTTED CIRCLE
+         U+0901: "ँ" DEVANAGARI SIGN CANDRABINDU -->
+    <Key
+        latin:keyStyle="moreKeysDevanagariSignCandrabindu"
+        latin:keyLabel="&#x25CC;&#x0901;"
+        latin:code="0x0901"
+        latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
+</merge>
diff --git a/java/res/xml/key_devanagari_sign_nukta.xml b/java/res/xml/key_devanagari_sign_nukta.xml
new file mode 100644
index 0000000..f7a03ee
--- /dev/null
+++ b/java/res/xml/key_devanagari_sign_nukta.xml
@@ -0,0 +1,52 @@
+<?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.
+*/
+-->
+
+<!-- The code point U+25CC for key label is needed because the font rendering system prior to
+     API version 16 can't automatically render dotted circle for incomplete combining letter
+     of some scripts. The files named res/xml/key_*.xml have this U+25CC hack, although the
+     counterpart files named res/xml-v16/key_*.xml don't have this hack. -->
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <switch>
+        <case
+            latin:keyboardLayoutSet="hindi"
+        >
+            <!-- U+25CC: "◌" DOTTED CIRCLE
+                 U+097D: "ॽ" DEVANAGARI LETTER GLOTTAL STOP
+                 U+0970: "॰" DEVANAGARI ABBREVIATION SIGN
+                 U+093D: "ऽ" DEVANAGARI SIGN AVAGRAHA -->
+             <key-style
+                latin:styleName="moreKeysDevanagariSignNukta"
+                latin:moreKeys="&#x25CC;&#x097D;|&#x097D;,&#x25CC;&#x0970;|&#x0970;,&#x25CC;&#x093D;|&#x093D;" />
+        </case>
+        <default>
+             <key-style
+                latin:styleName="moreKeysDevanagariSignNukta" />
+        </default>
+    </switch>
+    <!-- U+25CC: "◌" DOTTED CIRCLE
+         U+093C: "़" DEVANAGARI SIGN NUKTA -->
+    <Key
+        latin:keyStyle="moreKeysDevanagariSignNukta"
+        latin:keyLabel="&#x25CC;&#x093C;"
+        latin:code="0x093C"
+        latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
+</merge>
diff --git a/java/res/xml/key_hindi1_shift.xml b/java/res/xml/key_devanagari_vowel_sign_candra_o.xml
similarity index 77%
copy from java/res/xml/key_hindi1_shift.xml
copy to java/res/xml/key_devanagari_vowel_sign_candra_o.xml
index 0db5ae9..370fc54 100644
--- a/java/res/xml/key_hindi1_shift.xml
+++ b/java/res/xml/key_devanagari_vowel_sign_candra_o.xml
@@ -20,15 +20,15 @@
 
 <!-- The code point U+25CC for key label is needed because the font rendering system prior to
      API version 16 can't automatically render dotted circle for incomplete combining letter
-     of Hindi. The files named res/xml/{key,keys}_hindi*.xml have this U+25CC hack, although the
-     counterpart files named res/xml-v16/{key,keys}_hindi*.xml don't have this hack. -->
+     of some scripts. The files named res/xml/key_*.xml have this U+25CC hack, although the
+     counterpart files named res/xml-v16/key_*.xml don't have this hack. -->
 <merge
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
 >
     <!-- U+25CC: "◌" DOTTED CIRCLE
-         U+0903: "ः" DEVANAGARI SIGN VISARGA -->
+         U+0949: "ॉ" DEVANAGARI VOWEL SIGN CANDRA O -->
     <Key
-        latin:keyLabel="&#x25CC;&#x0903;"
-        latin:code="0x0903"
+        latin:keyLabel="&#x25CC;&#x0949;"
+        latin:code="0x0949"
         latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
 </merge>
diff --git a/java/res/xml/key_devanagari_vowel_sign_vocalic_r.xml b/java/res/xml/key_devanagari_vowel_sign_vocalic_r.xml
new file mode 100644
index 0000000..f150d7e
--- /dev/null
+++ b/java/res/xml/key_devanagari_vowel_sign_vocalic_r.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.
+*/
+-->
+
+<!-- The code point U+25CC for key label is needed because the font rendering system prior to
+     API version 16 can't automatically render dotted circle for incomplete combining letter
+     of some scripts. The files named res/xml/key_*.xml have this U+25CC hack, although the
+     counterpart files named res/xml-v16/key_*.xml don't have this hack. -->
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <switch>
+        <case
+            latin:keyboardLayoutSet="hindi"
+        >
+            <!-- U+25CC: "◌" DOTTED CIRCLE
+                 U+0944: "ॄ" DEVANAGARI VOWEL SIGN VOCALIC RR -->
+            <key-style
+                latin:styleName="moreKeysDevanagariVowelSignVocalicR"
+                latin:moreKeys="&#x25CC;&#x0944;|&#x0944;" />
+        </case>
+        <case
+            latin:keyboardLayoutSet="nepali_traditional"
+        >
+            <!-- U+0913: "ओ" DEVANAGARI LETTER O -->
+            <key-style
+                latin:styleName="moreKeysDevanagariVowelSignVocalicR"
+                latin:moreKeys="&#x0913;" />
+        </case>
+        <default>
+             <key-style
+                latin:styleName="moreKeysDevanagariVowelSignVocalicR" />
+        </default>
+    </switch>
+    <!-- U+25CC: "◌" DOTTED CIRCLE
+         U+0943: "ृ" DEVANAGARI VOWEL SIGN VOCALIC R -->
+    <Key
+        latin:keyStyle="moreKeysDevanagariVowelSignVocalicR"
+        latin:keyLabel="&#x25CC;&#x0943;"
+        latin:code="0x0943"
+        latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
+</merge>
diff --git a/java/res/xml-sw768dp/key_settings.xml b/java/res/xml/key_f2.xml
similarity index 78%
rename from java/res/xml-sw768dp/key_settings.xml
rename to java/res/xml/key_f2.xml
index 0d3bb59..473dd21 100644
--- a/java/res/xml-sw768dp/key_settings.xml
+++ b/java/res/xml/key_f2.xml
@@ -23,13 +23,16 @@
 >
     <switch>
         <case
-            latin:clobberSettingsKey="false"
+            latin:mode="email|url"
         >
             <Key
-                latin:keyStyle="settingsKeyStyle" />
+                latin:keyStyle="comKeyStyle"
+                latin:keyWidth="fillRight" />
         </case>
         <default>
-            <Spacer />
+            <Key
+                latin:keyStyle="emojiKeyStyle"
+                latin:keyWidth="fillRight" />
         </default>
     </switch>
 </merge>
diff --git a/java/res/xml/key_hindi3_right.xml b/java/res/xml/key_hindi3_right.xml
deleted file mode 100644
index 5a97355..0000000
--- a/java/res/xml/key_hindi3_right.xml
+++ /dev/null
@@ -1,38 +0,0 @@
-<?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.
-*/
--->
-
-<!-- The code point U+25CC for key label is needed because the font rendering system prior to
-     API version 16 can't automatically render dotted circle for incomplete combining letter
-     of Hindi. The files named res/xml/{key,keys}_hindi*.xml have this U+25CC hack, although the
-     counterpart files named res/xml-v16/{key,keys}_hindi*.xml don't have this hack. -->
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <!-- U+25CC: "◌" DOTTED CIRCLE
-         U+093C: "़" DEVANAGARI SIGN NUKTA
-         U+097D: "ॽ" DEVANAGARI LETTER GLOTTAL STOP
-         U+0970: "॰" DEVANAGARI ABBREVIATION SIGN
-         U+093D: "ऽ" DEVANAGARI SIGN AVAGRAHA -->
-    <Key
-        latin:keyLabel="&#x25CC;&#x093C;"
-        latin:code="0x093C"
-        latin:moreKeys="&#x25CC;&#x097D;|&#x097D;,&#x25CC;&#x0970;|&#x0970;,&#x25CC;&#x093D;|&#x093D;"
-        latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
-</merge>
diff --git a/java/res/xml/key_hindi3_shift_left.xml b/java/res/xml/key_hindi3_shift_left.xml
deleted file mode 100644
index c5e2f13..0000000
--- a/java/res/xml/key_hindi3_shift_left.xml
+++ /dev/null
@@ -1,36 +0,0 @@
-<?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.
-*/
--->
-
-<!-- The code point U+25CC for key label is needed because the font rendering system prior to
-     API version 16 can't automatically render dotted circle for incomplete combining letter
-     of Hindi. The files named res/xml/{key,keys}_hindi*.xml have this U+25CC hack, although the
-     counterpart files named res/xml-v16/{key,keys}_hindi*.xml don't have this hack. -->
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <!-- U+25CC: "◌" DOTTED CIRCLE
-         U+0901: "ँ" DEVANAGARI SIGN CANDRABINDU
-         U+0945: "ॅ" DEVANAGARI VOWEL SIGN CANDRA E -->
-    <Key
-        latin:keyLabel="&#x25CC;&#x0901;"
-        latin:code="0x0901"
-        latin:moreKeys="&#x25CC;&#x0945;|&#x0945;"
-        latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
-</merge>
diff --git a/java/res/xml/key_hindi3_shift_right.xml b/java/res/xml/key_hindi3_shift_right.xml
deleted file mode 100644
index 0da116a..0000000
--- a/java/res/xml/key_hindi3_shift_right.xml
+++ /dev/null
@@ -1,36 +0,0 @@
-<?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.
-*/
--->
-
-<!-- The code point U+25CC for key label is needed because the font rendering system prior to
-     API version 16 can't automatically render dotted circle for incomplete combining letter
-     of Hindi. The files named res/xml/{key,keys}_hindi*.xml have this U+25CC hack, although the
-     counterpart files named res/xml-v16/{key,keys}_hindi*.xml don't have this hack. -->
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <!-- U+25CC: "◌" DOTTED CIRCLE
-         U+0943: "ृ" DEVANAGARI VOWEL SIGN VOCALIC R
-         U+0944: "ॄ" DEVANAGARI VOWEL SIGN VOCALIC RR -->
-    <Key
-        latin:keyLabel="&#x25CC;&#x0943;"
-        latin:code="0x0943"
-        latin:moreKeys="&#x25CC;&#x0944;|&#x0944;"
-        latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
-</merge>
diff --git a/java/res/xml/key_nepali_traditional_period.xml b/java/res/xml/key_nepali_traditional_period.xml
new file mode 100644
index 0000000..0f575c5
--- /dev/null
+++ b/java/res/xml/key_nepali_traditional_period.xml
@@ -0,0 +1,49 @@
+<?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.
+*/
+-->
+
+<!-- The code point U+25CC for key label is needed because the font rendering system prior to
+     API version 16 can't automatically render dotted circle for incomplete combining letter
+     of Hindi. The files named res/xml/{key,keys}_nepali*.xml have this U+25CC hack, although the
+     counterpart files named res/xml-v16/{key,keys}_nepali*.xml don't have this hack. -->
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <switch>
+        <case
+            latin:keyboardLayoutSetElement="alphabetManualShifted|alphabetShiftLocked|alphabetShiftLockShifted"
+        >
+            <Key
+                latin:keyLabel=","
+                latin:backgroundType="functional" />
+        </case>
+        <default>
+            <!-- Because the font rendering system prior to API version 16 can't automatically
+                 render dotted circle for incomplete combining letter of some scripts, different
+                 set of Key definitions are needed based on the API version. -->
+            <include
+                latin:keyboardLayout="@xml/keystyle_devanagari_sign_virama" />
+            <Key
+                latin:keyStyle="baseKeyDevanagariSignVirama"
+                latin:keyLabelFlags="hasPopupHint"
+                latin:moreKeys="!fixedColumnOrder!4,.,!text/more_keys_for_punctuation"
+                latin:backgroundType="functional" />
+        </default>
+    </switch>
+</merge>
diff --git a/java/res/xml-sw600dp/key_f2.xml b/java/res/xml/key_space_3kw.xml
similarity index 66%
rename from java/res/xml-sw600dp/key_f2.xml
rename to java/res/xml/key_space_3kw.xml
index ca3b30b..20ec882 100644
--- a/java/res/xml-sw600dp/key_f2.xml
+++ b/java/res/xml/key_space_3kw.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2012, The Android Open Source Project
+** 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.
@@ -23,23 +23,19 @@
 >
     <switch>
         <case
-            latin:mode="email|url"
+            latin:languageSwitchKeyEnabled="true"
         >
             <Key
-                latin:keyStyle="comKeyStyle" />
-        </case>
-        <case
-            latin:imeAction="actionSearch"
-        >
+                latin:keyStyle="languageSwitchKeyStyle" />
             <Key
-                latin:keyLabel=":"
-                latin:keyHintLabel="+"
-                latin:moreKeys="+"
-                latin:keyStyle="hasShiftedLetterHintStyle" />
+                latin:keyStyle="spaceKeyStyle"
+                latin:keyWidth="20%p" />
         </case>
+        <!-- languageSwitchKeyEnabled="false" -->
         <default>
             <Key
-                latin:keyStyle="smileyKeyStyle" />
+                latin:keyStyle="spaceKeyStyle"
+                latin:keyWidth="30%p" />
         </default>
     </switch>
 </merge>
diff --git a/java/res/xml/key_space.xml b/java/res/xml/key_space_5kw.xml
similarity index 95%
rename from java/res/xml/key_space.xml
rename to java/res/xml/key_space_5kw.xml
index 02ee42f..b6d38fb 100644
--- a/java/res/xml/key_space.xml
+++ b/java/res/xml/key_space_5kw.xml
@@ -23,7 +23,7 @@
 >
     <switch>
         <case
-            latin:languageCode="fa"
+            latin:languageCode="fa|ne"
             latin:languageSwitchKeyEnabled="true"
         >
             <Key
@@ -35,7 +35,7 @@
                 latin:keyStyle="zwnjKeyStyle" />
         </case>
         <case
-            latin:languageCode="fa"
+            latin:languageCode="fa|ne"
             latin:languageSwitchKeyEnabled="false"
         >
             <Key
diff --git a/java/res/xml/kbd_10_10_7_symbols.xml b/java/res/xml/key_space_symbols.xml
similarity index 82%
rename from java/res/xml/kbd_10_10_7_symbols.xml
rename to java/res/xml/key_space_symbols.xml
index 4d9861b..1efc4ff 100644
--- a/java/res/xml/kbd_10_10_7_symbols.xml
+++ b/java/res/xml/key_space_symbols.xml
@@ -18,10 +18,9 @@
 */
 -->
 
-<Keyboard
+<merge
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:touchPositionCorrectionData="@array/touch_position_correction_data_default"
 >
     <include
-        latin:keyboardLayout="@xml/rows_symbols" />
-</Keyboard>
+        latin:keyboardLayout="@xml/key_space_3kw" />
+</merge>
diff --git a/java/res/xml/key_styles_common.xml b/java/res/xml/key_styles_common.xml
index 355455e..67ed962 100644
--- a/java/res/xml/key_styles_common.xml
+++ b/java/res/xml/key_styles_common.xml
@@ -21,6 +21,20 @@
 <merge
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
 >
+    <switch>
+        <case
+            latin:keyboardLayoutSetElement="alphabetManualShifted|alphabetShiftLockShifted"
+        >
+            <key-style
+                latin:styleName="hasShiftedLetterHintStyle"
+                latin:keyLabelFlags="hasShiftedLetterHint|shiftedLetterActivated" />
+        </case>
+        <default>
+            <key-style
+                latin:styleName="hasShiftedLetterHintStyle"
+                latin:keyLabelFlags="hasShiftedLetterHint" />
+        </default>
+    </switch>
     <!-- Base key style for the key which may have settings or tab key as popup key. -->
     <include
         latin:keyboardLayout="@xml/key_styles_f1" />
@@ -67,30 +81,6 @@
         latin:backgroundType="functional" />
     <include
         latin:keyboardLayout="@xml/key_styles_enter" />
-    <switch>
-        <!-- Shift + Enter in textMultiLine field. -->
-        <case
-            latin:isMultiLine="true"
-            latin:keyboardLayoutSetElement="alphabetManualShifted|alphabetShiftLockShifted"
-        >
-            <key-style
-                latin:styleName="enterKeyStyle"
-                latin:parentStyle="shiftEnterKeyStyle" />
-        </case>
-        <!-- Smiley in textShortMessage field.
-             Overrides common enter key style. -->
-        <case
-            latin:mode="im"
-        >
-            <key-style
-                latin:styleName="enterKeyStyle"
-                latin:keyLabel=":-)"
-                latin:keyOutputText=":-) "
-                latin:keyLabelFlags="hasPopupHint"
-                latin:moreKeys="!text/more_keys_for_smiley"
-                latin:backgroundType="functional" />
-        </case>
-    </switch>
     <key-style
         latin:styleName="spaceKeyStyle"
         latin:code="!code/key_space"
@@ -129,7 +119,8 @@
         latin:styleName="emojiKeyStyle"
         latin:code="!code/key_emoji"
         latin:keyIcon="!icon/emoji_key"
-        latin:keyActionFlags="noKeyPreview" />
+        latin:keyActionFlags="noKeyPreview"
+        latin:backgroundType="functional" />
     <key-style
         latin:styleName="tabKeyStyle"
         latin:code="!code/key_tab"
@@ -193,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/key_styles_currency.xml b/java/res/xml/key_styles_currency.xml
index 76fe0e6..84c2abc 100644
--- a/java/res/xml/key_styles_currency.xml
+++ b/java/res/xml/key_styles_currency.xml
@@ -95,22 +95,26 @@
         <!-- fa: Persian (Rial and Afgahni)
              hi: Hindi (Indian Rupee)
              iw: Hebrew (New Sheqel)
+             lo: Lao (Kip)
              mn: Mongolian (Tugrik)
+             ne: Nepali (Nepalese Rupee)
              th: Thai (Baht)
              uk: Ukrainian (Hryvnia)
              vi: Vietnamese (Dong)  -->
         <!-- TODO: The currency sign of Turkish Lira was created in 2012 and assigned U+20BA for
              its unicode, although there is no font glyph for it as of November 2012. -->
+        <!-- TODO: The currency sign of Armenian Dram was created in 2012 and assigned U+058F for
+             its unicode, although there is no font glyph for it as of September 2013. -->
         <case
-            latin:languageCode="fa|hi|iw|mn|th|uk|vi"
+            latin:languageCode="fa|hi|iw|lo|mn|ne|th|uk|vi"
         >
             <!-- U+00A3: "£" POUND SIGN
                  U+20AC: "€" EURO SIGN
                  U+00A2: "¢" CENT SIGN -->
             <key-style
                 latin:styleName="currencyKeyStyle"
-                latin:keyLabel="!text/keylabel_for_currency_generic"
-                latin:moreKeys="!text/more_keys_for_currency_generic" />
+                latin:keyLabel="!text/keylabel_for_currency"
+                latin:moreKeys="!text/more_keys_for_currency" />
             <key-style
                 latin:styleName="moreCurrency1KeyStyle"
                 latin:keyLabel="&#x00A3;" />
diff --git a/java/res/xml/key_symbols_period.xml b/java/res/xml/key_symbols_period.xml
new file mode 100644
index 0000000..6efc9de
--- /dev/null
+++ b/java/res/xml/key_symbols_period.xml
@@ -0,0 +1,47 @@
+<?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"
+>
+    <!-- U+2105: "℅" CARE OF
+         U+2122: "™" TRADE MARK SIGN
+         U+00AE: "®" REGISTERED SIGN
+         U+00A9: "©" COPYRIGHT SIGN
+         U+00A7: "§" SECTION SIGN
+         U+00B6: "¶" PILCROW SIGN
+         U+002C: "," COMMA
+         U+2022: "•" BULLET -->
+    <!-- U+00B0: "°" DEGREE SIGN
+         U+2032: "′" PRIME
+         U+2033: "″" DOUBLE PRIME
+         U+2191: "↑" UPWARDS ARROW
+         U+2193: "↓" DOWNWARDS ARROW
+         U+2190: "←" LEFTWARDS ARROW
+         U+2192: "→" RIGHTWARDS ARROW
+         U+2026: "…" HORIZONTAL ELLIPSIS -->
+    <!-- U+0394: "Δ" GREEK CAPITAL LETTER DELTA
+         U+03A0: "Π" GREEK CAPITAL LETTER PI
+         U+03C0: "π" GREEK SMALL LETTER PI -->
+    <Key
+        latin:keyLabel="."
+        latin:keyLabelFlags="hasPopupHint"
+        latin:moreKeys="!fixedColumnOrder!8,&#x2105;,&#x2122;,&#x00AE;,&#x00A9;,&#x00A7;,&#x00B6;,\\,,&#x2022;,&#x00B0;,&#x2032;,&#x2033;,&#x2191;,&#x2193;,&#x2190;,&#x2192;,&#x2026;,!text/more_keys_for_bullet,&#x0394;,&#x03A0;,&#x03C0;" />
+</merge>
diff --git a/java/res/xml/keyboard_layout_set_armenian_phonetic.xml b/java/res/xml/keyboard_layout_set_armenian_phonetic.xml
new file mode 100644
index 0000000..35bd43f
--- /dev/null
+++ b/java/res/xml/keyboard_layout_set_armenian_phonetic.xml
@@ -0,0 +1,42 @@
+<?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_armenian_phonetic"
+        latin:enableProximityCharsCorrection="true" />
+    <Element
+        latin:elementName="symbols"
+        latin:elementKeyboard="@xml/kbd_symbols" />
+    <Element
+        latin:elementName="symbolsShifted"
+        latin:elementKeyboard="@xml/kbd_symbols_shift" />
+    <Element
+        latin:elementName="phone"
+        latin:elementKeyboard="@xml/kbd_phone" />
+    <Element
+        latin:elementName="phoneSymbols"
+        latin:elementKeyboard="@xml/kbd_phone_symbols" />
+    <Element
+        latin:elementName="number"
+        latin:elementKeyboard="@xml/kbd_number" />
+</KeyboardLayoutSet>
diff --git a/java/res/xml/keyboard_layout_set_azerty.xml b/java/res/xml/keyboard_layout_set_azerty.xml
index 4d144ed..38797f9 100644
--- a/java/res/xml/keyboard_layout_set_azerty.xml
+++ b/java/res/xml/keyboard_layout_set_azerty.xml
@@ -26,10 +26,10 @@
         latin:enableProximityCharsCorrection="true" />
     <Element
         latin:elementName="symbols"
-        latin:elementKeyboard="@xml/kbd_10_10_7_symbols" />
+        latin:elementKeyboard="@xml/kbd_symbols" />
     <Element
         latin:elementName="symbolsShifted"
-        latin:elementKeyboard="@xml/kbd_10_10_7_symbols_shift" />
+        latin:elementKeyboard="@xml/kbd_symbols_shift" />
     <Element
         latin:elementName="phone"
         latin:elementKeyboard="@xml/kbd_phone" />
diff --git a/java/res/xml/keyboard_layout_set_colemak.xml b/java/res/xml/keyboard_layout_set_colemak.xml
index c18f132..3061872 100644
--- a/java/res/xml/keyboard_layout_set_colemak.xml
+++ b/java/res/xml/keyboard_layout_set_colemak.xml
@@ -26,10 +26,10 @@
         latin:enableProximityCharsCorrection="true" />
     <Element
         latin:elementName="symbols"
-        latin:elementKeyboard="@xml/kbd_10_10_7_symbols" />
+        latin:elementKeyboard="@xml/kbd_symbols" />
     <Element
         latin:elementName="symbolsShifted"
-        latin:elementKeyboard="@xml/kbd_10_10_7_symbols_shift" />
+        latin:elementKeyboard="@xml/kbd_symbols_shift" />
     <Element
         latin:elementName="phone"
         latin:elementKeyboard="@xml/kbd_phone" />
diff --git a/java/res/xml/keyboard_layout_set_dvorak.xml b/java/res/xml/keyboard_layout_set_dvorak.xml
index eb8e0c5..31aeec5 100644
--- a/java/res/xml/keyboard_layout_set_dvorak.xml
+++ b/java/res/xml/keyboard_layout_set_dvorak.xml
@@ -26,10 +26,10 @@
         latin:enableProximityCharsCorrection="true" />
     <Element
         latin:elementName="symbols"
-        latin:elementKeyboard="@xml/kbd_10_10_7_symbols" />
+        latin:elementKeyboard="@xml/kbd_symbols" />
     <Element
         latin:elementName="symbolsShifted"
-        latin:elementKeyboard="@xml/kbd_10_10_7_symbols_shift" />
+        latin:elementKeyboard="@xml/kbd_symbols_shift" />
     <Element
         latin:elementName="phone"
         latin:elementKeyboard="@xml/kbd_phone" />
diff --git a/java/res/xml/keyboard_layout_set_emoji.xml b/java/res/xml/keyboard_layout_set_emoji.xml
new file mode 100644
index 0000000..98e6b6b
--- /dev/null
+++ b/java/res/xml/keyboard_layout_set_emoji.xml
@@ -0,0 +1,44 @@
+<?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="emojiRecents"
+        latin:elementKeyboard="@xml/kbd_emoji_recents" />
+    <Element
+        latin:elementName="emojiCategory1"
+        latin:elementKeyboard="@xml/kbd_emoji_category1" />
+    <Element
+        latin:elementName="emojiCategory2"
+        latin:elementKeyboard="@xml/kbd_emoji_category2" />
+    <Element
+        latin:elementName="emojiCategory3"
+        latin:elementKeyboard="@xml/kbd_emoji_category3" />
+    <Element
+        latin:elementName="emojiCategory4"
+        latin:elementKeyboard="@xml/kbd_emoji_category4" />
+    <Element
+        latin:elementName="emojiCategory5"
+        latin:elementKeyboard="@xml/kbd_emoji_category5" />
+    <Element
+        latin:elementName="emojiCategory6"
+        latin:elementKeyboard="@xml/kbd_emoji_category6" />
+</KeyboardLayoutSet>
diff --git a/java/res/xml/keyboard_layout_set_hebrew.xml b/java/res/xml/keyboard_layout_set_hebrew.xml
index 212816d..d5b25b3 100644
--- a/java/res/xml/keyboard_layout_set_hebrew.xml
+++ b/java/res/xml/keyboard_layout_set_hebrew.xml
@@ -26,10 +26,10 @@
         latin:enableProximityCharsCorrection="true" />
     <Element
         latin:elementName="symbols"
-        latin:elementKeyboard="@xml/kbd_10_10_7_symbols" />
+        latin:elementKeyboard="@xml/kbd_symbols" />
     <Element
         latin:elementName="symbolsShifted"
-        latin:elementKeyboard="@xml/kbd_10_10_7_symbols_shift" />
+        latin:elementKeyboard="@xml/kbd_symbols_shift" />
     <Element
         latin:elementName="phone"
         latin:elementKeyboard="@xml/kbd_phone" />
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/keyboard_layout_set_lao.xml b/java/res/xml/keyboard_layout_set_lao.xml
new file mode 100644
index 0000000..2ffde45
--- /dev/null
+++ b/java/res/xml/keyboard_layout_set_lao.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2013, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<KeyboardLayoutSet
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin">
+    <Element
+        latin:elementName="alphabet"
+        latin:elementKeyboard="@xml/kbd_lao"
+        latin:enableProximityCharsCorrection="true" />
+    <Element
+        latin:elementName="alphabetAutomaticShifted"
+        latin:elementKeyboard="@xml/kbd_lao"
+        latin:enableProximityCharsCorrection="true" />
+    <!-- On these shifted alphabet layouts the proximity characters correction should be disabled
+         because the letters on these layouts aren't the ones in different case of the above
+         unshifted layouts. -->
+    <Element
+        latin:elementName="alphabetManualShifted"
+        latin:elementKeyboard="@xml/kbd_lao" />
+    <Element
+        latin:elementName="alphabetShiftLocked"
+        latin:elementKeyboard="@xml/kbd_lao" />
+    <Element
+        latin:elementName="alphabetShiftLockShifted"
+        latin:elementKeyboard="@xml/kbd_lao" />
+    <Element
+        latin:elementName="symbols"
+        latin:elementKeyboard="@xml/kbd_symbols" />
+    <Element
+        latin:elementName="symbolsShifted"
+        latin:elementKeyboard="@xml/kbd_symbols_shift" />
+    <Element
+        latin:elementName="phone"
+        latin:elementKeyboard="@xml/kbd_phone" />
+    <Element
+        latin:elementName="phoneSymbols"
+        latin:elementKeyboard="@xml/kbd_phone_symbols" />
+    <Element
+        latin:elementName="number"
+        latin:elementKeyboard="@xml/kbd_number" />
+</KeyboardLayoutSet>
diff --git a/java/res/xml/keyboard_layout_set_nepali_romanized.xml b/java/res/xml/keyboard_layout_set_nepali_romanized.xml
new file mode 100644
index 0000000..fbbc6a5
--- /dev/null
+++ b/java/res/xml/keyboard_layout_set_nepali_romanized.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_nepali_romanized"
+        latin:enableProximityCharsCorrection="true" />
+    <Element
+        latin:elementName="alphabetAutomaticShifted"
+        latin:elementKeyboard="@xml/kbd_nepali_romanized"
+        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_nepali_romanized" />
+    <Element
+        latin:elementName="alphabetShiftLocked"
+        latin:elementKeyboard="@xml/kbd_nepali_romanized" />
+    <Element
+        latin:elementName="alphabetShiftLockShifted"
+        latin:elementKeyboard="@xml/kbd_nepali_romanized" />
+    <Element
+        latin:elementName="symbols"
+        latin:elementKeyboard="@xml/kbd_symbols" />
+    <Element
+        latin:elementName="symbolsShifted"
+        latin:elementKeyboard="@xml/kbd_symbols_shift" />
+    <Element
+        latin:elementName="phone"
+        latin:elementKeyboard="@xml/kbd_phone" />
+    <Element
+        latin:elementName="phoneSymbols"
+        latin:elementKeyboard="@xml/kbd_phone_symbols" />
+    <Element
+        latin:elementName="number"
+        latin:elementKeyboard="@xml/kbd_number" />
+</KeyboardLayoutSet>
diff --git a/java/res/xml/keyboard_layout_set_nepali_traditional.xml b/java/res/xml/keyboard_layout_set_nepali_traditional.xml
new file mode 100644
index 0000000..4a3b601
--- /dev/null
+++ b/java/res/xml/keyboard_layout_set_nepali_traditional.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_nepali_traditional"
+        latin:enableProximityCharsCorrection="true" />
+    <Element
+        latin:elementName="alphabetAutomaticShifted"
+        latin:elementKeyboard="@xml/kbd_nepali_traditional"
+        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_nepali_traditional" />
+    <Element
+        latin:elementName="alphabetShiftLocked"
+        latin:elementKeyboard="@xml/kbd_nepali_traditional" />
+    <Element
+        latin:elementName="alphabetShiftLockShifted"
+        latin:elementKeyboard="@xml/kbd_nepali_traditional" />
+    <Element
+        latin:elementName="symbols"
+        latin:elementKeyboard="@xml/kbd_symbols" />
+    <Element
+        latin:elementName="symbolsShifted"
+        latin:elementKeyboard="@xml/kbd_symbols_shift" />
+    <Element
+        latin:elementName="phone"
+        latin:elementKeyboard="@xml/kbd_phone" />
+    <Element
+        latin:elementName="phoneSymbols"
+        latin:elementKeyboard="@xml/kbd_phone_symbols" />
+    <Element
+        latin:elementName="number"
+        latin:elementKeyboard="@xml/kbd_number" />
+</KeyboardLayoutSet>
diff --git a/java/res/xml/keyboard_layout_set_pcqwerty.xml b/java/res/xml/keyboard_layout_set_pcqwerty.xml
index 9367ed0..67fbd91 100644
--- a/java/res/xml/keyboard_layout_set_pcqwerty.xml
+++ b/java/res/xml/keyboard_layout_set_pcqwerty.xml
@@ -25,12 +25,6 @@
         latin:elementKeyboard="@xml/kbd_pcqwerty"
         latin:enableProximityCharsCorrection="true" />
     <Element
-        latin:elementName="symbols"
-        latin:elementKeyboard="@xml/kbd_pcqwerty_symbols" />
-    <Element
-        latin:elementName="symbolsShifted"
-        latin:elementKeyboard="@xml/kbd_pcqwerty_symbols" />
-    <Element
         latin:elementName="phone"
         latin:elementKeyboard="@xml/kbd_phone" />
     <Element
diff --git a/java/res/xml/keyboard_layout_set_spanish.xml b/java/res/xml/keyboard_layout_set_spanish.xml
index 57cef52..c454de3 100644
--- a/java/res/xml/keyboard_layout_set_spanish.xml
+++ b/java/res/xml/keyboard_layout_set_spanish.xml
@@ -26,10 +26,10 @@
         latin:enableProximityCharsCorrection="true" />
     <Element
         latin:elementName="symbols"
-        latin:elementKeyboard="@xml/kbd_10_10_7_symbols" />
+        latin:elementKeyboard="@xml/kbd_symbols" />
     <Element
         latin:elementName="symbolsShifted"
-        latin:elementKeyboard="@xml/kbd_10_10_7_symbols_shift" />
+        latin:elementKeyboard="@xml/kbd_symbols_shift" />
     <Element
         latin:elementName="phone"
         latin:elementKeyboard="@xml/kbd_phone" />
diff --git a/java/res/xml/keyboard_layout_set_thai.xml b/java/res/xml/keyboard_layout_set_thai.xml
index 94713e3..b8f9997 100644
--- a/java/res/xml/keyboard_layout_set_thai.xml
+++ b/java/res/xml/keyboard_layout_set_thai.xml
@@ -42,10 +42,10 @@
         latin:elementKeyboard="@xml/kbd_thai" />
     <Element
         latin:elementName="symbols"
-        latin:elementKeyboard="@xml/kbd_thai_symbols" />
+        latin:elementKeyboard="@xml/kbd_symbols" />
     <Element
         latin:elementName="symbolsShifted"
-        latin:elementKeyboard="@xml/kbd_thai_symbols_shift" />
+        latin:elementKeyboard="@xml/kbd_symbols_shift" />
     <Element
         latin:elementName="phone"
         latin:elementKeyboard="@xml/kbd_phone" />
diff --git a/java/res/xml/keys_comma_period.xml b/java/res/xml/keys_comma_period.xml
new file mode 100644
index 0000000..02b46c2
--- /dev/null
+++ b/java/res/xml/keys_comma_period.xml
@@ -0,0 +1,103 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <switch>
+        <case
+            latin:mode="email|url"
+        >
+            <Key
+                latin:keyLabel="."
+                latin:keyHintLabel="_"
+                latin:moreKeys="_"
+                latin:backgroundType="functional"
+                latin:keyStyle="hasShiftedLetterHintStyle" />
+            <Key
+                latin:keyLabel=","
+                latin:keyHintLabel="-"
+                latin:moreKeys="-"
+                latin:backgroundType="functional"
+                latin:keyStyle="hasShiftedLetterHintStyle" />
+        </case>
+        <case
+            latin:languageCode="ar"
+        >
+            <Key
+                latin:keyLabel="!text/keylabel_for_apostrophe"
+                latin:keyHintLabel="!text/keyhintlabel_for_apostrophe"
+                latin:moreKeys="!text/more_keys_for_apostrophe"
+                latin:backgroundType="functional"
+                latin:keyStyle="hasShiftedLetterHintStyle" />
+            <Key
+                latin:keyLabel="."
+                latin:keyHintLabel="!text/keyhintlabel_for_arabic_diacritics"
+                latin:keyLabelFlags="hasPopupHint"
+                latin:moreKeys="!text/more_keys_for_arabic_diacritics"
+                latin:backgroundType="functional"
+                latin:keyStyle="hasShiftedLetterHintStyle" />
+        </case>
+        <case
+            latin:languageCode="fa"
+        >
+            <Key
+                latin:keyLabel="!text/keylabel_for_apostrophe"
+                latin:keyHintLabel="!text/keyhintlabel_for_apostrophe"
+                latin:keyLabelFlags="hasPopupHint"
+                latin:moreKeys="!text/more_keys_for_apostrophe"
+                latin:backgroundType="functional"
+                latin:keyStyle="hasShiftedLetterHintStyle" />
+            <Key
+                latin:keyLabel="."
+                latin:keyHintLabel="!text/keyhintlabel_for_arabic_diacritics"
+                latin:keyLabelFlags="hasPopupHint"
+                latin:moreKeys="!text/more_keys_for_arabic_diacritics"
+                latin:backgroundType="functional"
+                latin:keyStyle="hasShiftedLetterHintStyle" />
+        </case>
+        <case
+            latin:languageCode="hy"
+        >
+            <!-- U+0589: "։" ARMENIAN FULL STOP -->
+            <Key
+                latin:keyLabel="&#x0589;"
+                latin:keyLabelFlags="hasPopupHint"
+                latin:backgroundType="functional"
+                latin:moreKeys="!text/more_keys_for_punctuation" />
+            <!-- U+055D: "՝" ARMENIAN COMMA -->
+            <Key
+                latin:keyLabel="&#x055D;"
+                latin:backgroundType="functional" />
+        </case>
+        <default>
+            <Key
+                latin:keyLabel="."
+                latin:keyHintLabel="!text/keyhintlabel_for_tablet_period"
+                latin:backgroundType="functional"
+                latin:moreKeys="!text/more_keys_for_tablet_period" />
+            <Key
+                latin:keyLabel="!text/keylabel_for_tablet_comma"
+                latin:keyHintLabel="!text/keyhintlabel_for_tablet_comma"
+                latin:backgroundType="functional"
+                latin:moreKeys="!text/more_keys_for_tablet_comma" />
+        </default>
+    </switch>
+</merge>
diff --git a/java/res/xml/keys_hindi1_left5.xml b/java/res/xml/keys_hindi1_left5.xml
deleted file mode 100644
index 8757afe..0000000
--- a/java/res/xml/keys_hindi1_left5.xml
+++ /dev/null
@@ -1,85 +0,0 @@
-<?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.
-*/
--->
-
-<!-- The code point U+25CC for key label is needed because the font rendering system prior to
-     API version 16 can't automatically render dotted circle for incomplete combining letter
-     of Hindi. The files named res/xml/{key,keys}_hindi*.xml have this U+25CC hack, although the
-     counterpart files named res/xml-v16/{key,keys}_hindi*.xml don't have this hack. -->
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <!-- U+25CC: "◌" DOTTED CIRCLE
-         U+094C: "ौ" DEVANAGARI VOWEL SIGN AU
-         U+094C/U+0902: "ौं" DEVANAGARI VOWEL SIGN AU/DEVANAGARI SIGN ANUSVARA
-         U+0967: "१" DEVANAGARI DIGIT ONE -->
-    <Key
-        latin:keyLabel="&#x25CC;&#x094C;"
-        latin:code="0x094C"
-        latin:moreKeys="&#x25CC;&#x094C;&#x0902;|&#x094C;&#x0902;,%"
-        latin:keyHintLabel="1"
-        latin:additionalMoreKeys="&#x0967;,1"
-        latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
-    <!-- U+25CC: "◌" DOTTED CIRCLE
-         U+0948: "ै" DEVANAGARI VOWEL SIGN AI
-         U+0948/U+0902: "ैं" DEVANAGARI VOWEL SIGN AI/DEVANAGARI SIGN ANUSVARA
-         U+0968: "२" DEVANAGARI DIGIT TWO -->
-    <Key
-        latin:keyLabel="&#x25CC;&#x0948;"
-        latin:code="0x0948"
-        latin:moreKeys="&#x25CC;&#x0948;&#x0902;|&#x0948;&#x0902;,%"
-        latin:keyHintLabel="2"
-        latin:additionalMoreKeys="&#x0968;,2"
-        latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
-    <!-- U+25CC: "◌" DOTTED CIRCLE
-         U+093E: "ा" DEVANAGARI VOWEL SIGN AA
-         U+093E/U+0902: "ां" DEVANAGARI VOWEL SIGN AA/DEVANAGARI SIGN ANUSVARA
-         U+093E/U+0901: "ाँ" DEVANAGARI VOWEL SIGN AA/DEVANAGARI SIGN CANDRABINDU
-         U+0969: "३" DEVANAGARI DIGIT THREE -->
-    <Key
-        latin:keyLabel="&#x25CC;&#x093E;"
-        latin:code="0x093E"
-        latin:moreKeys="&#x25CC;&#x093E;&#x0902;|&#x093E;&#x0902;,&#x25CC;&#x093E;&#x0901;|&#x093E;&#x0901;,%"
-        latin:keyHintLabel="3"
-        latin:additionalMoreKeys="&#x0969;,3"
-        latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
-    <!-- U+25CC: "◌" DOTTED CIRCLE
-         U+0940: "ी" DEVANAGARI VOWEL SIGN II
-         U+0940/U+0902: "ीं" DEVANAGARI VOWEL SIGN II/DEVANAGARI SIGN ANUSVARA
-         U+096A: "४" DEVANAGARI DIGIT FOUR -->
-    <Key
-        latin:keyLabel="&#x25CC;&#x0940;"
-        latin:code="0x0940"
-        latin:moreKeys="&#x25CC;&#x0940;&#x0902;|&#x0940;&#x0902;,%"
-        latin:keyHintLabel="4"
-        latin:additionalMoreKeys="&#x096A;,4"
-        latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
-    <!-- U+25CC: "◌" DOTTED CIRCLE
-         U+0942: "ू" DEVANAGARI VOWEL SIGN UU
-         U+0942/U+0902: "ूं" DEVANAGARI VOWEL SIGN UU/DEVANAGARI SIGN ANUSVARA
-         U+0942/U+0901: "ूँ" DEVANAGARI VOWEL SIGN UU/DEVANAGARI SIGN CANDRABINDU
-         U+096B: "५" DEVANAGARI DIGIT FIVE -->
-    <Key
-        latin:keyLabel="&#x25CC;&#x0942;"
-        latin:code="0x0942"
-        latin:moreKeys="&#x25CC;&#x0942;&#x0902;|&#x0942;&#x0902;,&#x25CC;&#x0942;&#x0901;|&#x0942;&#x0901;,%"
-        latin:keyHintLabel="5"
-        latin:additionalMoreKeys="&#x096B;,5"
-        latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
-</merge>
diff --git a/java/res/xml/keys_hindi2_left5.xml b/java/res/xml/keys_hindi2_left5.xml
deleted file mode 100644
index 4c3a5e0..0000000
--- a/java/res/xml/keys_hindi2_left5.xml
+++ /dev/null
@@ -1,69 +0,0 @@
-<?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.
-*/
--->
-
-<!-- The code point U+25CC for key label is needed because the font rendering system prior to
-     API version 16 can't automatically render dotted circle for incomplete combining letter
-     of Hindi. The files named res/xml/{key,keys}_hindi*.xml have this U+25CC hack, although the
-     counterpart files named res/xml-v16/{key,keys}_hindi*.xml don't have this hack. -->
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <!-- U+25CC: "◌" DOTTED CIRCLE
-         U+094B: "ो" DEVANAGARI VOWEL SIGN O
-         U+094B/U+0902: "қं" DEVANAGARI VOWEL SIGN O/DEVANAGARI SIGN ANUSVARA
-         U+0949: "ॉ" DEVANAGARI VOWEL SIGN CANDRA O
-         U+094A: "ॊ" DEVANAGARI VOWEL SIGN SHORT O -->
-    <Key
-        latin:keyLabel="&#x25CC;&#x094B;"
-        latin:code="0x094B"
-        latin:moreKeys="&#x25CC;&#x094B;&#x0902;|&#x094B;&#x0902;,&#x25CC;&#x0949;,&#x094A;|&#x0949;,&#x094A;"
-        latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
-    <!-- U+25CC: "◌" DOTTED CIRCLE
-         U+0947: "े" DEVANAGARI VOWEL SIGN E
-         U+0947/U+0902: "ें" DEVANAGARI VOWEL SIGN E/DEVANAGARI SIGN ANUSVARA -->
-    <Key
-        latin:keyLabel="&#x25CC;&#x0947;"
-        latin:code="0x0947"
-        latin:moreKeys="&#x25CC;&#x0947;&#x0902;|&#x0947;&#x0902;"
-        latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
-    <!-- U+25CC: "◌" DOTTED CIRCLE
-         U+094D: "्" DEVANAGARI SIGN VIRAMA -->
-    <Key
-        latin:keyLabel="&#x25CC;&#x094D;"
-        latin:code="0x094D"
-        latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
-    <!-- U+25CC: "◌" DOTTED CIRCLE
-         U+093F: "ि" DEVANAGARI VOWEL SIGN I
-         U+093F/U+0902: "िं" DEVANAGARI VOWEL SIGN I/DEVANAGARI SIGN ANUSVARA -->
-    <Key
-        latin:keyLabel="&#x093F;&#x25CC;"
-        latin:code="0x093F"
-        latin:moreKeys="&#x093F;&#x25CC;&#x0902;|&#x093F;&#x0902;"
-        latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
-    <!-- U+25CC: "◌" DOTTED CIRCLE
-         U+0941: "ु" DEVANAGARI VOWEL SIGN U
-         U+0941/U+0902: "ुं" DEVANAGARI VOWEL SIGN U/DEVANAGARI SIGN ANUSVARA
-         U+0941/U+0901: "ुँ" DEVANAGARI VOWEL SIGN U/DEVANAGARI SIGN CANDRABINDU -->
-    <Key
-        latin:keyLabel="&#x25CC;&#x0941;"
-        latin:code="0x0941"
-        latin:moreKeys="&#x25CC;&#x0941;&#x0902;|&#x0941;&#x0902;,&#x25CC;&#x0941;&#x0901;|&#x0941;&#x0901;"
-        latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
-</merge>
diff --git a/java/res/xml/keys_hindi3_left2.xml b/java/res/xml/keys_hindi3_left2.xml
deleted file mode 100644
index 4f1ad16..0000000
--- a/java/res/xml/keys_hindi3_left2.xml
+++ /dev/null
@@ -1,40 +0,0 @@
-<?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.
-*/
--->
-
-<!-- The code point U+25CC for key label is needed because the font rendering system prior to
-     API version 16 can't automatically render dotted circle for incomplete combining letter
-     of Hindi. The files named res/xml/{key,keys}_hindi*.xml have this U+25CC hack, although the
-     counterpart files named res/xml-v16/{key,keys}_hindi*.xml don't have this hack. -->
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <!-- U+25CC: "◌" DOTTED CIRCLE
-         U+0949: "ॉ" DEVANAGARI VOWEL SIGN CANDRA O -->
-    <Key
-        latin:keyLabel="&#x25CC;&#x0949;"
-        latin:code="0x0949"
-        latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
-    <!-- U+25CC: "◌" DOTTED CIRCLE
-         U+0902: "ं" DEVANAGARI SIGN ANUSVARA -->
-    <Key
-        latin:keyLabel="&#x25CC;&#x0902;"
-        latin:code="0x0902"
-        latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
-</merge>
diff --git a/java/res/xml/keys_less_greater.xml b/java/res/xml/keys_less_greater.xml
index bc9ecdf..56d0727 100644
--- a/java/res/xml/keys_less_greater.xml
+++ b/java/res/xml/keys_less_greater.xml
@@ -30,20 +30,24 @@
             <Key
                 latin:keyLabel="&#x00AB;"
                 latin:code="0x00BB"
+                latin:backgroundType="functional"
                 latin:moreKeys="!text/more_keys_for_less_than" />
             <Key
                 latin:keyLabel="&#x00BB;"
                 latin:code="0x00AB"
+                latin:backgroundType="functional"
                 latin:moreKeys="!text/more_keys_for_greater_than" />
         </case>
         <default>
             <Key
                 latin:keyLabel="&lt;"
                 latin:code="!code/key_less_than"
+                latin:backgroundType="functional"
                 latin:moreKeys="!text/more_keys_for_less_than" />
             <Key
                 latin:keyLabel="&gt;"
                 latin:code="!code/key_greater_than"
+                latin:backgroundType="functional"
                 latin:moreKeys="!text/more_keys_for_greater_than" />
         </default>
     </switch>
diff --git a/java/res/xml/keys_pcqwerty2_right3.xml b/java/res/xml/keys_pcqwerty2_right3.xml
index 2065e6b..6f86477 100644
--- a/java/res/xml/keys_pcqwerty2_right3.xml
+++ b/java/res/xml/keys_pcqwerty2_right3.xml
@@ -27,25 +27,22 @@
         >
             <Key
                 latin:keyLabel="["
-                latin:moreKeys="{" />
+                latin:additionalMoreKeys="{" />
             <Key
                 latin:keyLabel="]"
-                latin:moreKeys="}" />
-            <!-- U+00A6: "¦" BROKEN BAR -->
+                latin:additionalMoreKeys="}" />
             <Key
                 latin:keyLabel="\\"
-                latin:moreKeys="\\|,&#x00A6;" />
+                latin:additionalMoreKeys="\\|" />
         </case>
-        <!-- keyboardLayoutSetElement="alphabet*Shifted|symbols*" -->
+        <!-- keyboardLayoutSetElement="alphabetManualShifted|alphabetShiftLocked|alphabetShiftLockShifted" -->
         <default>
             <Key
                 latin:keyLabel="{" />
             <Key
                 latin:keyLabel="}" />
-            <!-- U+00A6: "¦" BROKEN BAR -->
             <Key
-                latin:keyLabel="|"
-                latin:moreKeys="&#x00A6;" />
+                latin:keyLabel="|" />
         </default>
     </switch>
 </merge>
diff --git a/java/res/xml/keys_pcqwerty3_right2.xml b/java/res/xml/keys_pcqwerty3_right2.xml
index aa150af..8da145b 100644
--- a/java/res/xml/keys_pcqwerty3_right2.xml
+++ b/java/res/xml/keys_pcqwerty3_right2.xml
@@ -27,12 +27,13 @@
         >
             <Key
                 latin:keyLabel=";"
-                latin:moreKeys=":" />
+                latin:additionalMoreKeys=":" />
             <Key
                 latin:keyLabel="\'"
-                latin:moreKeys="!fixedColumnOrder!4,!text/double_quotes,&quot;,!text/single_quotes" />
+                latin:additionalMoreKeys="&quot;"
+                latin:moreKeys="!fixedColumnOrder!4,!text/double_quotes,%,!text/single_quotes" />
         </case>
-        <!-- keyboardLayoutSetElement="alphabet*Shifted|symbols*" -->
+        <!-- keyboardLayoutSetElement="alphabetManualShifted|alphabetShiftLocked|alphabetShiftLockShifted" -->
         <default>
             <Key
                 latin:keyLabel=":" />
diff --git a/java/res/xml/keys_pcqwerty4_right3.xml b/java/res/xml/keys_pcqwerty4_right3.xml
index 7795b3d..e6084cb 100644
--- a/java/res/xml/keys_pcqwerty4_right3.xml
+++ b/java/res/xml/keys_pcqwerty4_right3.xml
@@ -27,16 +27,16 @@
         >
             <Key
                 latin:keyLabel=","
-                latin:moreKeys="&lt;" />
+                latin:additionalMoreKeys="&lt;" />
             <Key
                 latin:keyLabel="."
-                latin:moreKeys="&gt;" />
-            <!-- U+00BF: "¿" INVERTED QUESTION MARK -->
+                latin:additionalMoreKeys="&gt;" />
             <Key
                 latin:keyLabel="/"
-                latin:moreKeys="\?,&#x00BF;" />
+                latin:additionalMoreKeys="\?"
+                latin:moreKeys="!text/more_keys_for_symbols_question" />
         </case>
-        <!-- keyboardLayoutSetElement="alphabet*Shifted|symbols*" -->
+        <!-- keyboardLayoutSetElement="alphabetManualShifted|alphabetShiftLocked|alphabetShiftLockShifted" -->
         <default>
             <!-- U+2039: "‹" SINGLE LEFT-POINTING ANGLE QUOTATION MARK
                  U+203A: "›" SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
@@ -50,10 +50,9 @@
             <Key
                 latin:keyLabel="&gt;"
                 latin:moreKeys="!fixedColumnOrder!3,&#x203A;,&#x2265;,&#x00BB;" />
-            <!-- U+00BF: "¿" INVERTED QUESTION MARK -->
             <Key
                 latin:keyLabel="\?"
-                latin:moreKeys="&#x00BF;" />
+                latin:moreKeys="!text/more_keys_for_symbols_question" />
         </default>
     </switch>
 </merge>
diff --git a/java/res/xml/keys_pcqwerty_symbols2.xml b/java/res/xml/keys_pcqwerty_symbols2.xml
deleted file mode 100644
index d0ea984..0000000
--- a/java/res/xml/keys_pcqwerty_symbols2.xml
+++ /dev/null
@@ -1,59 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <!-- U+2022: "•" BULLET -->
-    <Key
-        latin:keyLabel="&#x2022;"
-        latin:moreKeys="!text/more_keys_for_bullet" />
-    <!-- U+00B1: "±" PLUS-MINUS SIGN -->
-    <Key
-        latin:keyLabel="&#x00B1;" />
-    <!-- U+00AC: "¬" NOT SIGN -->
-    <Key
-        latin:keyLabel="&#x00AC;" />
-    <!-- U+00A6: "¦" BROKEN BAR -->
-    <Key
-        latin:keyLabel="&#x00A6;" />
-    <!-- U+221A: "√" SQUARE ROOT -->
-    <Key
-        latin:keyLabel="&#x221A;" />
-    <!-- U+03C0: "π" GREEK SMALL LETTER PI
-         U+03A0: "Π" GREEK CAPITAL LETTER PI -->
-    <Key
-        latin:keyLabel="&#x03C0;"
-        latin:moreKeys="&#x03A0;" />
-    <!-- U+03CC: "σ" GREEK SMALL LETTER SIGMA
-         U+03A3: "Σ" GREEK CAPITAL LETTER SIGMA -->
-    <Key
-        latin:keyLabel="&#x03C3;"
-        latin:moreKeys="&#x03A3;" />
-    <!-- U+00B5: "µ" MICRO SIGN -->
-    <Key
-        latin:keyLabel="&#x00B5;" />
-    <!-- U+00F7: "÷" DIVISION SIGN -->
-    <Key
-        latin:keyLabel="&#x00F7;" />
-    <!-- U+00D7: "×" MULTIPLICATION SIGN -->
-    <Key
-        latin:keyLabel="&#x00D7;" />
-</merge>
diff --git a/java/res/xml/keys_pcqwerty_symbols3.xml b/java/res/xml/keys_pcqwerty_symbols3.xml
deleted file mode 100644
index 35279de..0000000
--- a/java/res/xml/keys_pcqwerty_symbols3.xml
+++ /dev/null
@@ -1,54 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <!-- U+00A3: "£" POUND SIGN; -->
-    <Key
-        latin:keyLabel="&#x00A3;" />
-    <!-- U+00A2: "¢" CENT SIGN -->
-    <Key
-        latin:keyLabel="&#x00A2;" />
-    <!-- U+20AC: "€" EURO SIGN -->
-    <Key
-        latin:keyLabel="&#x20AC;" />
-    <!-- U+00A5: "¥" YEN SIGN -->
-    <Key
-        latin:keyLabel="&#x00A5;" />
-    <!-- U+00A4: "¤" CURRENCY SIGN -->
-    <Key
-        latin:keyLabel="&#x00A4;" />
-    <!-- U+00B0: "°" DEGREE SIGN
-         U+2032: "′" PRIME
-         U+2033: "″" DOUBLE PRIME -->
-    <Key
-        latin:keyLabel="&#x00B0;"
-        latin:moreKeys="&#x2032;,&#x2033;" />
-    <!-- U+2260: "≠" NOT EQUAL TO -->
-    <Key
-        latin:keyLabel="&#x2260;" />
-    <!-- U+2248: "≈" ALMOST EQUAL TO -->
-    <Key
-        latin:keyLabel="&#x2248;" />
-    <!-- U+221E: "∞" INFINITY -->
-    <Key
-        latin:keyLabel="&#x221E;" />
-</merge>
diff --git a/java/res/xml/keys_pcqwerty_symbols4.xml b/java/res/xml/keys_pcqwerty_symbols4.xml
deleted file mode 100644
index 3c628f0..0000000
--- a/java/res/xml/keys_pcqwerty_symbols4.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <!-- U+2122: "™" TRADE MARK SIGN -->
-    <Key
-        latin:keyLabel="&#x2122;" />
-    <!-- U+00AE: "®" REGISTERED SIGN -->
-    <Key
-        latin:keyLabel="&#x00AE;" />
-    <!-- U+00A9: "©" COPYRIGHT SIGN -->
-    <Key
-        latin:keyLabel="&#x00A9;" />
-    <!-- U+00B6: "¶" PILCROW SIGN -->
-    <Key
-        latin:keyLabel="&#x00B6;" />
-    <!-- U+00A7: "§" SECTION SIGN -->
-    <Key
-        latin:keyLabel="&#x00A7;" />
-    <!-- U+2191: "↑" UPWARDS ARROW
-         U+2193: "↓" DOWNWARDS ARROW
-         U+2190: "←" LEFTWARDS ARROW
-         U+2192: "→" RIGHTWARDS ARROW -->
-    <Key
-        latin:keyLabel="&#x2191;"
-        latin:moreKeys="&#x2193;" />
-    <Key
-        latin:keyLabel="&#x2190;"
-        latin:moreKeys="&#x2192;" />
-</merge>
diff --git a/java/res/xml/key_hindi1_shift.xml b/java/res/xml/keystyle_devanagari_sign_virama.xml
similarity index 73%
copy from java/res/xml/key_hindi1_shift.xml
copy to java/res/xml/keystyle_devanagari_sign_virama.xml
index 0db5ae9..b22fbe8 100644
--- a/java/res/xml/key_hindi1_shift.xml
+++ b/java/res/xml/keystyle_devanagari_sign_virama.xml
@@ -20,15 +20,16 @@
 
 <!-- The code point U+25CC for key label is needed because the font rendering system prior to
      API version 16 can't automatically render dotted circle for incomplete combining letter
-     of Hindi. The files named res/xml/{key,keys}_hindi*.xml have this U+25CC hack, although the
-     counterpart files named res/xml-v16/{key,keys}_hindi*.xml don't have this hack. -->
+     of some scripts. The files named res/xml/key_*.xml have this U+25CC hack, although the
+     counterpart files named res/xml-v16/key_*.xml don't have this hack. -->
 <merge
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
 >
     <!-- U+25CC: "◌" DOTTED CIRCLE
-         U+0903: "ः" DEVANAGARI SIGN VISARGA -->
-    <Key
-        latin:keyLabel="&#x25CC;&#x0903;"
-        latin:code="0x0903"
+         U+094D: "्" DEVANAGARI SIGN VIRAMA -->
+    <key-style
+        latin:styleName="baseKeyDevanagariSignVirama"
+        latin:keyLabel="&#x25CC;&#x094D;"
+        latin:code="0x094D"
         latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
-</merge>
+ </merge>
diff --git a/java/res/xml/key_hindi1_shift.xml b/java/res/xml/keystyle_devanagari_sign_visarga.xml
similarity index 82%
rename from java/res/xml/key_hindi1_shift.xml
rename to java/res/xml/keystyle_devanagari_sign_visarga.xml
index 0db5ae9..cb29495 100644
--- a/java/res/xml/key_hindi1_shift.xml
+++ b/java/res/xml/keystyle_devanagari_sign_visarga.xml
@@ -20,14 +20,15 @@
 
 <!-- The code point U+25CC for key label is needed because the font rendering system prior to
      API version 16 can't automatically render dotted circle for incomplete combining letter
-     of Hindi. The files named res/xml/{key,keys}_hindi*.xml have this U+25CC hack, although the
-     counterpart files named res/xml-v16/{key,keys}_hindi*.xml don't have this hack. -->
+     of some scripts. The files named res/xml/key_*.xml have this U+25CC hack, although the
+     counterpart files named res/xml-v16/key_*.xml don't have this hack. -->
 <merge
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
 >
     <!-- U+25CC: "◌" DOTTED CIRCLE
          U+0903: "ः" DEVANAGARI SIGN VISARGA -->
-    <Key
+    <key-style
+        latin:styleName="baseKeyDevanagariSignVisarga"
         latin:keyLabel="&#x25CC;&#x0903;"
         latin:code="0x0903"
         latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
diff --git a/java/res/xml/keystyle_devanagari_vowel_sign_aa.xml b/java/res/xml/keystyle_devanagari_vowel_sign_aa.xml
new file mode 100644
index 0000000..2e78c53
--- /dev/null
+++ b/java/res/xml/keystyle_devanagari_vowel_sign_aa.xml
@@ -0,0 +1,52 @@
+<?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.
+*/
+-->
+
+<!-- The code point U+25CC for key label is needed because the font rendering system prior to
+     API version 16 can't automatically render dotted circle for incomplete combining letter
+     of some scripts. The files named res/xml/key_*.xml have this U+25CC hack, although the
+     counterpart files named res/xml-v16/key_*.xml don't have this hack. -->
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <switch>
+        <case
+            latin:keyboardLayoutSet="hindi"
+        >
+            <!-- U+25CC: "◌" DOTTED CIRCLE
+                 U+093E/U+0902: "ां" DEVANAGARI VOWEL SIGN AA/DEVANAGARI SIGN ANUSVARA
+                 U+093E/U+0901: "ाँ" DEVANAGARI VOWEL SIGN AA/DEVANAGARI SIGN CANDRABINDU -->
+            <key-style
+                latin:styleName="moreKeysDevanagariVowelSignAa"
+                latin:moreKeys="&#x25CC;&#x093E;&#x0902;|&#x093E;&#x0902;,&#x25CC;&#x093E;&#x0901;|&#x093E;&#x0901;,%" />
+        </case>
+        <default>
+            <key-style
+                latin:styleName="moreKeysDevanagariVowelSignAa" />
+        </default>
+    </switch>
+    <!-- U+25CC: "◌" DOTTED CIRCLE
+         U+093E: "ा" DEVANAGARI VOWEL SIGN AA -->
+    <key-style
+        latin:styleName="baseKeyDevanagariVowelSignAa"
+        latin:parentStyle="moreKeysDevanagariVowelSignAa"
+        latin:keyLabel="&#x25CC;&#x093E;"
+        latin:code="0x093E"
+        latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
+</merge>
diff --git a/java/res/xml/keystyle_devanagari_vowel_sign_ai.xml b/java/res/xml/keystyle_devanagari_vowel_sign_ai.xml
new file mode 100644
index 0000000..0554c0e
--- /dev/null
+++ b/java/res/xml/keystyle_devanagari_vowel_sign_ai.xml
@@ -0,0 +1,59 @@
+<?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.
+*/
+-->
+
+<!-- The code point U+25CC for key label is needed because the font rendering system prior to
+     API version 16 can't automatically render dotted circle for incomplete combining letter
+     of some scripts. The files named res/xml/key_*.xml have this U+25CC hack, although the
+     counterpart files named res/xml-v16/key_*.xml don't have this hack. -->
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <switch>
+        <case
+            latin:keyboardLayoutSet="hindi"
+        >
+            <!-- U+25CC: "◌" DOTTED CIRCLE
+                 U+0948/U+0902: "ैं" DEVANAGARI VOWEL SIGN AI/DEVANAGARI SIGN ANUSVARA -->
+            <key-style
+                latin:styleName="moreKeysDevanagariVowelSignAi"
+                latin:moreKeys="&#x25CC;&#x0948;&#x0902;|&#x0948;&#x0902;,%" />
+        </case>
+        <case
+            latin:keyboardLayoutSet="nepali_traditional"
+        >
+            <!-- U+0936/U+094D/U+0930: "श्र" DEVANAGARI LETTER SHA/DEVANAGARI SIGN VIRAMA/DEVANAGARI LETTER RA -->
+            <key-style
+                latin:styleName="moreKeysDevanagariVowelSignAi"
+                latin:moreKeys="&#x0936;&#x094D;&#x0930;" />
+        </case>
+        <default>
+            <key-style
+                latin:styleName="moreKeysDevanagariVowelSignAi" />
+        </default>
+    </switch>
+    <!-- U+25CC: "◌" DOTTED CIRCLE
+         U+0948: "ै" DEVANAGARI VOWEL SIGN AI -->
+    <key-style
+        latin:styleName="baseKeyDevanagariVowelSignAi"
+        latin:parentStyle="moreKeysDevanagariVowelSignAi"
+        latin:keyLabel="&#x25CC;&#x0948;"
+        latin:code="0x0948"
+        latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
+</merge>
diff --git a/java/res/xml/keystyle_devanagari_vowel_sign_au.xml b/java/res/xml/keystyle_devanagari_vowel_sign_au.xml
new file mode 100644
index 0000000..29a11a8
--- /dev/null
+++ b/java/res/xml/keystyle_devanagari_vowel_sign_au.xml
@@ -0,0 +1,50 @@
+<?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.
+*/
+-->
+
+<!-- The code point U+25CC for key label is needed because the font rendering system prior to
+     API version 16 can't automatically render dotted circle for incomplete combining letter
+     of some scripts. The files named res/xml/key_*.xml have this U+25CC hack, although the
+     counterpart files named res/xml-v16/key_*.xml don't have this hack. -->
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <switch>
+        <case
+            latin:keyboardLayoutSet="hindi"
+        >
+            <!-- U+25CC: "◌" DOTTED CIRCLE
+                U+094C/U+0902: "ौं" DEVANAGARI VOWEL SIGN AU/DEVANAGARI SIGN ANUSVARA -->
+            <key-style
+                latin:styleName="moreKeysDevanagariVowelSignAu"
+                latin:moreKeys="&#x25CC;&#x094C;&#x0902;|&#x094C;&#x0902;,%" />
+        </case>
+        <default>
+             <key-style
+                latin:styleName="moreKeysDevanagariVowelSignAu" />
+        </default>
+    </switch>
+    <!-- U+094C: "ौ" DEVANAGARI VOWEL SIGN AU -->
+    <key-style
+        latin:styleName="baseKeyDevanagariVowelSignAu"
+        latin:parentStyle="moreKeysDevanagariVowelSignAu"
+        latin:keyLabel="&#x25CC;&#x094C;"
+        latin:code="0x094C"
+        latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
+</merge>
diff --git a/java/res/xml/keystyle_devanagari_vowel_sign_e.xml b/java/res/xml/keystyle_devanagari_vowel_sign_e.xml
new file mode 100644
index 0000000..edd29c7
--- /dev/null
+++ b/java/res/xml/keystyle_devanagari_vowel_sign_e.xml
@@ -0,0 +1,59 @@
+<?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.
+*/
+-->
+
+<!-- The code point U+25CC for key label is needed because the font rendering system prior to
+     API version 16 can't automatically render dotted circle for incomplete combining letter
+     of some scripts. The files named res/xml/key_*.xml have this U+25CC hack, although the
+     counterpart files named res/xml-v16/key_*.xml don't have this hack. -->
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <switch>
+        <case
+            latin:keyboardLayoutSet="hindi"
+        >
+            <!-- U+25CC: "◌" DOTTED CIRCLE
+                 U+0947/U+0902: "ें" DEVANAGARI VOWEL SIGN E/DEVANAGARI SIGN ANUSVARA -->
+            <key-style
+                latin:styleName="moreKeysDevanagariVowelSignE"
+                latin:moreKeys="&#x25CC;&#x0947;&#x0902;|&#x0947;&#x0902;" />
+        </case>
+        <case
+            latin:keyboardLayoutSet="nepali_traditional"
+        >
+            <!-- U+25CC: "◌" DOTTED CIRCLE
+                 U+0903: "ः‍" DEVANAGARI SIGN VISARGA
+                 U+093D: "ऽ" DEVANAGARI SIGN AVAGRAHA -->
+            <key-style
+                latin:styleName="moreKeysDevanagariVowelSignE"
+                latin:moreKeys="&#x25CC;&#x0903;|&#x0903;,&#x093D;" />
+        </case>
+        <default>
+             <key-style
+                latin:styleName="moreKeysDevanagariVowelSignE" />
+        </default>
+    </switch>
+    <key-style
+        latin:styleName="baseKeyDevanagariVowelSignE"
+        latin:parentStyle="moreKeysDevanagariVowelSignE"
+        latin:keyLabel="&#x25CC;&#x0947;"
+        latin:code="0x0947"
+        latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
+</merge>
diff --git a/java/res/xml/keystyle_devanagari_vowel_sign_i.xml b/java/res/xml/keystyle_devanagari_vowel_sign_i.xml
new file mode 100644
index 0000000..200fed2
--- /dev/null
+++ b/java/res/xml/keystyle_devanagari_vowel_sign_i.xml
@@ -0,0 +1,51 @@
+<?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.
+*/
+-->
+
+<!-- The code point U+25CC for key label is needed because the font rendering system prior to
+     API version 16 can't automatically render dotted circle for incomplete combining letter
+     of some scripts. The files named res/xml/key_*.xml have this U+25CC hack, although the
+     counterpart files named res/xml-v16/key_*.xml don't have this hack. -->
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <switch>
+        <case
+            latin:keyboardLayoutSet="hindi"
+        >
+            <!-- U+25CC: "◌" DOTTED CIRCLE
+                 U+093F/U+0902: "िं" DEVANAGARI VOWEL SIGN I/DEVANAGARI SIGN ANUSVARA -->
+            <key-style
+                latin:styleName="moreKeysDevanagariVowelSignI"
+                latin:moreKeys="&#x093F;&#x25CC;&#x0902;|&#x093F;&#x0902;" />
+        </case>
+        <default>
+             <key-style
+                latin:styleName="moreKeysDevanagariVowelSignI" />
+        </default>
+    </switch>
+    <!-- U+25CC: "◌" DOTTED CIRCLE
+         U+093F: "ि" DEVANAGARI VOWEL SIGN I -->
+    <key-style
+        latin:styleName="baseKeyDevanagariVowelSignI"
+        latin:parentStyle="moreKeysDevanagariVowelSignI"
+        latin:keyLabel="&#x25CC;&#x093F;"
+        latin:code="0x093F"
+        latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
+</merge>
diff --git a/java/res/xml/keystyle_devanagari_vowel_sign_ii.xml b/java/res/xml/keystyle_devanagari_vowel_sign_ii.xml
new file mode 100644
index 0000000..6dc9951
--- /dev/null
+++ b/java/res/xml/keystyle_devanagari_vowel_sign_ii.xml
@@ -0,0 +1,51 @@
+<?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.
+*/
+-->
+
+<!-- The code point U+25CC for key label is needed because the font rendering system prior to
+     API version 16 can't automatically render dotted circle for incomplete combining letter
+     of some scripts. The files named res/xml/key_*.xml have this U+25CC hack, although the
+     counterpart files named res/xml-v16/key_*.xml don't have this hack. -->
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <switch>
+        <case
+            latin:keyboardLayoutSet="hindi"
+        >
+            <!-- U+0940: "ी" DEVANAGARI VOWEL SIGN II
+                 U+0940/U+0902: "ीं" DEVANAGARI VOWEL SIGN II/DEVANAGARI SIGN ANUSVARA -->
+            <key-style
+                latin:styleName="moreKeysDevanagariVowelSignIi"
+                latin:moreKeys="&#x25CC;&#x0940;&#x0902;|&#x0940;&#x0902;,%" />
+        </case>
+        <default>
+             <key-style
+                latin:styleName="moreKeysDevanagariVowelSignIi" />
+        </default>
+    </switch>
+    <!-- U+25CC: "◌" DOTTED CIRCLE
+         U+0940: "ी" DEVANAGARI VOWEL SIGN II -->
+    <key-style
+        latin:styleName="baseKeyDevanagariVowelSignIi"
+        latin:parentStyle="moreKeysDevanagariVowelSignIi"
+        latin:keyLabel="&#x25CC;&#x0940;"
+        latin:code="0x0940"
+        latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
+</merge>
diff --git a/java/res/xml/keystyle_devanagari_vowel_sign_o.xml b/java/res/xml/keystyle_devanagari_vowel_sign_o.xml
new file mode 100644
index 0000000..233ac86
--- /dev/null
+++ b/java/res/xml/keystyle_devanagari_vowel_sign_o.xml
@@ -0,0 +1,53 @@
+<?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.
+*/
+-->
+
+<!-- The code point U+25CC for key label is needed because the font rendering system prior to
+     API version 16 can't automatically render dotted circle for incomplete combining letter
+     of some scripts. The files named res/xml/key_*.xml have this U+25CC hack, although the
+     counterpart files named res/xml-v16/key_*.xml don't have this hack. -->
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <switch>
+        <case
+            latin:keyboardLayoutSet="hindi"
+        >
+            <!-- U+25CC: "◌" DOTTED CIRCLE
+                 U+094B/U+0902: "қं" DEVANAGARI VOWEL SIGN O/DEVANAGARI SIGN ANUSVARA
+                 U+0949: "ॉ" DEVANAGARI VOWEL SIGN CANDRA O
+                 U+094A: "ॊ" DEVANAGARI VOWEL SIGN SHORT O -->
+            <key-style
+                latin:styleName="moreKeysDevanagariVowelSignO"
+                latin:moreKeys="&#x25CC;&#x094B;&#x0902;|&#x094B;&#x0902;,&#x25CC;&#x0949;|&#x0949;,&#x25CC;&#x094A;|&#x094A;" />
+        </case>
+        <default>
+             <key-style
+                latin:styleName="moreKeysDevanagariVowelSignO" />
+        </default>
+    </switch>
+    <!-- U+25CC: "◌" DOTTED CIRCLE
+         U+094B: "ो" DEVANAGARI VOWEL SIGN O -->
+    <key-style
+        latin:styleName="baseKeyDevanagariVowelSignO"
+        latin:parentStyle="moreKeysDevanagariVowelSignO"
+        latin:keyLabel="&#x25CC;&#x094B;"
+        latin:code="0x094B"
+        latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
+</merge>
diff --git a/java/res/xml/keystyle_devanagari_vowel_sign_u.xml b/java/res/xml/keystyle_devanagari_vowel_sign_u.xml
new file mode 100644
index 0000000..7291b70
--- /dev/null
+++ b/java/res/xml/keystyle_devanagari_vowel_sign_u.xml
@@ -0,0 +1,52 @@
+<?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.
+*/
+-->
+
+<!-- The code point U+25CC for key label is needed because the font rendering system prior to
+     API version 16 can't automatically render dotted circle for incomplete combining letter
+     of some scripts. The files named res/xml/key_*.xml have this U+25CC hack, although the
+     counterpart files named res/xml-v16/key_*.xml don't have this hack. -->
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <switch>
+        <case
+            latin:keyboardLayoutSet="hindi"
+        >
+            <!-- U+25CC: "◌" DOTTED CIRCLE
+                 U+0941/U+0902: "ुं" DEVANAGARI VOWEL SIGN U/DEVANAGARI SIGN ANUSVARA
+                 U+0941/U+0901: "ुँ" DEVANAGARI VOWEL SIGN U/DEVANAGARI SIGN CANDRABINDU -->
+            <key-style
+                latin:styleName="moreKeysDevanagariVowelSignU"
+                latin:moreKeys="&#x25CC;&#x0941;&#x0902;|&#x0941;&#x0902;,&#x25CC;&#x0941;&#x0901;|&#x0941;&#x0901;" />
+        </case>
+        <default>
+             <key-style
+                latin:styleName="moreKeysDevanagariVowelSignU" />
+        </default>
+    </switch>
+    <!-- U+25CC: "◌" DOTTED CIRCLE
+         U+0941: "ु" DEVANAGARI VOWEL SIGN U -->
+    <key-style
+        latin:styleName="baseKeyDevanagariVowelSignU"
+        latin:parentStyle="moreKeysDevanagariVowelSignU"
+        latin:keyLabel="&#x25CC;&#x0941;"
+        latin:code="0x0941"
+        latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
+</merge>
diff --git a/java/res/xml/keystyle_devanagari_vowel_sign_uu.xml b/java/res/xml/keystyle_devanagari_vowel_sign_uu.xml
new file mode 100644
index 0000000..a95ab82
--- /dev/null
+++ b/java/res/xml/keystyle_devanagari_vowel_sign_uu.xml
@@ -0,0 +1,52 @@
+<?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.
+*/
+-->
+
+<!-- The code point U+25CC for key label is needed because the font rendering system prior to
+     API version 16 can't automatically render dotted circle for incomplete combining letter
+     of some scripts. The files named res/xml/key_*.xml have this U+25CC hack, although the
+     counterpart files named res/xml-v16/key_*.xml don't have this hack. -->
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <switch>
+        <case
+            latin:keyboardLayoutSet="hindi"
+        >
+            <!-- U+25CC: "◌" DOTTED CIRCLE
+                 U+0942/U+0902: "ूं" DEVANAGARI VOWEL SIGN UU/DEVANAGARI SIGN ANUSVARA
+                 U+0942/U+0901: "ूँ" DEVANAGARI VOWEL SIGN UU/DEVANAGARI SIGN CANDRABINDU -->
+            <key-style
+                latin:styleName="moreKeysDevanagariVowelSignUu"
+                latin:moreKeys="&#x25CC;&#x0942;&#x0902;|&#x0942;&#x0902;,&#x25CC;&#x0942;&#x0901;|&#x0942;&#x0901;,%" />
+        </case>
+        <default>
+             <key-style
+                latin:styleName="moreKeysDevanagariVowelSignUu" />
+        </default>
+    </switch>
+    <!-- U+25CC: "◌" DOTTED CIRCLE
+         U+0942: "ू" DEVANAGARI VOWEL SIGN UU -->
+    <key-style
+        latin:styleName="baseKeyDevanagariVowelSignUu"
+        latin:parentStyle="moreKeysDevanagariVowelSignUu"
+        latin:keyLabel="&#x25CC;&#x0942;"
+        latin:code="0x0942"
+        latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
+</merge>
diff --git a/java/res/xml/method.xml b/java/res/xml/method.xml
index 52d715a..f0e04c2 100644
--- a/java/res/xml/method.xml
+++ b/java/res/xml/method.xml
@@ -24,7 +24,7 @@
     keyboard_locale: script_name/keyboard_layout_set
     af: Afrikaans/qwerty
     ar: Arabic/arabic
-    (az: Azerbaijani/qwerty) # disabled temporarily. waiting for strnig resources.
+    az: Azerbaijani/qwerty
     be: Belarusian/east_slavic
     bg: Bulgarian/bulgarian
     bg: Bulgarian/bulgarian_bds
@@ -47,19 +47,24 @@
     hi: Hindi/hindi
     hr: Croatian/qwertz
     hu: Hungarian/qwertz
+    hy: Armenian Phonetic/armenian_phonetic
     in: Indonesian/qwerty    # "id" is official language code of Indonesian.
     is: Icelandic/qwerty
     it: Italian/qwerty
     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
     lv: Latvian/qwerty
     mk: Macedonian/south_slavic
     mn: Mongolian/mongolian
     ms: Malay/qwerty
     nb: Norwegian Bokmål/nordic
+    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
@@ -80,6 +85,7 @@
     vi: Vietnamese/qwerty
     zu: Zulu/qwerty
     zz: QWERTY/qwerty
+    (zz: Emoji/emoji)
     -->
 <!-- TODO: use <lang>_keyboard icon instead of a common keyboard icon. -->
 <!-- Note: SupportTouchPositionCorrection extra value is obsolete and maintained for backward
@@ -89,427 +95,474 @@
 <input-method xmlns:android="http://schemas.android.com/apk/res/android"
         android:settingsActivity="com.android.inputmethod.latin.settings.SettingsActivity"
         android:isDefault="@bool/im_is_default">
-    <subtype android:icon="@drawable/ic_subtype_keyboard"
+    <subtype android:icon="@drawable/ic_ime_switcher_dark"
             android:label="@string/subtype_en_US"
             android:subtypeId="0xc9194f98"
             android:imeSubtypeLocale="en_US"
             android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="TrySuppressingImeSwitcher,AsciiCapable,SupportTouchPositionCorrection"
+            android:imeSubtypeExtraValue="TrySuppressingImeSwitcher,AsciiCapable,SupportTouchPositionCorrection,EmojiCapable"
     />
-    <subtype android:icon="@drawable/ic_subtype_keyboard"
+    <subtype android:icon="@drawable/ic_ime_switcher_dark"
             android:label="@string/subtype_en_GB"
             android:subtypeId="0xb045e755"
             android:imeSubtypeLocale="en_GB"
             android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="TrySuppressingImeSwitcher,AsciiCapable,SupportTouchPositionCorrection"
+            android:imeSubtypeExtraValue="TrySuppressingImeSwitcher,AsciiCapable,SupportTouchPositionCorrection,EmojiCapable"
     />
-    <subtype android:icon="@drawable/ic_subtype_keyboard"
+    <subtype android:icon="@drawable/ic_ime_switcher_dark"
             android:label="@string/subtype_generic"
             android:subtypeId="0x6f972360"
             android:imeSubtypeLocale="af"
             android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="KeyboardLayoutSet=qwerty,AsciiCapable"
+            android:imeSubtypeExtraValue="KeyboardLayoutSet=qwerty,AsciiCapable,EmojiCapable"
     />
-    <subtype android:icon="@drawable/ic_subtype_keyboard"
+    <subtype android:icon="@drawable/ic_ime_switcher_dark"
             android:label="@string/subtype_generic"
             android:subtypeId="0x590dde40"
             android:imeSubtypeLocale="ar"
             android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="SupportTouchPositionCorrection"
+            android:imeSubtypeExtraValue="SupportTouchPositionCorrection,EmojiCapable"
     />
-    <!--
-    <subtype android:icon="@drawable/ic_subtype_keyboard"
+    <subtype android:icon="@drawable/ic_ime_switcher_dark"
             android:label="@string/subtype_generic"
             android:subtypeId="0x70b0f974"
             android:imeSubtypeLocale="az"
             android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="KeyboardLayoutSet=qwerty,AsciiCapable"
+            android:imeSubtypeExtraValue="KeyboardLayoutSet=qwerty,AsciiCapable,EmojiCapable"
     />
-    -->
-    <subtype android:icon="@drawable/ic_subtype_keyboard"
+    <subtype android:icon="@drawable/ic_ime_switcher_dark"
             android:label="@string/subtype_generic"
             android:subtypeId="0x1dc3a859"
             android:imeSubtypeLocale="be"
             android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="KeyboardLayoutSet=east_slavic"
+            android:imeSubtypeExtraValue="KeyboardLayoutSet=east_slavic,EmojiCapable"
     />
-    <subtype android:icon="@drawable/ic_subtype_keyboard"
+    <subtype android:icon="@drawable/ic_ime_switcher_dark"
             android:label="@string/subtype_generic"
             android:subtypeId="0x0ba9c0e8"
             android:imeSubtypeLocale="bg"
             android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="KeyboardLayoutSet=bulgarian"
+            android:imeSubtypeExtraValue="KeyboardLayoutSet=bulgarian,EmojiCapable"
     />
-    <subtype android:icon="@drawable/ic_subtype_keyboard"
+    <subtype android:icon="@drawable/ic_ime_switcher_dark"
             android:label="@string/subtype_bulgarian_bds"
             android:subtypeId="0x5f51ba9a"
             android:imeSubtypeLocale="bg"
             android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="KeyboardLayoutSet=bulgarian_bds"
+            android:imeSubtypeExtraValue="KeyboardLayoutSet=bulgarian_bds,EmojiCapable"
     />
-    <subtype android:icon="@drawable/ic_subtype_keyboard"
+    <subtype android:icon="@drawable/ic_ime_switcher_dark"
             android:label="@string/subtype_generic"
             android:subtypeId="0xd2e520d5"
             android:imeSubtypeLocale="ca"
             android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="KeyboardLayoutSet=spanish,AsciiCapable"
+            android:imeSubtypeExtraValue="KeyboardLayoutSet=spanish,AsciiCapable,EmojiCapable"
     />
-    <subtype android:icon="@drawable/ic_subtype_keyboard"
+    <subtype android:icon="@drawable/ic_ime_switcher_dark"
             android:label="@string/subtype_generic"
             android:subtypeId="0x2d3d2ed0"
             android:imeSubtypeLocale="cs"
             android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection"
+            android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection,EmojiCapable"
     />
-    <subtype android:icon="@drawable/ic_subtype_keyboard"
+    <subtype android:icon="@drawable/ic_ime_switcher_dark"
             android:label="@string/subtype_generic"
             android:subtypeId="0x2df4605d"
             android:imeSubtypeLocale="da"
             android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection"
+            android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection,EmojiCapable"
     />
-    <subtype android:icon="@drawable/ic_subtype_keyboard"
+    <subtype android:icon="@drawable/ic_ime_switcher_dark"
             android:label="@string/subtype_generic"
             android:subtypeId="0x2e2cbe61"
             android:imeSubtypeLocale="de"
             android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection"
+            android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection,EmojiCapable"
     />
-    <subtype android:icon="@drawable/ic_subtype_keyboard"
+    <subtype android:icon="@drawable/ic_ime_switcher_dark"
             android:label="@string/subtype_generic"
             android:subtypeId="0x0e7802d3"
             android:imeSubtypeLocale="el"
             android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="KeyboardLayoutSet=greek"
+            android:imeSubtypeExtraValue="KeyboardLayoutSet=greek,EmojiCapable"
     />
-    <subtype android:icon="@drawable/ic_subtype_keyboard"
+    <subtype android:icon="@drawable/ic_ime_switcher_dark"
             android:label="@string/subtype_generic"
             android:subtypeId="0x4090554a"
             android:imeSubtypeLocale="eo"
             android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="KeyboardLayoutSet=spanish"
+            android:imeSubtypeExtraValue="KeyboardLayoutSet=spanish,EmojiCapable"
     />
-    <subtype android:icon="@drawable/ic_subtype_keyboard"
+    <subtype android:icon="@drawable/ic_ime_switcher_dark"
             android:label="@string/subtype_generic"
             android:subtypeId="0x30a6e00e"
             android:imeSubtypeLocale="es"
             android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection"
+            android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection,EmojiCapable"
     />
-    <subtype android:icon="@drawable/ic_subtype_keyboard"
+    <subtype android:icon="@drawable/ic_ime_switcher_dark"
             android:label="@string/subtype_es_US"
             android:subtypeId="0x84d2efc6"
             android:imeSubtypeLocale="es_US"
             android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="KeyboardLayoutSet=spanish,AsciiCapable"
+            android:imeSubtypeExtraValue="KeyboardLayoutSet=spanish,AsciiCapable,EmojiCapable"
     />
     <!--
-    <subtype android:icon="@drawable/ic_subtype_keyboard"
+    <subtype android:icon="@drawable/ic_ime_switcher_dark"
             android:label="@string/subtype_generic"
             android:subtypeId="0x623f9286"
             android:imeSubtypeLocale="es_419"
             android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="KeyboardLayoutSet=spanish,AsciiCapable"
+            android:imeSubtypeExtraValue="KeyboardLayoutSet=spanish,AsciiCapable,EmojiCapable"
     />
     -->
-    <subtype android:icon="@drawable/ic_subtype_keyboard"
+    <subtype android:icon="@drawable/ic_ime_switcher_dark"
             android:label="@string/subtype_generic"
             android:subtypeId="0xec2d3955"
             android:imeSubtypeLocale="et"
             android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="KeyboardLayoutSet=nordic,AsciiCapable"
+            android:imeSubtypeExtraValue="KeyboardLayoutSet=nordic,AsciiCapable,EmojiCapable"
     />
-    <subtype android:icon="@drawable/ic_subtype_keyboard"
+    <subtype android:icon="@drawable/ic_ime_switcher_dark"
             android:label="@string/subtype_generic"
             android:subtypeId="0xbe66c254"
             android:imeSubtypeLocale="fa"
             android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="KeyboardLayoutSet=farsi"
+            android:imeSubtypeExtraValue="KeyboardLayoutSet=farsi,EmojiCapable"
     />
-    <subtype android:icon="@drawable/ic_subtype_keyboard"
+    <subtype android:icon="@drawable/ic_ime_switcher_dark"
             android:label="@string/subtype_generic"
             android:subtypeId="0x31cecda3"
             android:imeSubtypeLocale="fi"
             android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection"
+            android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection,EmojiCapable"
     />
-    <subtype android:icon="@drawable/ic_subtype_keyboard"
+    <subtype android:icon="@drawable/ic_ime_switcher_dark"
             android:label="@string/subtype_generic"
             android:subtypeId="0x324da12c"
             android:imeSubtypeLocale="fr"
             android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection"
+            android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection,EmojiCapable"
     />
-    <subtype android:icon="@drawable/ic_subtype_keyboard"
+    <subtype android:icon="@drawable/ic_ime_switcher_dark"
             android:label="@string/subtype_generic"
             android:subtypeId="0xeadbb691"
             android:imeSubtypeLocale="fr_CA"
             android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection"
+            android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection,EmojiCapable"
     />
-    <subtype android:icon="@drawable/ic_subtype_keyboard"
+    <subtype android:icon="@drawable/ic_ime_switcher_dark"
             android:label="@string/subtype_generic"
             android:subtypeId="0x39753b7f"
             android:imeSubtypeLocale="hi"
             android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="KeyboardLayoutSet=hindi"
+            android:imeSubtypeExtraValue="KeyboardLayoutSet=hindi,EmojiCapable"
     />
-    <subtype android:icon="@drawable/ic_subtype_keyboard"
+    <subtype android:icon="@drawable/ic_ime_switcher_dark"
             android:label="@string/subtype_generic"
             android:subtypeId="0x35b7526a"
             android:imeSubtypeLocale="hr"
             android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection"
+            android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection,EmojiCapable"
     />
-    <subtype android:icon="@drawable/ic_subtype_keyboard"
+    <subtype android:icon="@drawable/ic_ime_switcher_dark"
             android:label="@string/subtype_generic"
             android:subtypeId="0x35e198ed"
             android:imeSubtypeLocale="hu"
             android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection"
+            android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection,EmojiCapable"
+    />
+    <subtype android:icon="@drawable/ic_ime_switcher_dark"
+            android:label="@string/subtype_generic"
+            android:subtypeId="0xe39ac3ca"
+            android:imeSubtypeLocale="hy"
+            android:imeSubtypeMode="keyboard"
+            android:imeSubtypeExtraValue="KeyboardLayoutSet=armenian_phonetic,EmojiCapable"
     />
     <!-- Java uses the deprecated "in" code instead of the standard "id" code for Indonesian. -->
-    <subtype android:icon="@drawable/ic_subtype_keyboard"
+    <subtype android:icon="@drawable/ic_ime_switcher_dark"
             android:label="@string/subtype_generic"
             android:subtypeId="0x7daea460"
             android:imeSubtypeLocale="in"
             android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="KeyboardLayoutSet=qwerty,AsciiCapable"
+            android:imeSubtypeExtraValue="KeyboardLayoutSet=qwerty,AsciiCapable,EmojiCapable"
     />
-    <subtype android:icon="@drawable/ic_subtype_keyboard"
+    <subtype android:icon="@drawable/ic_ime_switcher_dark"
             android:label="@string/subtype_generic"
             android:subtypeId="0x7df519e5"
             android:imeSubtypeLocale="is"
             android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="KeyboardLayoutSet=qwerty,AsciiCapable"
+            android:imeSubtypeExtraValue="KeyboardLayoutSet=qwerty,AsciiCapable,EmojiCapable"
     />
-    <subtype android:icon="@drawable/ic_subtype_keyboard"
+    <subtype android:icon="@drawable/ic_ime_switcher_dark"
             android:label="@string/subtype_generic"
             android:subtypeId="0x37885a0b"
             android:imeSubtypeLocale="it"
             android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection"
+            android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection,EmojiCapable"
     />
     <!-- Java uses the deprecated "iw" code instead of the standard "he" code for Hebrew. -->
-    <subtype android:icon="@drawable/ic_subtype_keyboard"
+    <subtype android:icon="@drawable/ic_ime_switcher_dark"
             android:label="@string/subtype_generic"
             android:subtypeId="0x66fb18bd"
             android:imeSubtypeLocale="iw"
             android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="SupportTouchPositionCorrection"
+            android:imeSubtypeExtraValue="SupportTouchPositionCorrection,EmojiCapable"
     />
-    <subtype android:icon="@drawable/ic_subtype_keyboard"
+    <subtype android:icon="@drawable/ic_ime_switcher_dark"
             android:label="@string/subtype_generic"
             android:subtypeId="0x6e119e6a"
             android:imeSubtypeLocale="ka"
             android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="KeyboardLayoutSet=georgian"
+            android:imeSubtypeExtraValue="KeyboardLayoutSet=georgian,EmojiCapable"
     />
     <!--
-    <subtype android:icon="@drawable/ic_subtype_keyboard"
+    <subtype android:icon="@drawable/ic_ime_switcher_dark"
             android:label="@string/subtype_generic"
             android:subtypeId="0x2d73d2f6"
             android:imeSubtypeLocale="kk"
             android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="KeyboardLayoutSet=east_slavic"
+            android:imeSubtypeExtraValue="KeyboardLayoutSet=east_slavic,EmojiCapable"
     />
     -->
-    <subtype android:icon="@drawable/ic_subtype_keyboard"
+    <subtype android:icon="@drawable/ic_ime_switcher_dark"
+            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_ime_switcher_dark"
             android:label="@string/subtype_generic"
             android:subtypeId="0x2e391c04"
             android:imeSubtypeLocale="ky"
             android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="KeyboardLayoutSet=east_slavic"
+            android:imeSubtypeExtraValue="KeyboardLayoutSet=east_slavic,EmojiCapable"
     />
-    <subtype android:icon="@drawable/ic_subtype_keyboard"
+    <subtype android:icon="@drawable/ic_ime_switcher_dark"
+            android:label="@string/subtype_generic"
+            android:subtypeId="0x8315772c"
+            android:imeSubtypeLocale="lo"
+            android:imeSubtypeMode="keyboard"
+            android:imeSubtypeExtraValue="KeyboardLayoutSet=lao,EmojiCapable"
+    />
+    <subtype android:icon="@drawable/ic_ime_switcher_dark"
             android:label="@string/subtype_generic"
             android:subtypeId="0x8321bb43"
             android:imeSubtypeLocale="lt"
             android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="KeyboardLayoutSet=qwerty,AsciiCapable"
+            android:imeSubtypeExtraValue="KeyboardLayoutSet=qwerty,AsciiCapable,EmojiCapable"
     />
-    <subtype android:icon="@drawable/ic_subtype_keyboard"
+    <subtype android:icon="@drawable/ic_ime_switcher_dark"
             android:label="@string/subtype_generic"
             android:subtypeId="0x833dea45"
             android:imeSubtypeLocale="lv"
             android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="KeyboardLayoutSet=qwerty,AsciiCapable"
+            android:imeSubtypeExtraValue="KeyboardLayoutSet=qwerty,AsciiCapable,EmojiCapable"
     />
-    <subtype android:icon="@drawable/ic_subtype_keyboard"
+    <subtype android:icon="@drawable/ic_ime_switcher_dark"
             android:label="@string/subtype_generic"
             android:subtypeId="0xaf50ab7c"
             android:imeSubtypeLocale="mk"
             android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="KeyboardLayoutSet=south_slavic"
+            android:imeSubtypeExtraValue="KeyboardLayoutSet=south_slavic,EmojiCapable"
     />
-    <subtype android:icon="@drawable/ic_subtype_keyboard"
+    <subtype android:icon="@drawable/ic_ime_switcher_dark"
             android:label="@string/subtype_generic"
             android:subtypeId="0xcdcfc3ab"
             android:imeSubtypeLocale="mn"
             android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="KeyboardLayoutSet=mongolian"
+            android:imeSubtypeExtraValue="KeyboardLayoutSet=mongolian,EmojiCapable"
     />
-    <subtype android:icon="@drawable/ic_subtype_keyboard"
+    <subtype android:icon="@drawable/ic_ime_switcher_dark"
             android:label="@string/subtype_generic"
             android:subtypeId="0x84c87c61"
             android:imeSubtypeLocale="ms"
             android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="KeyboardLayoutSet=qwerty,AsciiCapable"
+            android:imeSubtypeExtraValue="KeyboardLayoutSet=qwerty,AsciiCapable,EmojiCapable"
     />
-    <subtype android:icon="@drawable/ic_subtype_keyboard"
+    <subtype android:icon="@drawable/ic_ime_switcher_dark"
             android:label="@string/subtype_generic"
             android:subtypeId="0x3f12ee14"
             android:imeSubtypeLocale="nb"
             android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection"
+            android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection,EmojiCapable"
     />
-    <subtype android:icon="@drawable/ic_subtype_keyboard"
+    <!--
+    <subtype android:icon="@drawable/ic_ime_switcher_dark"
+            android:label="@string/subtype_generic"
+            android:subtypeId="0xd80a4cee"
+            android:imeSubtypeLocale="ne"
+            android:imeSubtypeMode="keyboard"
+            android:imeSubtypeExtraValue="KeyboardLayoutSet=nepali_romanized,EmojiCapable"
+    />
+    <subtype android:icon="@drawable/ic_ime_switcher_dark"
+            android:label="@string/subtype_nepali_traditional"
+            android:subtypeId="0x5fafea88"
+            android:imeSubtypeLocale="ne"
+            android:imeSubtypeMode="keyboard"
+            android:imeSubtypeExtraValue="KeyboardLayoutSet=nepali_traditional,EmojiCapable"
+    />
+    -->
+    <subtype android:icon="@drawable/ic_ime_switcher_dark"
             android:label="@string/subtype_generic"
             android:subtypeId="0x3f9fd91e"
             android:imeSubtypeLocale="nl"
             android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection"
+            android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection,EmojiCapable"
     />
-    <subtype android:icon="@drawable/ic_subtype_keyboard"
+    <subtype android:icon="@drawable/ic_ime_switcher_dark"
             android:label="@string/subtype_generic"
             android:subtypeId="0x500ca92c"
             android:imeSubtypeLocale="nl_BE"
             android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="KeyboardLayoutSet=azerty,AsciiCapable"
+            android:imeSubtypeExtraValue="KeyboardLayoutSet=azerty,AsciiCapable,EmojiCapable"
     />
-    <subtype android:icon="@drawable/ic_subtype_keyboard"
+    <subtype android:icon="@drawable/ic_ime_switcher_dark"
             android:label="@string/subtype_generic"
             android:subtypeId="0x43098a5c"
             android:imeSubtypeLocale="pl"
             android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection"
+            android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection,EmojiCapable"
     />
-    <subtype android:icon="@drawable/ic_subtype_keyboard"
+    <subtype android:icon="@drawable/ic_ime_switcher_dark"
             android:label="@string/subtype_generic"
             android:subtypeId="0xcafff4a6"
             android:imeSubtypeLocale="pt_BR"
             android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="KeyboardLayoutSet=qwerty,AsciiCapable"
+            android:imeSubtypeExtraValue="KeyboardLayoutSet=qwerty,AsciiCapable,EmojiCapable"
     />
-    <subtype android:icon="@drawable/ic_subtype_keyboard"
+    <subtype android:icon="@drawable/ic_ime_switcher_dark"
             android:label="@string/subtype_generic"
             android:subtypeId="0xe2fffc5a"
             android:imeSubtypeLocale="pt_PT"
             android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="KeyboardLayoutSet=qwerty,AsciiCapable"
+            android:imeSubtypeExtraValue="KeyboardLayoutSet=qwerty,AsciiCapable,EmojiCapable"
     />
-    <subtype android:icon="@drawable/ic_subtype_keyboard"
+    <subtype android:icon="@drawable/ic_ime_switcher_dark"
             android:label="@string/subtype_generic"
             android:subtypeId="0x8d185978"
             android:imeSubtypeLocale="ro"
             android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="KeyboardLayoutSet=qwerty,AsciiCapable"
+            android:imeSubtypeExtraValue="KeyboardLayoutSet=qwerty,AsciiCapable,EmojiCapable"
     />
-    <subtype android:icon="@drawable/ic_subtype_keyboard"
+    <subtype android:icon="@drawable/ic_ime_switcher_dark"
             android:label="@string/subtype_generic"
             android:subtypeId="0x763a8752"
             android:imeSubtypeLocale="ru"
             android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="SupportTouchPositionCorrection"
+            android:imeSubtypeExtraValue="SupportTouchPositionCorrection,EmojiCapable"
     />
-    <subtype android:icon="@drawable/ic_subtype_keyboard"
+    <subtype android:icon="@drawable/ic_ime_switcher_dark"
             android:label="@string/subtype_generic"
             android:subtypeId="0x8e94d413"
             android:imeSubtypeLocale="sk"
             android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="KeyboardLayoutSet=qwerty,AsciiCapable"
+            android:imeSubtypeExtraValue="KeyboardLayoutSet=qwerty,AsciiCapable,EmojiCapable"
     />
-    <subtype android:icon="@drawable/ic_subtype_keyboard"
+    <subtype android:icon="@drawable/ic_ime_switcher_dark"
             android:label="@string/subtype_generic"
             android:subtypeId="0x8ea2eb94"
             android:imeSubtypeLocale="sl"
             android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="KeyboardLayoutSet=qwerty,AsciiCapable"
+            android:imeSubtypeExtraValue="KeyboardLayoutSet=qwerty,AsciiCapable,EmojiCapable"
     />
-    <subtype android:icon="@drawable/ic_subtype_keyboard"
+    <subtype android:icon="@drawable/ic_ime_switcher_dark"
             android:label="@string/subtype_generic"
             android:subtypeId="0x77c5196e"
             android:imeSubtypeLocale="sr"
             android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="SupportTouchPositionCorrection"
+            android:imeSubtypeExtraValue="SupportTouchPositionCorrection,EmojiCapable"
     />
     <!-- TODO: Uncomment once we can handle IETF language tag with script name specified.
-    <subtype android:icon="@drawable/ic_subtype_keyboard"
+    <subtype android:icon="@drawable/ic_ime_switcher_dark"
             android:label="@string/subtype_serbian_cyrillic"
             android:subtypeId="0xXXXXXXXX"
             android:imeSubtypeLocale="sr"
             android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="SupportTouchPositionCorrection"
+            android:imeSubtypeExtraValue="SupportTouchPositionCorrection,EmojiCapable"
     />
-    <subtype android:icon="@drawable/ic_subtype_keyboard"
+    <subtype android:icon="@drawable/ic_ime_switcher_dark"
             android:label="@string/subtype_serbian_latin"
             android:subtypeId="0xXXXXXXXX"
             android:imeSubtypeLocale="sr-Latn"
             android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="KeyboardLayoutSet=qwerty,AsciiCapable"
+            android:imeSubtypeExtraValue="KeyboardLayoutSet=qwerty,AsciiCapable,EmojiCapable"
     />
     -->
-    <subtype android:icon="@drawable/ic_subtype_keyboard"
+    <subtype android:icon="@drawable/ic_ime_switcher_dark"
             android:label="@string/subtype_generic"
             android:subtypeId="0x48b4ff43"
             android:imeSubtypeLocale="sv"
             android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection"
+            android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection,EmojiCapable"
     />
-    <subtype android:icon="@drawable/ic_subtype_keyboard"
+    <subtype android:icon="@drawable/ic_ime_switcher_dark"
             android:label="@string/subtype_generic"
             android:subtypeId="0x8f3dee1f"
             android:imeSubtypeLocale="sw"
             android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="KeyboardLayoutSet=qwerty,AsciiCapable"
+            android:imeSubtypeExtraValue="KeyboardLayoutSet=qwerty,AsciiCapable,EmojiCapable"
     />
-    <subtype android:icon="@drawable/ic_subtype_keyboard"
+    <subtype android:icon="@drawable/ic_ime_switcher_dark"
             android:label="@string/subtype_generic"
             android:subtypeId="0x1f94d5d4"
             android:imeSubtypeLocale="th"
             android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="KeyboardLayoutSet=thai"
+            android:imeSubtypeExtraValue="KeyboardLayoutSet=thai,EmojiCapable"
     />
-    <subtype android:icon="@drawable/ic_subtype_keyboard"
+    <subtype android:icon="@drawable/ic_ime_switcher_dark"
             android:label="@string/subtype_generic"
             android:subtypeId="0xf08285ef"
             android:imeSubtypeLocale="tl"
             android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="KeyboardLayoutSet=spanish,AsciiCapable"
+            android:imeSubtypeExtraValue="KeyboardLayoutSet=spanish,AsciiCapable,EmojiCapable"
     />
-    <subtype android:icon="@drawable/ic_subtype_keyboard"
+    <subtype android:icon="@drawable/ic_ime_switcher_dark"
             android:label="@string/subtype_generic"
             android:subtypeId="0x4a3179de"
             android:imeSubtypeLocale="tr"
             android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection"
+            android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection,EmojiCapable"
     />
-    <subtype android:icon="@drawable/ic_subtype_keyboard"
+    <subtype android:icon="@drawable/ic_ime_switcher_dark"
             android:label="@string/subtype_generic"
             android:subtypeId="0x3e84492c"
             android:imeSubtypeLocale="uk"
             android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="KeyboardLayoutSet=east_slavic"
+            android:imeSubtypeExtraValue="KeyboardLayoutSet=east_slavic,EmojiCapable"
     />
-    <subtype android:icon="@drawable/ic_subtype_keyboard"
+    <subtype android:icon="@drawable/ic_ime_switcher_dark"
             android:label="@string/subtype_generic"
             android:subtypeId="0x93972eee"
             android:imeSubtypeLocale="vi"
             android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="KeyboardLayoutSet=qwerty,AsciiCapable"
+            android:imeSubtypeExtraValue="KeyboardLayoutSet=qwerty,AsciiCapable,EmojiCapable"
     />
-    <subtype android:icon="@drawable/ic_subtype_keyboard"
+    <subtype android:icon="@drawable/ic_ime_switcher_dark"
             android:label="@string/subtype_generic"
             android:subtypeId="0x9b13ab76"
             android:imeSubtypeLocale="zu"
             android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="KeyboardLayoutSet=qwerty,AsciiCapable"
+            android:imeSubtypeExtraValue="KeyboardLayoutSet=qwerty,AsciiCapable,EmojiCapable"
     />
-    <subtype android:icon="@drawable/ic_subtype_keyboard"
+    <subtype android:icon="@drawable/ic_ime_switcher_dark"
             android:label="@string/subtype_no_language_qwerty"
             android:subtypeId="0xa239ebad"
             android:imeSubtypeLocale="zz"
             android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="KeyboardLayoutSet=qwerty,AsciiCapable,EnabledWhenDefaultIsNotAsciiCapable"
+            android:imeSubtypeExtraValue="KeyboardLayoutSet=qwerty,AsciiCapable,EnabledWhenDefaultIsNotAsciiCapable,EmojiCapable"
     />
+    <!-- Emoji subtype has to be an addtional subtype added at boot time because ICS doesn't
+         support Emoji. -->
+    <!--
+    <subtype android:icon="@drawable/ic_ime_switcher_dark"
+            android:label="@string/subtype_emoji"
+            android:subtypeId="0xc14d88b2"
+            android:imeSubtypeLocale="zz"
+            android:imeSubtypeMode="keyboard"
+            android:imeSubtypeExtraValue="KeyboardLayoutSet=emoji,EmojiCapable"
+    />
+    -->
 </input-method>
diff --git a/java/res/xml/prefs.xml b/java/res/xml/prefs.xml
index 2a726c4..6c36b0e 100644
--- a/java/res/xml/prefs.xml
+++ b/java/res/xml/prefs.xml
@@ -42,13 +42,11 @@
             android:title="@string/popup_on_keypress"
             android:persistent="true"
             android:defaultValue="@bool/config_default_key_preview_popup" />
-        <ListPreference
-            android:key="voice_mode"
+        <CheckBoxPreference
+            android:key="pref_voice_input_key"
             android:title="@string/voice_input"
             android:persistent="true"
-            android:entryValues="@array/voice_input_modes_values"
-            android:entries="@array/voice_input_modes"
-            android:defaultValue="@string/voice_mode_main" />
+            android:defaultValue="true" />
     </PreferenceCategory>
     <PreferenceCategory
         android:title="@string/correction_category"
diff --git a/java/res/xml/row_dvorak4.xml b/java/res/xml/row_dvorak4.xml
index e6d487e..02a95ac 100644
--- a/java/res/xml/row_dvorak4.xml
+++ b/java/res/xml/row_dvorak4.xml
@@ -65,7 +65,7 @@
         </switch>
         <include
             latin:keyXPos="25%p"
-            latin:keyboardLayout="@xml/key_space" />
+            latin:keyboardLayout="@xml/key_space_5kw" />
         <Key
             latin:keyLabel="z"
             latin:keyLabelFlags="hasPopupHint"
diff --git a/java/res/xml/row_pcqwerty5.xml b/java/res/xml/row_pcqwerty5.xml
index a8940af..4ec908b 100644
--- a/java/res/xml/row_pcqwerty5.xml
+++ b/java/res/xml/row_pcqwerty5.xml
@@ -24,36 +24,23 @@
     <Row
         latin:keyWidth="7.692%p"
     >
-        <switch>
-            <case
-                latin:keyboardLayoutSetElement="symbols|symbolsShifted"
-            >
-                <Key
-                    latin:keyStyle="toAlphaKeyStyle"
-                    latin:keyWidth="11.538%p" />
-            </case>
-            <!-- keyboardLayoutSetElement="alphabet*" -->
-            <default>
-                <Key
-                    latin:keyStyle="toSymbolKeyStyle"
-                    latin:keyIcon="!icon/undefined"
-                    latin:keyLabel="!text/label_to_symbol_key_pcqwerty"
-                    latin:keyWidth="11.538%p" />
-            </default>
-        </switch>
+        <Spacer
+            latin:keyWidth="11.538%p" />
         <switch>
             <case
                 latin:shortcutKeyEnabled="true"
             >
                 <Key
-                    latin:keyStyle="shortcutKeyStyle" />
-            </case>
+                    latin:keyStyle="shortcutKeyStyle"
+                    latin:keyWidth="11.538%p" />
+                </case>
             <case
                 latin:clobberSettingsKey="false"
             >
                 <Key
-                    latin:keyStyle="settingsKeyStyle" />
-            </case>
+                    latin:keyStyle="settingsKeyStyle"
+                    latin:keyWidth="11.538%p" />
+                </case>
         </switch>
         <switch>
             <case
@@ -61,21 +48,33 @@
             >
                 <Key
                     latin:keyStyle="languageSwitchKeyStyle"
-                    latin:keyXPos="19.230%p" />
+                    latin:keyWidth="11.538%p" />
                 <Key
                     latin:keyStyle="spaceKeyStyle"
-                    latin:keyWidth="53.844%p" />
-            </case>
+                    latin:keyWidth="38.464%p" />
+                </case>
             <!-- languageSwitchKeyEnabled="false" -->
             <default>
                 <Key
                     latin:keyStyle="spaceKeyStyle"
-                    latin:keyXPos="19.230%p"
-                    latin:keyWidth="61.536%p" />
+                    latin:keyWidth="50.002%p" />
             </default>
         </switch>
         <Key
-            latin:keyStyle="enterKeyStyle"
-            latin:keyWidth="fillRight" />
+            latin:keyStyle="defaultEnterKeyStyle"
+            latin:keyWidth="15.384%p" />
+        <switch>
+            <case
+                latin:keyboardLayoutSetElement="alphabet|alphabetAutomaticShifted"
+            >
+                <Spacer />
+            </case>
+            <!-- keyboardLayoutSetElement="alphabetManualShifted|alphabetShiftLocked|alphabetShiftLockShifted" -->
+            <default>
+                <Key
+                    latin:keyStyle="emojiKeyStyle"
+                    latin:keyWidth="fillRight" />
+            </default>
+        </switch>
     </Row>
 </merge>
diff --git a/java/res/xml/row_qwerty4.xml b/java/res/xml/row_qwerty4.xml
index c29fbf2..578bc12 100644
--- a/java/res/xml/row_qwerty4.xml
+++ b/java/res/xml/row_qwerty4.xml
@@ -31,7 +31,7 @@
             latin:keyboardLayout="@xml/key_f1" />
         <include
             latin:keyXPos="25%p"
-            latin:keyboardLayout="@xml/key_space" />
+            latin:keyboardLayout="@xml/key_space_5kw" />
         <switch>
             <case
                 latin:languageCode="ar|fa"
@@ -42,6 +42,21 @@
                     latin:moreKeys="!text/more_keys_for_arabic_diacritics"
                     latin:keyStyle="punctuationKeyStyle" />
             </case>
+            <case
+                latin:languageCode="ne"
+                latin:keyboardLayoutSet="nepali_traditional"
+            >
+                <include
+                    latin:keyboardLayout="@xml/key_nepali_traditional_period" />
+            </case>
+            <case
+                latin:languageCode="hy"
+            >
+                <!-- U+0589: "։" ARMENIAN FULL STOP -->
+                <Key
+                    latin:keyLabel="&#x0589;"
+                    latin:keyStyle="punctuationKeyStyle" />
+            </case>
             <default>
                 <Key
                     latin:keyStyle="punctuationKeyStyle" />
diff --git a/java/res/xml/row_symbols4.xml b/java/res/xml/row_symbols4.xml
index 150ad48..0bf412f 100644
--- a/java/res/xml/row_symbols4.xml
+++ b/java/res/xml/row_symbols4.xml
@@ -18,38 +18,29 @@
 */
 -->
 
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <Row
-        latin:keyWidth="10%p"
-    >
-        <Key
-            latin:keyStyle="toAlphaKeyStyle"
-            latin:keyWidth="15%p" />
-        <switch>
-            <case
-                latin:hasShortcutKey="true"
-            >
-                <Key
-                    latin:keyStyle="shortcutKeyStyle" />
-            </case>
-            <!-- latin:hasShortcutKey="false" -->
-            <default>
-                <Key
-                    latin:keyLabel="!text/keylabel_for_comma"
-                    latin:keyLabelFlags="hasPopupHint"
-                    latin:additionalMoreKeys="!text/more_keys_for_comma"
-                    latin:keyStyle="f1MoreKeysStyle" />
-            </default>
-        </switch>
-        <include
-            latin:keyXPos="25%p"
-            latin:keyboardLayout="@xml/key_space" />
-        <Key
-            latin:keyStyle="punctuationKeyStyle" />
-        <Key
-            latin:keyStyle="enterKeyStyle"
-            latin:keyWidth="fillRight" />
-    </Row>
+<merge xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin" >
+
+    <Key
+        latin:backgroundType="functional"
+        latin:keyLabel="_" />
+    <Key
+        latin:backgroundType="functional"
+        latin:keyLabel="/" />
+
+    <switch>
+        <case latin:hasShortcutKey="true" >
+            <Key latin:keyStyle="shortcutKeyStyle" />
+        </case>
+        <!-- latin:hasShortcutKey="false" -->
+        <default>
+        </default>
+    </switch>
+
+    <include latin:keyboardLayout="@xml/key_space_symbols" />
+    <include latin:keyboardLayout="@xml/keys_comma_period" />
+
+    <Key
+        latin:keyStyle="emojiKeyStyle"
+        latin:keyWidth="fillRight" />
+
 </merge>
diff --git a/java/res/xml/row_symbols_shift4.xml b/java/res/xml/row_symbols_shift4.xml
index 99a685c..72d24a3 100644
--- a/java/res/xml/row_symbols_shift4.xml
+++ b/java/res/xml/row_symbols_shift4.xml
@@ -17,31 +17,14 @@
 ** limitations under the License.
 */
 -->
+<merge xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin" >
 
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <Row
-        latin:keyWidth="10%p"
-    >
-        <Key
-            latin:keyStyle="toAlphaKeyStyle"
-            latin:keyWidth="15%p" />
-        <!-- U+201A: "‚" SINGLE LOW-9 QUOTATION MARK
-             U+201E: "„" DOUBLE LOW-9 QUOTATION MARK -->
-        <Key
-            latin:keyLabel="&#x201E;"
-            latin:moreKeys="&#x201A;"
-            latin:backgroundType="functional" />
-        <include
-            latin:keyXPos="25%p"
-            latin:keyboardLayout="@xml/key_space" />
-        <!-- U+2026: "…" HORIZONTAL ELLIPSIS -->
-        <Key
-            latin:keyLabel="&#x2026;"
-            latin:backgroundType="functional" />
-        <Key
-            latin:keyStyle="enterKeyStyle"
-            latin:keyWidth="fillRight" />
-    </Row>
+    <include latin:keyboardLayout="@xml/keys_less_greater" />
+    <include
+        latin:keyboardLayout="@xml/key_space_symbols" />
+    <include latin:keyboardLayout="@xml/keys_comma_period" />
+
+    <include
+        latin:keyboardLayout="@xml/key_f2" />
+
 </merge>
diff --git a/java/res/xml/rowkeys_armenian_phonetic1.xml b/java/res/xml/rowkeys_armenian_phonetic1.xml
new file mode 100644
index 0000000..1984fae
--- /dev/null
+++ b/java/res/xml/rowkeys_armenian_phonetic1.xml
@@ -0,0 +1,84 @@
+<?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"
+>
+    <!-- U+0567: "է" ARMENIAN SMALL LETTER EH -->
+    <Key
+        latin:keyLabel="&#x0567;"
+        latin:keyHintLabel="1"
+        latin:additionalMoreKeys="1"
+        latin:keyLabelFlags="fontNormal" />
+    <!-- U+0569: "թ" ARMENIAN SMALL LETTER TO -->
+    <Key
+        latin:keyLabel="&#x0569;"
+        latin:keyHintLabel="2"
+        latin:additionalMoreKeys="2"
+        latin:keyLabelFlags="fontNormal" />
+    <!-- U+0583: "փ" ARMENIAN SMALL LETTER PIWR -->
+    <Key
+        latin:keyLabel="&#x0583;"
+        latin:keyHintLabel="3"
+        latin:additionalMoreKeys="3"
+        latin:keyLabelFlags="fontNormal" />
+    <!-- U+0571: "ձ" ARMENIAN SMALL LETTER JA -->
+    <Key
+        latin:keyLabel="&#x0571;"
+        latin:keyHintLabel="4"
+        latin:additionalMoreKeys="4"
+        latin:keyLabelFlags="fontNormal" />
+    <!-- U+057B: "ջ" ARMENIAN SMALL LETTER JHEH -->
+    <Key
+        latin:keyLabel="&#x057B;"
+        latin:keyHintLabel="5"
+        latin:additionalMoreKeys="5"
+        latin:keyLabelFlags="fontNormal" />
+    <!-- U+0580: "ր" ARMENIAN SMALL LETTER REH -->
+    <Key
+        latin:keyLabel="&#x0580;"
+        latin:keyHintLabel="6"
+        latin:additionalMoreKeys="6"
+        latin:keyLabelFlags="fontNormal" />
+    <!-- U+0579: "չ" ARMENIAN SMALL LETTER CHA -->
+    <Key
+        latin:keyLabel="&#x0579;"
+        latin:keyHintLabel="7"
+        latin:additionalMoreKeys="7"
+        latin:keyLabelFlags="fontNormal" />
+    <!-- U+0573: "ճ" ARMENIAN SMALL LETTER CHEH -->
+    <Key
+        latin:keyLabel="&#x0573;"
+        latin:keyHintLabel="8"
+        latin:additionalMoreKeys="8"
+        latin:keyLabelFlags="fontNormal" />
+    <!-- U+056A: "ժ" ARMENIAN SMALL LETTER ZHE -->
+    <Key
+        latin:keyLabel="&#x056A;"
+        latin:keyHintLabel="9"
+        latin:additionalMoreKeys="9"
+        latin:keyLabelFlags="fontNormal" />
+    <!-- U+056E: "ծ" ARMENIAN SMALL LETTER CA -->
+    <Key
+        latin:keyLabel="&#x056E;"
+        latin:keyHintLabel="0"
+        latin:additionalMoreKeys="0"
+        latin:keyLabelFlags="fontNormal" />
+</merge>
diff --git a/java/res/xml/rowkeys_armenian_phonetic2.xml b/java/res/xml/rowkeys_armenian_phonetic2.xml
new file mode 100644
index 0000000..5dcabc3
--- /dev/null
+++ b/java/res/xml/rowkeys_armenian_phonetic2.xml
@@ -0,0 +1,66 @@
+<?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"
+>
+    <!-- U+0584: "ք" ARMENIAN SMALL LETTER KEH -->
+    <Key
+        latin:keyLabel="&#x0584;"
+        latin:keyLabelFlags="fontNormal" />
+    <!-- U+0578: "ո" ARMENIAN SMALL LETTER VO -->
+    <Key
+        latin:keyLabel="&#x0578;"
+        latin:keyLabelFlags="fontNormal" />
+    <!-- U+0565: "ե" ARMENIAN SMALL LETTER ECH
+         U+0587: "և" ARMENIAN SMALL LIGATURE ECH YIWN -->
+    <Key
+        latin:keyLabel="&#x0565;"
+        latin:moreKeys="&#x0587;"
+        latin:keyLabelFlags="fontNormal" />
+    <!-- U+057C: "ռ" ARMENIAN SMALL LETTER RA -->
+    <Key
+        latin:keyLabel="&#x057C;"
+        latin:keyLabelFlags="fontNormal" />
+    <!-- U+057F: "տ" ARMENIAN SMALL LETTER TIWN -->
+    <Key
+        latin:keyLabel="&#x057F;"
+        latin:keyLabelFlags="fontNormal" />
+    <!-- U+0568: "ը" ARMENIAN SMALL LETTER ET -->
+    <Key
+        latin:keyLabel="&#x0568;"
+        latin:keyLabelFlags="fontNormal" />
+    <!-- U+0582: "ւ" ARMENIAN SMALL LETTER YIWN -->
+    <Key
+        latin:keyLabel="&#x0582;"
+        latin:keyLabelFlags="fontNormal" />
+    <!-- U+056B: "ի" ARMENIAN SMALL LETTER INI -->
+    <Key
+        latin:keyLabel="&#x056B;"
+        latin:keyLabelFlags="fontNormal" />
+    <!-- U+0585: "օ" ARMENIAN SMALL LETTER OH -->
+    <Key
+        latin:keyLabel="&#x0585;"
+        latin:keyLabelFlags="fontNormal" />
+    <!-- U+057A: "պ" ARMENIAN SMALL LETTER PEH -->
+    <Key
+        latin:keyLabel="&#x057A;"
+        latin:keyLabelFlags="fontNormal" />
+</merge>
diff --git a/java/res/xml/rowkeys_armenian_phonetic3.xml b/java/res/xml/rowkeys_armenian_phonetic3.xml
new file mode 100644
index 0000000..3116811
--- /dev/null
+++ b/java/res/xml/rowkeys_armenian_phonetic3.xml
@@ -0,0 +1,60 @@
+<?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"
+>
+    <!-- U+0561: "ա" ARMENIAN SMALL LETTER AYB -->
+    <Key
+        latin:keyLabel="&#x0561;"
+        latin:keyLabelFlags="fontNormal" />
+    <!-- U+057D: "ս" ARMENIAN SMALL LETTER SEH -->
+    <Key
+        latin:keyLabel="&#x057D;"
+        latin:keyLabelFlags="fontNormal" />
+    <!-- U+0564: "դ" ARMENIAN SMALL LETTER DA -->
+    <Key
+        latin:keyLabel="&#x0564;"
+        latin:keyLabelFlags="fontNormal" />
+    <!-- U+0586: "ֆ" ARMENIAN SMALL LETTER FEH -->
+    <Key
+        latin:keyLabel="&#x0586;"
+        latin:keyLabelFlags="fontNormal" />
+    <!-- U+0563: "գ" ARMENIAN SMALL LETTER GIM -->
+    <Key
+        latin:keyLabel="&#x0563;"
+        latin:keyLabelFlags="fontNormal" />
+    <!-- U+0570: "հ" ARMENIAN SMALL LETTER HO -->
+    <Key
+        latin:keyLabel="&#x0570;"
+        latin:keyLabelFlags="fontNormal" />
+    <!-- U+0575: "յ" ARMENIAN SMALL LETTER YI -->
+    <Key
+        latin:keyLabel="&#x0575;"
+        latin:keyLabelFlags="fontNormal" />
+    <!-- U+056F: "կ" ARMENIAN SMALL LETTER KEN -->
+    <Key
+        latin:keyLabel="&#x056F;"
+        latin:keyLabelFlags="fontNormal" />
+    <!-- U+056C: "լ" ARMENIAN SMALL LETTER LIWN -->
+    <Key
+        latin:keyLabel="&#x056C;"
+        latin:keyLabelFlags="fontNormal" />
+</merge>
diff --git a/java/res/xml/rowkeys_armenian_phonetic4.xml b/java/res/xml/rowkeys_armenian_phonetic4.xml
new file mode 100644
index 0000000..922481a
--- /dev/null
+++ b/java/res/xml/rowkeys_armenian_phonetic4.xml
@@ -0,0 +1,52 @@
+<?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"
+>
+    <!-- U+0566: "զ" ARMENIAN SMALL LETTER ZA -->
+    <Key
+        latin:keyLabel="&#x0566;"
+        latin:keyLabelFlags="fontNormal" />
+    <!-- U+0572: "ղ" ARMENIAN SMALL LETTER GHAD -->
+    <Key
+        latin:keyLabel="&#x0572;"
+        latin:keyLabelFlags="fontNormal" />
+    <!-- U+0581: "ց" ARMENIAN SMALL LETTER CO -->
+    <Key
+        latin:keyLabel="&#x0581;"
+        latin:keyLabelFlags="fontNormal" />
+    <!-- U+057E: "վ" ARMENIAN SMALL LETTER VEW -->
+    <Key
+        latin:keyLabel="&#x057E;"
+        latin:keyLabelFlags="fontNormal" />
+    <!-- U+0562: "բ" ARMENIAN SMALL LETTER BEN -->
+    <Key
+        latin:keyLabel="&#x0562;"
+        latin:keyLabelFlags="fontNormal" />
+    <!-- U+0576: "ն" ARMENIAN SMALL LETTER NOW -->
+    <Key
+        latin:keyLabel="&#x0576;"
+        latin:keyLabelFlags="fontNormal" />
+    <!-- U+0574: "մ" ARMENIAN SMALL LETTER MEN -->
+    <Key
+        latin:keyLabel="&#x0574;"
+        latin:keyLabelFlags="fontNormal" />
+</merge>
diff --git a/java/res/xml/rowkeys_hindi1.xml b/java/res/xml/rowkeys_hindi1.xml
index 7457476..c0b3cb9 100644
--- a/java/res/xml/rowkeys_hindi1.xml
+++ b/java/res/xml/rowkeys_hindi1.xml
@@ -62,10 +62,12 @@
                 latin:keyLabel="&#x092D;"
                 latin:keyLabelFlags="fontNormal" />
             <!-- Because the font rendering system prior to API version 16 can't automatically
-                 render dotted circle for incomplete combining letter of Hindi, different set of
-                 Key definitions are needed based on the API version. -->
+                 render dotted circle for incomplete combining letter of some scripts, different
+                 set of Key definitions are needed based on the API version. -->
             <include
-                latin:keyboardLayout="@xml/key_hindi1_shift" />
+                latin:keyboardLayout="@xml/keystyle_devanagari_sign_visarga" />
+            <Key
+                latin:keyStyle="baseKeyDevanagariSignVisarga" />
             <!-- U+0918: "घ" DEVANAGARI LETTER GHA -->
             <Key
                 latin:keyLabel="&#x0918;"
@@ -88,11 +90,57 @@
         </case>
         <default>
             <!-- Because the font rendering system prior to API version 16 can't automatically
-                 render dotted circle for incomplete combining letter of Hindi, different set of
-                 Key definitions are needed based on the API version. -->
+                 render dotted circle for incomplete combining letter of some scripts, different
+                 set of Key definitions are needed based on the API version. -->
+            <!-- U+0967: "१" DEVANAGARI DIGIT ONE -->
             <include
-                latin:keyboardLayout="@xml/keys_hindi1_left5" />
+                latin:keyboardLayout="@xml/keystyle_devanagari_vowel_sign_au" />
+            <Key
+                latin:keyStyle="baseKeyDevanagariVowelSignAu"
+                latin:keyHintLabel="1"
+                latin:additionalMoreKeys="&#x0967;,1" />
+            <!-- Because the font rendering system prior to API version 16 can't automatically
+                 render dotted circle for incomplete combining letter of some scripts, different
+                 set of Key definitions are needed based on the API version. -->
+            <!-- U+0968: "२" DEVANAGARI DIGIT TWO -->
+            <include
+                latin:keyboardLayout="@xml/keystyle_devanagari_vowel_sign_ai" />
+            <Key
+                latin:keyStyle="baseKeyDevanagariVowelSignAi"
+                latin:keyHintLabel="2"
+                latin:additionalMoreKeys="&#x0968;,2" />
+            <!-- Because the font rendering system prior to API version 16 can't automatically
+                 render dotted circle for incomplete combining letter of some scripts, different
+                 set of Key definitions are needed based on the API version. -->
+            <!-- U+0969: "३" DEVANAGARI DIGIT THREE -->
+            <include
+                latin:keyboardLayout="@xml/keystyle_devanagari_vowel_sign_aa" />
+            <Key
+                latin:keyStyle="baseKeyDevanagariVowelSignAa"
+                latin:keyHintLabel="3"
+                latin:additionalMoreKeys="&#x0969;,3" />
+            <!-- Because the font rendering system prior to API version 16 can't automatically
+                 render dotted circle for incomplete combining letter of some scripts, different
+                 set of Key definitions are needed based on the API version. -->
+            <!-- U+096A: "४" DEVANAGARI DIGIT FOUR -->
+            <include
+                latin:keyboardLayout="@xml/keystyle_devanagari_vowel_sign_ii" />
+            <Key
+                latin:keyStyle="baseKeyDevanagariVowelSignIi"
+                latin:keyHintLabel="4"
+                latin:additionalMoreKeys="&#x096A;,4" />
+            <!-- Because the font rendering system prior to API version 16 can't automatically
+                 render dotted circle for incomplete combining letter of some scripts, different
+                 set of Key definitions are needed based on the API version. -->
+            <!-- U+096B: "५" DEVANAGARI DIGIT FIVE -->
+            <include
+                latin:keyboardLayout="@xml/keystyle_devanagari_vowel_sign_uu" />
+            <Key
+                latin:keyStyle="baseKeyDevanagariVowelSignUu"
+                latin:keyHintLabel="5"
+                latin:additionalMoreKeys="&#x096B;,5" />
             <!-- U+092C: "ब" DEVANAGARI LETTER BA
+                 U+096C: "६" DEVANAGARI DIGIT SIX
                  U+092C/U+0952: "ब॒" DEVANAGARI LETTER BA/DEVANAGARI STRESS SIGN ANUDATTA -->
             <Key
                 latin:keyLabel="&#x092C;"
diff --git a/java/res/xml/rowkeys_hindi2.xml b/java/res/xml/rowkeys_hindi2.xml
index 9545b84..70ac66e 100644
--- a/java/res/xml/rowkeys_hindi2.xml
+++ b/java/res/xml/rowkeys_hindi2.xml
@@ -97,10 +97,40 @@
         </case>
         <default>
             <!-- Because the font rendering system prior to API version 16 can't automatically
-                 render dotted circle for incomplete combining letter of Hindi, different set of
-                 Key definitions are needed based on the API version. -->
+                 render dotted circle for incomplete combining letter of some scripts, different
+                 set of Key definitions are needed based on the API version. -->
             <include
-                latin:keyboardLayout="@xml/keys_hindi2_left5" />
+                latin:keyboardLayout="@xml/keystyle_devanagari_vowel_sign_o" />
+            <Key
+                latin:keyStyle="baseKeyDevanagariVowelSignO" />
+            <!-- Because the font rendering system prior to API version 16 can't automatically
+                 render dotted circle for incomplete combining letter of some scripts, different
+                 set of Key definitions are needed based on the API version. -->
+            <include
+                latin:keyboardLayout="@xml/keystyle_devanagari_vowel_sign_e" />
+            <Key
+                latin:keyStyle="baseKeyDevanagariVowelSignE" />
+            <!-- Because the font rendering system prior to API version 16 can't automatically
+                 render dotted circle for incomplete combining letter of some scripts, different
+                 set of Key definitions are needed based on the API version. -->
+            <include
+                latin:keyboardLayout="@xml/keystyle_devanagari_sign_virama" />
+            <Key
+                latin:keyStyle="baseKeyDevanagariSignVirama" />
+            <!-- Because the font rendering system prior to API version 16 can't automatically
+                 render dotted circle for incomplete combining letter of some scripts, different
+                 set of Key definitions are needed based on the API version. -->
+            <include
+                latin:keyboardLayout="@xml/keystyle_devanagari_vowel_sign_i" />
+            <Key
+                latin:keyStyle="baseKeyDevanagariVowelSignI" />
+            <!-- Because the font rendering system prior to API version 16 can't automatically
+                 render dotted circle for incomplete combining letter of some scripts, different
+                 set of Key definitions are needed based on the API version. -->
+            <include
+                latin:keyboardLayout="@xml/keystyle_devanagari_vowel_sign_u" />
+            <Key
+                latin:keyStyle="baseKeyDevanagariVowelSignU" />
             <!-- U+092A: "प" DEVANAGARI LETTER PA -->
             <Key
                 latin:keyLabel="&#x092A;"
diff --git a/java/res/xml/rowkeys_hindi3.xml b/java/res/xml/rowkeys_hindi3.xml
index 3014907..136bc5f 100644
--- a/java/res/xml/rowkeys_hindi3.xml
+++ b/java/res/xml/rowkeys_hindi3.xml
@@ -30,10 +30,10 @@
                 latin:keyLabel="&#x0911;"
                 latin:keyLabelFlags="fontNormal" />
             <!-- Because the font rendering system prior to API version 16 can't automatically
-                 render dotted circle for incomplete combining letter of Hindi, different set of
-                 Key definitions are needed based on the API version. -->
+                 render dotted circle for incomplete combining letter of some scripts, different
+                 set of Key definitions are needed based on the API version. -->
             <include
-                latin:keyboardLayout="@xml/key_hindi3_shift_left" />
+                latin:keyboardLayout="@xml/key_devanagari_sign_candrabindu" />
             <!-- U+0923: "ण" DEVANAGARI LETTER NNA -->
             <Key
                 latin:keyLabel="&#x0923;"
@@ -56,10 +56,10 @@
                 latin:keyLabel="&#x0937;"
                 latin:keyLabelFlags="fontNormal" />
             <!-- Because the font rendering system prior to API version 16 can't automatically
-                 render dotted circle for incomplete combining letter of Hindi, different set of
-                 Key definitions are needed based on the API version. -->
+                 render dotted circle for incomplete combining letter of some scripts, different
+                 set of Key definitions are needed based on the API version. -->
             <include
-                latin:keyboardLayout="@xml/key_hindi3_shift_right" />
+                latin:keyboardLayout="@xml/key_devanagari_vowel_sign_vocalic_r" />
             <!-- U+091E: "ञ" DEVANAGARI LETTER NYA -->
             <Key
                 latin:keyLabel="&#x091E;"
@@ -67,10 +67,12 @@
         </case>
         <default>
             <!-- Because the font rendering system prior to API version 16 can't automatically
-                 render dotted circle for incomplete combining letter of Hindi, different set of
-                 Key definitions are needed based on the API version. -->
+                 render dotted circle for incomplete combining letter of some scripts, different
+                 set of Key definitions are needed based on the API version. -->
             <include
-                latin:keyboardLayout="@xml/keys_hindi3_left2" />
+                latin:keyboardLayout="@xml/key_devanagari_vowel_sign_candra_o" />
+            <include
+                latin:keyboardLayout="@xml/key_devanagari_sign_anusvara" />
             <!-- U+092E: "म" DEVANAGARI LETTER MA
                  U+0950: "ॐ" DEVANAGARI OM -->
             <Key
@@ -107,10 +109,10 @@
                 latin:moreKeys="&#x095F;"
                 latin:keyLabelFlags="fontNormal" />
             <!-- Because the font rendering system prior to API version 16 can't automatically
-                 render dotted circle for incomplete combining letter of Hindi, different set of
-                 Key definitions are needed based on the API version. -->
+                 render dotted circle for incomplete combining letter of some scripts, different
+                 set of Key definitions are needed based on the API version. -->
             <include
-                latin:keyboardLayout="@xml/key_hindi3_right" />
+                latin:keyboardLayout="@xml/key_devanagari_sign_nukta" />
          </default>
     </switch>
 </merge>
diff --git a/java/res/xml/rowkeys_khmer1.xml b/java/res/xml/rowkeys_khmer1.xml
new file mode 100644
index 0000000..25da664
--- /dev/null
+++ b/java/res/xml/rowkeys_khmer1.xml
@@ -0,0 +1,194 @@
+<?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;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- 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:keyHintLabel="&#x17A6;"
+                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:keyHintLabel="&#x17B1;"
+                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..ff6c9ca
--- /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="&#x1792;"
+                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/rowkeys_lao1.xml b/java/res/xml/rowkeys_lao1.xml
new file mode 100644
index 0000000..fa1ad97
--- /dev/null
+++ b/java/res/xml/rowkeys_lao1.xml
@@ -0,0 +1,164 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2013, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <switch>
+        <case
+            latin:keyboardLayoutSetElement="alphabetManualShifted|alphabetShiftLocked|alphabetShiftLockShifted"
+        >
+            <!-- U+0ED1: "໑" LAO DIGIT ONE -->
+            <Key
+                latin:keyLabel="&#x0ED1;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0ED2: "໒" LAO DIGIT TWO -->
+            <Key
+                latin:keyLabel="&#x0ED2;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0ED3: "໓" LAO DIGIT THREE -->
+            <Key
+                latin:keyLabel="&#x0ED3;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0ED4: "໔" LAO DIGIT FOUR -->
+            <Key
+                latin:keyLabel="&#x0ED4;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0ECC: "໌" LAO CANCELLATION MARK -->
+            <Key
+                latin:keyLabel="&#x0ECC;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0EBC: "ຼ" LAO SEMIVOWEL SIGN LO -->
+            <Key
+                latin:keyLabel="&#x0EBC;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0ED5: "໕" LAO DIGIT FIVE -->
+            <Key
+                latin:keyLabel="&#x0ED5;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0ED6: "໖" LAO DIGIT SIX -->
+            <Key
+                latin:keyLabel="&#x0ED6;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0ED7: "໗" LAO DIGIT SEVEN -->
+            <Key
+                latin:keyLabel="&#x0ED7;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0ED8: "໘" LAO DIGIT EIGHT -->
+            <Key
+                latin:keyLabel="&#x0ED8;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0ED9: "໙" LAO DIGIT NINE -->
+            <Key
+                latin:keyLabel="&#x0ED9;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0ECD/U+0EC8: "ໍ່" LAO NIGGAHITA/LAO TONE MAI EK -->
+            <Key
+                latin:keyLabel="&#x0ECD;&#x0EC8;"
+                latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
+        </case>
+        <default>
+            <!-- U+0EA2: "ຢ" LAO LETTER YO
+                 U+0ED1: "໑" LAO DIGIT ONE -->
+            <Key
+                latin:keyLabel="&#x0EA2;"
+                latin:keyHintLabel="1"
+                latin:additionalMoreKeys="1"
+                latin:moreKeys="&#x0ED1;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0E9F: "ຟ" LAO LETTER FO SUNG
+                 U+0ED2: "໒" LAO DIGIT TWO -->
+            <Key
+                latin:keyLabel="&#x0E9F;"
+                latin:keyHintLabel="2"
+                latin:additionalMoreKeys="2"
+                latin:moreKeys="&#x0ED2;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0EC2: "ໂ" LAO VOWEL SIGN O
+                 U+0ED3: "໓" LAO DIGIT THREE -->
+            <Key
+                latin:keyLabel="&#x0EC2;"
+                latin:keyHintLabel="3"
+                latin:additionalMoreKeys="3"
+                latin:moreKeys="&#x0ED3;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0E96: "ຖ" LAO LETTER THO SUNG
+                 U+0ED4: "໔" LAO DIGIT FOUR -->
+            <Key
+                latin:keyLabel="&#x0E96;"
+                latin:keyHintLabel="4"
+                latin:additionalMoreKeys="4"
+                latin:moreKeys="&#x0ED4;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0EB8: "ຸ" LAO VOWEL SIGN U -->
+            <Key
+                latin:keyLabel="&#x0EB8;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0EB9: "ູ" LAO VOWEL SIGN UU -->
+            <Key
+                latin:keyLabel="&#x0EB9;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0E84: "ຄ" LAO LETTER KHO TAM
+                 U+0ED5: "໕" LAO DIGIT FIVE -->
+            <Key
+                latin:keyLabel="&#x0E84;"
+                latin:keyHintLabel="5"
+                latin:additionalMoreKeys="5"
+                latin:moreKeys="&#x0ED5;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0E95: "ຕ" LAO LETTER TO
+                 U+0ED6: "໖" LAO DIGIT SIX -->
+            <Key
+                latin:keyLabel="&#x0E95;"
+                latin:keyHintLabel="6"
+                latin:additionalMoreKeys="6"
+                latin:moreKeys="&#x0ED6;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0E88: "ຈ" LAO LETTER CO
+                 U+0ED7: "໗" LAO DIGIT SEVEN -->
+            <Key
+                latin:keyLabel="&#x0E88;"
+                latin:keyHintLabel="7"
+                latin:additionalMoreKeys="7"
+                latin:moreKeys="&#x0ED7;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0E82: "ຂ" LAO LETTER KHO SUNG
+                 U+0ED8: "໘" LAO DIGIT EIGHT -->
+            <Key
+                latin:keyLabel="&#x0E82;"
+                latin:keyHintLabel="8"
+                latin:additionalMoreKeys="8"
+                latin:moreKeys="&#x0ED8;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0E8A: "ຊ" LAO LETTER SO TAM
+                 U+0ED9: "໙" LAO DIGIT NINE -->
+            <Key
+                latin:keyLabel="&#x0E8A;"
+                latin:keyHintLabel="9"
+                latin:additionalMoreKeys="9"
+                latin:moreKeys="&#x0ED9;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0ECD: "ໍ" LAO NIGGAHITA -->
+            <Key
+                latin:keyLabel="&#x0ECD;"
+                latin:keyLabelFlags="fontNormal" />
+        </default>
+    </switch>
+</merge>
diff --git a/java/res/xml/rowkeys_lao2.xml b/java/res/xml/rowkeys_lao2.xml
new file mode 100644
index 0000000..fca58ac
--- /dev/null
+++ b/java/res/xml/rowkeys_lao2.xml
@@ -0,0 +1,127 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2013, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <switch>
+        <case
+            latin:keyboardLayoutSetElement="alphabetManualShifted|alphabetShiftLocked|alphabetShiftLockShifted"
+        >
+            <!-- U+0EBB/U+0EC9: "" LAO VOWEL SIGN MAI KON/LAO TONE MAI THO -->
+            <Key
+                latin:keyLabel="&#x0EBB;&#x0EC9;"
+                latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
+            <!-- U+0ED0: "໐" LAO DIGIT ZERO -->
+            <Key
+                latin:keyLabel="&#x0ED0;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0EB3/U+0EC9: "ຳ້" LAO VOWEL SIGN AM/LAO TONE MAI THO -->
+            <Key
+                latin:keyLabel="&#x0EB3;&#x0EC9;"
+                latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
+            <Key
+                latin:keyLabel="_" />
+            <Key
+                latin:keyLabel="+" />
+            <!-- U+0EB4/U+0EC9: "ິ້" LAO VOWEL SIGN I/LAO TONE MAI THO -->
+            <Key
+                latin:keyLabel="&#x0EB4;&#x0EC9;"
+                latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
+            <!-- U+0EB5/U+0EC9: "ີ້" LAO VOWEL SIGN II/LAO TONE MAI THO -->
+            <Key
+                latin:keyLabel="&#x0EB5;&#x0EC9;"
+                latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
+            <!-- U+0EA3: "ຣ" LAO LETTER LO LING -->
+            <Key
+                latin:keyLabel="&#x0EA3;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0EDC: "ໜ" LAO HO NO -->
+            <Key
+                latin:keyLabel="&#x0EDC;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0EBD: "ຽ" LAO SEMIVOWEL SIGN NYO -->
+            <Key
+                latin:keyLabel="&#x0EBD;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0EAB/U+0EBC: "" LAO LETTER HO SUNG/LAO SEMIVOWEL SIGN LO -->
+            <Key
+                latin:keyLabel="&#x0EAB;&#x0EBC;"
+                latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
+            <!-- U+201D: "”" RIGHT DOUBLE QUOTATION MARK -->
+            <Key
+                latin:keyLabel="&#x201D;" />
+        </case>
+        <default>
+            <!-- U+0EBB: "ົ" LAO VOWEL SIGN MAI KON -->
+            <Key
+                latin:keyLabel="&#x0EBB;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0EC4: "ໄ" LAO VOWEL SIGN AI
+                 U+0ED0: "໐" LAO DIGIT ZERO -->
+            <Key
+                latin:keyLabel="&#x0EC4;"
+                latin:keyHintLabel="0"
+                latin:additionalMoreKeys="0"
+                latin:moreKeys="&#x0ED0;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0EB3: "ຳ" LAO VOWEL SIGN AM -->
+            <Key
+                latin:keyLabel="&#x0EB3;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0E9E: "ພ" LAO LETTER PHO TAM -->
+            <Key
+                latin:keyLabel="&#x0E9E;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0EB0: "ະ" LAO VOWEL SIGN A -->
+            <Key
+                latin:keyLabel="&#x0EB0;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0EB4: "ິ" LAO VOWEL SIGN I -->
+            <Key
+                latin:keyLabel="&#x0EB4;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0EB5: "ີ" LAO VOWEL SIGN II -->
+            <Key
+                latin:keyLabel="&#x0EB5;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0EAE: "ຮ" LAO LETTER HO TAM -->
+            <Key
+                latin:keyLabel="&#x0EAE;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0E99: "ນ" LAO LETTER NO -->
+            <Key
+                latin:keyLabel="&#x0E99;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0E8D: "ຍ" LAO LETTER NYO -->
+            <Key
+                latin:keyLabel="&#x0E8D;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0E9A: "ບ" LAO LETTER BO -->
+            <Key
+                latin:keyLabel="&#x0E9A;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0EA5: "ລ" LAO LETTER LO LOOT -->
+            <Key
+                latin:keyLabel="&#x0EA5;"
+                latin:keyLabelFlags="fontNormal" />
+        </default>
+    </switch>
+</merge>
diff --git a/java/res/xml/rowkeys_lao3.xml b/java/res/xml/rowkeys_lao3.xml
new file mode 100644
index 0000000..2a6c2d1
--- /dev/null
+++ b/java/res/xml/rowkeys_lao3.xml
@@ -0,0 +1,110 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2013, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <switch>
+        <case
+            latin:keyboardLayoutSetElement="alphabetManualShifted|alphabetShiftLocked|alphabetShiftLockShifted"
+        >
+            <!-- U+0EB1/U+0EC9: "ັ້" LAO VOWEL SIGN MAI KAN/LAO TONE MAI THO -->
+            <Key
+                latin:keyLabel="&#x0EB1;&#x0EC9;"
+                latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
+            <Key
+                latin:keyLabel=";" />
+            <Key
+                latin:keyLabel="." />
+            <Key
+                latin:keyLabel="," />
+            <Key
+                latin:keyLabel=":" />
+            <!-- U+0ECA: "໊" LAO TONE MAI TI -->
+            <Key
+                latin:keyLabel="&#x0ECA;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0ECB: "໋" LAO TONE MAI CATAWA -->
+            <Key
+                latin:keyLabel="&#x0ECB;"
+                latin:keyLabelFlags="fontNormal" />
+            <Key
+                latin:keyLabel="!" />
+            <Key
+                latin:keyLabel="\?" />
+            <Key
+                latin:keyLabel="%" />
+            <Key
+                latin:keyLabel="=" />
+            <!-- U+201C: "“" LEFT DOUBLE QUOTATION MARK -->
+            <Key
+                latin:keyLabel="&#x201C;" />
+        </case>
+        <default>
+            <!-- U+0EB1: "ັ" LAO VOWEL SIGN MAI KAN -->
+            <Key
+                latin:keyLabel="&#x0EB1;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0EAB: "ຫ" LAO LETTER HO SUNG -->
+            <Key
+                latin:keyLabel="&#x0EAB;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0E81: "ກ" LAO LETTER KO -->
+            <Key
+                latin:keyLabel="&#x0E81;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0E94: "ດ" LAO LETTER DO -->
+            <Key
+                latin:keyLabel="&#x0E94;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0EC0: "ເ" LAO VOWEL SIGN E -->
+            <Key
+                latin:keyLabel="&#x0EC0;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0EC9: "້" LAO TONE MAI THO -->
+            <Key
+                latin:keyLabel="&#x0EC9;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0EC8: "່" LAO TONE MAI EK -->
+            <Key
+                latin:keyLabel="&#x0EC8;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0EB2: "າ" LAO VOWEL SIGN AA -->
+            <Key
+                latin:keyLabel="&#x0EB2;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0EAA: "ສ" LAO LETTER SO SUNG -->
+            <Key
+                latin:keyLabel="&#x0EAA;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0EA7: "ວ" LAO LETTER WO -->
+            <Key
+                latin:keyLabel="&#x0EA7;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0E87: "ງ" LAO LETTER NGO -->
+            <Key
+                latin:keyLabel="&#x0E87;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+201C: "“" LEFT DOUBLE QUOTATION MARK -->
+            <Key
+                latin:keyLabel="&#x201C;" />
+        </default>
+    </switch>
+</merge>
diff --git a/java/res/xml/rowkeys_lao4.xml b/java/res/xml/rowkeys_lao4.xml
new file mode 100644
index 0000000..fae9cc9
--- /dev/null
+++ b/java/res/xml/rowkeys_lao4.xml
@@ -0,0 +1,103 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2013, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <switch>
+        <case
+            latin:keyboardLayoutSetElement="alphabetManualShifted|alphabetShiftLocked|alphabetShiftLockShifted"
+        >
+            <!-- U+20AD: "₭" KIP SIGN -->
+            <Key
+                latin:keyLabel="&#x20AD;" />
+            <Key
+                latin:keyLabel="(" />
+            <!-- U+0EAF: "ຯ" LAO ELLIPSIS -->
+            <Key
+                latin:keyLabel="&#x0EAF;"
+                latin:keyLabelFlags="fontNormal" />
+            <Key
+                latin:keyLabel="\@" />
+            <!-- U+0EB6/U+0EC9: "ຶ້" LAO VOWEL SIGN Y/LAO TONE MAI THO -->
+            <Key
+                latin:keyLabel="&#x0EB6;&#x0EC9;"
+                latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
+            <!-- U+0EB7/U+0EC9: "ື້" LAO VOWEL SIGN YY/LAO TONE MAI THO -->
+            <Key
+                latin:keyLabel="&#x0EB7;&#x0EC9;"
+                latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
+            <!-- U+0EC6: "ໆ" LAO KO LA -->
+            <Key
+                latin:keyLabel="&#x0EC6;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0EDD: "ໝ" LAO HO MO -->
+            <Key
+                latin:keyLabel="&#x0EDD;"
+                latin:keyLabelFlags="fontNormal" />
+            <Key
+                latin:keyLabel="$" />
+            <Key
+                latin:keyLabel=")" />
+        </case>
+        <default>
+            <!-- U+0E9C: "ຜ" LAO LETTER PHO SUNG -->
+            <Key
+                latin:keyLabel="&#x0E9C;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0E9B: "ປ" LAO LETTER PO -->
+            <Key
+                latin:keyLabel="&#x0E9B;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0EC1: "ແ" LAO VOWEL SIGN EI -->
+            <Key
+                latin:keyLabel="&#x0EC1;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0EAD: "ອ" LAO LETTER O -->
+            <Key
+                latin:keyLabel="&#x0EAD;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0EB6: "ຶ" LAO VOWEL SIGN Y -->
+            <Key
+                latin:keyLabel="&#x0EB6;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0EB7: "ື" LAO VOWEL SIGN YY -->
+            <Key
+                latin:keyLabel="&#x0EB7;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0E97: "ທ" LAO LETTER THO TAM -->
+            <Key
+                latin:keyLabel="&#x0E97;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0EA1: "ມ" LAO LETTER MO -->
+            <Key
+                latin:keyLabel="&#x0EA1;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0EC3: "ໃ" LAO VOWEL SIGN AY -->
+            <Key
+                latin:keyLabel="&#x0EC3;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0E9D: "ຝ" LAO LETTER FO TAM -->
+            <Key
+                latin:keyLabel="&#x0E9D;"
+                latin:keyLabelFlags="fontNormal" />
+        </default>
+    </switch>
+</merge>
diff --git a/java/res/xml/rowkeys_nepali_romanized1.xml b/java/res/xml/rowkeys_nepali_romanized1.xml
new file mode 100644
index 0000000..408a966
--- /dev/null
+++ b/java/res/xml/rowkeys_nepali_romanized1.xml
@@ -0,0 +1,177 @@
+<?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+0920: "ठ" DEVANAGARI LETTER TTHA -->
+            <Key
+                latin:keyLabel="&#x0920;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0914: "औ" DEVANAGARI LETTER AU -->
+            <Key
+                latin:keyLabel="&#x0914;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- Because the font rendering system prior to API version 16 can't automatically
+                 render dotted circle for incomplete combining letter of some scripts, different
+                 set of Key definitions are needed based on the API version. -->
+            <include
+                latin:keyboardLayout="@xml/keystyle_devanagari_vowel_sign_ai" />
+            <Key
+                latin:keyStyle="baseKeyDevanagariVowelSignAi" />
+            <!-- Because the font rendering system prior to API version 16 can't automatically
+                 render dotted circle for incomplete combining letter of some scripts, different
+                 set of Key definitions are needed based on the API version. -->
+            <include
+                latin:keyboardLayout="@xml/key_devanagari_vowel_sign_vocalic_r" />
+            <!-- U+0925: "थ" DEVANAGARI LETTER THA -->
+            <Key
+                latin:keyLabel="&#x0925;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+091E: "ञ" DEVANAGARI LETTER NYA -->
+            <Key
+                latin:keyLabel="&#x091E;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- Because the font rendering system prior to API version 16 can't automatically
+                 render dotted circle for incomplete combining letter of some scripts, different
+                 set of Key definitions are needed based on the API version. -->
+            <include
+                latin:keyboardLayout="@xml/keystyle_devanagari_vowel_sign_uu" />
+            <Key
+                latin:keyStyle="baseKeyDevanagariVowelSignUu" />
+            <!-- Because the font rendering system prior to API version 16 can't automatically
+                 render dotted circle for incomplete combining letter of some scripts, different
+                 set of Key definitions are needed based on the API version. -->
+            <include
+                latin:keyboardLayout="@xml/keystyle_devanagari_vowel_sign_ii" />
+            <Key
+                latin:keyStyle="baseKeyDevanagariVowelSignIi" />
+            <!-- U+0913: "ओ" DEVANAGARI LETTER O -->
+            <Key
+                latin:keyLabel="&#x0913;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+092B: "फ" DEVANAGARI LETTER PHA -->
+            <Key
+                latin:keyLabel="&#x092B;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0908: "ई" DEVANAGARI LETTER II -->
+            <Key
+                latin:keyLabel="&#x0908;"
+                latin:keyLabelFlags="fontNormal" />
+        </case>
+        <default>
+            <!-- U+091F: "ट" DEVANAGARI LETTER TTA
+                 U+0967: "१" DEVANAGARI DIGIT ONE
+                 U+093C: "़" DEVANAGARI SIGN NUKTA -->
+            <Key
+                latin:keyLabel="&#x091F;"
+                latin:keyHintLabel="1"
+                latin:additionalMoreKeys="&#x0967;,1"
+                latin:moreKeys="&#x093C;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- Because the font rendering system prior to API version 16 can't automatically
+                 render dotted circle for incomplete combining letter of some scripts, different
+                 set of Key definitions are needed based on the API version. -->
+            <!-- U+0968: "२" DEVANAGARI DIGIT TWO -->
+            <include
+                latin:keyboardLayout="@xml/keystyle_devanagari_vowel_sign_au" />
+            <Key
+                latin:keyStyle="baseKeyDevanagariVowelSignAu"
+                latin:keyHintLabel="2"
+                latin:additionalMoreKeys="&#x0968;,2" />
+            <!-- Because the font rendering system prior to API version 16 can't automatically
+                 render dotted circle for incomplete combining letter of some scripts, different
+                 set of Key definitions are needed based on the API version. -->
+            <!-- U+0969: "३" DEVANAGARI DIGIT THREE -->
+            <include
+                latin:keyboardLayout="@xml/keystyle_devanagari_vowel_sign_e" />
+            <Key
+                latin:keyStyle="baseKeyDevanagariVowelSignE"
+                latin:keyHintLabel="3"
+                latin:additionalMoreKeys="&#x0969;,3" />
+            <!-- U+0930: "र" DEVANAGARI LETTER RA
+                 U+096A: "४" DEVANAGARI DIGIT FOUR -->
+            <Key
+                latin:keyLabel="&#x0930;"
+                latin:keyHintLabel="4"
+                latin:additionalMoreKeys="&#x096A;,4"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0924: "त" DEVANAGARI LETTER TA
+                 U+096B: "५" DEVANAGARI DIGIT FIVE -->
+            <Key
+                latin:keyLabel="&#x0924;"
+                latin:keyHintLabel="5"
+                latin:additionalMoreKeys="&#x096B;,5"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+092F: "य" DEVANAGARI LETTER YA
+                 U+096C: "६" DEVANAGARI DIGIT SIX -->
+            <Key
+                latin:keyLabel="&#x092F;"
+                latin:keyHintLabel="6"
+                latin:additionalMoreKeys="&#x096C;,6"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- Because the font rendering system prior to API version 16 can't automatically
+                 render dotted circle for incomplete combining letter of some scripts, different
+                 set of Key definitions are needed based on the API version. -->
+            <!-- U+096D: "७" DEVANAGARI DIGIT SEVEN -->
+            <include
+                latin:keyboardLayout="@xml/keystyle_devanagari_vowel_sign_u" />
+            <Key
+                latin:keyStyle="baseKeyDevanagariVowelSignU"
+                latin:keyHintLabel="7"
+                latin:additionalMoreKeys="&#x096D;,7" />
+            <!-- Because the font rendering system prior to API version 16 can't automatically
+                 render dotted circle for incomplete combining letter of some scripts, different
+                 set of Key definitions are needed based on the API version. -->
+            <!-- U+096E: "८" DEVANAGARI DIGIT EIGHT -->
+            <include
+                latin:keyboardLayout="@xml/keystyle_devanagari_vowel_sign_i" />
+            <Key
+                latin:keyStyle="baseKeyDevanagariVowelSignI"
+                latin:keyHintLabel="8"
+                latin:additionalMoreKeys="&#x096E;,8" />
+            <!-- Because the font rendering system prior to API version 16 can't automatically
+                 render dotted circle for incomplete combining letter of some scripts, different
+                 set of Key definitions are needed based on the API version. -->
+            <!-- U+096F: "९" DEVANAGARI DIGIT NINE -->
+            <include
+                latin:keyboardLayout="@xml/keystyle_devanagari_vowel_sign_o" />
+            <Key
+                latin:keyStyle="baseKeyDevanagariVowelSignO"
+                latin:keyHintLabel="9"
+                latin:additionalMoreKeys="&#x096F;,9" />
+            <!-- U+092A: "प" DEVANAGARI LETTER PA
+                 U+0966: "०" DEVANAGARI DIGIT ZERO -->
+            <Key
+                latin:keyLabel="&#x092A;"
+                latin:keyHintLabel="0"
+                latin:additionalMoreKeys="&#x0966;,0"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0907: "इ" DEVANAGARI LETTER I -->
+            <Key
+                latin:keyLabel="&#x0907;"
+                latin:keyLabelFlags="fontNormal" />
+         </default>
+    </switch>
+</merge>
diff --git a/java/res/xml/rowkeys_nepali_romanized2.xml b/java/res/xml/rowkeys_nepali_romanized2.xml
new file mode 100644
index 0000000..66359ff
--- /dev/null
+++ b/java/res/xml/rowkeys_nepali_romanized2.xml
@@ -0,0 +1,126 @@
+<?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+0906: "आ" DEVANAGARI LETTER AA -->
+            <Key
+                latin:keyLabel="&#x0906;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0936: "श" DEVANAGARI LETTER SHA -->
+            <Key
+                latin:keyLabel="&#x0936;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0927: "ध" DEVANAGARI LETTER DHA -->
+            <Key
+                latin:keyLabel="&#x0927;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+090A: "ऊ" DEVANAGARI LETTER UU -->
+            <Key
+                latin:keyLabel="&#x090A;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0918: "घ" DEVANAGARI LETTER GHA -->
+            <Key
+                latin:keyLabel="&#x0918;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0905: "अ" DEVANAGARI LETTER A -->
+            <Key
+                latin:keyLabel="&#x0905;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+091D: "झ" DEVANAGARI LETTER JHA -->
+            <Key
+                latin:keyLabel="&#x091D;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0916: "ख" DEVANAGARI LETTER KHA -->
+            <Key
+                latin:keyLabel="&#x0916;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0965: "॥" DEVANAGARI DOUBLE DANDA -->
+            <Key
+                latin:keyLabel="&#x0965;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0910: "ऐ" DEVANAGARI LETTER AI -->
+            <Key
+                latin:keyLabel="&#x0910;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- Because the font rendering system prior to API version 16 can't automatically
+                 render dotted circle for incomplete combining letter of some scripts, different
+                 set of Key definitions are needed based on the API version. -->
+            <include
+                latin:keyboardLayout="@xml/keystyle_devanagari_sign_visarga" />
+            <Key
+                latin:keyStyle="baseKeyDevanagariSignVisarga" />
+        </case>
+        <default>
+            <!-- Because the font rendering system prior to API version 16 can't automatically
+                 render dotted circle for incomplete combining letter of some scripts, different
+                 set of Key definitions are needed based on the API version. -->
+            <include
+                latin:keyboardLayout="@xml/keystyle_devanagari_vowel_sign_aa" />
+            <Key
+                latin:keyStyle="baseKeyDevanagariVowelSignAa" />
+            <!-- U+0938: "स" DEVANAGARI LETTER SA -->
+            <Key
+                latin:keyLabel="&#x0938;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0926: "द" DEVANAGARI LETTER DA -->
+            <Key
+                latin:keyLabel="&#x0926;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0909: "उ" DEVANAGARI LETTER U -->
+            <Key
+                latin:keyLabel="&#x0909;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0917: "ग" DEVANAGARI LETTER GA -->
+            <Key
+                latin:keyLabel="&#x0917;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0939: "ह" DEVANAGARI LETTER HA -->
+            <Key
+                latin:keyLabel="&#x0939;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+091C: "ज" DEVANAGARI LETTER JA -->
+            <Key
+                latin:keyLabel="&#x091C;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0915: "क" DEVANAGARI LETTER KA -->
+            <Key
+                latin:keyLabel="&#x0915;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0932: "ल" DEVANAGARI LETTER LA -->
+            <Key
+                latin:keyLabel="&#x0932;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+090F: "ए" DEVANAGARI LETTER E -->
+            <Key
+                latin:keyLabel="&#x090F;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0950: "ॐ" DEVANAGARI OM -->
+            <Key
+                latin:keyLabel="&#x0950;"
+                latin:keyLabelFlags="fontNormal" />
+         </default>
+    </switch>
+</merge>
diff --git a/java/res/xml/rowkeys_nepali_romanized3.xml b/java/res/xml/rowkeys_nepali_romanized3.xml
new file mode 100644
index 0000000..166d028
--- /dev/null
+++ b/java/res/xml/rowkeys_nepali_romanized3.xml
@@ -0,0 +1,114 @@
+<?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+090B: "ऋ" DEVANAGARI LETTER VOCALIC R -->
+            <Key
+                latin:keyLabel="&#x090B;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0922: "ढ" DEVANAGARI LETTER DDHA -->
+            <Key
+                latin:keyLabel="&#x0922;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+091B: "छ" DEVANAGARI LETTER CHA -->
+            <Key
+                latin:keyLabel="&#x091B;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- Because the font rendering system prior to API version 16 can't automatically
+                 render dotted circle for incomplete combining letter of some scripts, different
+                 set of Key definitions are needed based on the API version. -->
+            <include
+                latin:keyboardLayout="@xml/key_devanagari_sign_candrabindu" />
+            <!-- U+092D: "भ" DEVANAGARI LETTER BHA -->
+            <Key
+                latin:keyLabel="&#x092D;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0923: "ण" DEVANAGARI LETTER NNA -->
+            <Key
+                latin:keyLabel="&#x0923;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- Because the font rendering system prior to API version 16 can't automatically
+                 render dotted circle for incomplete combining letter of some scripts, different
+                 set of Key definitions are needed based on the API version. -->
+            <include
+                latin:keyboardLayout="@xml/key_devanagari_sign_anusvara" />
+            <!-- U+0919: "ङ" DEVANAGARI LETTER NGA -->
+            <Key
+                latin:keyLabel="&#x0919;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- Because the font rendering system prior to API version 16 can't automatically
+                 render dotted circle for incomplete combining letter of some scripts, different
+                 set of Key definitions are needed based on the API version. -->
+            <include
+                latin:keyboardLayout="@xml/keystyle_devanagari_sign_virama" />
+            <Key
+                latin:keyStyle="baseKeyDevanagariSignVirama" />
+        </case>
+        <default>
+            <!-- U+0937: "ष" DEVANAGARI LETTER SSA -->
+            <Key
+                latin:keyLabel="&#x0937;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0921: "ड" DEVANAGARI LETTER DDA -->
+            <Key
+                latin:keyLabel="&#x0921;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+091A: "च" DEVANAGARI LETTER CA -->
+            <Key
+                latin:keyLabel="&#x091A;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0935: "व" DEVANAGARI LETTER VA -->
+            <Key
+                latin:keyLabel="&#x0935;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+092C: "ब" DEVANAGARI LETTER BHA -->
+            <Key
+                latin:keyLabel="&#x092C;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0928: "न" DEVANAGARI LETTER NA -->
+            <Key
+                latin:keyLabel="&#x0928;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+092E: "म" DEVANAGARI LETTER MA -->
+            <Key
+                latin:keyLabel="&#x092E;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0964: "।" DEVANAGARI DANDA
+                 U+093D: "ऽ" DEVANAGARI SIGN AVAGRAHA -->
+            <Key
+                latin:keyLabel="&#x0964;"
+                latin:moreKeys="&#x093D;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- Because the font rendering system prior to API version 16 can't automatically
+                 render dotted circle for incomplete combining letter of some scripts, different
+                 set of Key definitions are needed based on the API version. -->
+            <include
+                latin:keyboardLayout="@xml/keystyle_devanagari_sign_virama" />
+            <Key
+                latin:keyStyle="baseKeyDevanagariSignVirama" />
+        </default>
+    </switch>
+</merge>
diff --git a/java/res/xml/rowkeys_nepali_traditional1.xml b/java/res/xml/rowkeys_nepali_traditional1.xml
new file mode 100644
index 0000000..c7883c7
--- /dev/null
+++ b/java/res/xml/rowkeys_nepali_traditional1.xml
@@ -0,0 +1,175 @@
+<?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+0924/U+094D/U+0924: "त्त" DEVANAGARI LETTER TA/DEVANAGARI SIGN VIRAMA/DEVANAGARI LETTER TA
+                 U+091E: "ञ" DEVANAGARI LETTER NYA
+                 U+091C/U+094D/U+091E: "ज्ञ" DEVANAGARI LETTER JA/DEVANAGARI SIGN VIRAMA/DEVANAGARI LETTER NYA
+                 U+0965: "॥" DEVANAGARI DOUBLE DANDA -->
+            <Key
+                latin:keyLabel="&#x0924;&#x094D;&#x0924;"
+                latin:moreKeys="&#x091E;,&#x091C;&#x094D;&#x091E;,&#x0965;"
+                latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
+            <!-- U+0921/U+094D/U+0922: "ड्ढ" DEVANAGARI LETTER DDA/DEVANAGARI SIGN VIRAMA/DEVANAGARI LETTER DDHA
+                 U+0908: "ई" DEVANAGARI LETTER II -->
+            <Key
+                latin:keyLabel="&#x0921;&#x094D;&#x0922;"
+                latin:moreKeys="&#x0908;"
+                latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
+            <!-- U+0910: "ऐ" DEVANAGARI LETTER AI
+                 U+0918: "घ" DEVANAGARI LETTER GHA -->
+            <Key
+                latin:keyLabel="&#x0910;"
+                latin:moreKeys="&#x0918;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0926/U+094D/U+0935: "द्व" DEVANAGARI LETTER DA/DEVANAGARI SIGN VIRAMA/DEVANAGARI LETTER VA
+                 U+0926/U+094D/U+0927: "द्ध" DEVANAGARI LETTER DA/DEVANAGARI SIGN VIRAMA/DEVANAGARI LETTER DHA -->
+            <Key
+                latin:keyLabel="&#x0926;&#x094D;&#x0935;"
+                latin:moreKeys="&#x0926;&#x094D;&#x0927;"
+                latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
+            <!-- U+091F/U+094D/U+091F: "ट्ट" DEVANAGARI LETTER TTA/DEVANAGARI SIGN VIRAMA/DEVANAGARI LETTER TTA
+                 U+091B: "छ" DEVANAGARI LETTER CHA -->
+            <Key
+                latin:keyLabel="&#x091F;&#x094D;&#x091F;"
+                latin:moreKeys="&#x091B;"
+                latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
+            <!-- U+0920/U+094D/U+0920: "ठ्ठ" DEVANAGARI LETTER TTHA/DEVANAGARI SIGN VIRAMA/DEVANAGARI LETTER TTHA
+                 U+091F: "ट" DEVANAGARI LETTER TTA -->
+            <Key
+                latin:keyLabel="&#x0920;&#x094D;&#x0920;"
+                latin:moreKeys="&#x091F;"
+                latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
+            <!-- U+090A: "ऊ" DEVANAGARI LETTER UU
+                 U+0920: "ठ" DEVANAGARI LETTER TTHA -->
+            <Key
+                latin:keyLabel="&#x090A;"
+                latin:moreKeys="&#x0920;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0915/U+094D/U+0937: "क्ष" DEVANAGARI LETTER KA/DEVANAGARI SIGN VIRAMA/DEVANAGARI LETTER SSA
+                 U+0921: "ड" DEVANAGARI LETTER DDA -->
+            <Key
+                latin:keyLabel="&#x0915;&#x094D;&#x0937;"
+                latin:moreKeys="&#x0921;"
+                latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
+            <!-- U+0907: "इ" DEVANAGARI LETTER I
+                 U+0922: "ढ" DEVANAGARI LETTER DDHA -->
+            <Key
+                latin:keyLabel="&#x0907;"
+                latin:moreKeys="&#x0922;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+090F: "ए" DEVANAGARI LETTER E
+                 U+0923: "ण" DEVANAGARI LETTER NNA -->
+            <Key
+                latin:keyLabel="&#x090F;"
+                latin:moreKeys="&#x0923;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- Because the font rendering system prior to API version 16 can't automatically
+                 render dotted circle for incomplete combining letter of some scripts, different
+                 set of Key definitions are needed based on the API version. -->
+            <include
+                latin:keyboardLayout="@xml/key_devanagari_vowel_sign_vocalic_r" />
+        </case>
+        <default>
+            <!-- U+091F: "ट" DEVANAGARI LETTER TTA
+                 U+0967: "१" DEVANAGARI DIGIT ONE -->
+            <Key
+                latin:keyLabel="&#x091F;"
+                latin:keyHintLabel="1"
+                latin:additionalMoreKeys="&#x0967;,1"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0927: "ध" DEVANAGARI LETTER DHA
+                 U+0968: "२" DEVANAGARI DIGIT TWO -->
+            <Key
+                latin:keyLabel="&#x0927;"
+                latin:keyHintLabel="2"
+                latin:additionalMoreKeys="&#x0968;,2"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+092D: "भ" DEVANAGARI LETTER BHA
+                 U+0969: "३" DEVANAGARI DIGIT THREE -->
+            <Key
+                latin:keyLabel="&#x092D;"
+                latin:keyHintLabel="3"
+                latin:additionalMoreKeys="&#x0969;,3"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+091A: "च" DEVANAGARI LETTER CA
+                 U+096A: "४" DEVANAGARI DIGIT FOUR -->
+            <Key
+                latin:keyLabel="&#x091A;"
+                latin:keyHintLabel="4"
+                latin:additionalMoreKeys="&#x096A;,4"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0924: "त" DEVANAGARI LETTER TA
+                 U+096B: "५" DEVANAGARI DIGIT FIVE -->
+            <Key
+                latin:keyLabel="&#x0924;"
+                latin:keyHintLabel="5"
+                latin:additionalMoreKeys="&#x096B;,5"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0925: "थ" DEVANAGARI LETTER THA
+                 U+096C: "६" DEVANAGARI DIGIT SIX -->
+            <Key
+                latin:keyLabel="&#x0925;"
+                latin:keyHintLabel="6"
+                latin:additionalMoreKeys="&#x096C;,6"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0917: "ग" DEVANAGARI LETTER G
+                 U+096D: "७" DEVANAGARI DIGIT SEVEN -->
+            <Key
+                latin:keyLabel="&#x0917;"
+                latin:keyHintLabel="7"
+                latin:additionalMoreKeys="&#x096D;,7"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0937: "ष" DEVANAGARI LETTER SSA
+                 U+096E: "८" DEVANAGARI DIGIT EIGHT -->
+            <Key
+                latin:keyLabel="&#x0937;"
+                latin:keyHintLabel="8"
+                latin:additionalMoreKeys="&#x096E;,8"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+092F: "य" DEVANAGARI LETTER YA
+                 U+096F: "९" DEVANAGARI DIGIT NINE -->
+            <Key
+                latin:keyLabel="&#x092F;"
+                latin:keyHintLabel="9"
+                latin:additionalMoreKeys="&#x096F;,9"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0909: "उ" DEVANAGARI LETTER U
+                 U+0966: "०" DEVANAGARI DIGIT ZERO -->
+            <Key
+                latin:keyLabel="&#x0909;"
+                latin:keyHintLabel="0"
+                latin:additionalMoreKeys="&#x0966;,0"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0907: "इ" DEVANAGARI LETTER I
+                 U+0914: "औ" DEVANAGARI LETTER AU -->
+            <Key
+                latin:keyLabel="&#x0907;"
+                latin:moreKeys="&#x0914;"
+                latin:keyLabelFlags="fontNormal" />
+         </default>
+    </switch>
+</merge>
diff --git a/java/res/xml/rowkeys_nepali_traditional2.xml b/java/res/xml/rowkeys_nepali_traditional2.xml
new file mode 100644
index 0000000..45620a9
--- /dev/null
+++ b/java/res/xml/rowkeys_nepali_traditional2.xml
@@ -0,0 +1,139 @@
+<?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+0906: "आ" DEVANAGARI LETTER AA -->
+            <Key
+                latin:keyLabel="&#x0906;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0919/U+094D: "ङ्" DEVANAGARI LETTER NGA/DEVANAGARI SIGN VIRAMA -->
+            <Key
+                latin:keyLabel="&#x0919;&#x094D;"
+                latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
+            <!-- U+0921/U+094D/U+0921: "ड्ड" DEVANAGARI LETTER DDA/DEVANAGARI SIGN VIRAMA/DEVANAGARI LETTER DDA -->
+            <Key
+                latin:keyLabel="&#x0921;&#x094D;&#x0921;"
+                latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
+            <!-- Because the font rendering system prior to API version 16 can't automatically
+                 render dotted circle for incomplete combining letter of some scripts, different
+                 set of Key definitions are needed based on the API version. -->
+            <include
+                latin:keyboardLayout="@xml/key_devanagari_sign_candrabindu" />
+            <!-- U+0926/U+094D/U+0926: "द्द" DEVANAGARI LETTER DA/DEVANAGARI SIGN VIRAMA/DEVANAGARI LETTER DA -->
+            <Key
+                latin:keyLabel="&#x0926;&#x094D;&#x0926;"
+                latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
+            <!-- U+091D: "झ" DEVANAGARI LETTER JHA -->
+            <Key
+                latin:keyLabel="&#x091D;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- Because the font rendering system prior to API version 16 can't automatically
+                 render dotted circle for incomplete combining letter of some scripts, different
+                 set of Key definitions are needed based on the API version. -->
+            <include
+                latin:keyboardLayout="@xml/keystyle_devanagari_vowel_sign_o" />
+            <Key
+                latin:keyStyle="baseKeyDevanagariVowelSignO" />
+            <!-- U+092B: "फ" DEVANAGARI LETTER PHA -->
+            <Key
+                latin:keyLabel="&#x092B;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- Because the font rendering system prior to API version 16 can't automatically
+                 render dotted circle for incomplete combining letter of some scripts, different
+                 set of Key definitions are needed based on the API version. -->
+            <include
+                latin:keyboardLayout="@xml/keystyle_devanagari_vowel_sign_ii" />
+            <Key
+                latin:keyStyle="baseKeyDevanagariVowelSignIi" />
+            <!-- U+091F/U+094D/U+0920: "ट्ठ" DEVANAGARI LETTER TTA/DEVANAGARI SIGN VIRAMA/DEVANAGARI LETTER TTHA -->
+            <Key
+                latin:keyLabel="&#x091F;&#x094D;&#x0920;"
+                latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
+            <!-- Because the font rendering system prior to API version 16 can't automatically
+                 render dotted circle for incomplete combining letter of some scripts, different
+                 set of Key definitions are needed based on the API version. -->
+            <include
+                latin:keyboardLayout="@xml/keystyle_devanagari_vowel_sign_uu" />
+            <Key
+                latin:keyStyle="baseKeyDevanagariVowelSignUu" />
+        </case>
+        <default>
+            <!-- U+092C: "ब" DEVANAGARI LETTER BA -->
+            <Key
+                latin:keyLabel="&#x092C;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0915: "क" DEVANAGARI LETTER KA -->
+            <Key
+                latin:keyLabel="&#x0915;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+092E: "म" DEVANAGARI LETTER MA -->
+            <Key
+                latin:keyLabel="&#x092E;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- Because the font rendering system prior to API version 16 can't automatically
+                 render dotted circle for incomplete combining letter of some scripts, different
+                 set of Key definitions are needed based on the API version. -->
+            <include
+                latin:keyboardLayout="@xml/keystyle_devanagari_vowel_sign_aa" />
+            <Key
+                latin:keyStyle="baseKeyDevanagariVowelSignAa" />
+            <!-- U+0928: "न" DEVANAGARI LETTER NA -->
+            <Key
+                latin:keyLabel="&#x0928;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+091C: "ज" DEVANAGARI LETTER JA -->
+            <Key
+                latin:keyLabel="&#x091C;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0935: "व" DEVANAGARI LETTER VA -->
+            <Key
+                latin:keyLabel="&#x0935;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+092A: "प" DEVANAGARI LETTER PA -->
+            <Key
+                latin:keyLabel="&#x092A;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- Because the font rendering system prior to API version 16 can't automatically
+                 render dotted circle for incomplete combining letter of some scripts, different
+                 set of Key definitions are needed based on the API version. -->
+            <include
+                latin:keyboardLayout="@xml/keystyle_devanagari_vowel_sign_i" />
+            <Key
+                latin:keyStyle="baseKeyDevanagariVowelSignI" />
+            <!-- U+0938: "स" DEVANAGARI LETTER SA -->
+            <Key
+                latin:keyLabel="&#x0938;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- Because the font rendering system prior to API version 16 can't automatically
+                 render dotted circle for incomplete combining letter of some scripts, different
+                 set of Key definitions are needed based on the API version. -->
+            <include
+                latin:keyboardLayout="@xml/keystyle_devanagari_vowel_sign_u" />
+            <Key
+                latin:keyStyle="baseKeyDevanagariVowelSignU" />
+         </default>
+    </switch>
+</merge>
diff --git a/java/res/xml/rowkeys_nepali_traditional3_left6.xml b/java/res/xml/rowkeys_nepali_traditional3_left6.xml
new file mode 100644
index 0000000..1cacced
--- /dev/null
+++ b/java/res/xml/rowkeys_nepali_traditional3_left6.xml
@@ -0,0 +1,83 @@
+<?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+0915/U+094D: "क्" DEVANAGARI LETTER KA/DEVANAGARI SIGN VIRAMA -->
+            <Key
+                latin:keyLabel="&#x0915;&#x094D;"
+                latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
+            <!-- U+0939/U+094D/U+092E: "ह्म" DEVANAGARI LETTER HA/DEVANAGARI SIGN VIRAMA/DEVANAGARI LETTER MA -->
+            <Key
+                latin:keyLabel="&#x0939;&#x094D;&#x092E;"
+                latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
+            <!-- U+090B: "ऋ" DEVANAGARI LETTER VOCALIC R -->
+            <Key
+                latin:keyLabel="&#x090B;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0950: "ॐ" DEVANAGARI OM -->
+            <Key
+                latin:keyLabel="&#x0950;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- Because the font rendering system prior to API version 16 can't automatically
+                 render dotted circle for incomplete combining letter of some scripts, different
+                 set of Key definitions are needed based on the API version. -->
+            <include
+                latin:keyboardLayout="@xml/keystyle_devanagari_vowel_sign_au" />
+            <Key
+                latin:keyStyle="baseKeyDevanagariVowelSignAu" />
+            <!-- U+0926/U+094D/U+092F: "द्य" DEVANAGARI LETTER DA/DEVANAGARI SIGN VIRAMA/DEVANAGARI LETTER YA -->
+            <Key
+                latin:keyLabel="&#x0926;&#x094D;&#x092F;"
+                latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
+        </case>
+        <default>
+            <!-- U+0936: "श" DEVANAGARI LETTER SHA -->
+            <Key
+                latin:keyLabel="&#x0936;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0939: "ह" DEVANAGARI LETTER HA -->
+            <Key
+                latin:keyLabel="&#x0939;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0905: "अ" DEVANAGARI LETTER A -->
+            <Key
+                latin:keyLabel="&#x0905;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0916: "ख" DEVANAGARI LETTER KHA -->
+            <Key
+                latin:keyLabel="&#x0916;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0926: "द" DEVANAGARI LETTER DA -->
+            <Key
+                latin:keyLabel="&#x0926;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0932: "ल" DEVANAGARI LETTER LA -->
+            <Key
+                latin:keyLabel="&#x0932;"
+                latin:keyLabelFlags="fontNormal" />
+        </default>
+    </switch>
+</merge>
diff --git a/java/res/xml/rowkeys_nepali_traditional3_right3.xml b/java/res/xml/rowkeys_nepali_traditional3_right3.xml
new file mode 100644
index 0000000..b2e01e4
--- /dev/null
+++ b/java/res/xml/rowkeys_nepali_traditional3_right3.xml
@@ -0,0 +1,65 @@
+<?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"
+        >
+            <!-- Because the font rendering system prior to API version 16 can't automatically
+                 render dotted circle for incomplete combining letter of some scripts, different
+                 set of Key definitions are needed based on the API version. -->
+            <include
+                latin:keyboardLayout="@xml/key_devanagari_sign_anusvara" />
+            <!-- U+0919: "ङ" DEVANAGARI LETTER NGA -->
+            <Key
+                latin:keyLabel="&#x0919;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- Because the font rendering system prior to API version 16 can't automatically
+                 render dotted circle for incomplete combining letter of some scripts, different
+                 set of Key definitions are needed based on the API version. -->
+            <include
+                latin:keyboardLayout="@xml/keystyle_devanagari_vowel_sign_ai" />
+            <Key
+                latin:keyStyle="baseKeyDevanagariVowelSignAi" />
+        </case>
+        <default>
+            <!-- Because the font rendering system prior to API version 16 can't automatically
+                 render dotted circle for incomplete combining letter of some scripts, different
+                 set of Key definitions are needed based on the API version. -->
+            <include
+                latin:keyboardLayout="@xml/keystyle_devanagari_vowel_sign_e" />
+            <Key
+                latin:keyStyle="baseKeyDevanagariVowelSignE" />
+            <!-- U+0964: "।" DEVANAGARI DANDA -->
+            <Key
+                latin:keyLabel="&#x0964;"
+                latin:keyLabelFlags="fontNormal" />
+             <!-- U+0930: "र" DEVANAGARI LETTER RA
+                  U+0930/U+0941: "रु" DEVANAGARI LETTER RA/DEVANAGARI VOWEL SIGN U -->
+            <Key
+                latin:keyLabel="&#x0930;"
+                latin:moreKeys="&#x0930;&#x0941;"
+                latin:keyLabelFlags="fontNormal" />
+         </default>
+    </switch>
+</merge>
diff --git a/java/res/xml/rowkeys_nepali_traditional3_right5.xml b/java/res/xml/rowkeys_nepali_traditional3_right5.xml
new file mode 100644
index 0000000..87f0616
--- /dev/null
+++ b/java/res/xml/rowkeys_nepali_traditional3_right5.xml
@@ -0,0 +1,91 @@
+<?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"
+        >
+            <!-- Because the font rendering system prior to API version 16 can't automatically
+                 render dotted circle for incomplete combining letter of some scripts, different
+                 set of Key definitions are needed based on the API version. -->
+            <include
+                latin:keyboardLayout="@xml/key_devanagari_sign_anusvara" />
+            <!-- U+0919: "ङ" DEVANAGARI LETTER NGA -->
+            <Key
+                latin:keyLabel="&#x0919;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- Because the font rendering system prior to API version 16 can't automatically
+                 render dotted circle for incomplete combining letter of some scripts, different
+                 set of Key definitions are needed based on the API version. -->
+            <include
+                latin:keyboardLayout="@xml/keystyle_devanagari_vowel_sign_ai" />
+            <Key
+                latin:keyStyle="baseKeyDevanagariVowelSignAi" />
+            <!-- U+0930/U+0941: "रु" DEVANAGARI LETTER RA/DEVANAGARI VOWEL SIGN U -->
+            <Key
+                latin:keyLabel="&#x0930;&#x0941;"
+                latin:moreKeys="!"
+                latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
+            <Key
+                latin:keyLabel="\?" />
+        </case>
+        <default>
+            <!-- Because the font rendering system prior to API version 16 can't automatically
+                 render dotted circle for incomplete combining letter of some scripts, different
+                 set of Key definitions are needed based on the API version. -->
+            <!-- U+093D: "ऽ" DEVANAGARI SIGN AVAGRAHA -->
+            <include
+                latin:keyboardLayout="@xml/keystyle_devanagari_sign_visarga" />
+            <Key
+                latin:keyStyle="baseKeyDevanagariSignVisarga"
+                latin:moreKeys="&#x093D;" />
+            <!-- Because the font rendering system prior to API version 16 can't automatically
+                 render dotted circle for incomplete combining letter of some scripts, different
+                 set of Key definitions are needed based on the API version. -->
+            <include
+                latin:keyboardLayout="@xml/keystyle_devanagari_vowel_sign_e" />
+            <!-- Override more keys with empty definition -->
+            <key-style
+                latin:styleName="moreKeysDevanagariVowelSignE" />
+            <Key
+                latin:keyStyle="baseKeyDevanagariVowelSignE" />
+            <!-- U+0964: "।" DEVANAGARI DANDA -->
+            <Key
+                latin:keyLabel="&#x0964;"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- U+0930: "र" DEVANAGARI LETTER RA -->
+            <Key
+                latin:keyLabel="&#x0930;"
+                latin:moreKeys="!"
+                latin:keyLabelFlags="fontNormal" />
+            <!-- Because the font rendering system prior to API version 16 can't automatically
+                 render dotted circle for incomplete combining letter of some scripts, different
+                 set of Key definitions are needed based on the API version. -->
+            <include
+                latin:keyboardLayout="@xml/keystyle_devanagari_sign_virama" />
+            <Key
+                latin:keyStyle="baseKeyDevanagariSignVirama"
+                latin:moreKeys="\?" />
+        </default>
+    </switch>
+</merge>
diff --git a/java/res/xml/rowkeys_pcqwerty1.xml b/java/res/xml/rowkeys_pcqwerty1.xml
index b2d1d37..de548d0 100644
--- a/java/res/xml/rowkeys_pcqwerty1.xml
+++ b/java/res/xml/rowkeys_pcqwerty1.xml
@@ -21,67 +21,61 @@
 <merge
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
 >
-    <switch>
-        <case
-            latin:keyboardLayoutSetElement="alphabet|alphabetAutomaticShifted"
-        >
-            <!-- U+00AC: "¬" NOT SIGN -->
-            <Key
-                latin:keyLabel="`"
-                latin:moreKeys="~"
-                latin:additionalMoreKeys="&#x00AC;" />
-            <!-- U+00A1: "¡" NVERTED EXCLAMATION MARK -->
-            <Key
-                latin:keyLabel="1"
-                latin:additionalMoreKeys="!"
-                latin:moreKeys="&#x00A1;,!text/more_keys_for_symbols_1" />
-            <Key
-                latin:keyLabel="2"
-                latin:additionalMoreKeys="\@"
-                latin:moreKeys="!text/more_keys_for_symbols_2" />
-            <Key
-                latin:keyLabel="3"
-                latin:additionalMoreKeys="\#"
-                latin:moreKeys="!text/more_keys_for_symbols_3" />
-            <Key
-                latin:keyLabel="4"
-                latin:additionalMoreKeys="$"
-                latin:moreKeys="!text/more_keys_for_symbols_4" />
-            <Key
-                latin:keyLabel="5"
-                latin:additionalMoreKeys="\\%"
-                latin:moreKeys="!text/more_keys_for_symbols_5" />
-            <Key
-                latin:keyLabel="6"
-                latin:additionalMoreKeys="^"
-                latin:moreKeys="!text/more_keys_for_symbols_6" />
-            <Key
-                latin:keyLabel="7"
-                latin:additionalMoreKeys="&amp;"
-                latin:moreKeys="!text/more_keys_for_symbols_7" />
-            <Key
-                latin:keyLabel="8"
-                latin:additionalMoreKeys="*,%"
-                latin:moreKeys="!text/more_keys_for_symbols_8" />
-            <Key
-                latin:keyLabel="9"
-                latin:additionalMoreKeys="("
-                latin:moreKeys="!text/more_keys_for_symbols_9" />
-            <Key
-                latin:keyLabel="0"
-                latin:additionalMoreKeys=")"
-                latin:moreKeys="!text/more_keys_for_symbols_0" />
-            <Key
-                latin:keyLabel="-"
-                latin:moreKeys="_" />
-            <Key
-                latin:keyLabel="="
-                latin:moreKeys="+" />
-        </case>
-        <!-- keyboardLayoutSetElement="alphabet*Shifted|symbols*" -->
-        <default>
-            <include
-                latin:keyboardLayout="@xml/keys_pcqwerty_symbols1" />
-        </default>
-    </switch>
+    <Key
+        latin:keyLabel="`"
+        latin:additionalMoreKeys="~" />
+    <Key
+        latin:keyLabel="1"
+        latin:additionalMoreKeys="!,!text/more_keys_for_symbols_exclamation"
+        latin:moreKeys="!text/more_keys_for_symbols_1" />
+    <Key
+        latin:keyLabel="2"
+        latin:additionalMoreKeys="\@"
+        latin:moreKeys="!text/more_keys_for_symbols_2" />
+    <Key
+        latin:keyLabel="3"
+        latin:additionalMoreKeys="\#"
+        latin:moreKeys="!text/more_keys_for_symbols_3" />
+    <Key
+        latin:keyLabel="4"
+        latin:additionalMoreKeys="$"
+        latin:moreKeys="!text/more_keys_for_symbols_4" />
+    <Key
+        latin:keyLabel="5"
+        latin:additionalMoreKeys="\\%"
+        latin:moreKeys="!text/more_keys_for_symbols_5" />
+    <Key
+        latin:keyLabel="6"
+        latin:additionalMoreKeys="^"
+        latin:moreKeys="!text/more_keys_for_symbols_6" />
+    <Key
+        latin:keyLabel="7"
+        latin:additionalMoreKeys="&amp;"
+        latin:moreKeys="!text/more_keys_for_symbols_7" />
+    <Key
+        latin:keyLabel="8"
+        latin:additionalMoreKeys="*"
+        latin:moreKeys="!text/more_keys_for_symbols_8" />
+    <Key
+        latin:keyLabel="9"
+        latin:additionalMoreKeys="("
+        latin:moreKeys="!text/more_keys_for_symbols_9" />
+    <Key
+        latin:keyLabel="0"
+        latin:additionalMoreKeys=")"
+        latin:moreKeys="!text/more_keys_for_symbols_0" />
+    <!-- U+2013: "–" EN DASH
+         U+2014: "—" EM DASH
+         U+00B7: "·" MIDDLE DOT -->
+    <Key
+        latin:keyLabel="-"
+        latin:additionalMoreKeys="_"
+        latin:moreKeys="&#x2013;,&#x2014;,&#x00B7;" />
+    <!-- U+221E: "∞" INFINITY
+         U+2260: "≠" NOT EQUAL TO
+         U+2248: "≈" ALMOST EQUAL TO -->
+    <Key
+        latin:keyLabel="="
+        latin:additionalMoreKeys="+"
+        latin:moreKeys="!fixedColumnOrder!4,&#x221E;,&#x2260;,&#x2248;,%" />
 </merge>
diff --git a/java/res/xml/keys_pcqwerty_symbols1.xml b/java/res/xml/rowkeys_pcqwerty1_shift.xml
similarity index 64%
rename from java/res/xml/keys_pcqwerty_symbols1.xml
rename to java/res/xml/rowkeys_pcqwerty1_shift.xml
index 2364e10..bc39f94 100644
--- a/java/res/xml/keys_pcqwerty_symbols1.xml
+++ b/java/res/xml/rowkeys_pcqwerty1_shift.xml
@@ -21,37 +21,40 @@
 <merge
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
 >
-    <!-- U+00AC: "¬" NOT SIGN -->
     <Key
-        latin:keyLabel="~"
-        latin:moreKeys="&#x00AC;" />
-    <!-- U+00A1: "¡" NVERTED EXCLAMATION MARK -->
+        latin:keyLabel="~" />
     <Key
         latin:keyLabel="!"
-        latin:moreKeys="&#x00A1;" />
+        latin:additionalMoreKeys="!text/more_keys_for_symbols_exclamation" />
     <Key
         latin:keyLabel="\@" />
     <Key
         latin:keyLabel="\#" />
     <Key
-        latin:keyLabel="$" />
-    <!-- U+2030: "‰" PER MILLE SIGN -->
+        latin:keyLabel="$"
+        latin:additionalMoreKeys="!text/more_keys_for_currency_dollar" />
     <Key
         latin:keyLabel="%"
-        latin:moreKeys="&#x2030;" />
+        latin:additionalMoreKeys="!text/more_keys_for_symbols_percent" />
     <Key
         latin:keyLabel="^" />
     <Key
         latin:keyLabel="&amp;" />
     <Key
         latin:keyLabel="*"
-        latin:moreKeys="!text/more_keys_for_star" />
+        latin:additionalMoreKeys="!text/more_keys_for_star" />
     <Key
         latin:keyLabel="(" />
     <Key
         latin:keyLabel=")" />
     <Key
         latin:keyLabel="_" />
+    <!-- U+00B1: "±" PLUS-MINUS SIGN
+         U+00D7: "×" MULTIPLICATION SIGN
+         U+00F7: "÷" DIVISION SIGN
+         U+221A: "√" SQUARE ROOT -->
     <Key
-        latin:keyLabel="+" />
+        latin:keyLabel="+"
+        latin:additionalMoreKeys="!text/more_keys_for_plus"
+        latin:moreKeys="&#x00B1;,&#x00D7;,&#x00F7;,&#x221A;" />
 </merge>
diff --git a/java/res/xml/rowkeys_pcqwerty2.xml b/java/res/xml/rowkeys_pcqwerty2.xml
index cedf475..8db704d 100644
--- a/java/res/xml/rowkeys_pcqwerty2.xml
+++ b/java/res/xml/rowkeys_pcqwerty2.xml
@@ -21,21 +21,11 @@
 <merge
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
 >
-    <switch>
-        <case
-            latin:keyboardLayoutSetElement="symbols|symbolsShifted"
-        >
-            <include
-                latin:keyboardLayout="@xml/keys_pcqwerty_symbols2" />
-        </case>
-        <default>
-            <!-- The keys on this PC layout row2 consist of the letters of QWERTY layout row1 and
-                 some symbols keys. -->
-            <include
-                latin:keyboardLayout="@xml/rowkeys_qwerty1"
-                latin:keyLabelFlags="disableAdditionalMoreKeys|disableKeyHintLabel" />
-        </default>
-    </switch>
+    <!-- The keys on this PC layout row2 consist of the letters of QWERTY layout row1 and
+         some symbols keys. -->
+    <include
+        latin:keyboardLayout="@xml/rowkeys_qwerty1"
+        latin:keyLabelFlags="disableAdditionalMoreKeys|disableKeyHintLabel" />
     <include
         latin:keyboardLayout="@xml/keys_pcqwerty2_right3" />
 </merge>
diff --git a/java/res/xml/rowkeys_pcqwerty3.xml b/java/res/xml/rowkeys_pcqwerty3.xml
index 5044e5f..ad122d3 100644
--- a/java/res/xml/rowkeys_pcqwerty3.xml
+++ b/java/res/xml/rowkeys_pcqwerty3.xml
@@ -21,20 +21,10 @@
 <merge
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
 >
-    <switch>
-        <case
-            latin:keyboardLayoutSetElement="symbols|symbolsShifted"
-        >
-            <include
-                latin:keyboardLayout="@xml/keys_pcqwerty_symbols3" />
-        </case>
-        <default>
-            <!-- The keys on this PC layout row3 consist of the letters of QWERTY layout row2 and
-                 some symbols keys. -->
-            <include
-                latin:keyboardLayout="@xml/rowkeys_qwerty2" />
-        </default>
-    </switch>
+    <!-- The keys on this PC layout row3 consist of the letters of QWERTY layout row2 and
+         some symbols keys. -->
+    <include
+        latin:keyboardLayout="@xml/rowkeys_qwerty2" />
     <include
         latin:keyboardLayout="@xml/keys_pcqwerty3_right2" />
 </merge>
diff --git a/java/res/xml/rowkeys_pcqwerty4.xml b/java/res/xml/rowkeys_pcqwerty4.xml
index 4071e8c..b558f41 100644
--- a/java/res/xml/rowkeys_pcqwerty4.xml
+++ b/java/res/xml/rowkeys_pcqwerty4.xml
@@ -21,20 +21,10 @@
 <merge
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
 >
-    <switch>
-        <case
-            latin:keyboardLayoutSetElement="symbols|symbolsShifted"
-        >
-            <include
-                latin:keyboardLayout="@xml/keys_pcqwerty_symbols4" />
-        </case>
-        <default>
-            <!-- The keys on this PC layout row4 consist of the letters of QWERTY layout row3 and
-                 some symbols keys. -->
-            <include
-                latin:keyboardLayout="@xml/rowkeys_qwerty3" />
-        </default>
-    </switch>
+    <!-- The keys on this PC layout row4 consist of the letters of QWERTY layout row3 and
+         some symbols keys. -->
+    <include
+        latin:keyboardLayout="@xml/rowkeys_qwerty3" />
     <include
         latin:keyboardLayout="@xml/keys_pcqwerty4_right3" />
 </merge>
diff --git a/java/res/xml/rowkeys_symbols2.xml b/java/res/xml/rowkeys_symbols2.xml
index 3e27f15..76cbf62 100644
--- a/java/res/xml/rowkeys_symbols2.xml
+++ b/java/res/xml/rowkeys_symbols2.xml
@@ -50,9 +50,6 @@
         latin:moreKeys="!text/more_keys_for_symbols_percent" />
     <Key
         latin:keyLabel="&amp;" />
-    <Key
-        latin:keyLabel="*"
-        latin:moreKeys="!text/more_keys_for_star" />
     <!-- U+2013: "–" EN DASH
          U+2014: "—" EM DASH
          U+00B7: "·" MIDDLE DOT -->
diff --git a/java/res/xml/rowkeys_symbols3.xml b/java/res/xml/rowkeys_symbols3.xml
index 7722ca9..9f5e620 100644
--- a/java/res/xml/rowkeys_symbols3.xml
+++ b/java/res/xml/rowkeys_symbols3.xml
@@ -22,8 +22,8 @@
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
 >
     <Key
-        latin:keyLabel="!"
-        latin:moreKeys="!text/more_keys_for_symbols_exclamation" />
+        latin:keyLabel="*"
+        latin:moreKeys="!text/more_keys_for_star" />
     <switch>
         <case
             latin:languageCode="fa"
@@ -54,8 +54,9 @@
         latin:keyLabel="!text/keylabel_for_symbols_semicolon"
         latin:moreKeys="!text/more_keys_for_symbols_semicolon" />
     <Key
-        latin:keyLabel="/" />
-    <Key
         latin:keyLabel="!text/keylabel_for_symbols_question"
         latin:moreKeys="!text/more_keys_for_symbols_question" />
+    <Key
+        latin:keyLabel="!"
+        latin:moreKeys="!text/more_keys_for_symbols_exclamation" />
 </merge>
diff --git a/java/res/xml/rowkeys_symbols_shift1.xml b/java/res/xml/rowkeys_symbols_shift1.xml
index fea8ae3..6013493 100644
--- a/java/res/xml/rowkeys_symbols_shift1.xml
+++ b/java/res/xml/rowkeys_symbols_shift1.xml
@@ -34,17 +34,23 @@
     <!-- U+221A: "√" SQUARE ROOT -->
     <Key
         latin:keyLabel="&#x221A;" />
-    <!-- U+03C0: "π" GREEK SMALL LETTER PI
-         U+03A0: "Π" GREEK CAPITAL LETTER PI  -->
+    <!-- U+03A0: "Π" GREEK CAPITAL LETTER PI
+         U+03C0: "π" GREEK SMALL LETTER PI  -->
     <Key
-        latin:keyLabel="&#x03C0;"
-        latin:moreKeys="&#x03A0;" />
+        latin:keyLabel="&#x03A0;"
+        latin:moreKeys="&#x03C0;" />
     <!-- U+00F7: "÷" DIVISION SIGN -->
     <Key
         latin:keyLabel="&#x00F7;" />
     <!-- U+00D7: "×" MULTIPLICATION SIGN -->
     <Key
         latin:keyLabel="&#x00D7;" />
-    <include
-        latin:keyboardLayout="@xml/keys_curly_brackets" />
+    <!-- U+00B6: "¶" PILCROW SIGN
+         U+00A7: "§" SECTION SIGN -->
+    <Key
+        latin:keyLabel="&#x00B6;"
+        latin:moreKeys="&#x00A7;" />
+    <!-- U+2206: "∆" INCREMENT -->
+    <Key
+        latin:keyLabel="&#x2206;" />
 </merge>
diff --git a/java/res/xml/rowkeys_symbols_shift2.xml b/java/res/xml/rowkeys_symbols_shift2.xml
index 3fd8aac..36f9214 100644
--- a/java/res/xml/rowkeys_symbols_shift2.xml
+++ b/java/res/xml/rowkeys_symbols_shift2.xml
@@ -22,19 +22,13 @@
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
 >
     <Key
-        latin:keyStyle="nonSpecialBackgroundTabKeyStyle" />
-    <Key
         latin:keyStyle="moreCurrency1KeyStyle" />
     <Key
         latin:keyStyle="moreCurrency2KeyStyle" />
     <Key
         latin:keyStyle="moreCurrency3KeyStyle" />
-    <!-- U+00B0: "°" DEGREE SIGN
-         U+2032: "′" PRIME
-         U+2033: "″" DOUBLE PRIME -->
     <Key
-        latin:keyLabel="&#x00B0;"
-        latin:moreKeys="&#x2032;,&#x2033;" />
+        latin:keyStyle="moreCurrency4KeyStyle" />
     <!-- U+2191: "↑" UPWARDS ARROW
          U+2193: "↓" DOWNWARDS ARROW
          U+2190: "←" LEFTWARDS ARROW
@@ -42,8 +36,12 @@
     <Key
         latin:keyLabel="^"
         latin:moreKeys="&#x2191;,&#x2193;,&#x2190;,&#x2192;" />
+    <!-- U+00B0: "°" DEGREE SIGN
+         U+2032: "′" PRIME
+         U+2033: "″" DOUBLE PRIME -->
     <Key
-        latin:keyLabel="_" />
+        latin:keyLabel="&#x00B0;"
+        latin:moreKeys="&#x2032;,&#x2033;" />
     <!-- U+2260: "≠" NOT EQUAL TO
          U+2248: "≈" ALMOST EQUAL TO
          U+221E: "∞" INFINITY -->
@@ -51,5 +49,5 @@
         latin:keyLabel="="
         latin:moreKeys="&#x2260;,&#x2248;,&#x221E;" />
     <include
-        latin:keyboardLayout="@xml/keys_square_brackets" />
+        latin:keyboardLayout="@xml/keys_curly_brackets" />
 </merge>
diff --git a/java/res/xml/rowkeys_symbols_shift3.xml b/java/res/xml/rowkeys_symbols_shift3.xml
index a35af21..5fe1c74 100644
--- a/java/res/xml/rowkeys_symbols_shift3.xml
+++ b/java/res/xml/rowkeys_symbols_shift3.xml
@@ -21,22 +21,20 @@
 <merge
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
 >
-    <!-- U+2122: "™" TRADE MARK SIGN -->
     <Key
-        latin:keyLabel="&#x2122;" />
-    <!-- U+00AE: "®" REGISTERED SIGN -->
-    <Key
-        latin:keyLabel="&#x00AE;" />
+        latin:keyLabel="\\" />
     <!-- U+00A9: "©" COPYRIGHT SIGN -->
     <Key
         latin:keyLabel="&#x00A9;" />
-    <!-- U+00B6: "¶" PILCROW SIGN
-         U+00A7: "§" SECTION SIGN -->
+    <!-- U+00AE: "®" REGISTERED SIGN -->
     <Key
-        latin:keyLabel="&#x00B6;"
-        latin:moreKeys="&#x00A7;" />
+        latin:keyLabel="&#x00AE;" />
+    <!-- U+2122: "™" TRADE MARK SIGN -->
     <Key
-        latin:keyLabel="\\" />
+        latin:keyLabel="&#x2122;" />
+    <!-- U+2105: "℅" CARE OF -->
+    <Key
+        latin:keyLabel="&#x2105;" />
     <include
-        latin:keyboardLayout="@xml/keys_less_greater" />
+        latin:keyboardLayout="@xml/keys_square_brackets" />
 </merge>
diff --git a/java/res/xml/rows_armenian_phonetic.xml b/java/res/xml/rows_armenian_phonetic.xml
new file mode 100644
index 0000000..ea8870e
--- /dev/null
+++ b/java/res/xml/rows_armenian_phonetic.xml
@@ -0,0 +1,64 @@
+<?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" />
+    <include
+        latin:keyboardLayout="@xml/key_styles_currency" />
+    <Row
+        latin:keyWidth="10.0%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_armenian_phonetic1" />
+    </Row>
+    <Row
+        latin:keyWidth="10.0%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_armenian_phonetic2" />
+    </Row>
+    <Row
+        latin:keyWidth="10.0%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_armenian_phonetic3" />
+        <include
+            latin:keyboardLayout="@xml/key_armenian_xeh" />
+    </Row>
+    <Row
+        latin:keyWidth="9.8000%p"
+    >
+        <Key
+            latin:keyStyle="shiftKeyStyle"
+            latin:keyWidth="10.8%p" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_armenian_phonetic4" />
+        <include
+            latin:keyboardLayout="@xml/key_armenian_sha" />
+        <Key
+            latin:keyStyle="deleteKeyStyle"
+            latin:keyWidth="fillRight" />
+    </Row>
+    <include
+        latin:keyboardLayout="@xml/row_qwerty4" />
+</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/res/xml/rows_lao.xml b/java/res/xml/rows_lao.xml
new file mode 100644
index 0000000..321f411
--- /dev/null
+++ b/java/res/xml/rows_lao.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2013, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <include
+        latin:keyboardLayout="@xml/key_styles_common" />
+    <Row
+        latin:keyWidth="8.3333%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_lao1" />
+    </Row>
+    <Row
+        latin:keyWidth="8.3333%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_lao2" />
+    </Row>
+    <Row
+        latin:keyWidth="8.3333%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_lao3" />
+    </Row>
+    <Row
+        latin:keyWidth="8.3333%p"
+    >
+        <Key
+            latin:keyStyle="shiftKeyStyle" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_lao4" />
+        <Key
+            latin:keyStyle="deleteKeyStyle" />
+    </Row>
+    <include
+        latin:keyboardLayout="@xml/row_qwerty4" />
+</merge>
diff --git a/java/res/xml/rows_nepali_romanized.xml b/java/res/xml/rows_nepali_romanized.xml
new file mode 100644
index 0000000..6df09c8
--- /dev/null
+++ b/java/res/xml/rows_nepali_romanized.xml
@@ -0,0 +1,52 @@
+<?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="9.091%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_nepali_romanized1" />
+    </Row>
+    <Row
+            latin:keyWidth="9.091%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_nepali_romanized2" />
+    </Row>
+    <Row
+        latin:keyWidth="8.711%p"
+    >
+        <Key
+            latin:keyStyle="shiftKeyStyle"
+            latin:keyWidth="10.8%p" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_nepali_romanized3" />
+        <Key
+            latin:keyStyle="deleteKeyStyle"
+            latin:keyWidth="fillRight" />
+    </Row>
+    <include
+        latin:keyboardLayout="@xml/row_qwerty4" />
+</merge>
diff --git a/java/res/xml-sw600dp/rows_10_10_7_symbols.xml b/java/res/xml/rows_nepali_traditional.xml
similarity index 62%
copy from java/res/xml-sw600dp/rows_10_10_7_symbols.xml
copy to java/res/xml/rows_nepali_traditional.xml
index 0e4710c..7789135 100644
--- a/java/res/xml-sw600dp/rows_10_10_7_symbols.xml
+++ b/java/res/xml/rows_nepali_traditional.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2012, The Android Open Source Project
+** 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.
@@ -26,35 +26,31 @@
     <include
         latin:keyboardLayout="@xml/key_styles_currency" />
     <Row
-        latin:keyWidth="9.0%p"
+        latin:keyWidth="9.091%p"
     >
         <include
-            latin:keyboardLayout="@xml/rowkeys_symbols1" />
+            latin:keyboardLayout="@xml/rowkeys_nepali_traditional1" />
+    </Row>
+    <Row
+            latin:keyWidth="9.091%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_nepali_traditional2" />
+    </Row>
+    <Row
+        latin:keyWidth="8.711%p"
+    >
+        <Key
+            latin:keyStyle="shiftKeyStyle"
+            latin:keyWidth="10.8%p" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_nepali_traditional3_left6" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_nepali_traditional3_right3" />
         <Key
             latin:keyStyle="deleteKeyStyle"
             latin:keyWidth="fillRight" />
     </Row>
-    <Row
-        latin:keyWidth="9.0%p"
-    >
-        <include
-            latin:keyboardLayout="@xml/rowkeys_symbols2" />
-        <Key
-            latin:keyStyle="enterKeyStyle"
-            latin:keyWidth="fillRight" />
-    </Row>
-    <Row
-        latin:keyWidth="9.0%p"
-    >
-        <Key
-            latin:keyStyle="toMoreSymbolKeyStyle"
-            latin:keyWidth="10.0%p" />
-        <include
-            latin:keyboardLayout="@xml/rowkeys_symbols3" />
-        <Key
-            latin:keyStyle="toMoreSymbolKeyStyle"
-            latin:keyWidth="fillRight" />
-    </Row>
     <include
-        latin:keyboardLayout="@xml/row_symbols4" />
+        latin:keyboardLayout="@xml/row_qwerty4" />
 </merge>
diff --git a/java/res/xml/rows_pcqwerty.xml b/java/res/xml/rows_pcqwerty.xml
index a5ed745..8846989 100644
--- a/java/res/xml/rows_pcqwerty.xml
+++ b/java/res/xml/rows_pcqwerty.xml
@@ -26,8 +26,19 @@
     <Row
         latin:keyWidth="7.692%p"
     >
-        <include
-            latin:keyboardLayout="@xml/rowkeys_pcqwerty1" />
+        <switch>
+            <case
+                latin:keyboardLayoutSetElement="alphabet|alphabetAutomaticShifted"
+            >
+                <include
+                    latin:keyboardLayout="@xml/rowkeys_pcqwerty1" />
+            </case>
+            <!-- keyboardLayoutSetElement="alphabetManualShifted|alphabetShiftLocked|alphabetShiftLockShifted" -->
+            <default>
+                <include
+                     latin:keyboardLayout="@xml/rowkeys_pcqwerty1_shift" />
+            </default>
+        </switch>
     </Row>
     <Row
         latin:keyWidth="7.692%p"
diff --git a/java/res/xml/rows_pcqwerty_symbols.xml b/java/res/xml/rows_pcqwerty_symbols.xml
deleted file mode 100644
index 107a4ad..0000000
--- a/java/res/xml/rows_pcqwerty_symbols.xml
+++ /dev/null
@@ -1,60 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <include
-        latin:keyboardLayout="@xml/key_styles_common" />
-    <include
-        latin:keyboardLayout="@xml/key_styles_currency" />
-    <Row
-        latin:keyWidth="7.692%p"
-    >
-        <include
-            latin:keyboardLayout="@xml/rowkeys_pcqwerty1" />
-    </Row>
-    <Row
-        latin:keyWidth="7.692%p"
-    >
-        <include
-            latin:keyboardLayout="@xml/rowkeys_pcqwerty2" />
-    </Row>
-    <Row
-        latin:keyWidth="7.692%p"
-    >
-        <include
-            latin:keyboardLayout="@xml/rowkeys_pcqwerty3"
-            latin:keyXPos="3.846%p" />
-        <Key
-            latin:keyStyle="deleteKeyStyle"
-            latin:keyWidth="fillRight"
-            latin:visualInsetsLeft="1%p" />
-    </Row>
-    <Row
-        latin:keyWidth="7.692%p"
-    >
-        <include
-            latin:keyboardLayout="@xml/rowkeys_pcqwerty4"
-            latin:keyXPos="11.538%p" />
-    </Row>
-    <include
-        latin:keyboardLayout="@xml/row_pcqwerty5" />
-</merge>
diff --git a/java/res/xml/rows_symbols.xml b/java/res/xml/rows_symbols.xml
index bd1a57e..3f102e2 100644
--- a/java/res/xml/rows_symbols.xml
+++ b/java/res/xml/rows_symbols.xml
@@ -35,7 +35,8 @@
         latin:keyWidth="10%p"
     >
         <include
-            latin:keyboardLayout="@xml/rowkeys_symbols2" />
+            latin:keyboardLayout="@xml/rowkeys_symbols2"
+            latin:keyXPos="5%p" />
     </Row>
     <Row
         latin:keyWidth="10%p"
@@ -51,6 +52,13 @@
             latin:keyWidth="fillRight"
             latin:visualInsetsLeft="1%p" />
     </Row>
-    <include
-        latin:keyboardLayout="@xml/row_symbols4" />
+    <Row
+        latin:keyWidth="10%p"
+    >
+        <Key
+            latin:keyStyle="toAlphaKeyStyle"
+            latin:keyWidth="15%p" />
+        <include
+            latin:keyboardLayout="@xml/row_symbols4" />
+    </Row>
 </merge>
diff --git a/java/res/xml/rows_symbols_shift.xml b/java/res/xml/rows_symbols_shift.xml
index 9c03d90..45ada2a 100644
--- a/java/res/xml/rows_symbols_shift.xml
+++ b/java/res/xml/rows_symbols_shift.xml
@@ -35,6 +35,7 @@
         latin:keyWidth="10%p"
     >
         <include
+            latin:keyXPos="5%p"
             latin:keyboardLayout="@xml/rowkeys_symbols_shift2" />
     </Row>
     <Row
@@ -51,6 +52,13 @@
             latin:keyWidth="fillRight"
             latin:visualInsetsLeft="1%p" />
     </Row>
-    <include
-        latin:keyboardLayout="@xml/row_symbols_shift4" />
+    <Row
+        latin:keyWidth="10%p"
+    >
+        <Key
+            latin:keyStyle="toAlphaKeyStyle"
+            latin:keyWidth="15%p" />
+        <include
+            latin:keyboardLayout="@xml/row_symbols_shift4" />
+    </Row>
 </merge>
diff --git a/java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java b/java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java
index 77f3234..7639432 100644
--- a/java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java
+++ b/java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java
@@ -157,7 +157,7 @@
 
             // Add the virtual children of the root View.
             final Keyboard keyboard = mKeyboardView.getKeyboard();
-            final Key[] keys = keyboard.mKeys;
+            final Key[] keys = keyboard.getKeys();
             for (Key key : keys) {
                 final int childVirtualViewId = generateVirtualViewIdForKey(key);
                 rootInfo.addChild(mKeyboardView, childVirtualViewId);
@@ -172,7 +172,7 @@
             return null;
         }
         final String keyDescription = getKeyDescription(key);
-        final Rect boundsInParent = key.mHitBox;
+        final Rect boundsInParent = key.getHitBox();
 
         // Calculate the key's in-screen bounds.
         mTempBoundsInScreen.set(boundsInParent);
@@ -208,8 +208,8 @@
      * @param key The key to press.
      */
     void simulateKeyPress(final Key key) {
-        final int x = key.mHitBox.centerX();
-        final int y = key.mHitBox.centerY();
+        final int x = key.getHitBox().centerX();
+        final int y = key.getHitBox().centerY();
         final long downTime = SystemClock.uptimeMillis();
         final MotionEvent downEvent = MotionEvent.obtain(
                 downTime, downTime, MotionEvent.ACTION_DOWN, x, y, 0);
@@ -300,7 +300,7 @@
         }
         mVirtualViewIdToKey.clear();
 
-        final Key[] keys = keyboard.mKeys;
+        final Key[] keys = keyboard.getKeys();
         for (Key key : keys) {
             final int virtualViewId = generateVirtualViewIdForKey(key);
             mVirtualViewIdToKey.put(virtualViewId, key);
@@ -325,6 +325,6 @@
         // The key x- and y-coordinates are stable between layout changes.
         // Generate an identifier by bit-shifting the x-coordinate to the
         // left-half of the integer and OR'ing with the y-coordinate.
-        return ((0xFFFF & key.mX) << (Integer.SIZE / 2)) | (0xFFFF & key.mY);
+        return ((0xFFFF & key.getX()) << (Integer.SIZE / 2)) | (0xFFFF & key.getY());
     }
 }
diff --git a/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java b/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
index 41f5b9a..58624a2 100644
--- a/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
+++ b/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
@@ -97,7 +97,7 @@
      */
     public String getDescriptionForKey(final Context context, final Keyboard keyboard,
             final Key key, final boolean shouldObscure) {
-        final int code = key.mCode;
+        final int code = key.getCode();
 
         if (code == Constants.CODE_SWITCH_ALPHA_SYMBOL) {
             final String description = getDescriptionForSwitchAlphaSymbol(context, keyboard);
@@ -116,8 +116,8 @@
             return getDescriptionForActionKey(context, keyboard, key);
         }
 
-        if (!TextUtils.isEmpty(key.mLabel)) {
-            final String label = key.mLabel.toString().trim();
+        if (!TextUtils.isEmpty(key.getLabel())) {
+            final String label = key.getLabel().trim();
 
             // First, attempt to map the label to a pre-defined description.
             if (mKeyLabelMap.containsKey(label)) {
@@ -126,7 +126,7 @@
         }
 
         // Just attempt to speak the description.
-        if (key.mCode != Constants.CODE_UNSPECIFIED) {
+        if (key.getCode() != Constants.CODE_UNSPECIFIED) {
             return getDescriptionForKeyCode(context, keyboard, key, shouldObscure);
         }
         return null;
@@ -215,8 +215,8 @@
         final int resId;
 
         // Always use the label, if available.
-        if (!TextUtils.isEmpty(key.mLabel)) {
-            return key.mLabel.toString().trim();
+        if (!TextUtils.isEmpty(key.getLabel())) {
+            return key.getLabel().trim();
         }
 
         // Otherwise, use the action ID.
@@ -267,7 +267,7 @@
      */
     private String getDescriptionForKeyCode(final Context context, final Keyboard keyboard,
             final Key key, final boolean shouldObscure) {
-        final int code = key.mCode;
+        final int code = key.getCode();
 
         // If the key description should be obscured, now is the time to do it.
         final boolean isDefinedNonCtrl = Character.isDefined(code) && !Character.isISOControl(code);
@@ -280,8 +280,8 @@
         if (isDefinedNonCtrl) {
             return Character.toString((char) code);
         }
-        if (!TextUtils.isEmpty(key.mLabel)) {
-            return key.mLabel;
+        if (!TextUtils.isEmpty(key.getLabel())) {
+            return key.getLabel();
         }
         return context.getString(R.string.spoken_description_unknown, code);
     }
diff --git a/java/src/com/android/inputmethod/compat/ActivityManagerCompatUtils.java b/java/src/com/android/inputmethod/compat/ActivityManagerCompatUtils.java
new file mode 100644
index 0000000..385e3e0
--- /dev/null
+++ b/java/src/com/android/inputmethod/compat/ActivityManagerCompatUtils.java
@@ -0,0 +1,46 @@
+/*
+ * 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.compat;
+
+import android.app.ActivityManager;
+import android.content.Context;
+
+import java.lang.reflect.Method;
+
+public class ActivityManagerCompatUtils {
+    private static final Object LOCK = new Object();
+    private static volatile Boolean sBoolean = null;
+    private static final Method METHOD_isLowRamDevice = CompatUtils.getMethod(
+            ActivityManager.class, "isLowRamDevice");
+
+    private ActivityManagerCompatUtils() {
+        // Do not instantiate this class.
+    }
+
+    public static boolean isLowRamDevice(Context context) {
+        if (sBoolean == null) {
+            synchronized(LOCK) {
+                if (sBoolean == null) {
+                    final ActivityManager am =
+                            (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
+                    sBoolean = (Boolean)CompatUtils.invoke(am, false, METHOD_isLowRamDevice);
+                }
+            }
+        }
+        return sBoolean;
+    }
+}
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
new file mode 100644
index 0000000..4e61eda
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/EmojiKeyboardView.java
@@ -0,0 +1,766 @@
+/*
+ * 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 static com.android.inputmethod.latin.Constants.NOT_A_COORDINATE;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.res.ColorStateList;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Rect;
+import android.os.Build;
+import android.preference.PreferenceManager;
+import android.support.v4.view.PagerAdapter;
+import android.support.v4.view.ViewPager;
+import android.text.format.DateUtils;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.Pair;
+import android.util.SparseArray;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TabHost;
+import android.widget.TabHost.OnTabChangeListener;
+import android.widget.TextView;
+
+import com.android.inputmethod.keyboard.internal.DynamicGridKeyboard;
+import com.android.inputmethod.keyboard.internal.ScrollKeyboardView;
+import com.android.inputmethod.keyboard.internal.ScrollViewWithNotifier;
+import com.android.inputmethod.latin.Constants;
+import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.SubtypeSwitcher;
+import com.android.inputmethod.latin.settings.Settings;
+import com.android.inputmethod.latin.utils.CollectionUtils;
+import com.android.inputmethod.latin.utils.ResourceUtils;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * View class to implement Emoji keyboards.
+ * The Emoji keyboard consists of group of views {@link R.layout#emoji_keyboard_view}.
+ * <ol>
+ * <li> Emoji category tabs.
+ * <li> Delete button.
+ * <li> Emoji keyboard pages that can be scrolled by swiping horizontally or by selecting a tab.
+ * <li> Back to main keyboard button and enter button.
+ * </ol>
+ * Because of the above reasons, this class doesn't extend {@link KeyboardView}.
+ */
+public final class EmojiKeyboardView extends LinearLayout implements OnTabChangeListener,
+        ViewPager.OnPageChangeListener, View.OnClickListener,
+        ScrollKeyboardView.OnKeyClickListener {
+    private static final String TAG = EmojiKeyboardView.class.getSimpleName();
+    private final int mKeyBackgroundId;
+    private final int mEmojiFunctionalKeyBackgroundId;
+    private final KeyboardLayoutSet mLayoutSet;
+    private final ColorStateList mTabLabelColor;
+    private final DeleteKeyOnTouchListener mDeleteKeyOnTouchListener;
+    private EmojiKeyboardAdapter mEmojiKeyboardAdapter;
+
+    private TabHost mTabHost;
+    private ViewPager mEmojiPager;
+    private EmojiCategoryPageIndicatorView mEmojiCategoryPageIndicatorView;
+
+    private KeyboardActionListener mKeyboardActionListener = KeyboardActionListener.EMPTY_LISTENER;
+
+    private static final int CATEGORY_ID_UNSPECIFIED = -1;
+    public static final int CATEGORY_ID_RECENTS = 0;
+    public static final int CATEGORY_ID_PEOPLE = 1;
+    public static final int CATEGORY_ID_OBJECTS = 2;
+    public static final int CATEGORY_ID_NATURE = 3;
+    public static final int CATEGORY_ID_PLACES = 4;
+    public static final int CATEGORY_ID_SYMBOLS = 5;
+    public static final int CATEGORY_ID_EMOTICONS = 6;
+
+    private static class CategoryProperties {
+        public int mCategoryId;
+        public int mPageCount;
+        public CategoryProperties(final int categoryId, final int pageCount) {
+            mCategoryId = categoryId;
+            mPageCount = pageCount;
+        }
+    }
+
+    private static class EmojiCategory {
+        private static final String[] sCategoryName = {
+                "recents",
+                "people",
+                "objects",
+                "nature",
+                "places",
+                "symbols",
+                "emoticons" };
+        private static final int[] sCategoryIcon = new int[] {
+                R.drawable.ic_emoji_recent_light,
+                R.drawable.ic_emoji_people_light,
+                R.drawable.ic_emoji_objects_light,
+                R.drawable.ic_emoji_nature_light,
+                R.drawable.ic_emoji_places_light,
+                R.drawable.ic_emoji_symbols_light,
+                0 };
+        private static final String[] sCategoryLabel =
+                { null, null, null, null, null, null, ":-)" };
+        private static final int[] sCategoryElementId = {
+                KeyboardId.ELEMENT_EMOJI_RECENTS,
+                KeyboardId.ELEMENT_EMOJI_CATEGORY1,
+                KeyboardId.ELEMENT_EMOJI_CATEGORY2,
+                KeyboardId.ELEMENT_EMOJI_CATEGORY3,
+                KeyboardId.ELEMENT_EMOJI_CATEGORY4,
+                KeyboardId.ELEMENT_EMOJI_CATEGORY5,
+                KeyboardId.ELEMENT_EMOJI_CATEGORY6 };
+        private final SharedPreferences mPrefs;
+        private final int mMaxPageKeyCount;
+        private final KeyboardLayoutSet mLayoutSet;
+        private final HashMap<String, Integer> mCategoryNameToIdMap = CollectionUtils.newHashMap();
+        private final ArrayList<CategoryProperties> mShownCategories =
+                CollectionUtils.newArrayList();
+        private final ConcurrentHashMap<Long, DynamicGridKeyboard>
+                mCategoryKeyboardMap = new ConcurrentHashMap<Long, DynamicGridKeyboard>();
+
+        private int mCurrentCategoryId = CATEGORY_ID_UNSPECIFIED;
+        private int mCurrentCategoryPageId = 0;
+
+        public EmojiCategory(final SharedPreferences prefs, final Resources res,
+                final KeyboardLayoutSet layoutSet) {
+            mPrefs = prefs;
+            mMaxPageKeyCount = res.getInteger(R.integer.emoji_keyboard_max_key_count);
+            mLayoutSet = layoutSet;
+            for (int i = 0; i < sCategoryName.length; ++i) {
+                mCategoryNameToIdMap.put(sCategoryName[i], i);
+            }
+            addShownCategoryId(CATEGORY_ID_RECENTS);
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
+                addShownCategoryId(CATEGORY_ID_PEOPLE);
+                addShownCategoryId(CATEGORY_ID_OBJECTS);
+                addShownCategoryId(CATEGORY_ID_NATURE);
+                addShownCategoryId(CATEGORY_ID_PLACES);
+                mCurrentCategoryId = CATEGORY_ID_PEOPLE;
+            } else {
+                mCurrentCategoryId = CATEGORY_ID_SYMBOLS;
+            }
+            addShownCategoryId(CATEGORY_ID_SYMBOLS);
+            addShownCategoryId(CATEGORY_ID_EMOTICONS);
+            getKeyboard(CATEGORY_ID_RECENTS, 0 /* cagetoryPageId */)
+                    .loadRecentKeys(mCategoryKeyboardMap.values());
+        }
+
+        private void addShownCategoryId(int categoryId) {
+            // Load a keyboard of categoryId
+            getKeyboard(categoryId, 0 /* cagetoryPageId */);
+            final CategoryProperties properties =
+                    new CategoryProperties(categoryId, getCategoryPageCount(categoryId));
+            mShownCategories.add(properties);
+        }
+
+        public String getCategoryName(int categoryId, int categoryPageId) {
+            return sCategoryName[categoryId] + "-" + categoryPageId;
+        }
+
+        public int getCategoryId(String name) {
+            final String[] strings = name.split("-");
+            return mCategoryNameToIdMap.get(strings[0]);
+        }
+
+        public int getCategoryIcon(int categoryId) {
+            return sCategoryIcon[categoryId];
+        }
+
+        public String getCategoryLabel(int categoryId) {
+            return sCategoryLabel[categoryId];
+        }
+
+        public ArrayList<CategoryProperties> getShownCategories() {
+            return mShownCategories;
+        }
+
+        public int getCurrentCategoryId() {
+            return mCurrentCategoryId;
+        }
+
+        public int getCurrentCategoryPageSize() {
+            return getCategoryPageSize(mCurrentCategoryId);
+        }
+
+        public int getCategoryPageSize(int categoryId) {
+            for (final CategoryProperties prop : mShownCategories) {
+                if (prop.mCategoryId == categoryId) {
+                    return prop.mPageCount;
+                }
+            }
+            Log.w(TAG, "Invalid category id: " + categoryId);
+            // Should not reach here.
+            return 0;
+        }
+
+        public void setCurrentCategoryId(int categoryId) {
+            mCurrentCategoryId = categoryId;
+        }
+
+        public void setCurrentCategoryPageId(int id) {
+            mCurrentCategoryPageId = id;
+        }
+
+        public int getCurrentCategoryPageId() {
+            return mCurrentCategoryPageId;
+        }
+
+        public void saveLastTypedCategoryPage() {
+            Settings.writeEmojiCategoryLastTypedId(
+                    mPrefs, mCurrentCategoryId, mCurrentCategoryPageId);
+        }
+
+        public boolean isInRecentTab() {
+            return mCurrentCategoryId == CATEGORY_ID_RECENTS;
+        }
+
+        public int getTabIdFromCategoryId(int categoryId) {
+            for (int i = 0; i < mShownCategories.size(); ++i) {
+                if (mShownCategories.get(i).mCategoryId == categoryId) {
+                    return i;
+                }
+            }
+            Log.w(TAG, "categoryId not found: " + categoryId);
+            return 0;
+        }
+
+        // Returns the view pager's page position for the categoryId
+        public int getPageIdFromCategoryId(int categoryId) {
+            final int lastSavedCategoryPageId =
+                    Settings.readEmojiCategoryLastTypedId(mPrefs, categoryId);
+            int sum = 0;
+            for (int i = 0; i < mShownCategories.size(); ++i) {
+                final CategoryProperties props = mShownCategories.get(i);
+                if (props.mCategoryId == categoryId) {
+                    return sum + lastSavedCategoryPageId;
+                }
+                sum += props.mPageCount;
+            }
+            Log.w(TAG, "categoryId not found: " + categoryId);
+            return 0;
+        }
+
+        public int getRecentTabId() {
+            return getTabIdFromCategoryId(CATEGORY_ID_RECENTS);
+        }
+
+        private int getCategoryPageCount(int categoryId) {
+            final Keyboard keyboard = mLayoutSet.getKeyboard(sCategoryElementId[categoryId]);
+            return (keyboard.getKeys().length - 1) / mMaxPageKeyCount + 1;
+        }
+
+        // Returns a pair of the category id and the category page id from the view pager's page
+        // position. The category page id is numbered in each category. And the view page position
+        // is the position of the current shown page in the view pager which contains all pages of
+        // all categories.
+        public Pair<Integer, Integer> getCategoryIdAndPageIdFromPagePosition(int position) {
+            int sum = 0;
+            for (CategoryProperties properties : mShownCategories) {
+                final int temp = sum;
+                sum += properties.mPageCount;
+                if (sum > position) {
+                    return new Pair<Integer, Integer>(properties.mCategoryId, position - temp);
+                }
+            }
+            return null;
+        }
+
+        // Returns a keyboard from the view pager's page position.
+        public DynamicGridKeyboard getKeyboardFromPagePosition(int position) {
+            final Pair<Integer, Integer> categoryAndId =
+                    getCategoryIdAndPageIdFromPagePosition(position);
+            if (categoryAndId != null) {
+                return getKeyboard(categoryAndId.first, categoryAndId.second);
+            }
+            return null;
+        }
+
+        public DynamicGridKeyboard getKeyboard(int categoryId, int id) {
+            synchronized(mCategoryKeyboardMap) {
+                final long key = (((long) categoryId) << Constants.MAX_INT_BIT_COUNT) | id;
+                final DynamicGridKeyboard kbd;
+                if (!mCategoryKeyboardMap.containsKey(key)) {
+                    if (categoryId != CATEGORY_ID_RECENTS) {
+                        final Keyboard keyboard =
+                                mLayoutSet.getKeyboard(sCategoryElementId[categoryId]);
+                        final Key[][] sortedKeys = sortKeys(keyboard.getKeys(), mMaxPageKeyCount);
+                        for (int i = 0; i < sortedKeys.length; ++i) {
+                            final DynamicGridKeyboard tempKbd = new DynamicGridKeyboard(mPrefs,
+                                    mLayoutSet.getKeyboard(KeyboardId.ELEMENT_EMOJI_RECENTS),
+                                    mMaxPageKeyCount, categoryId, i /* categoryPageId */);
+                            for (Key emojiKey : sortedKeys[i]) {
+                                if (emojiKey == null) {
+                                    break;
+                                }
+                                tempKbd.addKeyLast(emojiKey);
+                            }
+                            mCategoryKeyboardMap.put((((long) categoryId)
+                                    << Constants.MAX_INT_BIT_COUNT) | i, tempKbd);
+                        }
+                        kbd = mCategoryKeyboardMap.get(key);
+                    } else {
+                        kbd = new DynamicGridKeyboard(mPrefs,
+                                mLayoutSet.getKeyboard(KeyboardId.ELEMENT_EMOJI_RECENTS),
+                                mMaxPageKeyCount, categoryId, 0 /* categoryPageId */);
+                        mCategoryKeyboardMap.put(key, kbd);
+                    }
+                } else {
+                    kbd = mCategoryKeyboardMap.get(key);
+                }
+                return kbd;
+            }
+        }
+
+        public int getTotalPageCountOfAllCategories() {
+            int sum = 0;
+            for (CategoryProperties properties : mShownCategories) {
+                sum += properties.mPageCount;
+            }
+            return sum;
+        }
+
+        private Key[][] sortKeys(Key[] inKeys, int maxPageCount) {
+            Key[] keys = Arrays.copyOf(inKeys, inKeys.length);
+            Arrays.sort(keys, 0, keys.length, new Comparator<Key>() {
+                @Override
+                public int compare(Key lhs, Key rhs) {
+                    final Rect lHitBox = lhs.getHitBox();
+                    final Rect rHitBox = rhs.getHitBox();
+                    if (lHitBox.top < rHitBox.top) {
+                        return -1;
+                    } else if (lHitBox.top > rHitBox.top) {
+                        return 1;
+                    }
+                    if (lHitBox.left < rHitBox.left) {
+                        return -1;
+                    } else if (lHitBox.left > rHitBox.left) {
+                        return 1;
+                    }
+                    if (lhs.getCode() == rhs.getCode()) {
+                        return 0;
+                    }
+                    return lhs.getCode() < rhs.getCode() ? -1 : 1;
+                }
+            });
+            final int pageCount = (keys.length - 1) / maxPageCount + 1;
+            final Key[][] retval = new Key[pageCount][maxPageCount];
+            for (int i = 0; i < keys.length; ++i) {
+                retval[i / maxPageCount][i % maxPageCount] = keys[i];
+            }
+            return retval;
+        }
+    }
+
+    private final EmojiCategory mEmojiCategory;
+
+    public EmojiKeyboardView(final Context context, final AttributeSet attrs) {
+        this(context, attrs, R.attr.emojiKeyboardViewStyle);
+    }
+
+    public EmojiKeyboardView(final Context context, final AttributeSet attrs, final int defStyle) {
+        super(context, attrs, defStyle);
+        final TypedArray keyboardViewAttr = context.obtainStyledAttributes(attrs,
+                R.styleable.KeyboardView, defStyle, R.style.KeyboardView);
+        mKeyBackgroundId = keyboardViewAttr.getResourceId(
+                R.styleable.KeyboardView_keyBackground, 0);
+        mEmojiFunctionalKeyBackgroundId = keyboardViewAttr.getResourceId(
+                R.styleable.KeyboardView_keyBackgroundEmojiFunctional, 0);
+        keyboardViewAttr.recycle();
+        final TypedArray emojiKeyboardViewAttr = context.obtainStyledAttributes(attrs,
+                R.styleable.EmojiKeyboardView, defStyle, R.style.EmojiKeyboardView);
+        mTabLabelColor = emojiKeyboardViewAttr.getColorStateList(
+                R.styleable.EmojiKeyboardView_emojiTabLabelColor);
+        emojiKeyboardViewAttr.recycle();
+        final KeyboardLayoutSet.Builder builder = new KeyboardLayoutSet.Builder(
+                context, null /* editorInfo */);
+        final Resources res = context.getResources();
+        final EmojiLayoutParams emojiLp = new EmojiLayoutParams(res);
+        builder.setSubtype(SubtypeSwitcher.getInstance().getEmojiSubtype());
+        builder.setKeyboardGeometry(ResourceUtils.getDefaultKeyboardWidth(res),
+                emojiLp.mEmojiKeyboardHeight);
+        builder.setOptions(false, false, false /* lanuageSwitchKeyEnabled */);
+        mLayoutSet = builder.build();
+        mEmojiCategory = new EmojiCategory(PreferenceManager.getDefaultSharedPreferences(context),
+                context.getResources(), builder.build());
+        mDeleteKeyOnTouchListener = new DeleteKeyOnTouchListener(context);
+    }
+
+    @Override
+    protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        final Resources res = getContext().getResources();
+        // The main keyboard expands to the entire this {@link KeyboardView}.
+        final int width = ResourceUtils.getDefaultKeyboardWidth(res)
+                + getPaddingLeft() + getPaddingRight();
+        final int height = ResourceUtils.getDefaultKeyboardHeight(res)
+                + res.getDimensionPixelSize(R.dimen.suggestions_strip_height)
+                + getPaddingTop() + getPaddingBottom();
+        setMeasuredDimension(width, height);
+    }
+
+    private void addTab(final TabHost host, final int categoryId) {
+        final String tabId = mEmojiCategory.getCategoryName(categoryId, 0 /* categoryPageId */);
+        final TabHost.TabSpec tspec = host.newTabSpec(tabId);
+        tspec.setContent(R.id.emoji_keyboard_dummy);
+        if (mEmojiCategory.getCategoryIcon(categoryId) != 0) {
+            final ImageView iconView = (ImageView)LayoutInflater.from(getContext()).inflate(
+                    R.layout.emoji_keyboard_tab_icon, null);
+            iconView.setImageResource(mEmojiCategory.getCategoryIcon(categoryId));
+            tspec.setIndicator(iconView);
+        }
+        if (mEmojiCategory.getCategoryLabel(categoryId) != null) {
+            final TextView textView = (TextView)LayoutInflater.from(getContext()).inflate(
+                    R.layout.emoji_keyboard_tab_label, null);
+            textView.setText(mEmojiCategory.getCategoryLabel(categoryId));
+            textView.setTextColor(mTabLabelColor);
+            tspec.setIndicator(textView);
+        }
+        host.addTab(tspec);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        mTabHost = (TabHost)findViewById(R.id.emoji_category_tabhost);
+        mTabHost.setup();
+        for (final CategoryProperties properties : mEmojiCategory.getShownCategories()) {
+            addTab(mTabHost, properties.mCategoryId);
+        }
+        mTabHost.setOnTabChangedListener(this);
+        mTabHost.getTabWidget().setStripEnabled(true);
+
+        mEmojiKeyboardAdapter = new EmojiKeyboardAdapter(mEmojiCategory, mLayoutSet, this);
+
+        mEmojiPager = (ViewPager)findViewById(R.id.emoji_keyboard_pager);
+        mEmojiPager.setAdapter(mEmojiKeyboardAdapter);
+        mEmojiPager.setOnPageChangeListener(this);
+        mEmojiPager.setOffscreenPageLimit(0);
+        final Resources res = getResources();
+        final EmojiLayoutParams emojiLp = new EmojiLayoutParams(res);
+        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.setActionBarProperties(actionBar);
+
+        final ImageView deleteKey = (ImageView)findViewById(R.id.emoji_keyboard_delete);
+        deleteKey.setTag(Constants.CODE_DELETE);
+        deleteKey.setOnTouchListener(mDeleteKeyOnTouchListener);
+        final ImageView alphabetKey = (ImageView)findViewById(R.id.emoji_keyboard_alphabet);
+        alphabetKey.setBackgroundResource(mEmojiFunctionalKeyBackgroundId);
+        alphabetKey.setTag(Constants.CODE_SWITCH_ALPHA_SYMBOL);
+        alphabetKey.setOnClickListener(this);
+        final ImageView spaceKey = (ImageView)findViewById(R.id.emoji_keyboard_space);
+        spaceKey.setBackgroundResource(mKeyBackgroundId);
+        spaceKey.setTag(Constants.CODE_SPACE);
+        spaceKey.setOnClickListener(this);
+        emojiLp.setKeyProperties(spaceKey);
+        final ImageView sendKey = (ImageView)findViewById(R.id.emoji_keyboard_send);
+        sendKey.setBackgroundResource(mEmojiFunctionalKeyBackgroundId);
+        sendKey.setTag(Constants.CODE_ENTER);
+        sendKey.setOnClickListener(this);
+    }
+
+    @Override
+    public void onTabChanged(final String tabId) {
+        final int categoryId = mEmojiCategory.getCategoryId(tabId);
+        setCurrentCategoryId(categoryId, false /* force */);
+        updateEmojiCategoryPageIdView();
+    }
+
+
+    @Override
+    public void onPageSelected(final int position) {
+        final Pair<Integer, Integer> newPos =
+                mEmojiCategory.getCategoryIdAndPageIdFromPagePosition(position);
+        setCurrentCategoryId(newPos.first /* categoryId */, false /* force */);
+        mEmojiCategory.setCurrentCategoryPageId(newPos.second /* categoryPageId */);
+        updateEmojiCategoryPageIdView();
+    }
+
+    @Override
+    public void onPageScrollStateChanged(final int state) {
+        // Ignore this message. Only want the actual page selected.
+    }
+
+    @Override
+    public void onPageScrolled(final int position, final float positionOffset,
+            final int positionOffsetPixels) {
+        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
+    public void onClick(final View v) {
+        if (v.getTag() instanceof Integer) {
+            final int code = (Integer)v.getTag();
+            registerCode(code);
+            return;
+        }
+    }
+
+    private void registerCode(final int code) {
+        mKeyboardActionListener.onPressKey(code, 0 /* repeatCount */, true /* isSinglePointer */);
+        mKeyboardActionListener.onCodeInput(code, NOT_A_COORDINATE, NOT_A_COORDINATE);
+        mKeyboardActionListener.onReleaseKey(code, false /* withSliding */);
+    }
+
+    @Override
+    public void onKeyClick(final Key key) {
+        mEmojiKeyboardAdapter.addRecentKey(key);
+        mEmojiCategory.saveLastTypedCategoryPage();
+        final int code = key.getCode();
+        if (code == Constants.CODE_OUTPUT_TEXT) {
+            mKeyboardActionListener.onTextInput(key.getOutputText());
+            return;
+        }
+        registerCode(code);
+    }
+
+    public void setHardwareAcceleratedDrawingEnabled(final boolean enabled) {
+        // TODO:
+    }
+
+    public void setKeyboardActionListener(final KeyboardActionListener listener) {
+        mKeyboardActionListener = listener;
+        mDeleteKeyOnTouchListener.setKeyboardActionListener(mKeyboardActionListener);
+    }
+
+    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;
+        }
+
+        mEmojiCategory.setCurrentCategoryId(categoryId);
+        final int newTabId = mEmojiCategory.getTabIdFromCategoryId(categoryId);
+        final int newCategoryPageId = mEmojiCategory.getPageIdFromCategoryId(categoryId);
+        if (force || mEmojiCategory.getCategoryIdAndPageIdFromPagePosition(
+                mEmojiPager.getCurrentItem()).first != categoryId) {
+            mEmojiPager.setCurrentItem(newCategoryPageId, false /* smoothScroll */);
+        }
+        if (force || mTabHost.getCurrentTab() != newTabId) {
+            mTabHost.setCurrentTab(newTabId);
+        }
+    }
+
+    private static class EmojiKeyboardAdapter extends PagerAdapter {
+        private final ScrollKeyboardView.OnKeyClickListener mListener;
+        private final DynamicGridKeyboard mRecentsKeyboard;
+        private final SparseArray<ScrollKeyboardView> mActiveKeyboardView =
+                CollectionUtils.newSparseArray();
+        private final EmojiCategory mEmojiCategory;
+        private int mActivePosition = 0;
+
+        public EmojiKeyboardAdapter(final EmojiCategory emojiCategory,
+                final KeyboardLayoutSet layoutSet,
+                final ScrollKeyboardView.OnKeyClickListener listener) {
+            mEmojiCategory = emojiCategory;
+            mListener = listener;
+            mRecentsKeyboard = mEmojiCategory.getKeyboard(CATEGORY_ID_RECENTS, 0);
+        }
+
+        public void addRecentKey(final Key key) {
+            if (mEmojiCategory.isInRecentTab()) {
+                return;
+            }
+            mRecentsKeyboard.addKeyFirst(key);
+            final KeyboardView recentKeyboardView =
+                    mActiveKeyboardView.get(mEmojiCategory.getRecentTabId());
+            if (recentKeyboardView != null) {
+                recentKeyboardView.invalidateAllKeys();
+            }
+        }
+
+        @Override
+        public int getCount() {
+            return mEmojiCategory.getTotalPageCountOfAllCategories();
+        }
+
+        @Override
+        public void setPrimaryItem(final View container, final int position, final Object object) {
+            if (mActivePosition == position) {
+                return;
+            }
+            final ScrollKeyboardView oldKeyboardView = mActiveKeyboardView.get(mActivePosition);
+            if (oldKeyboardView != null) {
+                oldKeyboardView.releaseCurrentKey();
+                oldKeyboardView.deallocateMemory();
+            }
+            mActivePosition = position;
+        }
+
+        @Override
+        public Object instantiateItem(final ViewGroup container, final int position) {
+            final Keyboard keyboard =
+                    mEmojiCategory.getKeyboardFromPagePosition(position);
+            final LayoutInflater inflater = LayoutInflater.from(container.getContext());
+            final View view = inflater.inflate(
+                    R.layout.emoji_keyboard_page, container, false /* attachToRoot */);
+            final ScrollKeyboardView keyboardView = (ScrollKeyboardView)view.findViewById(
+                    R.id.emoji_keyboard_page);
+            keyboardView.setKeyboard(keyboard);
+            keyboardView.setOnKeyClickListener(mListener);
+            final ScrollViewWithNotifier scrollView = (ScrollViewWithNotifier)view.findViewById(
+                    R.id.emoji_keyboard_scroller);
+            keyboardView.setScrollView(scrollView);
+            container.addView(view);
+            mActiveKeyboardView.put(position, keyboardView);
+            return view;
+        }
+
+        @Override
+        public boolean isViewFromObject(final View view, final Object object) {
+            return view == object;
+        }
+
+        @Override
+        public void destroyItem(final ViewGroup container, final int position,
+                final Object object) {
+            final ScrollKeyboardView keyboardView = mActiveKeyboardView.get(position);
+            if (keyboardView != null) {
+                keyboardView.deallocateMemory();
+                mActiveKeyboardView.remove(position);
+            }
+            container.removeView(keyboardView);
+        }
+    }
+
+    // TODO: Do the same things done in PointerTracker
+    private static class DeleteKeyOnTouchListener implements OnTouchListener {
+        private static final long MAX_REPEAT_COUNT_TIME = 30 * DateUtils.SECOND_IN_MILLIS;
+        private final int mDeleteKeyPressedBackgroundColor;
+        private final long mKeyRepeatStartTimeout;
+        private final long mKeyRepeatInterval;
+
+        public DeleteKeyOnTouchListener(Context context) {
+            final Resources res = context.getResources();
+            mDeleteKeyPressedBackgroundColor =
+                    res.getColor(R.color.emoji_key_pressed_background_color);
+            mKeyRepeatStartTimeout = res.getInteger(R.integer.config_key_repeat_start_timeout);
+            mKeyRepeatInterval = res.getInteger(R.integer.config_key_repeat_interval);
+        }
+
+        private KeyboardActionListener mKeyboardActionListener =
+                KeyboardActionListener.EMPTY_LISTENER;
+        private DummyRepeatKeyRepeatTimer mTimer;
+
+        private synchronized void startRepeat() {
+            if (mTimer != null) {
+                abortRepeat();
+            }
+            mTimer = new DummyRepeatKeyRepeatTimer();
+            mTimer.start();
+        }
+
+        private synchronized void abortRepeat() {
+            mTimer.abort();
+            mTimer = null;
+        }
+
+        // TODO: Remove
+        // This function is mimicking the repeat code in PointerTracker.
+        // Specifically referring to PointerTracker#startRepeatKey and PointerTracker#onKeyRepeat.
+        private class DummyRepeatKeyRepeatTimer extends Thread {
+            public boolean mAborted = false;
+
+            @Override
+            public void run() {
+                int timeCount = 0;
+                while (timeCount < MAX_REPEAT_COUNT_TIME && !mAborted) {
+                    if (timeCount > mKeyRepeatStartTimeout) {
+                        pressDelete();
+                    }
+                    timeCount += mKeyRepeatInterval;
+                    try {
+                        Thread.sleep(mKeyRepeatInterval);
+                    } catch (InterruptedException e) {
+                    }
+                }
+            }
+
+            public void abort() {
+                mAborted = true;
+            }
+        }
+
+        public void pressDelete() {
+            mKeyboardActionListener.onPressKey(
+                    Constants.CODE_DELETE, 0 /* repeatCount */, true /* isSinglePointer */);
+            mKeyboardActionListener.onCodeInput(
+                    Constants.CODE_DELETE, NOT_A_COORDINATE, NOT_A_COORDINATE);
+            mKeyboardActionListener.onReleaseKey(
+                    Constants.CODE_DELETE, false /* withSliding */);
+        }
+
+        public void setKeyboardActionListener(KeyboardActionListener listener) {
+            mKeyboardActionListener = listener;
+        }
+
+        @Override
+        public boolean onTouch(View v, MotionEvent event) {
+            switch(event.getAction()) {
+                case MotionEvent.ACTION_DOWN:
+                    v.setBackgroundColor(mDeleteKeyPressedBackgroundColor);
+                    pressDelete();
+                    startRepeat();
+                    return true;
+                case MotionEvent.ACTION_UP:
+                    v.setBackgroundColor(0);
+                    abortRepeat();
+                    return true;
+            }
+            return false;
+        }
+    }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/EmojiLayoutParams.java b/java/src/com/android/inputmethod/keyboard/EmojiLayoutParams.java
new file mode 100644
index 0000000..267fad5
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/EmojiLayoutParams.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.keyboard;
+
+import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.utils.ResourceUtils;
+
+import android.content.res.Resources;
+import android.support.v4.view.ViewPager;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+
+public class EmojiLayoutParams {
+    private static final int DEFAULT_KEYBOARD_ROWS = 4;
+
+    public final int 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;
+    private final int mBottomPadding;
+    private final int mTopPadding;
+
+    public EmojiLayoutParams(Resources res) {
+        final int defaultKeyboardHeight = ResourceUtils.getDefaultKeyboardHeight(res);
+        final int defaultKeyboardWidth = ResourceUtils.getDefaultKeyboardWidth(res);
+        mKeyVerticalGap = (int) res.getFraction(R.fraction.key_bottom_gap_ics,
+                (int) defaultKeyboardHeight, (int) defaultKeyboardHeight);
+        mBottomPadding = (int) res.getFraction(R.fraction.keyboard_bottom_padding_ics,
+                (int) defaultKeyboardHeight, (int) defaultKeyboardHeight);
+        mTopPadding = (int) res.getFraction(R.fraction.keyboard_top_padding_ics,
+                (int) defaultKeyboardHeight, (int) defaultKeyboardHeight);
+        mKeyHorizontalGap = (int) (res.getFraction(R.fraction.key_horizontal_gap_ics,
+                defaultKeyboardWidth, defaultKeyboardWidth));
+        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
+                - mEmojiCategoryPageIdViewHeight;
+        mEmojiPagerBottomMargin = mKeyVerticalGap / 2;
+        mEmojiKeyboardHeight = mEmojiPagerHeight - mEmojiPagerBottomMargin - 1;
+    }
+
+    public void setPagerProperties(ViewPager vp) {
+        final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) vp.getLayoutParams();
+        lp.height = mEmojiKeyboardHeight;
+        lp.bottomMargin = mEmojiPagerBottomMargin;
+        vp.setLayoutParams(lp);
+    }
+
+    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;
+        lp.bottomMargin = mBottomPadding;
+        ll.setLayoutParams(lp);
+    }
+
+    public void setKeyProperties(ImageView ib) {
+        final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) ib.getLayoutParams();
+        lp.leftMargin = mKeyHorizontalGap / 2;
+        lp.rightMargin = mKeyHorizontalGap / 2;
+        ib.setLayoutParams(lp);
+    }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/Key.java b/java/src/com/android/inputmethod/keyboard/Key.java
index 6180528..3ea6880 100644
--- a/java/src/com/android/inputmethod/keyboard/Key.java
+++ b/java/src/com/android/inputmethod/keyboard/Key.java
@@ -58,12 +58,12 @@
     /**
      * The key code (unicode or custom code) that this key generates.
      */
-    public final int mCode;
+    private final int mCode;
 
     /** Label to display */
-    public final String mLabel;
+    private final String mLabel;
     /** Hint label to display on the key in conjunction with the label */
-    public final String mHintLabel;
+    private final String mHintLabel;
     /** Flags of the label */
     private final int mLabelFlags;
     private static final int LABEL_FLAGS_ALIGN_LEFT = 0x01;
@@ -95,18 +95,18 @@
     private final int mIconId;
 
     /** Width of the key, not including the gap */
-    public final int mWidth;
+    private final int mWidth;
     /** Height of the key, not including the gap */
-    public final int mHeight;
+    private final int mHeight;
     /** X coordinate of the key in the keyboard layout */
-    public final int mX;
+    private final int mX;
     /** Y coordinate of the key in the keyboard layout */
-    public final int mY;
+    private final int mY;
     /** Hit bounding box of the key */
-    public final Rect mHitBox = new Rect();
+    private final Rect mHitBox = new Rect();
 
     /** More keys. It is guaranteed that this is null or an array of one or more elements */
-    public final MoreKeySpec[] mMoreKeys;
+    private final MoreKeySpec[] mMoreKeys;
     /** More keys column number and flags */
     private final int mMoreKeysColumnAndFlags;
     private static final int MORE_KEYS_COLUMN_MASK = 0x000000ff;
@@ -121,12 +121,13 @@
     private static final String MORE_KEYS_NO_PANEL_AUTO_MORE_KEY = "!noPanelAutoMoreKey!";
 
     /** Background type that represents different key background visual than normal one. */
-    public final int mBackgroundType;
-    public static final int BACKGROUND_TYPE_NORMAL = 0;
-    public static final int BACKGROUND_TYPE_FUNCTIONAL = 1;
-    public static final int BACKGROUND_TYPE_ACTION = 2;
-    public static final int BACKGROUND_TYPE_STICKY_OFF = 3;
-    public static final int BACKGROUND_TYPE_STICKY_ON = 4;
+    private final int mBackgroundType;
+    public static final int BACKGROUND_TYPE_EMPTY = 0;
+    public static final int BACKGROUND_TYPE_NORMAL = 1;
+    public static final int BACKGROUND_TYPE_FUNCTIONAL = 2;
+    public static final int BACKGROUND_TYPE_ACTION = 3;
+    public static final int BACKGROUND_TYPE_STICKY_OFF = 4;
+    public static final int BACKGROUND_TYPE_STICKY_ON = 5;
 
     private final int mActionFlags;
     private static final int ACTION_FLAGS_IS_REPEATABLE = 0x01;
@@ -134,7 +135,7 @@
     private static final int ACTION_FLAGS_ALT_CODE_WHILE_TYPING = 0x04;
     private static final int ACTION_FLAGS_ENABLE_LONG_PRESS = 0x08;
 
-    public final KeyVisualAttributes mKeyVisualAttributes;
+    private final KeyVisualAttributes mKeyVisualAttributes;
 
     private final OptionalAttributes mOptionalAttributes;
 
@@ -150,7 +151,7 @@
         public final int mVisualInsetsLeft;
         public final int mVisualInsetsRight;
 
-        public OptionalAttributes(final String outputText, final int altCode,
+        private OptionalAttributes(final String outputText, final int altCode,
                 final int disabledIconId, final int previewIconId,
                 final int visualInsetsLeft, final int visualInsetsRight) {
             mOutputText = outputText;
@@ -160,6 +161,18 @@
             mVisualInsetsLeft = visualInsetsLeft;
             mVisualInsetsRight = visualInsetsRight;
         }
+
+        public static OptionalAttributes newInstance(final String outputText, final int altCode,
+                final int disabledIconId, final int previewIconId,
+                final int visualInsetsLeft, final int visualInsetsRight) {
+            if (outputText == null && altCode == CODE_UNSPECIFIED
+                    && disabledIconId == ICON_UNDEFINED && previewIconId == ICON_UNDEFINED
+                    && visualInsetsLeft == 0 && visualInsetsRight == 0) {
+                return null;
+            }
+            return new OptionalAttributes(outputText, altCode, disabledIconId, previewIconId,
+                    visualInsetsLeft, visualInsetsRight);
+        }
     }
 
     private final int mHashCode;
@@ -175,7 +188,7 @@
     public Key(final KeyboardParams params, final MoreKeySpec moreKeySpec, final int x, final int y,
             final int width, final int height, final int labelFlags) {
         this(params, moreKeySpec.mLabel, null, moreKeySpec.mIconId, moreKeySpec.mCode,
-                moreKeySpec.mOutputText, x, y, width, height, labelFlags);
+                moreKeySpec.mOutputText, x, y, width, height, labelFlags, BACKGROUND_TYPE_NORMAL);
     }
 
     /**
@@ -183,22 +196,19 @@
      */
     public Key(final KeyboardParams params, final String label, final String hintLabel,
             final int iconId, final int code, final String outputText, final int x, final int y,
-            final int width, final int height, final int labelFlags) {
+            final int width, final int height, final int labelFlags, final int backgroundType) {
         mHeight = height - params.mVerticalGap;
         mWidth = width - params.mHorizontalGap;
         mHintLabel = hintLabel;
         mLabelFlags = labelFlags;
-        mBackgroundType = BACKGROUND_TYPE_NORMAL;
+        mBackgroundType = backgroundType;
         mActionFlags = 0;
         mMoreKeys = null;
         mMoreKeysColumnAndFlags = 0;
         mLabel = label;
-        if (outputText == null) {
-            mOptionalAttributes = null;
-        } else {
-            mOptionalAttributes = new OptionalAttributes(outputText, CODE_UNSPECIFIED,
-                    ICON_UNDEFINED, ICON_UNDEFINED, 0, 0);
-        }
+        mOptionalAttributes = OptionalAttributes.newInstance(outputText, CODE_UNSPECIFIED,
+                ICON_UNDEFINED, ICON_UNDEFINED,
+                0 /* visualInsetsLeft */, 0 /* visualInsetsRight */);
         mCode = code;
         mEnabled = (code != CODE_UNSPECIFIED);
         mIconId = iconId;
@@ -224,7 +234,7 @@
     public Key(final Resources res, final KeyboardParams params, final KeyboardRow row,
             final XmlPullParser parser) throws XmlPullParserException {
         final float horizontalGap = isSpacer() ? 0 : params.mHorizontalGap;
-        final int rowHeight = row.mRowHeight;
+        final int rowHeight = row.getRowHeight();
         mHeight = rowHeight - params.mVerticalGap;
 
         final TypedArray keyAttr = res.obtainAttributes(Xml.asAttributeSet(parser),
@@ -259,11 +269,11 @@
         final int previewIconId = KeySpecParser.getIconId(style.getString(keyAttr,
                 R.styleable.Keyboard_Key_keyIconPreview));
 
-        mLabelFlags = style.getFlag(keyAttr, R.styleable.Keyboard_Key_keyLabelFlags)
+        mLabelFlags = style.getFlags(keyAttr, R.styleable.Keyboard_Key_keyLabelFlags)
                 | row.getDefaultKeyLabelFlags();
         final boolean needsToUpperCase = needsToUpperCase(mLabelFlags, params.mId.mElementId);
         final Locale locale = params.mId.mLocale;
-        int actionFlags = style.getFlag(keyAttr, R.styleable.Keyboard_Key_keyActionFlags);
+        int actionFlags = style.getFlags(keyAttr, R.styleable.Keyboard_Key_keyActionFlags);
         String[] moreKeys = style.getStringArray(keyAttr, R.styleable.Keyboard_Key_moreKeys);
 
         int moreKeysColumn = style.getInt(keyAttr,
@@ -359,15 +369,8 @@
                 KeySpecParser.parseCode(style.getString(keyAttr,
                 R.styleable.Keyboard_Key_altCode), params.mCodesSet, CODE_UNSPECIFIED),
                 needsToUpperCase, locale);
-        if (outputText == null && altCode == CODE_UNSPECIFIED
-                && disabledIconId == ICON_UNDEFINED && previewIconId == ICON_UNDEFINED
-                && visualInsetsLeft == 0 && visualInsetsRight == 0) {
-            mOptionalAttributes = null;
-        } else {
-            mOptionalAttributes = new OptionalAttributes(outputText, altCode,
-                    disabledIconId, previewIconId,
-                    visualInsetsLeft, visualInsetsRight);
-        }
+        mOptionalAttributes = OptionalAttributes.newInstance(outputText, altCode,
+                disabledIconId, previewIconId, visualInsetsLeft, visualInsetsRight);
         mKeyVisualAttributes = KeyVisualAttributes.newInstance(keyAttr);
         keyAttr.recycle();
         mHashCode = computeHashCode(this);
@@ -376,6 +379,35 @@
         }
     }
 
+    /**
+     * Copy constructor.
+     *
+     * @param key the original key.
+     */
+    protected Key(final Key key) {
+        // Final attributes.
+        mCode = key.mCode;
+        mLabel = key.mLabel;
+        mHintLabel = key.mHintLabel;
+        mLabelFlags = key.mLabelFlags;
+        mIconId = key.mIconId;
+        mWidth = key.mWidth;
+        mHeight = key.mHeight;
+        mX = key.mX;
+        mY = key.mY;
+        mHitBox.set(key.mHitBox);
+        mMoreKeys = key.mMoreKeys;
+        mMoreKeysColumnAndFlags = key.mMoreKeysColumnAndFlags;
+        mBackgroundType = key.mBackgroundType;
+        mActionFlags = key.mActionFlags;
+        mKeyVisualAttributes = key.mKeyVisualAttributes;
+        mOptionalAttributes = key.mOptionalAttributes;
+        mHashCode = key.mHashCode;
+        // Key state.
+        mPressed = key.mPressed;
+        mEnabled = key.mEnabled;
+    }
+
     private static boolean needsToUpperCase(final int labelFlags, final int keyboardElementId) {
         if ((labelFlags & LABEL_FLAGS_PRESERVE_CASE) != 0) return false;
         switch (keyboardElementId) {
@@ -465,6 +497,7 @@
 
     private static String backgroundName(final int backgroundType) {
         switch (backgroundType) {
+        case BACKGROUND_TYPE_EMPTY: return "empty";
         case BACKGROUND_TYPE_NORMAL: return "normal";
         case BACKGROUND_TYPE_FUNCTIONAL: return "functional";
         case BACKGROUND_TYPE_ACTION: return "action";
@@ -474,6 +507,22 @@
         }
     }
 
+    public int getCode() {
+        return mCode;
+    }
+
+    public String getLabel() {
+        return mLabel;
+    }
+
+    public String getHintLabel() {
+        return mHintLabel;
+    }
+
+    public MoreKeySpec[] getMoreKeys() {
+        return mMoreKeys;
+    }
+
     public void markAsLeftEdge(final KeyboardParams params) {
         mHitBox.left = params.mLeftPadding;
     }
@@ -520,6 +569,10 @@
                 && (mLabelFlags & LABEL_FLAGS_SHIFTED_LETTER_ACTIVATED) == 0;
     }
 
+    public KeyVisualAttributes getVisualAttributes() {
+        return mKeyVisualAttributes;
+    }
+
     public final Typeface selectTypeface(final KeyDrawParams params) {
         // TODO: Handle "bold" here too?
         if ((mLabelFlags & LABEL_FLAGS_FONT_NORMAL) != 0) {
@@ -694,9 +747,26 @@
                 ? iconSet.getIconDrawable(previewIconId) : iconSet.getIconDrawable(mIconId);
     }
 
+    public int getWidth() {
+        return mWidth;
+    }
+
+    public int getHeight() {
+        return mHeight;
+    }
+
+    public int getX() {
+        return mX;
+    }
+
+    public int getY() {
+        return mY;
+    }
+
     public final int getDrawX() {
+        final int x = getX();
         final OptionalAttributes attrs = mOptionalAttributes;
-        return (attrs == null) ? mX : mX + attrs.mVisualInsetsLeft;
+        return (attrs == null) ? x : x + attrs.mVisualInsetsLeft;
     }
 
     public final int getDrawWidth() {
@@ -731,6 +801,10 @@
         mEnabled = enabled;
     }
 
+    public Rect getHitBox() {
+        return mHitBox;
+    }
+
     /**
      * Detects if a point falls on this key.
      * @param x the x-coordinate of the point
@@ -750,9 +824,9 @@
      * @return the square of the distance of the point from the nearest edge of the key
      */
     public int squaredDistanceToEdge(final int x, final int y) {
-        final int left = mX;
+        final int left = getX();
         final int right = left + mWidth;
-        final int top = mY;
+        final int top = getY();
         final int bottom = top + mHeight;
         final int edgeX = x < left ? left : (x > right ? right : x);
         final int edgeY = y < top ? top : (y > bottom ? bottom : y);
@@ -788,6 +862,10 @@
         android.R.attr.state_pressed
     };
 
+    private final static int[] KEY_STATE_EMPTY = {
+        android.R.attr.state_empty
+    };
+
     // functional normal state (with properties)
     private static final int[] KEY_STATE_FUNCTIONAL_NORMAL = {
             android.R.attr.state_single
@@ -825,6 +903,8 @@
             return mPressed ? KEY_STATE_PRESSED_HIGHLIGHT_OFF : KEY_STATE_NORMAL_HIGHLIGHT_OFF;
         case BACKGROUND_TYPE_STICKY_ON:
             return mPressed ? KEY_STATE_PRESSED_HIGHLIGHT_ON : KEY_STATE_NORMAL_HIGHLIGHT_ON;
+        case BACKGROUND_TYPE_EMPTY:
+            return mPressed ? KEY_STATE_PRESSED : KEY_STATE_EMPTY;
         default: /* BACKGROUND_TYPE_NORMAL */
             return mPressed ? KEY_STATE_PRESSED : KEY_STATE_NORMAL;
         }
@@ -842,7 +922,7 @@
         protected Spacer(final KeyboardParams params, final int x, final int y, final int width,
                 final int height) {
             super(params, null, null, ICON_UNDEFINED, CODE_UNSPECIFIED,
-                    null, x, y, width, height, 0);
+                    null, x, y, width, height, 0, BACKGROUND_TYPE_EMPTY);
         }
     }
 }
diff --git a/java/src/com/android/inputmethod/keyboard/KeyDetector.java b/java/src/com/android/inputmethod/keyboard/KeyDetector.java
index 17e707f..befb6fa 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyDetector.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyDetector.java
@@ -108,8 +108,9 @@
             if (distance > minDistance) {
                 continue;
             }
-            // To take care of hitbox overlaps, we compare mCode here too.
-            if (primaryKey == null || distance < minDistance || key.mCode > primaryKey.mCode) {
+            // To take care of hitbox overlaps, we compare key's code here too.
+            if (primaryKey == null || distance < minDistance
+                    || key.getCode() > primaryKey.getCode()) {
                 minDistance = distance;
                 primaryKey = key;
             }
@@ -118,7 +119,7 @@
     }
 
     public static String printableCode(Key key) {
-        return key != null ? Constants.printableCode(key.mCode) : "none";
+        return key != null ? Constants.printableCode(key.getCode()) : "none";
     }
 
     public static String printableCodes(int[] codes) {
diff --git a/java/src/com/android/inputmethod/keyboard/Keyboard.java b/java/src/com/android/inputmethod/keyboard/Keyboard.java
index fefac96..bc1383a 100644
--- a/java/src/com/android/inputmethod/keyboard/Keyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/Keyboard.java
@@ -51,6 +51,11 @@
     /** Total width of the keyboard, including the padding and keys */
     public final int mOccupiedWidth;
 
+    /** Base height of the keyboard, used to calculate rows' height */
+    public final int mBaseHeight;
+    /** Base width of the keyboard, used to calculate keys' width */
+    public final int mBaseWidth;
+
     /** The padding above the keyboard */
     public final int mTopPadding;
     /** Default gap between rows */
@@ -69,7 +74,7 @@
     public final int mMaxMoreKeysKeyboardColumn;
 
     /** Array of keys and icons in this keyboard */
-    public final Key[] mKeys;
+    private final Key[] mKeys;
     public final Key[] mShiftKeys;
     public final Key[] mAltCodeKeysWhileTyping;
     public final KeyboardIconsSet mIconsSet;
@@ -84,6 +89,8 @@
         mThemeId = params.mThemeId;
         mOccupiedHeight = params.mOccupiedHeight;
         mOccupiedWidth = params.mOccupiedWidth;
+        mBaseHeight = params.mBaseHeight;
+        mBaseWidth = params.mBaseWidth;
         mMostCommonKeyHeight = params.mMostCommonKeyHeight;
         mMostCommonKeyWidth = params.mMostCommonKeyWidth;
         mMoreKeysTemplate = params.mMoreKeysTemplate;
@@ -104,6 +111,30 @@
         mProximityCharsCorrectionEnabled = params.mProximityCharsCorrectionEnabled;
     }
 
+    protected Keyboard(final Keyboard keyboard) {
+        mId = keyboard.mId;
+        mThemeId = keyboard.mThemeId;
+        mOccupiedHeight = keyboard.mOccupiedHeight;
+        mOccupiedWidth = keyboard.mOccupiedWidth;
+        mBaseHeight = keyboard.mBaseHeight;
+        mBaseWidth = keyboard.mBaseWidth;
+        mMostCommonKeyHeight = keyboard.mMostCommonKeyHeight;
+        mMostCommonKeyWidth = keyboard.mMostCommonKeyWidth;
+        mMoreKeysTemplate = keyboard.mMoreKeysTemplate;
+        mMaxMoreKeysKeyboardColumn = keyboard.mMaxMoreKeysKeyboardColumn;
+        mKeyVisualAttributes = keyboard.mKeyVisualAttributes;
+        mTopPadding = keyboard.mTopPadding;
+        mVerticalGap = keyboard.mVerticalGap;
+
+        mKeys = keyboard.mKeys;
+        mShiftKeys = keyboard.mShiftKeys;
+        mAltCodeKeysWhileTyping = keyboard.mAltCodeKeysWhileTyping;
+        mIconsSet = keyboard.mIconsSet;
+
+        mProximityInfo = keyboard.mProximityInfo;
+        mProximityCharsCorrectionEnabled = keyboard.mProximityCharsCorrectionEnabled;
+    }
+
     public boolean hasProximityCharsCorrection(final int code) {
         if (!mProximityCharsCorrectionEnabled) {
             return false;
@@ -120,6 +151,19 @@
         return mProximityInfo;
     }
 
+    public Key[] getKeys() {
+        return mKeys;
+    }
+
+    public Key getKeyFromOutputText(final String outputText) {
+        for (final Key key : getKeys()) {
+            if (outputText.equals(key.getOutputText())) {
+                return key;
+            }
+        }
+        return null;
+    }
+
     public Key getKey(final int code) {
         if (code == Constants.CODE_UNSPECIFIED) {
             return null;
@@ -130,8 +174,8 @@
                 return mKeyCache.valueAt(index);
             }
 
-            for (final Key key : mKeys) {
-                if (key.mCode == code) {
+            for (final Key key : getKeys()) {
+                if (key.getCode() == code) {
                     mKeyCache.put(code, key);
                     return key;
                 }
@@ -146,9 +190,9 @@
             return true;
         }
 
-        for (final Key key : mKeys) {
+        for (final Key key : getKeys()) {
             if (key == aKey) {
-                mKeyCache.put(key.mCode, key);
+                mKeyCache.put(key.getCode(), key);
                 return true;
             }
         }
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java b/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java
index b266986..dc760e6 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java
@@ -26,10 +26,10 @@
      *
      * @param primaryCode the unicode of the key being pressed. If the touch is not on a valid key,
      *            the value will be zero.
-     * @param isRepeatKey true if pressing has occurred while key repeat input.
+     * @param repeatCount how many times the key was repeated. Zero if it is the first press.
      * @param isSinglePointer true if pressing has occurred while no other key is being pressed.
      */
-    public void onPressKey(int primaryCode, boolean isRepeatKey, boolean isSinglePointer);
+    public void onPressKey(int primaryCode, int repeatCount, boolean isSinglePointer);
 
     /**
      * Called when the user releases a key. This is sent after the {@link #onCodeInput} is called.
@@ -103,7 +103,7 @@
 
     public static class Adapter implements KeyboardActionListener {
         @Override
-        public void onPressKey(int primaryCode, boolean isRepeatKey, boolean isSinglePointer) {}
+        public void onPressKey(int primaryCode, int repeatCount, boolean isSinglePointer) {}
         @Override
         public void onReleaseKey(int primaryCode, boolean withSliding) {}
         @Override
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardId.java b/java/src/com/android/inputmethod/keyboard/KeyboardId.java
index 1dc3c6a..736f13e 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardId.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardId.java
@@ -54,6 +54,13 @@
     public static final int ELEMENT_PHONE = 7;
     public static final int ELEMENT_PHONE_SYMBOLS = 8;
     public static final int ELEMENT_NUMBER = 9;
+    public static final int ELEMENT_EMOJI_RECENTS = 10;
+    public static final int ELEMENT_EMOJI_CATEGORY1 = 11;
+    public static final int ELEMENT_EMOJI_CATEGORY2 = 12;
+    public static final int ELEMENT_EMOJI_CATEGORY3 = 13;
+    public static final int ELEMENT_EMOJI_CATEGORY4 = 14;
+    public static final int ELEMENT_EMOJI_CATEGORY5 = 15;
+    public static final int ELEMENT_EMOJI_CATEGORY6 = 16;
 
     public final InputMethodSubtype mSubtype;
     public final Locale mLocale;
@@ -217,6 +224,13 @@
         case ELEMENT_PHONE: return "phone";
         case ELEMENT_PHONE_SYMBOLS: return "phoneSymbols";
         case ELEMENT_NUMBER: return "number";
+        case ELEMENT_EMOJI_RECENTS: return "emojiRecents";
+        case ELEMENT_EMOJI_CATEGORY1: return "emojiCategory1";
+        case ELEMENT_EMOJI_CATEGORY2: return "emojiCategory2";
+        case ELEMENT_EMOJI_CATEGORY3: return "emojiCategory3";
+        case ELEMENT_EMOJI_CATEGORY4: return "emojiCategory4";
+        case ELEMENT_EMOJI_CATEGORY5: return "emojiCategory5";
+        case ELEMENT_EMOJI_CATEGORY6: return "emojiCategory6";
         default: return null;
         }
     }
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
index e97f294..1eccdf3 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
@@ -106,6 +106,8 @@
         EditorInfo mEditorInfo;
         boolean mDisableTouchPositionCorrectionDataForTest;
         boolean mVoiceKeyEnabled;
+        // TODO: Remove mVoiceKeyOnMain when it's certainly confirmed that we no longer show
+        // the voice input key on the symbol layout
         boolean mVoiceKeyOnMain;
         boolean mNoSettingsKey;
         boolean mLanguageSwitchKeyEnabled;
@@ -162,7 +164,8 @@
         final KeyboardId id = new KeyboardId(keyboardLayoutSetElementId, mParams);
         try {
             return getKeyboard(elementParams, id);
-        } catch (RuntimeException e) {
+        } catch (final RuntimeException e) {
+            Log.e(TAG, "Can't create keyboard: " + id, e);
             throw new KeyboardLayoutSetException(e, id);
         }
     }
@@ -213,7 +216,6 @@
         private final Context mContext;
         private final String mPackageName;
         private final Resources mResources;
-        private final EditorInfo mEditorInfo;
 
         private final Params mParams = new Params();
 
@@ -223,13 +225,12 @@
             mContext = context;
             mPackageName = context.getPackageName();
             mResources = context.getResources();
-            mEditorInfo = editorInfo;
             final Params params = mParams;
 
             params.mMode = getKeyboardMode(editorInfo);
             params.mEditorInfo = (editorInfo != null) ? editorInfo : EMPTY_EDITOR_INFO;
             params.mNoSettingsKey = InputAttributes.inPrivateImeOptions(
-                    mPackageName, NO_SETTINGS_KEY, mEditorInfo);
+                    mPackageName, NO_SETTINGS_KEY, params.mEditorInfo);
         }
 
         public Builder setKeyboardGeometry(final int keyboardWidth, final int keyboardHeight) {
@@ -242,7 +243,7 @@
             final boolean asciiCapable = subtype.containsExtraValueKey(ASCII_CAPABLE);
             @SuppressWarnings("deprecation")
             final boolean deprecatedForceAscii = InputAttributes.inPrivateImeOptions(
-                    mPackageName, FORCE_ASCII, mEditorInfo);
+                    mPackageName, FORCE_ASCII, mParams.mEditorInfo);
             final boolean forceAscii = EditorInfoCompatUtils.hasFlagForceAscii(
                     mParams.mEditorInfo.imeOptions)
                     || deprecatedForceAscii;
@@ -260,13 +261,15 @@
             return this;
         }
 
+        // TODO: Remove mVoiceKeyOnMain when it's certainly confirmed that we no longer show
+        // the voice input key on the symbol layout
         public Builder setOptions(final boolean voiceKeyEnabled, final boolean voiceKeyOnMain,
                 final boolean languageSwitchKeyEnabled) {
             @SuppressWarnings("deprecation")
             final boolean deprecatedNoMicrophone = InputAttributes.inPrivateImeOptions(
-                    null, NO_MICROPHONE_COMPAT, mEditorInfo);
+                    null, NO_MICROPHONE_COMPAT, mParams.mEditorInfo);
             final boolean noMicrophone = InputAttributes.inPrivateImeOptions(
-                    mPackageName, NO_MICROPHONE, mEditorInfo)
+                    mPackageName, NO_MICROPHONE, mParams.mEditorInfo)
                     || deprecatedNoMicrophone;
             mParams.mVoiceKeyEnabled = voiceKeyEnabled && !noMicrophone;
             mParams.mVoiceKeyOnMain = voiceKeyOnMain;
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
index 1ea0f8b..cc1ffd1 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
@@ -58,19 +58,17 @@
     }
 
     private static final KeyboardTheme[] KEYBOARD_THEMES = {
-        new KeyboardTheme(0, R.style.KeyboardTheme),
-        new KeyboardTheme(1, R.style.KeyboardTheme_HighContrast),
-        new KeyboardTheme(6, R.style.KeyboardTheme_Stone),
-        new KeyboardTheme(7, R.style.KeyboardTheme_Stone_Bold),
-        new KeyboardTheme(8, R.style.KeyboardTheme_Gingerbread),
-        new KeyboardTheme(5, R.style.KeyboardTheme_IceCreamSandwich),
+        new KeyboardTheme(0, R.style.KeyboardTheme_ICS),
+        new KeyboardTheme(1, R.style.KeyboardTheme_GB),
     };
 
     private SubtypeSwitcher mSubtypeSwitcher;
     private SharedPreferences mPrefs;
 
     private InputView mCurrentInputView;
+    private View mMainKeyboardFrame;
     private MainKeyboardView mKeyboardView;
+    private EmojiKeyboardView mEmojiKeyboardView;
     private LatinIME mLatinIME;
     private Resources mResources;
 
@@ -121,8 +119,9 @@
         } catch (NumberFormatException e) {
             // Format error, keyboard theme is default to 0.
         }
-        Log.w(TAG, "Illegal keyboard theme in preference: " + themeIndex + ", default to 0");
-        return KEYBOARD_THEMES[0];
+        Log.w(TAG, "Illegal keyboard theme in preference: " + themeIndex + ", default to "
+                + defaultIndex);
+        return KEYBOARD_THEMES[Integer.valueOf(defaultIndex)];
     }
 
     private void setContextThemeWrapper(final Context context, final KeyboardTheme keyboardTheme) {
@@ -143,7 +142,7 @@
         builder.setSubtype(mSubtypeSwitcher.getCurrentSubtype());
         builder.setOptions(
                 settingsValues.isVoiceKeyEnabled(editorInfo),
-                settingsValues.isVoiceKeyOnMain(),
+                true /* always show a voice key on the main keyboard */,
                 settingsValues.isLanguageSwitchKeyEnabled());
         mKeyboardLayoutSet = builder.build();
         try {
@@ -170,6 +169,8 @@
     }
 
     private void setKeyboard(final Keyboard keyboard) {
+        // Make {@link MainKeyboardView} visible and hide {@link EmojiKeyboardView}.
+        setMainKeyboardFrame();
         final MainKeyboardView keyboardView = mKeyboardView;
         final Keyboard oldKeyboard = keyboardView.getKeyboard();
         keyboardView.setKeyboard(keyboard);
@@ -256,6 +257,18 @@
         setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_SYMBOLS));
     }
 
+    private void setMainKeyboardFrame() {
+        mMainKeyboardFrame.setVisibility(View.VISIBLE);
+        mEmojiKeyboardView.setVisibility(View.GONE);
+    }
+
+    // Implements {@link KeyboardState.SwitchActions}.
+    @Override
+    public void setEmojiKeyboard() {
+        mMainKeyboardFrame.setVisibility(View.GONE);
+        mEmojiKeyboardView.setVisibility(View.VISIBLE);
+    }
+
     // Implements {@link KeyboardState.SwitchActions}.
     @Override
     public void setSymbolsShiftedKeyboard() {
@@ -301,6 +314,24 @@
         mState.onCodeInput(code, mLatinIME.getCurrentAutoCapsState());
     }
 
+    public boolean isShowingEmojiKeyboard() {
+        return mEmojiKeyboardView.getVisibility() == View.VISIBLE;
+    }
+
+    public boolean isShowingMoreKeysPanel() {
+        if (isShowingEmojiKeyboard()) {
+            return false;
+        }
+        return mKeyboardView.isShowingMoreKeysPanel();
+    }
+
+    public View getVisibleKeyboardView() {
+        if (isShowingEmojiKeyboard()) {
+            return mEmojiKeyboardView;
+        }
+        return mKeyboardView;
+    }
+
     public MainKeyboardView getMainKeyboardView() {
         return mKeyboardView;
     }
@@ -313,10 +344,16 @@
         setContextThemeWrapper(mLatinIME, mKeyboardTheme);
         mCurrentInputView = (InputView)LayoutInflater.from(mThemeContext).inflate(
                 R.layout.input_view, null);
+        mMainKeyboardFrame = mCurrentInputView.findViewById(R.id.main_keyboard_frame);
+        mEmojiKeyboardView = (EmojiKeyboardView)mCurrentInputView.findViewById(
+                R.id.emoji_keyboard_view);
 
         mKeyboardView = (MainKeyboardView) mCurrentInputView.findViewById(R.id.keyboard_view);
         mKeyboardView.setHardwareAcceleratedDrawingEnabled(isHardwareAcceleratedDrawingEnabled);
         mKeyboardView.setKeyboardActionListener(mLatinIME);
+        mEmojiKeyboardView.setHardwareAcceleratedDrawingEnabled(
+                isHardwareAcceleratedDrawingEnabled);
+        mEmojiKeyboardView.setKeyboardActionListener(mLatinIME);
 
         // This always needs to be set since the accessibility state can
         // potentially change without the input view being re-created.
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
index 054c503..aeb9e67 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
@@ -26,6 +26,7 @@
 import android.graphics.PorterDuff;
 import android.graphics.Rect;
 import android.graphics.Region;
+import android.graphics.Typeface;
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
 import android.view.View;
@@ -196,13 +197,14 @@
 
     @Override
     protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
-        if (mKeyboard != null) {
-            // The main keyboard expands to the display width.
-            final int height = mKeyboard.mOccupiedHeight + getPaddingTop() + getPaddingBottom();
-            setMeasuredDimension(widthMeasureSpec, height);
-        } else {
+        if (mKeyboard == null) {
             super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+            return;
         }
+        // The main keyboard expands to the entire this {@link KeyboardView}.
+        final int width = mKeyboard.mOccupiedWidth + getPaddingLeft() + getPaddingRight();
+        final int height = mKeyboard.mOccupiedHeight + getPaddingTop() + getPaddingBottom();
+        setMeasuredDimension(width, height);
     }
 
     @Override
@@ -264,9 +266,9 @@
             mClipRegion.setEmpty();
             for (final Key key : mInvalidatedKeys) {
                 if (mKeyboard.hasKey(key)) {
-                    final int x = key.mX + getPaddingLeft();
-                    final int y = key.mY + getPaddingTop();
-                    mWorkingRect.set(x, y, x + key.mWidth, y + key.mHeight);
+                    final int x = key.getX() + getPaddingLeft();
+                    final int y = key.getY() + getPaddingTop();
+                    mWorkingRect.set(x, y, x + key.getWidth(), y + key.getHeight());
                     mClipRegion.union(mWorkingRect);
                 }
             }
@@ -284,7 +286,7 @@
         // TODO: Confirm if it's really required to draw all keys when hardware acceleration is on.
         if (drawAllKeys || isHardwareAccelerated) {
             // Draw all keys.
-            for (final Key key : mKeyboard.mKeys) {
+            for (final Key key : mKeyboard.getKeys()) {
                 onDrawKey(key, canvas, paint);
             }
         } else {
@@ -309,11 +311,11 @@
 
     private void onDrawKey(final Key key, final Canvas canvas, final Paint paint) {
         final int keyDrawX = key.getDrawX() + getPaddingLeft();
-        final int keyDrawY = key.mY + getPaddingTop();
+        final int keyDrawY = key.getY() + getPaddingTop();
         canvas.translate(keyDrawX, keyDrawY);
 
         final int keyHeight = mKeyboard.mMostCommonKeyHeight - mKeyboard.mVerticalGap;
-        final KeyVisualAttributes attr = key.mKeyVisualAttributes;
+        final KeyVisualAttributes attr = key.getVisualAttributes();
         final KeyDrawParams params = mKeyDrawParams.mayCloneAndUpdateParams(keyHeight, attr);
         params.mAnimAlpha = Constants.Color.ALPHA_OPAQUE;
 
@@ -329,7 +331,7 @@
     protected void onDrawKeyBackground(final Key key, final Canvas canvas) {
         final Rect padding = mKeyBackgroundPadding;
         final int bgWidth = key.getDrawWidth() + padding.left + padding.right;
-        final int bgHeight = key.mHeight + padding.top + padding.bottom;
+        final int bgHeight = key.getHeight() + padding.top + padding.bottom;
         final int bgX = -padding.left;
         final int bgY = -padding.top;
         final int[] drawableState = key.getCurrentDrawableState();
@@ -351,7 +353,7 @@
     protected void onDrawKeyTopVisuals(final Key key, final Canvas canvas, final Paint paint,
             final KeyDrawParams params) {
         final int keyWidth = key.getDrawWidth();
-        final int keyHeight = key.mHeight;
+        final int keyHeight = key.getHeight();
         final float centerX = keyWidth * 0.5f;
         final float centerY = keyHeight * 0.5f;
 
@@ -362,8 +364,8 @@
         // Draw key label.
         final Drawable icon = key.getIcon(mKeyboard.mIconsSet, params.mAnimAlpha);
         float positionX = centerX;
-        if (key.mLabel != null) {
-            final String label = key.mLabel;
+        final String label = key.getLabel();
+        if (label != null) {
             paint.setTypeface(key.selectTypeface(params));
             paint.setTextSize(key.selectTextSize(params));
             final float labelCharHeight = TypefaceUtils.getCharHeight(
@@ -440,10 +442,12 @@
         }
 
         // Draw hint label.
-        if (key.mHintLabel != null) {
-            final String hintLabel = key.mHintLabel;
+        final String hintLabel = key.getHintLabel();
+        if (hintLabel != null) {
             paint.setTextSize(key.selectHintTextSize(params));
             paint.setColor(key.selectHintTextColor(params));
+            // TODO: Should add a way to specify type face for hint letters
+            paint.setTypeface(Typeface.DEFAULT_BOLD);
             blendAlpha(paint, params.mAnimAlpha);
             final float hintX, hintY;
             if (key.hasHintLabel()) {
@@ -464,9 +468,13 @@
                 paint.setTextAlign(Align.CENTER);
             } else { // key.hasHintLetter()
                 // The hint letter is placed at top-right corner of the key. Used mainly on phone.
+                final float keyNumericHintLabelReferenceCharWidth =
+                        TypefaceUtils.getCharWidth(KEY_NUMERIC_HINT_LABEL_REFERENCE_CHAR, paint);
+                final float keyHintLabelStringWidth =
+                        TypefaceUtils.getStringWidth(hintLabel, paint);
                 hintX = keyWidth - mKeyHintLetterPadding
-                        - TypefaceUtils.getCharWidth(KEY_NUMERIC_HINT_LABEL_REFERENCE_CHAR, paint)
-                        / 2.0f;
+                        - Math.max(keyNumericHintLabelReferenceCharWidth, keyHintLabelStringWidth)
+                                / 2.0f;
                 hintY = -paint.ascent();
                 paint.setTextAlign(Align.CENTER);
             }
@@ -480,7 +488,7 @@
         }
 
         // Draw key icon.
-        if (key.mLabel == null && icon != null) {
+        if (label == null && icon != null) {
             final int iconWidth = Math.min(icon.getIntrinsicWidth(), keyWidth);
             final int iconHeight = icon.getIntrinsicHeight();
             final int iconX, alignX;
@@ -504,7 +512,7 @@
             }
         }
 
-        if (key.hasPopupHint() && key.mMoreKeys != null) {
+        if (key.hasPopupHint() && key.getMoreKeys() != null) {
             drawKeyPopupHint(key, canvas, paint, params);
         }
     }
@@ -513,7 +521,7 @@
     protected void drawKeyPopupHint(final Key key, final Canvas canvas, final Paint paint,
             final KeyDrawParams params) {
         final int keyWidth = key.getDrawWidth();
-        final int keyHeight = key.mHeight;
+        final int keyHeight = key.getHeight();
 
         paint.setTypeface(params.mTypeface);
         paint.setTextSize(params.mHintLetterSize);
@@ -601,9 +609,9 @@
         if (mInvalidateAllKeys) return;
         if (key == null) return;
         mInvalidatedKeys.add(key);
-        final int x = key.mX + getPaddingLeft();
-        final int y = key.mY + getPaddingTop();
-        invalidate(x, y, x + key.mWidth, y + key.mHeight);
+        final int x = key.getX() + getPaddingLeft();
+        final int y = key.getY() + getPaddingTop();
+        invalidate(x, y, x + key.getWidth(), y + key.getHeight());
     }
 
     @Override
diff --git a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
index 526c2f1..13db470 100644
--- a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
@@ -155,7 +155,6 @@
     private final SlidingKeyInputPreview mSlidingKeyInputPreview;
 
     // Key preview
-    private static final int PREVIEW_ALPHA = 240;
     private final int mKeyPreviewLayoutId;
     private final int mKeyPreviewOffset;
     private final int mKeyPreviewHeight;
@@ -183,6 +182,7 @@
     private final NonDistinctMultitouchHelper mNonDistinctMultitouchHelper;
 
     private final KeyTimerHandler mKeyTimerHandler;
+    private final int mLanguageOnSpacebarHorizontalMargin;
 
     private static final class KeyTimerHandler extends StaticInnerHandlerWrapper<MainKeyboardView>
             implements TimerProxy {
@@ -217,7 +217,7 @@
                 startWhileTypingFadeinAnimation(keyboardView);
                 break;
             case MSG_REPEAT_KEY:
-                tracker.onKeyRepeat(msg.arg1);
+                tracker.onKeyRepeat(msg.arg1 /* code */, msg.arg2 /* repeatCount */);
                 break;
             case MSG_LONGPRESS_KEY:
                 keyboardView.onLongPress(tracker);
@@ -230,12 +230,14 @@
         }
 
         @Override
-        public void startKeyRepeatTimer(final PointerTracker tracker, final int delay) {
+        public void startKeyRepeatTimer(final PointerTracker tracker, final int repeatCount,
+                final int delay) {
             final Key key = tracker.getKey();
             if (key == null || delay == 0) {
                 return;
             }
-            sendMessageDelayed(obtainMessage(MSG_REPEAT_KEY, key.mCode, 0, tracker), delay);
+            sendMessageDelayed(
+                    obtainMessage(MSG_REPEAT_KEY, key.getCode(), repeatCount, tracker), delay);
         }
 
         public void cancelKeyRepeatTimer() {
@@ -296,7 +298,7 @@
             final MainKeyboardView keyboardView = getOuterInstance();
 
             // When user hits the space or the enter key, just cancel the while-typing timer.
-            final int typedCode = typedKey.mCode;
+            final int typedCode = typedKey.getCode();
             if (typedCode == Constants.CODE_SPACE || typedCode == Constants.CODE_ENTER) {
                 if (isTyping) {
                     startWhileTypingFadeinAnimation(keyboardView);
@@ -510,6 +512,9 @@
                 altCodeKeyWhileTypingFadeinAnimatorResId, this);
 
         mKeyboardActionListener = KeyboardActionListener.EMPTY_LISTENER;
+
+        mLanguageOnSpacebarHorizontalMargin =
+                (int) getResources().getDimension(R.dimen.language_on_spacebar_horizontal_margin);
     }
 
     @Override
@@ -636,7 +641,6 @@
         mKeyPreviewLingerTimeout = delay;
     }
 
-
     private void locatePreviewPlacerView() {
         if (mPreviewPlacerView.getParent() != null) {
             return;
@@ -805,13 +809,12 @@
         }
         // The key preview is placed vertically above the top edge of the parent key with an
         // arbitrary offset.
-        final int previewY = key.mY - previewHeight + mKeyPreviewOffset
+        final int previewY = key.getY() - previewHeight + mKeyPreviewOffset
                 + CoordinateUtils.y(mOriginCoords);
 
         if (background != null) {
-            final int hasMoreKeys = (key.mMoreKeys != null) ? STATE_HAS_MOREKEYS : STATE_NORMAL;
+            final int hasMoreKeys = (key.getMoreKeys() != null) ? STATE_HAS_MOREKEYS : STATE_NORMAL;
             background.setState(KEY_PREVIEW_BACKGROUND_STATE_TABLE[statePosition][hasMoreKeys]);
-            background.setAlpha(PREVIEW_ALPHA);
         }
         ViewLayoutUtils.placeViewAt(
                 previewText, previewX, previewY, previewWidth, previewHeight);
@@ -901,7 +904,7 @@
     }
 
     private MoreKeysPanel onCreateMoreKeysPanel(final Key key, final Context context) {
-        if (key.mMoreKeys == null) {
+        if (key.getMoreKeys() == null) {
             return null;
         }
         Keyboard moreKeysKeyboard = mMoreKeysKeyboardCache.get(key);
@@ -936,15 +939,15 @@
         }
         final KeyboardActionListener listener = mKeyboardActionListener;
         if (key.hasNoPanelAutoMoreKey()) {
-            final int moreKeyCode = key.mMoreKeys[0].mCode;
+            final int moreKeyCode = key.getMoreKeys()[0].mCode;
             tracker.onLongPressed();
-            listener.onPressKey(moreKeyCode, false /* isRepeatKey */, true /* isSinglePointer */);
+            listener.onPressKey(moreKeyCode, 0 /* repeatCount */, true /* isSinglePointer */);
             listener.onCodeInput(moreKeyCode,
                     Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
             listener.onReleaseKey(moreKeyCode, false /* withSliding */);
             return;
         }
-        final int code = key.mCode;
+        final int code = key.getCode();
         if (code == Constants.CODE_SPACE || code == Constants.CODE_LANGUAGE_SWITCH) {
             // Long pressing the space key invokes IME switcher dialog.
             if (listener.onCustomRequest(Constants.CUSTOM_CODE_SHOW_INPUT_METHOD_PICKER)) {
@@ -970,13 +973,13 @@
         // keys keyboard is placed at the touch point of the parent key.
         final int pointX = (mConfigShowMoreKeysKeyboardAtTouchedPoint && !keyPreviewEnabled)
                 ? CoordinateUtils.x(lastCoords)
-                : key.mX + key.mWidth / 2;
+                : key.getX() + key.getWidth() / 2;
         // The more keys keyboard is usually vertically aligned with the top edge of the parent key
         // (plus vertical gap). If the key preview is enabled, the more keys keyboard is vertically
         // aligned with the bottom edge of the visible part of the key preview.
         // {@code mPreviewVisibleOffset} has been set appropriately in
         // {@link KeyboardView#showKeyPreview(PointerTracker)}.
-        final int pointY = key.mY + mKeyPreviewDrawParams.mPreviewVisibleOffset;
+        final int pointY = key.getY() + mKeyPreviewDrawParams.mPreviewVisibleOffset;
         moreKeysPanel.showMoreKeysPanel(this, this, pointX, pointY, mKeyboardActionListener);
         tracker.onShowMoreKeysPanel(moreKeysPanel);
     }
@@ -1172,13 +1175,14 @@
         if (key.altCodeWhileTyping() && key.isEnabled()) {
             params.mAnimAlpha = mAltCodeKeyWhileTypingAnimAlpha;
         }
-        if (key.mCode == Constants.CODE_SPACE) {
+        final int code = key.getCode();
+        if (code == Constants.CODE_SPACE) {
             drawSpacebar(key, canvas, paint);
             // Whether space key needs to show the "..." popup hint for special purposes
             if (key.isLongPressEnabled() && mHasMultipleEnabledIMEsOrSubtypes) {
                 drawKeyPopupHint(key, canvas, paint, params);
             }
-        } else if (key.mCode == Constants.CODE_LANGUAGE_SWITCH) {
+        } else if (code == Constants.CODE_LANGUAGE_SWITCH) {
             super.onDrawKeyTopVisuals(key, canvas, paint, params);
             drawKeyPopupHint(key, canvas, paint, params);
         } else {
@@ -1186,26 +1190,27 @@
         }
     }
 
-    private static boolean fitsTextIntoWidth(final int width, final String text,
-            final Paint paint) {
+    private boolean fitsTextIntoWidth(final int width, final String text, final Paint paint) {
+        final int maxTextWidth = width - mLanguageOnSpacebarHorizontalMargin * 2;
         paint.setTextScaleX(1.0f);
         final float textWidth = TypefaceUtils.getLabelWidth(text, paint);
         if (textWidth < width) {
             return true;
         }
 
-        final float scaleX = width / textWidth;
+        final float scaleX = maxTextWidth / textWidth;
         if (scaleX < MINIMUM_XSCALE_OF_LANGUAGE_NAME) {
             return false;
         }
 
         paint.setTextScaleX(scaleX);
-        return TypefaceUtils.getLabelWidth(text, paint) < width;
+        return TypefaceUtils.getLabelWidth(text, paint) < maxTextWidth;
     }
 
     // Layout language name on spacebar.
-    private static String layoutLanguageOnSpacebar(final Paint paint,
+    private String layoutLanguageOnSpacebar(final Paint paint,
             final InputMethodSubtype subtype, final int width) {
+
         // Choose appropriate language name to fit into the width.
         final String fullText = SubtypeLocaleUtils.getFullDisplayName(subtype);
         if (fitsTextIntoWidth(width, fullText, paint)) {
@@ -1226,8 +1231,8 @@
     }
 
     private void drawSpacebar(final Key key, final Canvas canvas, final Paint paint) {
-        final int width = key.mWidth;
-        final int height = key.mHeight;
+        final int width = key.getWidth();
+        final int height = key.getHeight();
 
         // If input language are explicitly selected.
         if (mNeedsToDisplayLanguage) {
diff --git a/java/src/com/android/inputmethod/keyboard/MoreKeysDetector.java b/java/src/com/android/inputmethod/keyboard/MoreKeysDetector.java
index a2001cb..6b76e24 100644
--- a/java/src/com/android/inputmethod/keyboard/MoreKeysDetector.java
+++ b/java/src/com/android/inputmethod/keyboard/MoreKeysDetector.java
@@ -39,7 +39,7 @@
 
         Key nearestKey = null;
         int nearestDist = (y < 0) ? mSlideAllowanceSquareTop : mSlideAllowanceSquare;
-        for (final Key key : getKeyboard().mKeys) {
+        for (final Key key : getKeyboard().getKeys()) {
             final int dist = key.squaredDistanceToEdge(touchX, touchY);
             if (dist < nearestDist) {
                 nearestKey = key;
diff --git a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java
index 3fd29dc..8256d46 100644
--- a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java
@@ -276,6 +276,7 @@
             mParams.mVerticalGap = parentKeyboard.mVerticalGap / 2;
             mParentKey = parentKey;
 
+            final MoreKeySpec[] moreKeys = parentKey.getMoreKeys();
             final int width, height;
             // {@link KeyPreviewDrawParams#mPreviewVisibleWidth} should have been set at
             // {@link MainKeyboardView#showKeyPreview(PointerTracker}, though there may be
@@ -283,7 +284,7 @@
             // zero-division error at
             // {@link MoreKeysKeyboardParams#setParameters(int,int,int,int,int,int,boolean,int)}.
             final boolean singleMoreKeyWithPreview = parentKeyboardView.isKeyPreviewPopupEnabled()
-                    && !parentKey.noKeyPreview() && parentKey.mMoreKeys.length == 1
+                    && !parentKey.noKeyPreview() && moreKeys.length == 1
                     && keyPreviewDrawParams.mPreviewVisibleWidth > 0;
             if (singleMoreKeyWithPreview) {
                 // Use pre-computed width and height if this more keys keyboard has only one key to
@@ -312,8 +313,8 @@
                 mDivider = null;
                 dividerWidth = 0;
             }
-            mParams.setParameters(parentKey.mMoreKeys.length, parentKey.getMoreKeysColumn(),
-                    width, height, parentKey.mX + parentKey.mWidth / 2,
+            mParams.setParameters(moreKeys.length, parentKey.getMoreKeysColumn(),
+                    width, height, parentKey.getX() + parentKey.getWidth() / 2,
                     parentKeyboard.mId.mWidth, parentKey.isFixedColumnOrderMoreKeys(),
                     dividerWidth);
         }
@@ -321,7 +322,7 @@
         private static int getMaxKeyWidth(final Key parentKey, final int minKeyWidth,
                 final float padding, final Paint paint) {
             int maxWidth = minKeyWidth;
-            for (final MoreKeySpec spec : parentKey.mMoreKeys) {
+            for (final MoreKeySpec spec : parentKey.getMoreKeys()) {
                 final String label = spec.mLabel;
                 // If the label is single letter, minKeyWidth is enough to hold the label.
                 if (label != null && StringUtils.codePointCount(label) > 1) {
@@ -336,7 +337,7 @@
         public MoreKeysKeyboard build() {
             final MoreKeysKeyboardParams params = mParams;
             final int moreKeyFlags = mParentKey.getMoreKeyLabelFlags();
-            final MoreKeySpec[] moreKeys = mParentKey.mMoreKeys;
+            final MoreKeySpec[] moreKeys = mParentKey.getMoreKeys();
             for (int n = 0; n < moreKeys.length; n++) {
                 final MoreKeySpec moreKeySpec = moreKeys[n];
                 final int row = n / params.mNumColumns;
diff --git a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java
index f00f5a9..973128d 100644
--- a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java
@@ -127,7 +127,7 @@
     public void onUpEvent(final int x, final int y, final int pointerId, final long eventTime) {
         if (mCurrentKey != null && mActivePointerId == pointerId) {
             updateReleaseKeyGraphics(mCurrentKey);
-            onCodeInput(mCurrentKey.mCode, x, y);
+            onCodeInput(mCurrentKey.getCode(), x, y);
             mCurrentKey = null;
         }
     }
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
index b66ee2a..d4d0d87 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
@@ -64,7 +64,7 @@
 
         /**
          * Get KeyboardActionListener object that is used to register key code and so on.
-         * @return the KeyboardActionListner for this PointerTracker
+         * @return the KeyboardActionListner for this PointerTracke
          */
         public KeyboardActionListener getKeyboardActionListener();
 
@@ -94,7 +94,7 @@
     public interface TimerProxy {
         public void startTypingStateTimer(Key typedKey);
         public boolean isTypingState();
-        public void startKeyRepeatTimer(PointerTracker tracker, int delay);
+        public void startKeyRepeatTimer(PointerTracker tracker, int repeatCount, int delay);
         public void startLongPressTimer(PointerTracker tracker, int delay);
         public void cancelLongPressTimer();
         public void startDoubleTapShiftKeyTimer();
@@ -111,7 +111,7 @@
             @Override
             public boolean isTypingState() { return false; }
             @Override
-            public void startKeyRepeatTimer(PointerTracker tracker, int delay) {}
+            public void startKeyRepeatTimer(PointerTracker tracker, int repeatCount, int delay) {}
             @Override
             public void startLongPressTimer(PointerTracker tracker, int delay) {}
             @Override
@@ -490,7 +490,7 @@
 
     // Returns true if keyboard has been changed by this callback.
     private boolean callListenerOnPressAndCheckKeyboardLayoutChange(final Key key,
-            final boolean isRepeatKey) {
+            final int repeatCount) {
         // While gesture input is going on, this method should be a no-operation. But when gesture
         // input has been canceled, <code>sInGesture</code> and <code>mIsDetectingGesture</code>
         // are set to false. To keep this method is a no-operation,
@@ -504,13 +504,13 @@
                     KeyDetector.printableCode(key),
                     ignoreModifierKey ? " ignoreModifier" : "",
                     key.isEnabled() ? "" : " disabled",
-                    isRepeatKey ? " repeat" : ""));
+                    repeatCount > 0 ? " repeatCount=" + repeatCount : ""));
         }
         if (ignoreModifierKey) {
             return false;
         }
         if (key.isEnabled()) {
-            mListener.onPressKey(key.mCode, isRepeatKey, getActivePointerTrackerCount() == 1);
+            mListener.onPressKey(key.getCode(), repeatCount, getActivePointerTrackerCount() == 1);
             final boolean keyboardLayoutHasBeenChanged = mKeyboardLayoutHasBeenChanged;
             mKeyboardLayoutHasBeenChanged = false;
             mTimerProxy.startTypingStateTimer(key);
@@ -776,7 +776,7 @@
         if (sInGesture || !mGestureStrokeWithPreviewPoints.isStartOfAGesture()) {
             return;
         }
-        if (key == null || !Character.isLetter(key.mCode)) {
+        if (key == null || !Character.isLetter(key.getCode())) {
             return;
         }
         if (DEBUG_LISTENER) {
@@ -967,7 +967,7 @@
             // This onPress call may have changed keyboard layout. Those cases are detected at
             // {@link #setKeyboard}. In those cases, we should update key according to the new
             // keyboard layout.
-            if (callListenerOnPressAndCheckKeyboardLayoutChange(key, false /* isRepeatKey */)) {
+            if (callListenerOnPressAndCheckKeyboardLayoutChange(key, 0 /* repeatCount */)) {
                 key = onDownKey(x, y, eventTime);
             }
 
@@ -1057,7 +1057,7 @@
         // at {@link #setKeyboard}. In those cases, we should update key according
         // to the new keyboard layout.
         Key key = newKey;
-        if (callListenerOnPressAndCheckKeyboardLayoutChange(key, false /* isRepeatKey */)) {
+        if (callListenerOnPressAndCheckKeyboardLayoutChange(key, 0 /* repeatCount */)) {
             key = onMoveKey(x, y);
         }
         onMoveToNewKey(key, x, y);
@@ -1075,8 +1075,8 @@
                     + " phantom sudden move event (distance=%d) is translated to "
                     + "up[%d,%d,%s]/down[%d,%d,%s] events", mPointerId,
                     getDistance(x, y, lastX, lastY),
-                    lastX, lastY, Constants.printableCode(oldKey.mCode),
-                    x, y, Constants.printableCode(key.mCode)));
+                    lastX, lastY, Constants.printableCode(oldKey.getCode()),
+                    x, y, Constants.printableCode(key.getCode())));
         }
         // TODO: This should be moved to outside of this nested if-clause?
         if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
@@ -1098,8 +1098,8 @@
                     + " bogus down-move-up event (raidus=%.2f key diagonal) is "
                     + " translated to up[%d,%d,%s]/down[%d,%d,%s] events",
                     mPointerId, radiusRatio,
-                    lastX, lastY, Constants.printableCode(oldKey.mCode),
-                    x, y, Constants.printableCode(key.mCode)));
+                    lastX, lastY, Constants.printableCode(oldKey.getCode()),
+                    x, y, Constants.printableCode(key.getCode())));
         }
         onUpEventInternal(x, y, eventTime);
         onDownEventInternal(x, y, eventTime);
@@ -1107,7 +1107,7 @@
 
     private void processSildeOutFromOldKey(final Key oldKey) {
         setReleasedKeyGraphics(oldKey);
-        callListenerOnRelease(oldKey, oldKey.mCode, true /* withSliding */);
+        callListenerOnRelease(oldKey, oldKey.getCode(), true /* withSliding */);
         startSlidingKeyInput(oldKey);
         mTimerProxy.cancelKeyTimers();
     }
@@ -1263,7 +1263,7 @@
 
         if (sInGesture) {
             if (currentKey != null) {
-                callListenerOnRelease(currentKey, currentKey.mCode, true /* withSliding */);
+                callListenerOnRelease(currentKey, currentKey.getCode(), true /* withSliding */);
             }
             mayEndBatchInput(eventTime);
             return;
@@ -1376,9 +1376,9 @@
         // doesn't have its more keys. (e.g. spacebar, globe key)
         // We always need to start the long press timer if the key has its more keys regardless of
         // whether or not we are in the sliding input mode.
-        if (mIsInSlidingKeyInput && key.mMoreKeys == null) return;
+        if (mIsInSlidingKeyInput && key.getMoreKeys() == null) return;
         final int delay;
-        switch (key.mCode) {
+        switch (key.getCode()) {
         case Constants.CODE_SHIFT:
             delay = sParams.mLongPressShiftLockTimeout;
             break;
@@ -1401,7 +1401,7 @@
             return;
         }
 
-        final int code = key.mCode;
+        final int code = key.getCode();
         callListenerOnCodeInput(key, code, x, y, eventTime);
         callListenerOnRelease(key, code, false /* withSliding */);
     }
@@ -1412,17 +1412,19 @@
         if (!key.isRepeatable()) return;
         // Don't start key repeat when we are in sliding input mode.
         if (mIsInSlidingKeyInput) return;
-        detectAndSendKey(key, key.mX, key.mY, SystemClock.uptimeMillis());
-        mTimerProxy.startKeyRepeatTimer(this, sParams.mKeyRepeatStartTimeout);
+        detectAndSendKey(key, key.getX(), key.getY(), SystemClock.uptimeMillis());
+        final int startRepeatCount = 1;
+        mTimerProxy.startKeyRepeatTimer(this, startRepeatCount, sParams.mKeyRepeatStartTimeout);
     }
 
-    public void onKeyRepeat(final int code) {
+    public void onKeyRepeat(final int code, final int repeatCount) {
         final Key key = getKey();
-        if (key == null || key.mCode != code) {
+        if (key == null || key.getCode() != code) {
             return;
         }
-        mTimerProxy.startKeyRepeatTimer(this, sParams.mKeyRepeatInterval);
-        callListenerOnPressAndCheckKeyboardLayoutChange(key, true /* isRepeatKey */);
+        final int nextRepeatCount = repeatCount + 1;
+        mTimerProxy.startKeyRepeatTimer(this, nextRepeatCount, sParams.mKeyRepeatInterval);
+        callListenerOnPressAndCheckKeyboardLayoutChange(key, repeatCount);
         callListenerOnCodeInput(key, code, mKeyX, mKeyY, SystemClock.uptimeMillis());
     }
 
diff --git a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
index 9b0a33c..a031669 100644
--- a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
+++ b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
@@ -96,7 +96,7 @@
 
     private static boolean needsProximityInfo(final Key key) {
         // Don't include special keys into ProximityInfo.
-        return key.mCode >= Constants.CODE_SPACE;
+        return key.getCode() >= Constants.CODE_SPACE;
     }
 
     private static int getProximityInfoKeysCount(final Key[] keys) {
@@ -122,7 +122,7 @@
                 if (!needsProximityInfo(neighborKey)) {
                     continue;
                 }
-                proximityCharsArray[infoIndex] = neighborKey.mCode;
+                proximityCharsArray[infoIndex] = neighborKey.getCode();
                 infoIndex++;
             }
         }
@@ -159,11 +159,11 @@
             if (!needsProximityInfo(key)) {
                 continue;
             }
-            keyXCoordinates[infoIndex] = key.mX;
-            keyYCoordinates[infoIndex] = key.mY;
-            keyWidths[infoIndex] = key.mWidth;
-            keyHeights[infoIndex] = key.mHeight;
-            keyCharCodes[infoIndex] = key.mCode;
+            keyXCoordinates[infoIndex] = key.getX();
+            keyYCoordinates[infoIndex] = key.getY();
+            keyWidths[infoIndex] = key.getWidth();
+            keyHeights[infoIndex] = key.getHeight();
+            keyCharCodes[infoIndex] = key.getCode();
             infoIndex++;
         }
 
@@ -183,7 +183,7 @@
                 if (!needsProximityInfo(key)) {
                     continue;
                 }
-                final Rect hitBox = key.mHitBox;
+                final Rect hitBox = key.getHitBox();
                 sweetSpotCenterXs[infoIndex] = hitBox.exactCenterX();
                 sweetSpotCenterYs[infoIndex] = hitBox.exactCenterY();
                 sweetSpotRadii[infoIndex] = defaultRadius;
@@ -204,7 +204,7 @@
                             "  [%2d] row=%d x/y/r=%7.2f/%7.2f/%5.2f %s code=%s", infoIndex, row,
                             sweetSpotCenterXs[infoIndex], sweetSpotCenterYs[infoIndex],
                             sweetSpotRadii[infoIndex], (row < rows ? "correct" : "default"),
-                            Constants.printableCode(key.mCode)));
+                            Constants.printableCode(key.getCode())));
                 }
                 infoIndex++;
             }
@@ -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,
@@ -322,19 +322,21 @@
   have to align this on the center of the key. Hence, we don't need a separate value for
   bottomPixelWithinThreshold and call this yEnd right away.
 */
-            final int topPixelWithinThreshold = key.mY - threshold;
+            final int keyX = key.getX();
+            final int keyY = key.getY();
+            final int topPixelWithinThreshold = keyY - threshold;
             final int yDeltaToGrid = topPixelWithinThreshold % mCellHeight;
             final int yMiddleOfTopCell = topPixelWithinThreshold - yDeltaToGrid + halfCellHeight;
             final int yStart = Math.max(halfCellHeight,
                     yMiddleOfTopCell + (yDeltaToGrid <= halfCellHeight ? 0 : mCellHeight));
-            final int yEnd = Math.min(fullGridHeight, key.mY + key.mHeight + threshold);
+            final int yEnd = Math.min(lastPixelYCoordinate, keyY + key.getHeight() + threshold);
 
-            final int leftPixelWithinThreshold = key.mX - 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, key.mX + key.mWidth + 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) {
@@ -372,7 +374,7 @@
             if (index >= destLength) {
                 break;
             }
-            final int code = key.mCode;
+            final int code = key.getCode();
             if (code <= Constants.CODE_SPACE) {
                 break;
             }
diff --git a/java/src/com/android/inputmethod/keyboard/internal/CodesArrayParser.java b/java/src/com/android/inputmethod/keyboard/internal/CodesArrayParser.java
new file mode 100644
index 0000000..c10fdba
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/internal/CodesArrayParser.java
@@ -0,0 +1,85 @@
+/*
+ * 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.internal;
+
+import com.android.inputmethod.latin.Constants;
+
+/**
+ * The string parser of codesArray specification for <GridRows />. The attribute codesArray is an
+ * array of string.
+ * Each element of the array defines a key label by specifying a code point as a hexadecimal string.
+ * A key label may consist of multiple code points separated by comma.
+ * Each element of the array optionally can have an output text definition after vertical bar
+ * marker. An output text may consist of multiple code points separated by comma.
+ * The format of the codesArray element should be:
+ * <pre>
+ *   codePointInHex[,codePoint2InHex]*(|outputTextCodePointInHex[,outputTextCodePoint2InHex]*)?
+ * </pre>
+ */
+// TODO: Write unit tests for this class.
+public final class CodesArrayParser {
+    // Constants for parsing.
+    private static final char COMMA = ',';
+    private static final char VERTICAL_BAR = '|';
+    private static final String COMMA_STRING = ",";
+    private static final int BASE_HEX = 16;
+
+    private CodesArrayParser() {
+     // This utility class is not publicly instantiable.
+    }
+
+    private static String getLabelSpec(final String codesArraySpec) {
+        final int pos = codesArraySpec.indexOf(VERTICAL_BAR);
+        return (pos < 0) ? codesArraySpec : codesArraySpec.substring(0, pos);
+    }
+
+    public static String parseLabel(final String codesArraySpec) {
+        final String labelSpec = getLabelSpec(codesArraySpec);
+        final StringBuilder sb = new StringBuilder();
+        for (final String codeInHex : labelSpec.split(COMMA_STRING)) {
+            final int codePoint = Integer.parseInt(codeInHex, BASE_HEX);
+            sb.appendCodePoint(codePoint);
+        }
+        return sb.toString();
+    }
+
+    private static String getCodeSpec(final String codesArraySpec) {
+        final int pos = codesArraySpec.indexOf(VERTICAL_BAR);
+        return (pos < 0) ? codesArraySpec : codesArraySpec.substring(pos + 1);
+    }
+
+    public static int parseCode(final String codesArraySpec) {
+        final String codeSpec = getCodeSpec(codesArraySpec);
+        if (codeSpec.indexOf(COMMA) < 0) {
+            return Integer.parseInt(codeSpec, BASE_HEX);
+        }
+        return Constants.CODE_OUTPUT_TEXT;
+    }
+
+    public static String parseOutputText(final String codesArraySpec) {
+        final String codeSpec = getCodeSpec(codesArraySpec);
+        if (codeSpec.indexOf(COMMA) < 0) {
+            return null;
+        }
+        final StringBuilder sb = new StringBuilder();
+        for (final String codeInHex : codeSpec.split(COMMA_STRING)) {
+            final int codePoint = Integer.parseInt(codeInHex, BASE_HEX);
+            sb.appendCodePoint(codePoint);
+        }
+        return sb.toString();
+    }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/DynamicGridKeyboard.java b/java/src/com/android/inputmethod/keyboard/internal/DynamicGridKeyboard.java
new file mode 100644
index 0000000..0dd71e2
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/internal/DynamicGridKeyboard.java
@@ -0,0 +1,223 @@
+/*
+ * 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.internal;
+
+import android.content.SharedPreferences;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.inputmethod.keyboard.EmojiKeyboardView;
+import com.android.inputmethod.keyboard.Key;
+import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.latin.settings.Settings;
+import com.android.inputmethod.latin.utils.CollectionUtils;
+import com.android.inputmethod.latin.utils.StringUtils;
+
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * This is a Keyboard class where you can add keys dynamically shown in a grid layout
+ */
+public class DynamicGridKeyboard extends Keyboard {
+    private static final String TAG = DynamicGridKeyboard.class.getSimpleName();
+    private static final int TEMPLATE_KEY_CODE_0 = 0x30;
+    private static final int TEMPLATE_KEY_CODE_1 = 0x31;
+
+    private final SharedPreferences mPrefs;
+    private final int mLeftPadding;
+    private final int mHorizontalStep;
+    private final int mVerticalStep;
+    private final int mColumnsNum;
+    private final int mMaxKeyCount;
+    private final boolean mIsRecents;
+    private final ArrayDeque<GridKey> mGridKeys = CollectionUtils.newArrayDeque();
+
+    private Key[] mCachedGridKeys;
+
+    public DynamicGridKeyboard(final SharedPreferences prefs, final Keyboard templateKeyboard,
+            final int maxKeyCount, final int categoryId, final int categoryPageId) {
+        super(templateKeyboard);
+        final Key key0 = getTemplateKey(TEMPLATE_KEY_CODE_0);
+        final Key key1 = getTemplateKey(TEMPLATE_KEY_CODE_1);
+        mLeftPadding = key0.getX();
+        mHorizontalStep = Math.abs(key1.getX() - key0.getX());
+        mVerticalStep = key0.getHeight() + mVerticalGap;
+        mColumnsNum = mBaseWidth / mHorizontalStep;
+        mMaxKeyCount = maxKeyCount;
+        mIsRecents = categoryId == EmojiKeyboardView.CATEGORY_ID_RECENTS;
+        mPrefs = prefs;
+    }
+
+    private Key getTemplateKey(final int code) {
+        for (final Key key : super.getKeys()) {
+            if (key.getCode() == code) {
+                return key;
+            }
+        }
+        throw new RuntimeException("Can't find template key: code=" + code);
+    }
+
+    public void addKeyFirst(final Key usedKey) {
+        addKey(usedKey, true);
+        if (mIsRecents) {
+            saveRecentKeys();
+        }
+    }
+
+    public void addKeyLast(final Key usedKey) {
+        addKey(usedKey, false);
+    }
+
+    private void addKey(final Key usedKey, final boolean addFirst) {
+        if (usedKey == null) {
+            return;
+        }
+        synchronized (mGridKeys) {
+            mCachedGridKeys = null;
+            final GridKey key = new GridKey(usedKey);
+            while (mGridKeys.remove(key)) {
+                // Remove duplicate keys.
+            }
+            if (addFirst) {
+                mGridKeys.addFirst(key);
+            } else {
+                mGridKeys.addLast(key);
+            }
+            while (mGridKeys.size() > mMaxKeyCount) {
+                mGridKeys.removeLast();
+            }
+            int index = 0;
+            for (final GridKey gridKey : mGridKeys) {
+                final int keyX = getKeyX(index);
+                final int keyY = getKeyY(index);
+                gridKey.updateCorrdinates(keyX, keyY);
+                index++;
+            }
+        }
+    }
+
+    private void saveRecentKeys() {
+        final ArrayList<Object> keys = CollectionUtils.newArrayList();
+        for (final Key key : mGridKeys) {
+            if (key.getOutputText() != null) {
+                keys.add(key.getOutputText());
+            } else {
+                keys.add(key.getCode());
+            }
+        }
+        final String jsonStr = StringUtils.listToJsonStr(keys);
+        Settings.writeEmojiRecentKeys(mPrefs, jsonStr);
+    }
+
+    private static Key getKey(final Collection<DynamicGridKeyboard> keyboards, final Object o) {
+        for (final DynamicGridKeyboard kbd : keyboards) {
+            if (o instanceof Integer) {
+                final int code = (Integer) o;
+                final Key key = kbd.getKey(code);
+                if (key != null) {
+                    return key;
+                }
+            } else if (o instanceof String) {
+                final String outputText = (String) o;
+                final Key key = kbd.getKeyFromOutputText(outputText);
+                if (key != null) {
+                    return key;
+                }
+            } else {
+                Log.w(TAG, "Invalid object: " + o);
+            }
+        }
+        return null;
+    }
+
+    public void loadRecentKeys(Collection<DynamicGridKeyboard> keyboards) {
+        final String str = Settings.readEmojiRecentKeys(mPrefs);
+        final List<Object> keys = StringUtils.jsonStrToList(str);
+        for (final Object o : keys) {
+            addKeyLast(getKey(keyboards, o));
+        }
+    }
+
+    private int getKeyX(final int index) {
+        final int column = index % mColumnsNum;
+        return column * mHorizontalStep + mLeftPadding;
+    }
+
+    private int getKeyY(final int index) {
+        final int row = index / mColumnsNum;
+        return row * mVerticalStep + mTopPadding;
+    }
+
+    @Override
+    public Key[] getKeys() {
+        synchronized (mGridKeys) {
+            if (mCachedGridKeys != null) {
+                return mCachedGridKeys;
+            }
+            mCachedGridKeys = mGridKeys.toArray(new Key[mGridKeys.size()]);
+            return mCachedGridKeys;
+        }
+    }
+
+    @Override
+    public Key[] getNearestKeys(final int x, final int y) {
+        // TODO: Calculate the nearest key index in mGridKeys from x and y.
+        return getKeys();
+    }
+
+    static final class GridKey extends Key {
+        private int mCurrentX;
+        private int mCurrentY;
+
+        public GridKey(final Key originalKey) {
+            super(originalKey);
+        }
+
+        public void updateCorrdinates(final int x, final int y) {
+            mCurrentX = x;
+            mCurrentY = y;
+            getHitBox().set(x, y, x + getWidth(), y + getHeight());
+        }
+
+        @Override
+        public int getX() {
+            return mCurrentX;
+        }
+
+        @Override
+        public int getY() {
+            return mCurrentY;
+        }
+
+        @Override
+        public boolean equals(final Object o) {
+            if (!(o instanceof Key)) return false;
+            final Key key = (Key)o;
+            if (getCode() != key.getCode()) return false;
+            if (!TextUtils.equals(getLabel(), key.getLabel())) return false;
+            return TextUtils.equals(getOutputText(), key.getOutputText());
+        }
+
+        @Override
+        public String toString() {
+            return "GridKey: " + super.toString();
+        }
+    }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyStyle.java b/java/src/com/android/inputmethod/keyboard/internal/KeyStyle.java
index f650569..e6a6743 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyStyle.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyStyle.java
@@ -24,7 +24,7 @@
     public abstract String[] getStringArray(TypedArray a, int index);
     public abstract String getString(TypedArray a, int index);
     public abstract int getInt(TypedArray a, int index, int defaultValue);
-    public abstract int getFlag(TypedArray a, int index);
+    public abstract int getFlags(TypedArray a, int index);
 
     protected KeyStyle(final KeyboardTextsSet textsSet) {
         mTextsSet = textsSet;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyStylesSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyStylesSet.java
index 6aab3e7..05d855e 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyStylesSet.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyStylesSet.java
@@ -66,7 +66,7 @@
         }
 
         @Override
-        public int getFlag(final TypedArray a, final int index) {
+        public int getFlags(final TypedArray a, final int index) {
             return a.getInt(index, 0);
         }
     }
@@ -123,14 +123,12 @@
         }
 
         @Override
-        public int getFlag(final TypedArray a, final int index) {
-            int flags = a.getInt(index, 0);
-            final Object value = mStyleAttributes.get(index);
-            if (value != null) {
-                flags |= (Integer)value;
-            }
-            final KeyStyle parentStyle = mStyles.get(mParentStyleName);
-            return flags | parentStyle.getFlag(a, index);
+        public int getFlags(final TypedArray a, final int index) {
+            final int parentFlags = mStyles.get(mParentStyleName).getFlags(a, index);
+            final Integer value = (Integer)mStyleAttributes.get(index);
+            final int styleFlags = (value != null) ? value : 0;
+            final int flags = a.getInt(index, 0);
+            return flags | styleFlags | parentFlags;
         }
 
         public void readKeyAttributes(final TypedArray keyAttr) {
@@ -142,13 +140,13 @@
             readString(keyAttr, R.styleable.Keyboard_Key_keyHintLabel);
             readStringArray(keyAttr, R.styleable.Keyboard_Key_moreKeys);
             readStringArray(keyAttr, R.styleable.Keyboard_Key_additionalMoreKeys);
-            readFlag(keyAttr, R.styleable.Keyboard_Key_keyLabelFlags);
+            readFlags(keyAttr, R.styleable.Keyboard_Key_keyLabelFlags);
             readString(keyAttr, R.styleable.Keyboard_Key_keyIcon);
             readString(keyAttr, R.styleable.Keyboard_Key_keyIconDisabled);
             readString(keyAttr, R.styleable.Keyboard_Key_keyIconPreview);
             readInt(keyAttr, R.styleable.Keyboard_Key_maxMoreKeysColumn);
             readInt(keyAttr, R.styleable.Keyboard_Key_backgroundType);
-            readFlag(keyAttr, R.styleable.Keyboard_Key_keyActionFlags);
+            readFlags(keyAttr, R.styleable.Keyboard_Key_keyActionFlags);
         }
 
         private void readString(final TypedArray a, final int index) {
@@ -163,10 +161,11 @@
             }
         }
 
-        private void readFlag(final TypedArray a, final int index) {
+        private void readFlags(final TypedArray a, final int index) {
             if (a.hasValue(index)) {
                 final Integer value = (Integer)mStyleAttributes.get(index);
-                mStyleAttributes.put(index, a.getInt(index, 0) | (value != null ? value : 0));
+                final int styleFlags = value != null ? value : 0;
+                mStyleAttributes.put(index, a.getInt(index, 0) | styleFlags);
             }
         }
 
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java
index b34d7c4..22f7a83 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java
@@ -29,6 +29,7 @@
 import com.android.inputmethod.keyboard.Key;
 import com.android.inputmethod.keyboard.Keyboard;
 import com.android.inputmethod.keyboard.KeyboardId;
+import com.android.inputmethod.latin.Constants;
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.utils.ResourceUtils;
 import com.android.inputmethod.latin.utils.RunInLocale;
@@ -113,6 +114,7 @@
  * </pre>
  */
 
+// TODO: Write unit tests for this class.
 public class KeyboardBuilder<KP extends KeyboardParams> {
     private static final String BUILDER_TAG = "Keyboard.Builder";
     private static final boolean DEBUG = false;
@@ -120,6 +122,7 @@
     // Keyboard XML Tags
     private static final String TAG_KEYBOARD = "Keyboard";
     private static final String TAG_ROW = "Row";
+    private static final String TAG_GRID_ROWS = "GridRows";
     private static final String TAG_KEY = "Key";
     private static final String TAG_SPACER = "Spacer";
     private static final String TAG_INCLUDE = "include";
@@ -218,20 +221,18 @@
                     parseKeyboardAttributes(parser);
                     startKeyboard();
                     parseKeyboardContent(parser, false);
-                    break;
-                } else {
-                    throw new XmlParseUtils.IllegalStartTag(parser, tag, TAG_KEYBOARD);
+                    return;
                 }
+                throw new XmlParseUtils.IllegalStartTag(parser, tag, TAG_KEYBOARD);
             }
         }
     }
 
     private void parseKeyboardAttributes(final XmlPullParser parser) {
+        final AttributeSet attr = Xml.asAttributeSet(parser);
         final TypedArray keyboardAttr = mContext.obtainStyledAttributes(
-                Xml.asAttributeSet(parser), R.styleable.Keyboard, R.attr.keyboardStyle,
-                R.style.Keyboard);
-        final TypedArray keyAttr = mResources.obtainAttributes(Xml.asAttributeSet(parser),
-                R.styleable.Keyboard_Key);
+                attr, R.styleable.Keyboard, R.attr.keyboardStyle, R.style.Keyboard);
+        final TypedArray keyAttr = mResources.obtainAttributes(attr, R.styleable.Keyboard_Key);
         try {
             final KeyboardParams params = mParams;
             final int height = params.mId.mHeight;
@@ -314,6 +315,9 @@
                         startRow(row);
                     }
                     parseRowContent(parser, row, skip);
+                } else if (TAG_GRID_ROWS.equals(tag)) {
+                    if (DEBUG) startTag("<%s>%s", TAG_GRID_ROWS, skip ? " skipped" : "");
+                    parseGridRows(parser, skip);
                 } else if (TAG_INCLUDE.equals(tag)) {
                     parseIncludeKeyboardContent(parser, skip);
                 } else if (TAG_SWITCH.equals(tag)) {
@@ -328,31 +332,30 @@
                 if (DEBUG) endTag("</%s>", tag);
                 if (TAG_KEYBOARD.equals(tag)) {
                     endKeyboard();
-                    break;
-                } else if (TAG_CASE.equals(tag) || TAG_DEFAULT.equals(tag)
-                        || TAG_MERGE.equals(tag)) {
-                    break;
-                } else {
-                    throw new XmlParseUtils.IllegalEndTag(parser, tag, TAG_ROW);
+                    return;
                 }
+                if (TAG_CASE.equals(tag) || TAG_DEFAULT.equals(tag) || TAG_MERGE.equals(tag)) {
+                    return;
+                }
+                throw new XmlParseUtils.IllegalEndTag(parser, tag, TAG_ROW);
             }
         }
     }
 
     private KeyboardRow parseRowAttributes(final XmlPullParser parser)
             throws XmlPullParserException {
-        final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser),
-                R.styleable.Keyboard);
+        final AttributeSet attr = Xml.asAttributeSet(parser);
+        final TypedArray keyboardAttr = mResources.obtainAttributes(attr, R.styleable.Keyboard);
         try {
-            if (a.hasValue(R.styleable.Keyboard_horizontalGap)) {
+            if (keyboardAttr.hasValue(R.styleable.Keyboard_horizontalGap)) {
                 throw new XmlParseUtils.IllegalAttribute(parser, TAG_ROW, "horizontalGap");
             }
-            if (a.hasValue(R.styleable.Keyboard_verticalGap)) {
+            if (keyboardAttr.hasValue(R.styleable.Keyboard_verticalGap)) {
                 throw new XmlParseUtils.IllegalAttribute(parser, TAG_ROW, "verticalGap");
             }
             return new KeyboardRow(mResources, mParams, parser, mCurrentY);
         } finally {
-            a.recycle();
+            keyboardAttr.recycle();
         }
     }
 
@@ -382,34 +385,97 @@
                     if (!skip) {
                         endRow(row);
                     }
-                    break;
-                } else if (TAG_CASE.equals(tag) || TAG_DEFAULT.equals(tag)
-                        || TAG_MERGE.equals(tag)) {
-                    break;
-                } else {
-                    throw new XmlParseUtils.IllegalEndTag(parser, tag, TAG_ROW);
+                    return;
                 }
+                if (TAG_CASE.equals(tag) || TAG_DEFAULT.equals(tag) || TAG_MERGE.equals(tag)) {
+                    return;
+                }
+                throw new XmlParseUtils.IllegalEndTag(parser, tag, TAG_ROW);
             }
         }
     }
 
+    private void parseGridRows(final XmlPullParser parser, final boolean skip)
+            throws XmlPullParserException, IOException {
+        if (skip) {
+            XmlParseUtils.checkEndTag(TAG_GRID_ROWS, parser);
+            if (DEBUG) {
+                startEndTag("<%s /> skipped", TAG_GRID_ROWS);
+            }
+            return;
+        }
+        final KeyboardRow gridRows = new KeyboardRow(mResources, mParams, parser, mCurrentY);
+        final TypedArray gridRowAttr = mResources.obtainAttributes(
+                Xml.asAttributeSet(parser), R.styleable.Keyboard_GridRows);
+        final int codesArrayId = gridRowAttr.getResourceId(
+                R.styleable.Keyboard_GridRows_codesArray, 0);
+        final int textsArrayId = gridRowAttr.getResourceId(
+                R.styleable.Keyboard_GridRows_textsArray, 0);
+        gridRowAttr.recycle();
+        if (codesArrayId == 0 && textsArrayId == 0) {
+            throw new XmlParseUtils.ParseException(
+                    "Missing codesArray or textsArray attributes", parser);
+        }
+        if (codesArrayId != 0 && textsArrayId != 0) {
+            throw new XmlParseUtils.ParseException(
+                    "Both codesArray and textsArray attributes specifed", parser);
+        }
+        final String[] array = mResources.getStringArray(
+                codesArrayId != 0 ? codesArrayId : textsArrayId);
+        final int counts = array.length;
+        final float keyWidth = gridRows.getKeyWidth(null, 0.0f);
+        final int numColumns = (int)(mParams.mOccupiedWidth / keyWidth);
+        for (int index = 0; index < counts; index += numColumns) {
+            final KeyboardRow row = new KeyboardRow(mResources, mParams, parser, mCurrentY);
+            startRow(row);
+            for (int c = 0; c < numColumns; c++) {
+                final int i = index + c;
+                if (i >= counts) {
+                    break;
+                }
+                final String label;
+                final int code;
+                final String outputText;
+                if (codesArrayId != 0) {
+                    final String codeArraySpec = array[i];
+                    label = CodesArrayParser.parseLabel(codeArraySpec);
+                    code = CodesArrayParser.parseCode(codeArraySpec);
+                    outputText = CodesArrayParser.parseOutputText(codeArraySpec);
+                } else {
+                    final String textArraySpec = array[i];
+                    // TODO: Utilize KeySpecParser or write more generic TextsArrayParser.
+                    label = textArraySpec;
+                    code = Constants.CODE_OUTPUT_TEXT;
+                    outputText = textArraySpec + (char)Constants.CODE_SPACE;
+                }
+                final int x = (int)row.getKeyX(null);
+                final int y = row.getKeyY();
+                final Key key = new Key(mParams, label, null /* hintLabel */, 0 /* iconId */,
+                        code, outputText, x, y, (int)keyWidth, (int)row.getRowHeight(),
+                        row.getDefaultKeyLabelFlags(), row.getDefaultBackgroundType());
+                endKey(key);
+                row.advanceXPos(keyWidth);
+            }
+            endRow(row);
+        }
+
+        XmlParseUtils.checkEndTag(TAG_GRID_ROWS, parser);
+    }
+
     private void parseKey(final XmlPullParser parser, final KeyboardRow row, final boolean skip)
             throws XmlPullParserException, IOException {
         if (skip) {
             XmlParseUtils.checkEndTag(TAG_KEY, parser);
-            if (DEBUG) {
-                startEndTag("<%s /> skipped", TAG_KEY);
-            }
-        } else {
-            final Key key = new Key(mResources, mParams, row, parser);
-            if (DEBUG) {
-                startEndTag("<%s%s %s moreKeys=%s />", TAG_KEY,
-                        (key.isEnabled() ? "" : " disabled"), key,
-                        Arrays.toString(key.mMoreKeys));
-            }
-            XmlParseUtils.checkEndTag(TAG_KEY, parser);
-            endKey(key);
+            if (DEBUG) startEndTag("<%s /> skipped", TAG_KEY);
+            return;
         }
+        final Key key = new Key(mResources, mParams, row, parser);
+        if (DEBUG) {
+            startEndTag("<%s%s %s moreKeys=%s />", TAG_KEY, (key.isEnabled() ? "" : " disabled"),
+                    key, Arrays.toString(key.getMoreKeys()));
+        }
+        XmlParseUtils.checkEndTag(TAG_KEY, parser);
+        endKey(key);
     }
 
     private void parseSpacer(final XmlPullParser parser, final KeyboardRow row, final boolean skip)
@@ -417,12 +483,12 @@
         if (skip) {
             XmlParseUtils.checkEndTag(TAG_SPACER, parser);
             if (DEBUG) startEndTag("<%s /> skipped", TAG_SPACER);
-        } else {
-            final Key.Spacer spacer = new Key.Spacer(mResources, mParams, row, parser);
-            if (DEBUG) startEndTag("<%s />", TAG_SPACER);
-            XmlParseUtils.checkEndTag(TAG_SPACER, parser);
-            endKey(spacer);
+            return;
         }
+        final Key.Spacer spacer = new Key.Spacer(mResources, mParams, row, parser);
+        if (DEBUG) startEndTag("<%s />", TAG_SPACER);
+        XmlParseUtils.checkEndTag(TAG_SPACER, parser);
+        endKey(spacer);
     }
 
     private void parseIncludeKeyboardContent(final XmlPullParser parser, final boolean skip)
@@ -440,66 +506,44 @@
         if (skip) {
             XmlParseUtils.checkEndTag(TAG_INCLUDE, parser);
             if (DEBUG) startEndTag("</%s> skipped", TAG_INCLUDE);
-        } else {
-            final AttributeSet attr = Xml.asAttributeSet(parser);
-            final TypedArray keyboardAttr = mResources.obtainAttributes(attr,
-                    R.styleable.Keyboard_Include);
-            final TypedArray keyAttr = mResources.obtainAttributes(attr,
-                    R.styleable.Keyboard_Key);
-            int keyboardLayout = 0;
-            float savedDefaultKeyWidth = 0;
-            int savedDefaultKeyLabelFlags = 0;
-            int savedDefaultBackgroundType = Key.BACKGROUND_TYPE_NORMAL;
-            try {
-                XmlParseUtils.checkAttributeExists(keyboardAttr,
-                        R.styleable.Keyboard_Include_keyboardLayout, "keyboardLayout",
-                        TAG_INCLUDE, parser);
-                keyboardLayout = keyboardAttr.getResourceId(
-                        R.styleable.Keyboard_Include_keyboardLayout, 0);
-                if (row != null) {
-                    if (keyAttr.hasValue(R.styleable.Keyboard_Key_keyXPos)) {
-                        // Override current x coordinate.
-                        row.setXPos(row.getKeyX(keyAttr));
-                    }
-                    // TODO: Remove this if-clause and do the same as backgroundType below.
-                    savedDefaultKeyWidth = row.getDefaultKeyWidth();
-                    if (keyAttr.hasValue(R.styleable.Keyboard_Key_keyWidth)) {
-                        // Override default key width.
-                        row.setDefaultKeyWidth(row.getKeyWidth(keyAttr));
-                    }
-                    savedDefaultKeyLabelFlags = row.getDefaultKeyLabelFlags();
-                    // Bitwise-or default keyLabelFlag if exists.
-                    row.setDefaultKeyLabelFlags(keyAttr.getInt(
-                            R.styleable.Keyboard_Key_keyLabelFlags, 0)
-                            | savedDefaultKeyLabelFlags);
-                    savedDefaultBackgroundType = row.getDefaultBackgroundType();
-                    // Override default backgroundType if exists.
-                    row.setDefaultBackgroundType(keyAttr.getInt(
-                            R.styleable.Keyboard_Key_backgroundType,
-                            savedDefaultBackgroundType));
-                }
-            } finally {
-                keyboardAttr.recycle();
-                keyAttr.recycle();
+            return;
+        }
+        final AttributeSet attr = Xml.asAttributeSet(parser);
+        final TypedArray keyboardAttr = mResources.obtainAttributes(
+                attr, R.styleable.Keyboard_Include);
+        final TypedArray keyAttr = mResources.obtainAttributes(attr, R.styleable.Keyboard_Key);
+        int keyboardLayout = 0;
+        try {
+            XmlParseUtils.checkAttributeExists(
+                    keyboardAttr, R.styleable.Keyboard_Include_keyboardLayout, "keyboardLayout",
+                    TAG_INCLUDE, parser);
+            keyboardLayout = keyboardAttr.getResourceId(
+                    R.styleable.Keyboard_Include_keyboardLayout, 0);
+            if (row != null) {
+                // Override current x coordinate.
+                row.setXPos(row.getKeyX(keyAttr));
+                // Push current Row attributes and update with new attributes.
+                row.pushRowAttributes(keyAttr);
             }
+        } finally {
+            keyboardAttr.recycle();
+            keyAttr.recycle();
+        }
 
-            XmlParseUtils.checkEndTag(TAG_INCLUDE, parser);
-            if (DEBUG) {
-                startEndTag("<%s keyboardLayout=%s />",TAG_INCLUDE,
-                        mResources.getResourceEntryName(keyboardLayout));
+        XmlParseUtils.checkEndTag(TAG_INCLUDE, parser);
+        if (DEBUG) {
+            startEndTag("<%s keyboardLayout=%s />",TAG_INCLUDE,
+                    mResources.getResourceEntryName(keyboardLayout));
+        }
+        final XmlResourceParser parserForInclude = mResources.getXml(keyboardLayout);
+        try {
+            parseMerge(parserForInclude, row, skip);
+        } finally {
+            if (row != null) {
+                // Restore Row attributes.
+                row.popRowAttributes();
             }
-            final XmlResourceParser parserForInclude = mResources.getXml(keyboardLayout);
-            try {
-                parseMerge(parserForInclude, row, skip);
-            } finally {
-                if (row != null) {
-                    // Restore default keyWidth, keyLabelFlags, and backgroundType.
-                    row.setDefaultKeyWidth(savedDefaultKeyWidth);
-                    row.setDefaultKeyLabelFlags(savedDefaultKeyLabelFlags);
-                    row.setDefaultBackgroundType(savedDefaultBackgroundType);
-                }
-                parserForInclude.close();
-            }
+            parserForInclude.close();
         }
     }
 
@@ -516,11 +560,10 @@
                     } else {
                         parseRowContent(parser, row, skip);
                     }
-                    break;
-                } else {
-                    throw new XmlParseUtils.ParseException(
-                            "Included keyboard layout must have <merge> root element", parser);
+                    return;
                 }
+                throw new XmlParseUtils.ParseException(
+                        "Included keyboard layout must have <merge> root element", parser);
             }
         }
     }
@@ -554,10 +597,9 @@
                 final String tag = parser.getName();
                 if (TAG_SWITCH.equals(tag)) {
                     if (DEBUG) endTag("</%s>", TAG_SWITCH);
-                    break;
-                } else {
-                    throw new XmlParseUtils.IllegalEndTag(parser, tag, TAG_SWITCH);
+                    return;
                 }
+                throw new XmlParseUtils.IllegalEndTag(parser, tag, TAG_SWITCH);
             }
         }
     }
@@ -580,86 +622,92 @@
         if (id == null) {
             return true;
         }
-        final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser),
-                R.styleable.Keyboard_Case);
+        final AttributeSet attr = Xml.asAttributeSet(parser);
+        final TypedArray caseAttr = mResources.obtainAttributes(attr, R.styleable.Keyboard_Case);
         try {
-            final boolean keyboardLayoutSetElementMatched = matchTypedValue(a,
+            final boolean keyboardLayoutSetMatched = matchString(caseAttr,
+                    R.styleable.Keyboard_Case_keyboardLayoutSet,
+                    SubtypeLocaleUtils.getKeyboardLayoutSetName(id.mSubtype));
+            final boolean keyboardLayoutSetElementMatched = matchTypedValue(caseAttr,
                     R.styleable.Keyboard_Case_keyboardLayoutSetElement, id.mElementId,
                     KeyboardId.elementIdToName(id.mElementId));
-            final boolean modeMatched = matchTypedValue(a,
+            final boolean modeMatched = matchTypedValue(caseAttr,
                     R.styleable.Keyboard_Case_mode, id.mMode, KeyboardId.modeName(id.mMode));
-            final boolean navigateNextMatched = matchBoolean(a,
+            final boolean navigateNextMatched = matchBoolean(caseAttr,
                     R.styleable.Keyboard_Case_navigateNext, id.navigateNext());
-            final boolean navigatePreviousMatched = matchBoolean(a,
+            final boolean navigatePreviousMatched = matchBoolean(caseAttr,
                     R.styleable.Keyboard_Case_navigatePrevious, id.navigatePrevious());
-            final boolean passwordInputMatched = matchBoolean(a,
+            final boolean passwordInputMatched = matchBoolean(caseAttr,
                     R.styleable.Keyboard_Case_passwordInput, id.passwordInput());
-            final boolean clobberSettingsKeyMatched = matchBoolean(a,
+            final boolean clobberSettingsKeyMatched = matchBoolean(caseAttr,
                     R.styleable.Keyboard_Case_clobberSettingsKey, id.mClobberSettingsKey);
-            final boolean shortcutKeyEnabledMatched = matchBoolean(a,
+            final boolean shortcutKeyEnabledMatched = matchBoolean(caseAttr,
                     R.styleable.Keyboard_Case_shortcutKeyEnabled, id.mShortcutKeyEnabled);
-            final boolean shortcutKeyOnSymbolsMatched = matchBoolean(a,
+            final boolean shortcutKeyOnSymbolsMatched = matchBoolean(caseAttr,
                     R.styleable.Keyboard_Case_shortcutKeyOnSymbols, id.mShortcutKeyOnSymbols);
-            final boolean hasShortcutKeyMatched = matchBoolean(a,
+            final boolean hasShortcutKeyMatched = matchBoolean(caseAttr,
                     R.styleable.Keyboard_Case_hasShortcutKey, id.mHasShortcutKey);
-            final boolean languageSwitchKeyEnabledMatched = matchBoolean(a,
+            final boolean languageSwitchKeyEnabledMatched = matchBoolean(caseAttr,
                     R.styleable.Keyboard_Case_languageSwitchKeyEnabled,
                     id.mLanguageSwitchKeyEnabled);
-            final boolean isMultiLineMatched = matchBoolean(a,
+            final boolean isMultiLineMatched = matchBoolean(caseAttr,
                     R.styleable.Keyboard_Case_isMultiLine, id.isMultiLine());
-            final boolean imeActionMatched = matchInteger(a,
+            final boolean imeActionMatched = matchInteger(caseAttr,
                     R.styleable.Keyboard_Case_imeAction, id.imeAction());
-            final boolean localeCodeMatched = matchString(a,
+            final boolean localeCodeMatched = matchString(caseAttr,
                     R.styleable.Keyboard_Case_localeCode, id.mLocale.toString());
-            final boolean languageCodeMatched = matchString(a,
+            final boolean languageCodeMatched = matchString(caseAttr,
                     R.styleable.Keyboard_Case_languageCode, id.mLocale.getLanguage());
-            final boolean countryCodeMatched = matchString(a,
+            final boolean countryCodeMatched = matchString(caseAttr,
                     R.styleable.Keyboard_Case_countryCode, id.mLocale.getCountry());
-            final boolean selected = keyboardLayoutSetElementMatched && modeMatched
-                    && navigateNextMatched && navigatePreviousMatched && passwordInputMatched
-                    && clobberSettingsKeyMatched && shortcutKeyEnabledMatched
-                    && shortcutKeyOnSymbolsMatched && hasShortcutKeyMatched
-                    && languageSwitchKeyEnabledMatched && isMultiLineMatched && imeActionMatched
-                    && localeCodeMatched && languageCodeMatched && countryCodeMatched;
+            final boolean selected = keyboardLayoutSetMatched && keyboardLayoutSetElementMatched
+                    && modeMatched && navigateNextMatched && navigatePreviousMatched
+                    && passwordInputMatched && clobberSettingsKeyMatched
+                    && shortcutKeyEnabledMatched && shortcutKeyOnSymbolsMatched
+                    && hasShortcutKeyMatched && languageSwitchKeyEnabledMatched
+                    && isMultiLineMatched && imeActionMatched && localeCodeMatched
+                    && languageCodeMatched && countryCodeMatched;
 
             if (DEBUG) {
-                startTag("<%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s>%s", TAG_CASE,
-                        textAttr(a.getString(
+                startTag("<%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s>%s", TAG_CASE,
+                        textAttr(caseAttr.getString(
+                                R.styleable.Keyboard_Case_keyboardLayoutSet), "keyboardLayoutSet"),
+                        textAttr(caseAttr.getString(
                                 R.styleable.Keyboard_Case_keyboardLayoutSetElement),
                                 "keyboardLayoutSetElement"),
-                        textAttr(a.getString(R.styleable.Keyboard_Case_mode), "mode"),
-                        textAttr(a.getString(R.styleable.Keyboard_Case_imeAction),
+                        textAttr(caseAttr.getString(R.styleable.Keyboard_Case_mode), "mode"),
+                        textAttr(caseAttr.getString(R.styleable.Keyboard_Case_imeAction),
                                 "imeAction"),
-                        booleanAttr(a, R.styleable.Keyboard_Case_navigateNext,
+                        booleanAttr(caseAttr, R.styleable.Keyboard_Case_navigateNext,
                                 "navigateNext"),
-                        booleanAttr(a, R.styleable.Keyboard_Case_navigatePrevious,
+                        booleanAttr(caseAttr, R.styleable.Keyboard_Case_navigatePrevious,
                                 "navigatePrevious"),
-                        booleanAttr(a, R.styleable.Keyboard_Case_clobberSettingsKey,
+                        booleanAttr(caseAttr, R.styleable.Keyboard_Case_clobberSettingsKey,
                                 "clobberSettingsKey"),
-                        booleanAttr(a, R.styleable.Keyboard_Case_passwordInput,
+                        booleanAttr(caseAttr, R.styleable.Keyboard_Case_passwordInput,
                                 "passwordInput"),
-                        booleanAttr(a, R.styleable.Keyboard_Case_shortcutKeyEnabled,
+                        booleanAttr(caseAttr, R.styleable.Keyboard_Case_shortcutKeyEnabled,
                                 "shortcutKeyEnabled"),
-                        booleanAttr(a, R.styleable.Keyboard_Case_shortcutKeyOnSymbols,
+                        booleanAttr(caseAttr, R.styleable.Keyboard_Case_shortcutKeyOnSymbols,
                                 "shortcutKeyOnSymbols"),
-                        booleanAttr(a, R.styleable.Keyboard_Case_hasShortcutKey,
+                        booleanAttr(caseAttr, R.styleable.Keyboard_Case_hasShortcutKey,
                                 "hasShortcutKey"),
-                        booleanAttr(a, R.styleable.Keyboard_Case_languageSwitchKeyEnabled,
+                        booleanAttr(caseAttr, R.styleable.Keyboard_Case_languageSwitchKeyEnabled,
                                 "languageSwitchKeyEnabled"),
-                        booleanAttr(a, R.styleable.Keyboard_Case_isMultiLine,
+                        booleanAttr(caseAttr, R.styleable.Keyboard_Case_isMultiLine,
                                 "isMultiLine"),
-                        textAttr(a.getString(R.styleable.Keyboard_Case_localeCode),
+                        textAttr(caseAttr.getString(R.styleable.Keyboard_Case_localeCode),
                                 "localeCode"),
-                        textAttr(a.getString(R.styleable.Keyboard_Case_languageCode),
+                        textAttr(caseAttr.getString(R.styleable.Keyboard_Case_languageCode),
                                 "languageCode"),
-                        textAttr(a.getString(R.styleable.Keyboard_Case_countryCode),
+                        textAttr(caseAttr.getString(R.styleable.Keyboard_Case_countryCode),
                                 "countryCode"),
                         selected ? "" : " skipped");
             }
 
             return selected;
         } finally {
-            a.recycle();
+            caseAttr.recycle();
         }
     }
 
@@ -692,7 +740,8 @@
         }
         if (ResourceUtils.isIntegerValue(v)) {
             return intValue == a.getInt(index, 0);
-        } else if (ResourceUtils.isStringValue(v)) {
+        }
+        if (ResourceUtils.isStringValue(v)) {
             return StringUtils.containsInArray(strValue, a.getString(index).split("\\|"));
         }
         return false;
@@ -711,10 +760,10 @@
 
     private void parseKeyStyle(final XmlPullParser parser, final boolean skip)
             throws XmlPullParserException, IOException {
-        TypedArray keyStyleAttr = mResources.obtainAttributes(Xml.asAttributeSet(parser),
-                R.styleable.Keyboard_KeyStyle);
-        TypedArray keyAttrs = mResources.obtainAttributes(Xml.asAttributeSet(parser),
-                R.styleable.Keyboard_Key);
+        final AttributeSet attr = Xml.asAttributeSet(parser);
+        final TypedArray keyStyleAttr = mResources.obtainAttributes(
+                attr, R.styleable.Keyboard_KeyStyle);
+        final TypedArray keyAttrs = mResources.obtainAttributes(attr, R.styleable.Keyboard_Key);
         try {
             if (!keyStyleAttr.hasValue(R.styleable.Keyboard_KeyStyle_styleName)) {
                 throw new XmlParseUtils.ParseException("<" + TAG_KEY_STYLE
@@ -756,7 +805,7 @@
             mRightEdgeKey = null;
         }
         addEdgeSpace(mParams.mRightPadding, row);
-        mCurrentY += row.mRowHeight;
+        mCurrentY += row.getRowHeight();
         mCurrentRow = null;
         mTopEdge = false;
     }
@@ -774,7 +823,10 @@
     }
 
     private void endKeyboard() {
-        // nothing to do here.
+        // {@link #parseGridRows(XmlPullParser,boolean)} may populate keyboard rows higher than
+        // previously expected.
+        final int actualHeight = mCurrentY - mParams.mVerticalGap + mParams.mBottomPadding;
+        mParams.mOccupiedHeight = Math.max(mParams.mOccupiedHeight, actualHeight);
     }
 
     private void addEdgeSpace(final float width, final KeyboardRow row) {
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java
index a57b83a..d32bb75 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java
@@ -85,7 +85,7 @@
     public void onAddKey(final Key newKey) {
         final Key key = (mKeysCache != null) ? mKeysCache.get(newKey) : newKey;
         final boolean isSpacer = key.isSpacer();
-        if (isSpacer && key.mWidth == 0) {
+        if (isSpacer && key.getWidth() == 0) {
             // Ignore zero width {@link Spacer}.
             return;
         }
@@ -94,7 +94,7 @@
             return;
         }
         updateHistogram(key);
-        if (key.mCode == Constants.CODE_SHIFT) {
+        if (key.getCode() == Constants.CODE_SHIFT) {
             mShiftKeys.add(key);
         }
         if (key.altCodeWhileTyping()) {
@@ -125,14 +125,14 @@
     }
 
     private void updateHistogram(final Key key) {
-        final int height = key.mHeight + mVerticalGap;
+        final int height = key.getHeight() + mVerticalGap;
         final int heightCount = updateHistogramCounter(mHeightHistogram, height);
         if (heightCount > mMaxHeightCount) {
             mMaxHeightCount = heightCount;
             mMostCommonKeyHeight = height;
         }
 
-        final int width = key.mWidth + mHorizontalGap;
+        final int width = key.getWidth() + mHorizontalGap;
         final int widthCount = updateHistogramCounter(mWidthHistogram, width);
         if (widthCount > mMaxWidthCount) {
             mMaxWidthCount = widthCount;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardRow.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardRow.java
index 5fe84a7..0f9497c 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardRow.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardRow.java
@@ -23,10 +23,13 @@
 import com.android.inputmethod.keyboard.Key;
 import com.android.inputmethod.keyboard.Keyboard;
 import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.utils.CollectionUtils;
 import com.android.inputmethod.latin.utils.ResourceUtils;
 
 import org.xmlpull.v1.XmlPullParser;
 
+import java.util.ArrayDeque;
+
 /**
  * Container for keys in the keyboard. All keys in a row are at the same Y-coordinate.
  * Some of the key size defaults can be overridden per row from what the {@link Keyboard}
@@ -38,64 +41,100 @@
     private static final int KEYWIDTH_FILL_RIGHT = -1;
 
     private final KeyboardParams mParams;
-    /** Default width of a key in this row. */
-    private float mDefaultKeyWidth;
-    /** Default height of a key in this row. */
-    public final int mRowHeight;
-    /** Default keyLabelFlags in this row. */
-    private int mDefaultKeyLabelFlags;
-    /** Default backgroundType for this row */
-    private int mDefaultBackgroundType;
+    /** The height of this row. */
+    private final int mRowHeight;
+
+    private final ArrayDeque<RowAttributes> mRowAttributesStack = CollectionUtils.newArrayDeque();
+
+    private static class RowAttributes {
+        /** Default width of a key in this row. */
+        public final float mDefaultKeyWidth;
+        /** Default keyLabelFlags in this row. */
+        public final int mDefaultKeyLabelFlags;
+        /** Default backgroundType for this row */
+        public final int mDefaultBackgroundType;
+
+        /**
+         * Parse and create key attributes. This constructor is used to parse Row tag.
+         *
+         * @param keyAttr an attributes array of Row tag.
+         * @param defaultKeyWidth a default key width.
+         * @param keyboardWidth the keyboard width that is required to calculate keyWidth attribute.
+         */
+        public RowAttributes(final TypedArray keyAttr, final float defaultKeyWidth,
+                final int keyboardWidth) {
+            mDefaultKeyWidth = keyAttr.getFraction(R.styleable.Keyboard_Key_keyWidth,
+                    keyboardWidth, keyboardWidth, defaultKeyWidth);
+            mDefaultKeyLabelFlags = keyAttr.getInt(R.styleable.Keyboard_Key_keyLabelFlags, 0);
+            mDefaultBackgroundType = keyAttr.getInt(R.styleable.Keyboard_Key_backgroundType,
+                    Key.BACKGROUND_TYPE_NORMAL);
+        }
+
+        /**
+         * Parse and update key attributes using default attributes. This constructor is used
+         * to parse include tag.
+         *
+         * @param keyAttr an attributes array of include tag.
+         * @param defaultRowAttr default Row attributes.
+         * @param keyboardWidth the keyboard width that is required to calculate keyWidth attribute.
+         */
+        public RowAttributes(final TypedArray keyAttr, final RowAttributes defaultRowAttr,
+                final int keyboardWidth) {
+            mDefaultKeyWidth = keyAttr.getFraction(R.styleable.Keyboard_Key_keyWidth,
+                    keyboardWidth, keyboardWidth, defaultRowAttr.mDefaultKeyWidth);
+            mDefaultKeyLabelFlags = keyAttr.getInt(R.styleable.Keyboard_Key_keyLabelFlags, 0)
+                    | defaultRowAttr.mDefaultKeyLabelFlags;
+            mDefaultBackgroundType = keyAttr.getInt(R.styleable.Keyboard_Key_backgroundType,
+                    defaultRowAttr.mDefaultBackgroundType);
+        }
+    }
 
     private final int mCurrentY;
     // Will be updated by {@link Key}'s constructor.
     private float mCurrentX;
 
-    public KeyboardRow(final Resources res, final KeyboardParams params, final XmlPullParser parser,
-            final int y) {
+    public KeyboardRow(final Resources res, final KeyboardParams params,
+            final XmlPullParser parser, final int y) {
         mParams = params;
         final TypedArray keyboardAttr = res.obtainAttributes(Xml.asAttributeSet(parser),
                 R.styleable.Keyboard);
         mRowHeight = (int)ResourceUtils.getDimensionOrFraction(keyboardAttr,
-                R.styleable.Keyboard_rowHeight,
-                params.mBaseHeight, params.mDefaultRowHeight);
+                R.styleable.Keyboard_rowHeight, params.mBaseHeight, params.mDefaultRowHeight);
         keyboardAttr.recycle();
         final TypedArray keyAttr = res.obtainAttributes(Xml.asAttributeSet(parser),
                 R.styleable.Keyboard_Key);
-        mDefaultKeyWidth = keyAttr.getFraction(R.styleable.Keyboard_Key_keyWidth,
-                params.mBaseWidth, params.mBaseWidth, params.mDefaultKeyWidth);
-        mDefaultBackgroundType = keyAttr.getInt(R.styleable.Keyboard_Key_backgroundType,
-                Key.BACKGROUND_TYPE_NORMAL);
+        mRowAttributesStack.push(new RowAttributes(
+                keyAttr, params.mDefaultKeyWidth, params.mBaseWidth));
         keyAttr.recycle();
 
-        // TODO: Initialize this with <Row> attribute as backgroundType is done.
-        mDefaultKeyLabelFlags = 0;
         mCurrentY = y;
         mCurrentX = 0.0f;
     }
 
-    public float getDefaultKeyWidth() {
-        return mDefaultKeyWidth;
+    public int getRowHeight() {
+        return mRowHeight;
     }
 
-    public void setDefaultKeyWidth(final float defaultKeyWidth) {
-        mDefaultKeyWidth = defaultKeyWidth;
+    public void pushRowAttributes(final TypedArray keyAttr) {
+        final RowAttributes newAttributes = new RowAttributes(
+                keyAttr, mRowAttributesStack.peek(), mParams.mBaseWidth);
+        mRowAttributesStack.push(newAttributes);
+    }
+
+    public void popRowAttributes() {
+        mRowAttributesStack.pop();
+    }
+
+    public float getDefaultKeyWidth() {
+        return mRowAttributesStack.peek().mDefaultKeyWidth;
     }
 
     public int getDefaultKeyLabelFlags() {
-        return mDefaultKeyLabelFlags;
-    }
-
-    public void setDefaultKeyLabelFlags(final int keyLabelFlags) {
-        mDefaultKeyLabelFlags = keyLabelFlags;
+        return mRowAttributesStack.peek().mDefaultKeyLabelFlags;
     }
 
     public int getDefaultBackgroundType() {
-        return mDefaultBackgroundType;
-    }
-
-    public void setDefaultBackgroundType(final int backgroundType) {
-        mDefaultBackgroundType = backgroundType;
+        return mRowAttributesStack.peek().mDefaultBackgroundType;
     }
 
     public void setXPos(final float keyXPos) {
@@ -111,29 +150,27 @@
     }
 
     public float getKeyX(final TypedArray keyAttr) {
-        if (keyAttr.hasValue(R.styleable.Keyboard_Key_keyXPos)) {
-            final float keyXPos = keyAttr.getFraction(R.styleable.Keyboard_Key_keyXPos,
-                    mParams.mBaseWidth, mParams.mBaseWidth, 0);
-            if (keyXPos < 0) {
-                // If keyXPos is negative, the actual x-coordinate will be
-                // keyboardWidth + keyXPos.
-                // keyXPos shouldn't be less than mCurrentX because drawable area for this
-                // key starts at mCurrentX. Or, this key will overlaps the adjacent key on
-                // its left hand side.
-                final int keyboardRightEdge = mParams.mOccupiedWidth - mParams.mRightPadding;
-                return Math.max(keyXPos + keyboardRightEdge, mCurrentX);
-            } else {
-                return keyXPos + mParams.mLeftPadding;
-            }
+        if (keyAttr == null || !keyAttr.hasValue(R.styleable.Keyboard_Key_keyXPos)) {
+            return mCurrentX;
         }
-        return mCurrentX;
-    }
-
-    public float getKeyWidth(final TypedArray keyAttr) {
-        return getKeyWidth(keyAttr, mCurrentX);
+        final float keyXPos = keyAttr.getFraction(R.styleable.Keyboard_Key_keyXPos,
+                mParams.mBaseWidth, mParams.mBaseWidth, 0);
+        if (keyXPos >= 0) {
+            return keyXPos + mParams.mLeftPadding;
+        }
+        // If keyXPos is negative, the actual x-coordinate will be
+        // keyboardWidth + keyXPos.
+        // keyXPos shouldn't be less than mCurrentX because drawable area for this
+        // key starts at mCurrentX. Or, this key will overlaps the adjacent key on
+        // its left hand side.
+        final int keyboardRightEdge = mParams.mOccupiedWidth - mParams.mRightPadding;
+        return Math.max(keyXPos + keyboardRightEdge, mCurrentX);
     }
 
     public float getKeyWidth(final TypedArray keyAttr, final float keyXPos) {
+        if (keyAttr == null) {
+            return getDefaultKeyWidth();
+        }
         final int widthType = ResourceUtils.getEnumValue(keyAttr,
                 R.styleable.Keyboard_Key_keyWidth, KEYWIDTH_NOT_ENUM);
         switch (widthType) {
@@ -144,7 +181,7 @@
             return keyboardRightEdge - keyXPos;
         default: // KEYWIDTH_NOT_ENUM
             return keyAttr.getFraction(R.styleable.Keyboard_Key_keyWidth,
-                    mParams.mBaseWidth, mParams.mBaseWidth, mDefaultKeyWidth);
+                    mParams.mBaseWidth, mParams.mBaseWidth, getDefaultKeyWidth());
         }
     }
 }
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
index 164910d..b3491d8 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
@@ -45,6 +45,7 @@
         public void setAlphabetAutomaticShiftedKeyboard();
         public void setAlphabetShiftLockedKeyboard();
         public void setAlphabetShiftLockShiftedKeyboard();
+        public void setEmojiKeyboard();
         public void setSymbolsKeyboard();
         public void setSymbolsShiftedKeyboard();
 
@@ -74,7 +75,10 @@
     private static final int SWITCH_STATE_MOMENTARY_ALPHA_SHIFT = 5;
     private int mSwitchState = SWITCH_STATE_ALPHA;
 
+    // TODO: Consolidate these two mode booleans into one integer to distinguish between alphabet,
+    // symbols, and emoji mode.
     private boolean mIsAlphabetMode;
+    private boolean mIsEmojiMode;
     private AlphabetShiftState mAlphabetShiftState = new AlphabetShiftState();
     private boolean mIsSymbolShifted;
     private boolean mPrevMainKeyboardWasShiftLocked;
@@ -91,6 +95,7 @@
         public boolean mIsValid;
         public boolean mIsAlphabetMode;
         public boolean mIsAlphabetShiftLocked;
+        public boolean mIsEmojiMode;
         public int mShiftMode;
 
         @Override
@@ -99,6 +104,8 @@
             if (mIsAlphabetMode) {
                 if (mIsAlphabetShiftLocked) return "ALPHABET_SHIFT_LOCKED";
                 return "ALPHABET_" + shiftModeToString(mShiftMode);
+            } else if (mIsEmojiMode) {
+                return "EMOJI";
             } else {
                 return "SYMBOLS_" + shiftModeToString(mShiftMode);
             }
@@ -131,6 +138,7 @@
     public void onSaveKeyboardState() {
         final SavedKeyboardState state = mSavedKeyboardState;
         state.mIsAlphabetMode = mIsAlphabetMode;
+        state.mIsEmojiMode = mIsEmojiMode;
         if (mIsAlphabetMode) {
             state.mIsAlphabetShiftLocked = mAlphabetShiftState.isShiftLocked();
             state.mShiftMode = mAlphabetShiftState.isAutomaticShifted() ? AUTOMATIC_SHIFT
@@ -152,6 +160,8 @@
         }
         if (!state.mIsValid || state.mIsAlphabetMode) {
             setAlphabetKeyboard();
+        } else if (state.mIsEmojiMode) {
+            setEmojiKeyboard();
         } else {
             if (state.mShiftMode == MANUAL_SHIFT) {
                 setSymbolsShiftedKeyboard();
@@ -280,6 +290,7 @@
 
         mSwitchActions.setAlphabetKeyboard();
         mIsAlphabetMode = true;
+        mIsEmojiMode = false;
         mIsSymbolShifted = false;
         mRecapitalizeMode = RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE;
         mSwitchState = SWITCH_STATE_ALPHA;
@@ -310,6 +321,15 @@
         mSwitchState = SWITCH_STATE_SYMBOL_BEGIN;
     }
 
+    private void setEmojiKeyboard() {
+        if (DEBUG_ACTION) {
+            Log.d(TAG, "setEmojiKeyboard");
+        }
+        mIsAlphabetMode = false;
+        mIsEmojiMode = true;
+        mSwitchActions.setEmojiKeyboard();
+    }
+
     public void onPressKey(final int code, final boolean isSinglePointer, final int autoCaps) {
         if (DEBUG_EVENT) {
             Log.d(TAG, "onPressKey: code=" + Constants.printableCode(code)
@@ -460,7 +480,8 @@
             } else {
                 if (mAlphabetShiftState.isShiftLocked()) {
                     // Shift key is pressed while shift locked state, we will treat this state as
-                    // shift lock shifted state and mark as if shift key pressed while normal state.
+                    // shift lock shifted state and mark as if shift key pressed while normal
+                    // state.
                     setShifted(SHIFT_LOCK_SHIFTED);
                     mShiftKeyState.onPress();
                 } else if (mAlphabetShiftState.isAutomaticShifted()) {
@@ -587,7 +608,8 @@
             break;
         case SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE:
             if (code == Constants.CODE_SHIFT) {
-                // Detected only the shift key has been pressed on symbol layout, and then released.
+                // Detected only the shift key has been pressed on symbol layout, and then
+                // released.
                 mSwitchState = SWITCH_STATE_SYMBOL_BEGIN;
             }
             break;
@@ -610,6 +632,8 @@
         // If the code is a letter, update keyboard shift state.
         if (Constants.isLetterCode(code)) {
             updateAlphabetShiftState(autoCaps, RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE);
+        } else if (code == Constants.CODE_EMOJI) {
+            setEmojiKeyboard();
         }
     }
 
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java
index b55e19d..67553fb 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java
@@ -27,18 +27,18 @@
 /**
  * !!!!! DO NOT EDIT THIS FILE !!!!!
  *
- * This file is generated by tools/maketext. The base template file is
- *   tools/maketext/res/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.tmpl
+ * This file is generated by tools/make-keyboard-text. The base template file is
+ *   tools/make-keyboard-text/res/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.tmpl
  *
  * This file must be updated when any text resources in keyboard layout files have been changed.
  * These text resources are referred as "!text/<resource_name>" in keyboard XML definitions,
  * and should be defined in
- *   tools/maketext/res/values-<locale>/donottranslate-more-keys.xml
+ *   tools/make-keyboard-text/res/values-<locale>/donottranslate-more-keys.xml
  *
  * To update this file, please run the following commands.
  *   $ cd $ANDROID_BUILD_TOP
- *   $ mmm packages/inputmethods/LatinIME/tools/maketext
- *   $ maketext -java packages/inputmethods/LatinIME/java/src
+ *   $ mmm packages/inputmethods/LatinIME/tools/make-keyboard-text
+ *   $ make-keyboard-text -java packages/inputmethods/LatinIME/java/src
  *
  * The updated source file will be generated to the following path (this file).
  *   packages/inputmethods/LatinIME/java/src/com/android/inputmethod/keyboard/internal/
@@ -153,8 +153,8 @@
         /* 48 */ "single_angle_quotes",
         /* 49 */ "double_angle_quotes",
         /* 50 */ "more_keys_for_currency_dollar",
-        /* 51 */ "keylabel_for_currency_generic",
-        /* 52 */ "more_keys_for_currency_generic",
+        /* 51 */ "keylabel_for_currency",
+        /* 52 */ "more_keys_for_currency",
         /* 53 */ "more_keys_for_punctuation",
         /* 54 */ "more_keys_for_star",
         /* 55 */ "more_keys_for_bullet",
@@ -233,25 +233,24 @@
         /* 128 */ "label_to_phone_symbols_key",
         /* 129 */ "label_time_am",
         /* 130 */ "label_time_pm",
-        /* 131 */ "label_to_symbol_key_pcqwerty",
-        /* 132 */ "keylabel_for_popular_domain",
-        /* 133 */ "more_keys_for_popular_domain",
-        /* 134 */ "more_keys_for_smiley",
-        /* 135 */ "single_laqm_raqm",
-        /* 136 */ "single_laqm_raqm_rtl",
-        /* 137 */ "single_raqm_laqm",
-        /* 138 */ "double_laqm_raqm",
-        /* 139 */ "double_laqm_raqm_rtl",
-        /* 140 */ "double_raqm_laqm",
-        /* 141 */ "single_lqm_rqm",
-        /* 142 */ "single_9qm_lqm",
-        /* 143 */ "single_9qm_rqm",
-        /* 144 */ "double_lqm_rqm",
-        /* 145 */ "double_9qm_lqm",
-        /* 146 */ "double_9qm_rqm",
-        /* 147 */ "more_keys_for_single_quote",
-        /* 148 */ "more_keys_for_double_quote",
-        /* 149 */ "more_keys_for_tablet_double_quote",
+        /* 131 */ "keylabel_for_popular_domain",
+        /* 132 */ "more_keys_for_popular_domain",
+        /* 133 */ "more_keys_for_smiley",
+        /* 134 */ "single_laqm_raqm",
+        /* 135 */ "single_laqm_raqm_rtl",
+        /* 136 */ "single_raqm_laqm",
+        /* 137 */ "double_laqm_raqm",
+        /* 138 */ "double_laqm_raqm_rtl",
+        /* 139 */ "double_raqm_laqm",
+        /* 140 */ "single_lqm_rqm",
+        /* 141 */ "single_9qm_lqm",
+        /* 142 */ "single_9qm_rqm",
+        /* 143 */ "double_lqm_rqm",
+        /* 144 */ "double_9qm_lqm",
+        /* 145 */ "double_9qm_rqm",
+        /* 146 */ "more_keys_for_single_quote",
+        /* 147 */ "more_keys_for_double_quote",
+        /* 148 */ "more_keys_for_tablet_double_quote",
     };
 
     private static final String EMPTY = "";
@@ -278,7 +277,7 @@
         /* 50 */ "\u00A2,\u00A3,\u20AC,\u00A5,\u20B1",
         /* 51 */ "$",
         /* 52 */ "$,\u00A2,\u20AC,\u00A3,\u00A5,\u20B1",
-        /* 53 */ "!fixedColumnOrder!8,\",\',#,-,:,!,\\,,?,@,&,\\%,+,;,/,(,)",
+        /* 53 */ "!fixedColumnOrder!3,!,\\,,?,:,;,@",
         // U+2020: "†" DAGGER
         // U+2021: "‡" DOUBLE DAGGER
         // U+2605: "★" BLACK STAR
@@ -361,10 +360,11 @@
         // U+2030: "‰" PER MILLE SIGN
         /* 103 */ "\u2030",
         /* 104 */ ",",
-        /* 105 */ "!",
-        /* 106 */ "!",
-        /* 107 */ "?",
-        /* 108 */ "?",
+        /* 105~ */
+        EMPTY, EMPTY, EMPTY,
+        /* ~107 */
+        // U+2026: "…" HORIZONTAL ELLIPSIS
+        /* 108 */ "\u2026",
         /* 109 */ "\'",
         /* 110 */ "\"",
         /* 111 */ "\"",
@@ -383,7 +383,7 @@
         // Label for "switch to more symbol" modifier key.  Must be short to fit on key!
         /* 124 */ "= \\ <",
         // Label for "switch to more symbol" modifier key on tablets.  Must be short to fit on key!
-        /* 125 */ "~ \\ {",
+        /* 125 */ "~ [ <",
         // Label for "Tab" key.  Must be short to fit on key!
         /* 126 */ "Tab",
         // Label for "switch to phone numeric" key.  Must be short to fit on key!
@@ -396,12 +396,10 @@
         /* 129 */ "AM",
         // Key label for "post meridiem"
         /* 130 */ "PM",
-        // Label for "switch to symbols" key on PC QWERTY layout
-        /* 131 */ "Sym",
-        /* 132 */ ".com",
+        /* 131 */ ".com",
         // popular web domains for the locale - most popular, displayed on the keyboard
-        /* 133 */ "!hasLabels!,.net,.org,.gov,.edu",
-        /* 134 */ "!fixedColumnOrder!5,!hasLabels!,=-O|=-O ,:-P|:-P ,;-)|;-) ,:-(|:-( ,:-)|:-) ,:-!|:-! ,:-$|:-$ ,B-)|B-) ,:O|:O ,:-*|:-* ,:-D|:-D ,:\'(|:\'( ,:-\\\\|:-\\\\ ,O:-)|O:-) ,:-[|:-[ ",
+        /* 132 */ "!hasLabels!,.net,.org,.gov,.edu",
+        /* 133 */ "!fixedColumnOrder!5,!hasLabels!,=-O|=-O ,:-P|:-P ,;-)|;-) ,:-(|:-( ,:-)|:-) ,:-!|:-! ,:-$|:-$ ,B-)|B-) ,:O|:O ,:-*|:-* ,:-D|:-D ,:\'(|:\'( ,:-\\\\|:-\\\\ ,O:-)|O:-) ,:-[|:-[ ",
         // U+2039: "‹" SINGLE LEFT-POINTING ANGLE QUOTATION MARK
         // U+203A: "›" SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
         // U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
@@ -423,24 +421,24 @@
         // The following each quotation mark pair consist of
         // <opening quotation mark>, <closing quotation mark>
         // and is named after (single|double)_<opening quotation mark>_<closing quotation mark>.
-        /* 135 */ "\u2039,\u203A",
-        /* 136 */ "\u2039|\u203A,\u203A|\u2039",
-        /* 137 */ "\u203A,\u2039",
-        /* 138 */ "\u00AB,\u00BB",
-        /* 139 */ "\u00AB|\u00BB,\u00BB|\u00AB",
-        /* 140 */ "\u00BB,\u00AB",
+        /* 134 */ "\u2039,\u203A",
+        /* 135 */ "\u2039|\u203A,\u203A|\u2039",
+        /* 136 */ "\u203A,\u2039",
+        /* 137 */ "\u00AB,\u00BB",
+        /* 138 */ "\u00AB|\u00BB,\u00BB|\u00AB",
+        /* 139 */ "\u00BB,\u00AB",
         // The following each quotation mark triplet consists of
         // <another quotation mark>, <opening quotation mark>, <closing quotation mark>
         // and is named after (single|double)_<opening quotation mark>_<closing quotation mark>.
-        /* 141 */ "\u201A,\u2018,\u2019",
-        /* 142 */ "\u2019,\u201A,\u2018",
-        /* 143 */ "\u2018,\u201A,\u2019",
-        /* 144 */ "\u201E,\u201C,\u201D",
-        /* 145 */ "\u201D,\u201E,\u201C",
-        /* 146 */ "\u201C,\u201E,\u201D",
-        /* 147 */ "!fixedColumnOrder!5,!text/single_quotes,!text/single_angle_quotes",
-        /* 148 */ "!fixedColumnOrder!5,!text/double_quotes,!text/double_angle_quotes",
-        /* 149 */ "!fixedColumnOrder!6,!text/double_quotes,!text/single_quotes,!text/double_angle_quotes,!text/single_angle_quotes",
+        /* 140 */ "\u201A,\u2018,\u2019",
+        /* 141 */ "\u2019,\u201A,\u2018",
+        /* 142 */ "\u2018,\u201A,\u2019",
+        /* 143 */ "\u201E,\u201C,\u201D",
+        /* 144 */ "\u201D,\u201E,\u201C",
+        /* 145 */ "\u201C,\u201E,\u201D",
+        /* 146 */ "!fixedColumnOrder!5,!text/single_quotes,!text/single_angle_quotes",
+        /* 147 */ "!fixedColumnOrder!5,!text/double_quotes,!text/double_angle_quotes",
+        /* 148 */ "!fixedColumnOrder!6,!text/double_quotes,!text/single_quotes,!text/double_angle_quotes,!text/single_angle_quotes",
     };
 
     /* Language af: Afrikaans */
@@ -519,7 +517,7 @@
         // U+061F: "؟" ARABIC QUESTION MARK
         // U+060C: "،" ARABIC COMMA
         // U+061B: "؛" ARABIC SEMICOLON
-        /* 53 */ "!fixedColumnOrder!8,\",\',#,-,:,!,\u060C,\u061F,@,&,\\%,+,\u061B,/,(,)",
+        /* 53 */ "!fixedColumnOrder!8,\",\',#,-,:,!,\u060C,\u061F,@,&,\\%,+,\u061B,/,(|),)|(",
         // U+2605: "★" BLACK STAR
         // U+066D: "٭" ARABIC FIVE POINTED STAR
         /* 54 */ "\u2605,\u066D",
@@ -785,7 +783,7 @@
         null, null, null, null, null, null, null, null,
         /* ~52 */
         // U+00B7: "·" MIDDLE DOT
-        /* 53 */ "!fixedColumnOrder!9,\u00B7,\",\',#,-,:,!,\\,,?,@,&,\\%,+,;,/,(,)",
+        /* 53 */ "!fixedColumnOrder!4,\u00B7,!,\\,,?,:,;,@",
         /* 54~ */
         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,
@@ -1257,7 +1255,7 @@
         /* ~52 */
         // U+00A1: "¡" INVERTED EXCLAMATION MARK
         // U+00BF: "¿" INVERTED QUESTION MARK
-        /* 53 */ "!fixedColumnOrder!9,\u00A1,\",\',#,-,:,!,\\,,?,\u00BF,@,&,\\%,+,;,/,(,)",
+        /* 53 */ "!fixedColumnOrder!4,;,!,\\,,?,:,\u00A1,@,\u00BF",
         /* 54~ */
         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,
@@ -1402,13 +1400,14 @@
         /* 47 */ null,
         /* 48 */ "!text/single_laqm_raqm_rtl",
         /* 49 */ "!text/double_laqm_raqm_rtl",
-        /* 50~ */
-        null, null, null,
-        /* ~52 */
+        /* 50 */ null,
+        // U+FDFC: "﷼" RIAL SIGN
+        /* 51 */ "\uFDFC",
+        /* 52 */ null,
         // U+061F: "؟" ARABIC QUESTION MARK
         // U+060C: "،" ARABIC COMMA
         // U+061B: "؛" ARABIC SEMICOLON
-        /* 53 */ "!fixedColumnOrder!8,\",\',#,-,:,!,\u060C,\u061F,@,&,\\%,+,\u061B,/,(,)",
+        /* 53 */ "!fixedColumnOrder!8,\",\',#,-,:,!,\u060C,\u061F,@,&,\\%,+,\u061B,/,(|),)|(",
         // U+2605: "★" BLACK STAR
         // U+066D: "٭" ARABIC FIVE POINTED STAR
         /* 54 */ "\u2605,\u066D",
@@ -1762,6 +1761,37 @@
         /* 49 */ "!text/double_raqm_laqm",
     };
 
+    /* Language hy: Armenian */
+    private static final String[] LANGUAGE_hy = {
+        /* 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,
+        null, null, null, null, null, null, null, null,
+        /* ~52 */
+        // U+058A: "֊" ARMENIAN HYPHEN
+        // U+055C: "՜" ARMENIAN EXCLAMATION MARK
+        // U+055D: "՝" ARMENIAN COMMA
+        // U+055E: "՞" ARMENIAN QUESTION MARK
+        // U+0559: "ՙ" ARMENIAN MODIFIER LETTER LEFT HALF RING
+        // U+055A: "՚" ARMENIAN APOSTROPHE
+        // U+055B: "՛" ARMENIAN EMPHASIS MARK
+        // U+055F: "՟" ARMENIAN ABBREVIATION MARK
+        /* 53 */ "!fixedColumnOrder!8,!,?,\\,,.,\u058A,\u055C,\u055D,\u055E,:,;,@,\u0559,\u055A,\u055B,\u055F",
+        /* 54~ */
+        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,
+        null,
+        /* ~99 */
+        // U+055C: "՜" ARMENIAN EXCLAMATION MARK
+        // U+00A1: "¡" INVERTED EXCLAMATION MARK
+        /* 100 */ "\u055C,\u00A1",
+        // U+055E: "՞" ARMENIAN QUESTION MARK
+        // U+00BF: "¿" INVERTED QUESTION MARK
+        /* 101 */ "\u055E,\u00BF",
+    };
+
     /* Language is: Icelandic */
     private static final String[] LANGUAGE_is = {
         // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE
@@ -1899,9 +1929,11 @@
         /* 47 */ "\u201C,\u201D,\u201E",
         /* 48 */ "!text/single_laqm_raqm_rtl",
         /* 49 */ "!text/double_laqm_raqm_rtl",
-        /* 50~ */
-        null, null, null, null,
-        /* ~53 */
+        /* 50 */ null,
+        // U+20AA: "₪" NEW SHEQEL SIGN
+        /* 51 */ "\u20AA",
+        /* 52 */ null,
+        /* 53 */ null,
         // U+2605: "★" BLACK STAR
         /* 54 */ "\u2605",
         /* 55 */ null,
@@ -1920,6 +1952,15 @@
         // U+203A: "›" SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
         /* 59 */ "!fixedColumnOrder!3,\u2039|\u203A,\u2264|\u2265,\u00AB|\u00BB",
         /* 60 */ "!fixedColumnOrder!3,\u203A|\u2039,\u2265|\u2264,\u00BB|\u00AB",
+        /* 61~ */
+        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,
+        /* ~104 */
+        /* 105 */ "!",
+        /* 106 */ "!",
+        /* 107 */ "?",
+        /* 108 */ "?",
     };
 
     /* Language ka: Georgian */
@@ -1986,6 +2027,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~ */
@@ -2027,6 +2087,25 @@
         /* 45 */ "\u0410\u0411\u0412",
     };
 
+    /* Language lo: Lao */
+    private static final String[] LANGUAGE_lo = {
+        /* 0~ */
+        null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+        null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+        null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+        /* ~44 */
+        // Label for "switch to alphabetic" key.
+        // U+0E81: "ກ" LAO LETTER KO
+        // U+0E82: "ຂ" LAO LETTER KHO SUNG
+        // U+0E84: "ຄ" LAO LETTER KHO TAM
+        /* 45 */ "\u0E81\u0E82\u0E84",
+        /* 46~ */
+        null, null, null, null, null,
+        /* ~50 */
+        // U+20AD: "₭" KIP SIGN
+        /* 51 */ "\u20AD",
+    };
+
     /* Language lt: Lithuanian */
     private static final String[] LANGUAGE_lt = {
         // U+0105: "ą" LATIN SMALL LETTER A WITH OGONEK
@@ -2318,6 +2397,63 @@
         /* 47 */ "!text/double_9qm_rqm",
     };
 
+    /* Language ne: Nepali */
+    private static final String[] LANGUAGE_ne = {
+        /* 0~ */
+        null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+        null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+        null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+        /* ~44 */
+        // Label for "switch to alphabetic" key.
+        // U+0915: "क" DEVANAGARI LETTER KA
+        // U+0916: "ख" DEVANAGARI LETTER KHA
+        // U+0917: "ग" DEVANAGARI LETTER GA
+        /* 45 */ "\u0915\u0916\u0917",
+        /* 46~ */
+        null, null, null, null, null,
+        /* ~50 */
+        // U+0930/U+0941/U+002E "रु." NEPALESE RUPEE SIGN
+        /* 51 */ "\u0930\u0941.",
+        /* 52~ */
+        null, null, null, null, null, null, null, null, null, null, null,
+        /* ~62 */
+        // U+0967: "१" DEVANAGARI DIGIT ONE
+        /* 63 */ "\u0967",
+        // U+0968: "२" DEVANAGARI DIGIT TWO
+        /* 64 */ "\u0968",
+        // U+0969: "३" DEVANAGARI DIGIT THREE
+        /* 65 */ "\u0969",
+        // U+096A: "४" DEVANAGARI DIGIT FOUR
+        /* 66 */ "\u096A",
+        // U+096B: "५" DEVANAGARI DIGIT FIVE
+        /* 67 */ "\u096B",
+        // U+096C: "६" DEVANAGARI DIGIT SIX
+        /* 68 */ "\u096C",
+        // U+096D: "७" DEVANAGARI DIGIT SEVEN
+        /* 69 */ "\u096D",
+        // U+096E: "८" DEVANAGARI DIGIT EIGHT
+        /* 70 */ "\u096E",
+        // U+096F: "९" DEVANAGARI DIGIT NINE
+        /* 71 */ "\u096F",
+        // U+0966: "०" DEVANAGARI DIGIT ZERO
+        /* 72 */ "\u0966",
+        // Label for "switch to symbols" key.
+        /* 73 */ "?\u0967\u0968\u0969",
+        // Label for "switch to symbols with microphone" key. This string shouldn't include the "mic"
+        // part because it'll be appended by the code.
+        /* 74 */ "\u0967\u0968\u0969",
+        /* 75 */ "1",
+        /* 76 */ "2",
+        /* 77 */ "3",
+        /* 78 */ "4",
+        /* 79 */ "5",
+        /* 80 */ "6",
+        /* 81 */ "7",
+        /* 82 */ "8",
+        /* 83 */ "9",
+        /* 84 */ "0",
+    };
+
     /* Language nl: Dutch */
     private static final String[] LANGUAGE_nl = {
         // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE
@@ -3296,17 +3432,21 @@
         "hi", LANGUAGE_hi, /* Hindi */
         "hr", LANGUAGE_hr, /* Croatian */
         "hu", LANGUAGE_hu, /* Hungarian */
+        "hy", LANGUAGE_hy, /* Armenian */
         "is", LANGUAGE_is, /* Icelandic */
         "it", LANGUAGE_it, /* Italian */
         "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 */
         "lv", LANGUAGE_lv, /* Latvian */
         "mk", LANGUAGE_mk, /* Macedonian */
         "mn", LANGUAGE_mn, /* Mongolian */
         "nb", LANGUAGE_nb, /* Norwegian Bokmål */
+        "ne", LANGUAGE_ne, /* Nepali */
         "nl", LANGUAGE_nl, /* Dutch */
         "pl", LANGUAGE_pl, /* Polish */
         "pt", LANGUAGE_pt, /* Portuguese */
diff --git a/java/src/com/android/inputmethod/keyboard/internal/ScrollKeyboardView.java b/java/src/com/android/inputmethod/keyboard/internal/ScrollKeyboardView.java
new file mode 100644
index 0000000..b8ee976
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/internal/ScrollKeyboardView.java
@@ -0,0 +1,212 @@
+/*
+ * 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.internal;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+import android.widget.ScrollView;
+import android.widget.Scroller;
+
+import com.android.inputmethod.keyboard.Key;
+import com.android.inputmethod.keyboard.KeyDetector;
+import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.keyboard.KeyboardView;
+import com.android.inputmethod.latin.R;
+
+/**
+ * This is an extended {@link KeyboardView} class that hosts a scroll keyboard.
+ * Multi-touch unsupported. No {@link PointerTracker}s. No gesture support.
+ */
+// TODO: Implement key popup preview.
+public final class ScrollKeyboardView extends KeyboardView implements
+        ScrollViewWithNotifier.ScrollListener, GestureDetector.OnGestureListener {
+    private static final boolean PAGINATION = false;
+
+    public interface OnKeyClickListener {
+        public void onKeyClick(Key key);
+    }
+
+    private static final OnKeyClickListener EMPTY_LISTENER = new OnKeyClickListener() {
+        @Override
+        public void onKeyClick(final Key key) {}
+    };
+
+    private OnKeyClickListener mListener = EMPTY_LISTENER;
+    private final KeyDetector mKeyDetector = new KeyDetector(0.0f /*keyHysteresisDistance */);
+    private final GestureDetector mGestureDetector;
+
+    private final Scroller mScroller;
+    private ScrollViewWithNotifier mScrollView;
+
+    public ScrollKeyboardView(final Context context, final AttributeSet attrs) {
+        this(context, attrs, R.attr.keyboardViewStyle);
+    }
+
+    public ScrollKeyboardView(final Context context, final AttributeSet attrs, final int defStyle) {
+        super(context, attrs, defStyle);
+        mGestureDetector = new GestureDetector(context, this);
+        mGestureDetector.setIsLongpressEnabled(false /* isLongpressEnabled */);
+        mScroller = new Scroller(context);
+    }
+
+    public void setScrollView(final ScrollViewWithNotifier scrollView) {
+        mScrollView = scrollView;
+        scrollView.setScrollListener(this);
+    }
+
+    private final Runnable mScrollTask = new Runnable() {
+        @Override
+        public void run() {
+            final Scroller scroller = mScroller;
+            final ScrollView scrollView = mScrollView;
+            scroller.computeScrollOffset();
+            scrollView.scrollTo(0, scroller.getCurrY());
+            if (!scroller.isFinished()) {
+                scrollView.post(this);
+            }
+        }
+    };
+
+    // {@link ScrollViewWithNotified#ScrollListener} methods.
+    @Override
+    public void notifyScrollChanged(final int scrollX, final int scrollY, final int oldX,
+            final int oldY) {
+        if (PAGINATION) {
+            mScroller.forceFinished(true /* finished */);
+            mScrollView.removeCallbacks(mScrollTask);
+            final int currentTop = mScrollView.getScrollY();
+            final int pageHeight = getKeyboard().mBaseHeight;
+            final int lastPageNo = currentTop / pageHeight;
+            final int lastPageTop = lastPageNo * pageHeight;
+            final int nextPageNo = lastPageNo + 1;
+            final int nextPageTop = Math.min(nextPageNo * pageHeight, getHeight() - pageHeight);
+            final int scrollTo = (currentTop - lastPageTop) < (nextPageTop - currentTop)
+                    ? lastPageTop : nextPageTop;
+            final int deltaY = scrollTo - currentTop;
+            mScroller.startScroll(0, currentTop, 0, deltaY, 300);
+            mScrollView.post(mScrollTask);
+        }
+    }
+
+    @Override
+    public void notifyOverScrolled(final int scrollX, final int scrollY, final boolean clampedX,
+            final boolean clampedY) {
+        releaseCurrentKey();
+    }
+
+    public void setOnKeyClickListener(final OnKeyClickListener listener) {
+        mListener = listener;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setKeyboard(final Keyboard keyboard) {
+        super.setKeyboard(keyboard);
+        mKeyDetector.setKeyboard(keyboard, 0 /* correctionX */, 0 /* correctionY */);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean onTouchEvent(final MotionEvent e) {
+        if (mGestureDetector.onTouchEvent(e)) {
+            return true;
+        }
+        final Key key = getKey(e);
+        if (key != null && key != mCurrentKey) {
+            releaseCurrentKey();
+        }
+        return true;
+    }
+
+    // {@link GestureDetector#OnGestureListener} methods.
+    private Key mCurrentKey;
+
+    private Key getKey(final MotionEvent e) {
+        final int index = e.getActionIndex();
+        final int x = (int)e.getX(index);
+        final int y = (int)e.getY(index);
+        return mKeyDetector.detectHitKey(x, y);
+    }
+
+    public void releaseCurrentKey() {
+        final Key currentKey = mCurrentKey;
+        if (currentKey == null) {
+            return;
+        }
+        currentKey.onReleased();
+        invalidateKey(currentKey);
+        mCurrentKey = null;
+    }
+
+    @Override
+    public boolean onDown(final MotionEvent e) {
+        final Key key = getKey(e);
+        releaseCurrentKey();
+        mCurrentKey = key;
+        if (key == null) {
+            return false;
+        }
+        // TODO: May call {@link KeyboardActionListener#onPressKey(int,int,boolean)}.
+        key.onPressed();
+        invalidateKey(key);
+        return false;
+    }
+
+    @Override
+    public void onShowPress(final MotionEvent e) {
+        // User feedback is done at {@link #onDown(MotionEvent)}.
+    }
+
+    @Override
+    public boolean onSingleTapUp(final MotionEvent e) {
+        final Key key = getKey(e);
+        releaseCurrentKey();
+        if (key == null) {
+            return false;
+        }
+        // TODO: May call {@link KeyboardActionListener#onReleaseKey(int,boolean)}.
+        key.onReleased();
+        invalidateKey(key);
+        mListener.onKeyClick(key);
+        return true;
+    }
+
+    @Override
+    public boolean onScroll(final MotionEvent e1, final MotionEvent e2, final float distanceX,
+           final float distanceY) {
+        releaseCurrentKey();
+        return false;
+    }
+
+    @Override
+    public boolean onFling(final MotionEvent e1, final MotionEvent e2, final float velocityX,
+            final float velocityY) {
+        releaseCurrentKey();
+        return false;
+    }
+
+    @Override
+    public void onLongPress(final MotionEvent e) {
+        // Long press detection of {@link #mGestureDetector} is disabled and not used.
+    }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/ScrollViewWithNotifier.java b/java/src/com/android/inputmethod/keyboard/internal/ScrollViewWithNotifier.java
new file mode 100644
index 0000000..d1ccdc7
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/internal/ScrollViewWithNotifier.java
@@ -0,0 +1,66 @@
+/*
+ * 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.internal;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.ScrollView;
+
+/**
+ * This is an extended {@link ScrollView} that can notify
+ * {@link ScrollView#onScrollChanged(int,int,int,int} and
+ * {@link ScrollView#onOverScrolled(int,int,int,int)} to a content view.
+ */
+public class ScrollViewWithNotifier extends ScrollView {
+    private ScrollListener mScrollListener = EMPTY_LISTER;
+
+    public interface ScrollListener {
+        public void notifyScrollChanged(int scrollX, int scrollY, int oldX, int oldY);
+        public void notifyOverScrolled(int scrollX, int scrollY, boolean clampedX,
+                boolean clampedY);
+    }
+
+    private static final ScrollListener EMPTY_LISTER = new ScrollListener() {
+        @Override
+        public void notifyScrollChanged(int scrollX, int scrollY, int oldX, int oldY) {}
+        @Override
+        public void notifyOverScrolled(int scrollX, int scrollY, boolean clampedX,
+                boolean clampedY) {}
+    };
+
+    public ScrollViewWithNotifier(final Context context, final AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    protected void onScrollChanged(final int scrollX, final int scrollY, final int oldX,
+            final int oldY) {
+        super.onScrollChanged(scrollX, scrollY, oldX, oldY);
+        mScrollListener.notifyScrollChanged(scrollX, scrollY, oldX, oldY);
+    }
+
+    @Override
+    protected void onOverScrolled(final int scrollX, final int scrollY, final boolean clampedX,
+            final boolean clampedY) {
+        super.onOverScrolled(scrollX, scrollY, clampedX, clampedY);
+        mScrollListener.notifyOverScrolled(scrollX, scrollY, clampedX, clampedY);
+    }
+
+    public void setScrollListener(final ScrollListener listener) {
+        mScrollListener = listener;
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/AbstractDictionaryWriter.java b/java/src/com/android/inputmethod/latin/AbstractDictionaryWriter.java
index ebbcedc..845a9b9 100644
--- a/java/src/com/android/inputmethod/latin/AbstractDictionaryWriter.java
+++ b/java/src/com/android/inputmethod/latin/AbstractDictionaryWriter.java
@@ -19,10 +19,11 @@
 import android.content.Context;
 import android.util.Log;
 
+import com.android.inputmethod.latin.makedict.DictEncoder;
 import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
+import com.android.inputmethod.latin.makedict.Ver3DictEncoder;
 
 import java.io.File;
-import java.io.FileOutputStream;
 import java.io.IOException;
 
 // TODO: Quit extending Dictionary after implementing dynamic binary dictionary.
@@ -42,37 +43,28 @@
     abstract public void addUnigramWord(final String word, final String shortcutTarget,
             final int frequency, final boolean isNotAWord);
 
+    // TODO: Remove lastModifiedTime after making binary dictionary support forgetting curve.
     abstract public void addBigramWords(final String word0, final String word1,
-            final int frequency, final boolean isValid);
+            final int frequency, final boolean isValid,
+            final long lastModifiedTime);
 
     abstract public void removeBigramWords(final String word0, final String word1);
 
-    abstract protected void writeBinaryDictionary(final FileOutputStream out)
+    abstract protected void writeDictionary(final DictEncoder dictEncoder)
             throws IOException, UnsupportedFormatException;
 
     public void write(final String fileName) {
         final String tempFileName = fileName + ".temp";
         final File file = new File(mContext.getFilesDir(), fileName);
         final File tempFile = new File(mContext.getFilesDir(), tempFileName);
-        FileOutputStream out = null;
         try {
-            out = new FileOutputStream(tempFile);
-            writeBinaryDictionary(out);
-            out.flush();
-            out.close();
+            final DictEncoder dictEncoder = new Ver3DictEncoder(tempFile);
+            writeDictionary(dictEncoder);
             tempFile.renameTo(file);
         } catch (IOException e) {
             Log.e(TAG, "IO exception while writing file", e);
         } catch (UnsupportedFormatException e) {
             Log.e(TAG, "Unsupported format", e);
-        } finally {
-            if (out != null) {
-                try {
-                    out.close();
-                } catch (IOException e) {
-                    // ignore
-                }
-            }
         }
     }
 }
diff --git a/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java b/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java
index 42c5794..54bc295 100644
--- a/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java
+++ b/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java
@@ -57,10 +57,10 @@
         mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
     }
 
-    public void hapticAndAudioFeedback(final int primaryCode,
+    public void performHapticAndAudioFeedback(final int code,
             final View viewToPerformHapticFeedbackOn) {
-        vibrateInternal(viewToPerformHapticFeedbackOn);
-        playKeyClick(primaryCode);
+        performHapticFeedback(viewToPerformHapticFeedbackOn);
+        performAudioFeedback(code);
     }
 
     public boolean hasVibrator() {
@@ -81,14 +81,14 @@
         return mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_NORMAL;
     }
 
-    private void playKeyClick(final int primaryCode) {
+    public void performAudioFeedback(final int code) {
         // if mAudioManager is null, we can't play a sound anyway, so return
         if (mAudioManager == null) {
             return;
         }
         if (mSoundOn) {
             final int sound;
-            switch (primaryCode) {
+            switch (code) {
             case Constants.CODE_DELETE:
                 sound = AudioManager.FX_KEYPRESS_DELETE;
                 break;
@@ -106,7 +106,7 @@
         }
     }
 
-    private void vibrateInternal(final View viewToPerformHapticFeedbackOn) {
+    public void performHapticFeedback(final View viewToPerformHapticFeedbackOn) {
         if (!mSettingsValues.mVibrateOn) {
             return;
         }
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
index d181bf6..61ccfcf 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
@@ -19,14 +19,15 @@
 import android.text.TextUtils;
 import android.util.SparseArray;
 
+import com.android.inputmethod.annotations.UsedForTesting;
 import com.android.inputmethod.keyboard.ProximityInfo;
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
-import com.android.inputmethod.latin.settings.AdditionalFeaturesSettingUtils;
 import com.android.inputmethod.latin.settings.NativeSuggestOptions;
 import com.android.inputmethod.latin.utils.CollectionUtils;
 import com.android.inputmethod.latin.utils.JniUtils;
 import com.android.inputmethod.latin.utils.StringUtils;
 
+import java.io.File;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Locale;
@@ -41,14 +42,20 @@
     private static final int MAX_WORD_LENGTH = Constants.DICTIONARY_MAX_WORD_LENGTH;
     // Must be equal to MAX_RESULTS in native/jni/src/defines.h
     private static final int MAX_RESULTS = 18;
+    // Required space count for auto commit.
+    // TODO: Remove this heuristic.
+    private static final int SPACE_COUNT_FOR_AUTO_COMMIT = 3;
 
     private long mNativeDict;
     private final Locale mLocale;
+    private final long mDictSize;
+    private final String mDictFilePath;
     private final int[] mInputCodePoints = new int[MAX_WORD_LENGTH];
     private final int[] mOutputCodePoints = new int[MAX_WORD_LENGTH * MAX_RESULTS];
     private final int[] mSpaceIndices = new int[MAX_RESULTS];
     private final int[] mOutputScores = new int[MAX_RESULTS];
     private final int[] mOutputTypes = new int[MAX_RESULTS];
+    private final int[] mOutputAutoCommitFirstWordConfidence = new int[MAX_RESULTS];
 
     private final NativeSuggestOptions mNativeSuggestOptions = new NativeSuggestOptions();
 
@@ -63,7 +70,7 @@
             if (traverseSession == null) {
                 traverseSession = mDicTraverseSessions.get(traverseSessionId);
                 if (traverseSession == null) {
-                    traverseSession = new DicTraverseSession(mLocale, mNativeDict);
+                    traverseSession = new DicTraverseSession(mLocale, mNativeDict, mDictSize);
                     mDicTraverseSessions.put(traverseSessionId, traverseSession);
                 }
             }
@@ -86,6 +93,8 @@
             final boolean isUpdatable) {
         super(dictType);
         mLocale = locale;
+        mDictSize = length;
+        mDictFilePath = filename;
         mNativeSuggestOptions.setUseFullEditDistance(useFullEditDistance);
         loadDictionary(filename, offset, length, isUpdatable);
     }
@@ -96,20 +105,26 @@
 
     private static native long openNative(String sourceDir, long dictOffset, long dictSize,
             boolean isUpdatable);
+    private static native void flushNative(long dict, String filePath);
+    private static native boolean needsToRunGCNative(long dict);
+    private static native void flushWithGCNative(long dict, String filePath);
     private static native void closeNative(long dict);
     private static native int getProbabilityNative(long dict, int[] word);
-    private static native boolean isValidBigramNative(long dict, int[] word0, int[] word1);
+    private static native int getBigramProbabilityNative(long dict, int[] word0, int[] word1);
     private static native int getSuggestionsNative(long dict, long proximityInfo,
             long traverseSession, int[] xCoordinates, int[] yCoordinates, int[] times,
             int[] pointerIds, int[] inputCodePoints, int inputSize, int commitPoint,
             int[] suggestOptions, int[] prevWordCodePointArray,
-            int[] outputCodePoints, int[] outputScores, int[] outputIndices, int[] outputTypes);
+            int[] outputCodePoints, int[] outputScores, int[] outputIndices, int[] outputTypes,
+            int[] outputAutoCommitFirstWordConfidence);
     private static native float calcNormalizedScoreNative(int[] before, int[] after, int score);
     private static native int editDistanceNative(int[] before, int[] after);
     private static native void addUnigramWordNative(long dict, int[] word, int probability);
     private static native void addBigramWordsNative(long dict, int[] word0, int[] word1,
             int probability);
     private static native void removeBigramWordsNative(long dict, int[] word0, int[] word1);
+    private static native int calculateProbabilityNative(long dict, int unigramProbability,
+            int bigramProbability);
 
     // TODO: Move native dict into session
     private final void loadDictionary(final String path, final long startOffset,
@@ -120,15 +135,16 @@
     @Override
     public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
             final String prevWord, final ProximityInfo proximityInfo,
-            final boolean blockOffensiveWords) {
+            final boolean blockOffensiveWords, final int[] additionalFeaturesOptions) {
         return getSuggestionsWithSessionId(composer, prevWord, proximityInfo, blockOffensiveWords,
-                0 /* sessionId */);
+                additionalFeaturesOptions, 0 /* sessionId */);
     }
 
     @Override
     public ArrayList<SuggestedWordInfo> getSuggestionsWithSessionId(final WordComposer composer,
             final String prevWord, final ProximityInfo proximityInfo,
-            final boolean blockOffensiveWords, final int sessionId) {
+            final boolean blockOffensiveWords, final int[] additionalFeaturesOptions,
+            final int sessionId) {
         if (!isValidDictionary()) return null;
 
         Arrays.fill(mInputCodePoints, Constants.NOT_A_CODE);
@@ -148,15 +164,14 @@
         final InputPointers ips = composer.getInputPointers();
         final int inputSize = isGesture ? ips.getPointerSize() : composerSize;
         mNativeSuggestOptions.setIsGesture(isGesture);
-        mNativeSuggestOptions.setAdditionalFeaturesOptions(
-                AdditionalFeaturesSettingUtils.getAdditionalNativeSuggestOptions());
+        mNativeSuggestOptions.setAdditionalFeaturesOptions(additionalFeaturesOptions);
         // proximityInfo and/or prevWordForBigrams may not be null.
         final int count = getSuggestionsNative(mNativeDict, proximityInfo.getNativeProximityInfo(),
                 getTraverseSession(sessionId).getSession(), ips.getXCoordinates(),
                 ips.getYCoordinates(), ips.getTimes(), ips.getPointerIds(), mInputCodePoints,
                 inputSize, 0 /* commitPoint */, mNativeSuggestOptions.getOptions(),
                 prevWordCodePointArray, mOutputCodePoints, mOutputScores, mSpaceIndices,
-                mOutputTypes);
+                mOutputTypes, mOutputAutoCommitFirstWordConfidence);
         final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList();
         for (int j = 0; j < count; ++j) {
             final int start = j * MAX_WORD_LENGTH;
@@ -179,7 +194,9 @@
                 // TODO: check that all users of the `kind' parameter are ready to accept
                 // flags too and pass mOutputTypes[j] instead of kind
                 suggestions.add(new SuggestedWordInfo(new String(mOutputCodePoints, start, len),
-                        score, kind, mDictType));
+                        score, kind, this /* sourceDict */,
+                        mSpaceIndices[j] /* indexOfTouchPointOfSecondWord */,
+                        mOutputAutoCommitFirstWordConfidence[0]));
             }
         }
         return suggestions;
@@ -205,12 +222,12 @@
 
     @Override
     public boolean isValidWord(final String word) {
-        return getFrequency(word) >= 0;
+        return getFrequency(word) != NOT_A_PROBABILITY;
     }
 
     @Override
     public int getFrequency(final String word) {
-        if (word == null) return -1;
+        if (word == null) return NOT_A_PROBABILITY;
         int[] codePoints = StringUtils.toCodePointArray(word);
         return getProbabilityNative(mNativeDict, codePoints);
     }
@@ -218,10 +235,20 @@
     // TODO: Add a batch process version (isValidBigramMultiple?) to avoid excessive numbers of jni
     // calls when checking for changes in an entire dictionary.
     public boolean isValidBigram(final String word0, final String word1) {
-        if (TextUtils.isEmpty(word0) || TextUtils.isEmpty(word1)) return false;
+        return getBigramProbability(word0, word1) != NOT_A_PROBABILITY;
+    }
+
+    public int getBigramProbability(final String word0, final String word1) {
+        if (TextUtils.isEmpty(word0) || TextUtils.isEmpty(word1)) return NOT_A_PROBABILITY;
         final int[] codePoints0 = StringUtils.toCodePointArray(word0);
         final int[] codePoints1 = StringUtils.toCodePointArray(word1);
-        return isValidBigramNative(mNativeDict, codePoints0, codePoints1);
+        return getBigramProbabilityNative(mNativeDict, codePoints0, codePoints1);
+    }
+
+    private void runGCIfRequired() {
+        if (needsToRunGCNative(mNativeDict)) {
+            flushWithGC();
+        }
     }
 
     // Add a unigram entry to binary dictionary in native code.
@@ -229,6 +256,7 @@
         if (TextUtils.isEmpty(word)) {
             return;
         }
+        runGCIfRequired();
         final int[] codePoints = StringUtils.toCodePointArray(word);
         addUnigramWordNative(mNativeDict, codePoints, probability);
     }
@@ -238,6 +266,7 @@
         if (TextUtils.isEmpty(word0) || TextUtils.isEmpty(word1)) {
             return;
         }
+        runGCIfRequired();
         final int[] codePoints0 = StringUtils.toCodePointArray(word0);
         final int[] codePoints1 = StringUtils.toCodePointArray(word1);
         addBigramWordsNative(mNativeDict, codePoints0, codePoints1, probability);
@@ -248,11 +277,57 @@
         if (TextUtils.isEmpty(word0) || TextUtils.isEmpty(word1)) {
             return;
         }
+        runGCIfRequired();
         final int[] codePoints0 = StringUtils.toCodePointArray(word0);
         final int[] codePoints1 = StringUtils.toCodePointArray(word1);
         removeBigramWordsNative(mNativeDict, codePoints0, codePoints1);
     }
 
+    public void flush() {
+        if (!isValidDictionary()) return;
+        flushNative(mNativeDict, mDictFilePath);
+        closeNative(mNativeDict);
+        final File dictFile = new File(mDictFilePath);
+        mNativeDict = openNative(dictFile.getAbsolutePath(), 0 /* startOffset */,
+                dictFile.length(), true /* isUpdatable */);
+    }
+
+    public void flushWithGC() {
+        if (!isValidDictionary()) return;
+        flushWithGCNative(mNativeDict, mDictFilePath);
+        closeNative(mNativeDict);
+        final File dictFile = new File(mDictFilePath);
+        mNativeDict = openNative(dictFile.getAbsolutePath(), 0 /* startOffset */,
+                dictFile.length(), true /* isUpdatable */);
+    }
+
+    public boolean needsToRunGC() {
+        if (!isValidDictionary()) return false;
+        return needsToRunGCNative(mNativeDict);
+    }
+
+    @UsedForTesting
+    public int calculateProbability(final int unigramProbability, final int bigramProbability) {
+        if (!isValidDictionary()) return NOT_A_PROBABILITY;
+        return calculateProbabilityNative(mNativeDict, unigramProbability, bigramProbability);
+    }
+
+    @Override
+    public boolean shouldAutoCommit(final SuggestedWordInfo candidate) {
+        // TODO: actually use the confidence rather than use this completely broken heuristic
+        final String word = candidate.mWord;
+        final int length = word.length();
+        int remainingSpaces = SPACE_COUNT_FOR_AUTO_COMMIT;
+        for (int i = 0; i < length; ++i) {
+            // This is okay because no low-surrogate and no high-surrogate can ever match the
+            // space character, so we don't need to take care of iterating on code points.
+            if (Constants.CODE_SPACE == word.charAt(i)) {
+                if (0 >= --remainingSpaces) return true;
+            }
+        }
+        return false;
+    }
+
     @Override
     public void close() {
         synchronized (mDicTraverseSessions) {
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
index fa301b5..181ad17 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
@@ -21,17 +21,17 @@
 import android.content.res.AssetFileDescriptor;
 import android.util.Log;
 
-import com.android.inputmethod.latin.makedict.BinaryDictInputOutput;
+import com.android.inputmethod.latin.makedict.DictDecoder;
 import com.android.inputmethod.latin.makedict.FormatSpec;
+import com.android.inputmethod.latin.makedict.FormatSpec.FileHeader;
+import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
 import com.android.inputmethod.latin.utils.CollectionUtils;
 import com.android.inputmethod.latin.utils.DictionaryInfoUtils;
 import com.android.inputmethod.latin.utils.LocaleUtils;
 
 import java.io.File;
-import java.io.FileInputStream;
 import java.io.IOException;
 import java.nio.BufferUnderflowException;
-import java.nio.channels.FileChannel;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Locale;
@@ -227,23 +227,12 @@
     // those do not include whitelist entries, the new code with an old version of the dictionary
     // would lose whitelist functionality.
     private static boolean hackCanUseDictionaryFile(final Locale locale, final File f) {
-        FileInputStream inStream = null;
         try {
             // Read the version of the file
-            inStream = new FileInputStream(f);
-            final BinaryDictInputOutput.ByteBufferWrapper buffer =
-                    new BinaryDictInputOutput.ByteBufferWrapper(inStream.getChannel().map(
-                            FileChannel.MapMode.READ_ONLY, 0, f.length()));
-            final int magic = buffer.readInt();
-            if (magic != FormatSpec.VERSION_2_MAGIC_NUMBER) {
-                return false;
-            }
-            final int formatVersion = buffer.readInt();
-            final int headerSize = buffer.readInt();
-            final HashMap<String, String> options = CollectionUtils.newHashMap();
-            BinaryDictInputOutput.populateOptions(buffer, headerSize, options);
+            final DictDecoder dictDecoder = FormatSpec.getDictDecoder(f);
+            final FileHeader header = dictDecoder.readHeader();
 
-            final String version = options.get(VERSION_KEY);
+            final String version = header.mDictionaryOptions.mAttributes.get(VERSION_KEY);
             if (null == version) {
                 // No version in the options : the format is unexpected
                 return false;
@@ -259,14 +248,8 @@
             return false;
         } catch (BufferUnderflowException e) {
             return false;
-        } finally {
-            if (inStream != null) {
-                try {
-                    inStream.close();
-                } catch (IOException e) {
-                    // do nothing
-                }
-            }
+        } catch (UnsupportedFormatException e) {
+            return false;
         }
     }
 
@@ -303,7 +286,8 @@
             }
             if (!dictPackSettings.isWordListActive(wordListId)) continue;
             if (canUse) {
-                fileList.add(AssetFileAddress.makeFromFileName(f.getPath()));
+                final AssetFileAddress afa = AssetFileAddress.makeFromFileName(f.getPath());
+                if (null != afa) fileList.add(afa);
             } else {
                 Log.e(TAG, "Found a cached dictionary file but cannot read or use it");
             }
diff --git a/java/src/com/android/inputmethod/latin/Constants.java b/java/src/com/android/inputmethod/latin/Constants.java
index 8aec03f..c4f9601 100644
--- a/java/src/com/android/inputmethod/latin/Constants.java
+++ b/java/src/com/android/inputmethod/latin/Constants.java
@@ -76,6 +76,11 @@
             public static final String ASCII_CAPABLE = "AsciiCapable";
 
             /**
+             * The subtype extra value used to indicate that the subtype keyboard layout is capable
+             * for typing EMOJI characters.
+             */
+            public static final String EMOJI_CAPABLE = "EmojiCapable";
+            /**
              * The subtype extra value used to indicate that the subtype require network connection
              * to work.
              */
@@ -133,6 +138,9 @@
     public static final int SPELL_CHECKER_COORDINATE = -3;
     public static final int EXTERNAL_KEYBOARD_COORDINATE = -4;
 
+    // A hint on how many characters to cache from the TextView. A good value of this is given by
+    // how many characters we need to be able to almost always find the caps mode.
+    public static final int EDITOR_CONTENTS_CACHE_SIZE = 1024;
 
     // Must be equal to MAX_WORD_LENGTH in native/jni/src/defines.h
     public static final int DICTIONARY_MAX_WORD_LENGTH = 48;
@@ -157,6 +165,7 @@
     public static final int CODE_TAB = '\t';
     public static final int CODE_SPACE = ' ';
     public static final int CODE_PERIOD = '.';
+    public static final int CODE_ARMENIAN_PERIOD = 0x0589;
     public static final int CODE_DASH = '-';
     public static final int CODE_SINGLE_QUOTE = '\'';
     public static final int CODE_DOUBLE_QUOTE = '"';
@@ -164,9 +173,7 @@
     public static final int CODE_EXCLAMATION_MARK = '!';
     public static final int CODE_SLASH = '/';
     public static final int CODE_COMMERCIAL_AT = '@';
-    // TODO: Check how this should work for right-to-left languages. It seems to stand
-    // that for rtl languages, a closing parenthesis is a left parenthesis. Is this
-    // managed by the font? Or is it a different char?
+    public static final int CODE_PLUS = '+';
     public static final int CODE_CLOSING_PARENTHESIS = ')';
     public static final int CODE_CLOSING_SQUARE_BRACKET = ']';
     public static final int CODE_CLOSING_CURLY_BRACKET = '}';
@@ -220,7 +227,10 @@
         }
     }
 
+    public static final int MAX_INT_BIT_COUNT = 32;
+
     private Constants() {
         // This utility class is not publicly instantiable.
     }
+
 }
diff --git a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
index c99d0e2..ffeb927 100644
--- a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
@@ -22,6 +22,7 @@
 import android.content.Context;
 import android.database.ContentObserver;
 import android.database.Cursor;
+import android.database.sqlite.SQLiteException;
 import android.net.Uri;
 import android.os.SystemClock;
 import android.provider.BaseColumns;
@@ -70,7 +71,8 @@
     private final boolean mUseFirstLastBigrams;
 
     public ContactsBinaryDictionary(final Context context, final Locale locale) {
-        super(context, getFilenameWithLocale(NAME, locale.toString()), Dictionary.TYPE_CONTACTS);
+        super(context, getFilenameWithLocale(NAME, locale.toString()), Dictionary.TYPE_CONTACTS,
+                false /* isUpdatable */);
         mLocale = locale;
         mUseFirstLastBigrams = useFirstLastBigramsForLocale(locale);
         registerObserver(context);
@@ -144,8 +146,10 @@
                     cursor.close();
                 }
             }
-        } catch (IllegalStateException e) {
-            Log.e(TAG, "Contacts DB is having problems");
+        } catch (final SQLiteException e) {
+            Log.e(TAG, "SQLiteException in the remote Contacts process.", e);
+        } catch (final IllegalStateException e) {
+            Log.e(TAG, "Contacts DB is having problems", e);
         }
     }
 
@@ -172,14 +176,18 @@
     private int getContactCount() {
         // TODO: consider switching to a rawQuery("select count(*)...") on the database if
         // performance is a bottleneck.
-        final Cursor cursor = mContext.getContentResolver().query(
-                Contacts.CONTENT_URI, PROJECTION_ID_ONLY, null, null, null);
-        if (cursor != null) {
-            try {
-                return cursor.getCount();
-            } finally {
-                cursor.close();
+        try {
+            final Cursor cursor = mContext.getContentResolver().query(
+                    Contacts.CONTENT_URI, PROJECTION_ID_ONLY, null, null, null);
+            if (cursor != null) {
+                try {
+                    return cursor.getCount();
+                } finally {
+                    cursor.close();
+                }
             }
+        } catch (final SQLiteException e) {
+            Log.e(TAG, "SQLiteException in the remote Contacts process.", e);
         }
         return 0;
     }
@@ -208,7 +216,8 @@
                             false /* isNotAWord */);
                     if (!TextUtils.isEmpty(prevWord)) {
                         if (mUseFirstLastBigrams) {
-                            super.setBigram(prevWord, word, FREQUENCY_FOR_CONTACTS_BIGRAM);
+                            super.addBigram(prevWord, word, FREQUENCY_FOR_CONTACTS_BIGRAM,
+                                    0 /* lastModifiedTime */);
                         }
                     }
                     prevWord = word;
diff --git a/java/src/com/android/inputmethod/latin/DicTraverseSession.java b/java/src/com/android/inputmethod/latin/DicTraverseSession.java
index 45b2813..8d295ad 100644
--- a/java/src/com/android/inputmethod/latin/DicTraverseSession.java
+++ b/java/src/com/android/inputmethod/latin/DicTraverseSession.java
@@ -25,16 +25,16 @@
         JniUtils.loadNativeLibrary();
     }
 
-    private static native long setDicTraverseSessionNative(String locale);
+    private static native long setDicTraverseSessionNative(String locale, long dictSize);
     private static native void initDicTraverseSessionNative(long nativeDicTraverseSession,
             long dictionary, int[] previousWord, int previousWordLength);
     private static native void releaseDicTraverseSessionNative(long nativeDicTraverseSession);
 
     private long mNativeDicTraverseSession;
 
-    public DicTraverseSession(Locale locale, long dictionary) {
+    public DicTraverseSession(Locale locale, long dictionary, long dictSize) {
         mNativeDicTraverseSession = createNativeDicTraverseSession(
-                locale != null ? locale.toString() : "");
+                locale != null ? locale.toString() : "", dictSize);
         initSession(dictionary);
     }
 
@@ -51,8 +51,8 @@
                 mNativeDicTraverseSession, dictionary, previousWord, previousWordLength);
     }
 
-    private final long createNativeDicTraverseSession(String locale) {
-        return setDicTraverseSessionNative(locale);
+    private final long createNativeDicTraverseSession(String locale, long dictSize) {
+        return setDicTraverseSessionNative(locale, dictSize);
     }
 
     private void closeInternal() {
diff --git a/java/src/com/android/inputmethod/latin/Dictionary.java b/java/src/com/android/inputmethod/latin/Dictionary.java
index 7c3e4a7..fa79f5a 100644
--- a/java/src/com/android/inputmethod/latin/Dictionary.java
+++ b/java/src/com/android/inputmethod/latin/Dictionary.java
@@ -28,9 +28,26 @@
 public abstract class Dictionary {
     public static final int NOT_A_PROBABILITY = -1;
 
+    // The following types do not actually come from real dictionary instances, so we create
+    // corresponding instances.
     public static final String TYPE_USER_TYPED = "user_typed";
+    public static final Dictionary DICTIONARY_USER_TYPED = new PhonyDictionary(TYPE_USER_TYPED);
+
     public static final String TYPE_APPLICATION_DEFINED = "application_defined";
+    public static final Dictionary DICTIONARY_APPLICATION_DEFINED =
+            new PhonyDictionary(TYPE_APPLICATION_DEFINED);
+
     public static final String TYPE_HARDCODED = "hardcoded"; // punctuation signs and such
+    public static final Dictionary DICTIONARY_HARDCODED =
+            new PhonyDictionary(TYPE_HARDCODED);
+
+    // Spawned by resuming suggestions. Comes from a span that was in the TextView.
+    public static final String TYPE_RESUMED = "resumed";
+    public static final Dictionary DICTIONARY_RESUMED =
+            new PhonyDictionary(TYPE_RESUMED);
+
+    // The following types of dictionary have actual functional instances. We don't need final
+    // phony dictionary instances for them.
     public static final String TYPE_MAIN = "main";
     public static final String TYPE_CONTACTS = "contacts";
     // User dictionary, the system-managed one.
@@ -42,9 +59,7 @@
     // Personalization prediction dictionary internal to LatinIME's Java code.
     public static final String TYPE_PERSONALIZATION_PREDICTION_IN_JAVA =
             "personalization_prediction_in_java";
-    // Spawned by resuming suggestions. Comes from a span that was in the TextView.
-    public static final String TYPE_RESUMED = "resumed";
-    protected final String mDictType;
+    public final String mDictType;
 
     public Dictionary(final String dictType) {
         mDictType = dictType;
@@ -57,20 +72,23 @@
      * @param prevWord the previous word, or null if none
      * @param proximityInfo the object for key proximity. May be ignored by some implementations.
      * @param blockOffensiveWords whether to block potentially offensive words
+     * @param additionalFeaturesOptions options about additional features used for the suggestion.
      * @return the list of suggestions (possibly null if none)
      */
     // TODO: pass more context than just the previous word, to enable better suggestions (n-gram
     // and more)
     abstract public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
             final String prevWord, final ProximityInfo proximityInfo,
-            final boolean blockOffensiveWords);
+            final boolean blockOffensiveWords, final int[] additionalFeaturesOptions);
 
     // The default implementation of this method ignores sessionId.
     // Subclasses that want to use sessionId need to override this method.
     public ArrayList<SuggestedWordInfo> getSuggestionsWithSessionId(final WordComposer composer,
             final String prevWord, final ProximityInfo proximityInfo,
-            final boolean blockOffensiveWords, final int sessionId) {
-        return getSuggestions(composer, prevWord, proximityInfo, blockOffensiveWords);
+            final boolean blockOffensiveWords, final int[] additionalFeaturesOptions,
+            final int sessionId) {
+        return getSuggestions(composer, prevWord, proximityInfo, blockOffensiveWords,
+                additionalFeaturesOptions);
     }
 
     /**
@@ -114,8 +132,43 @@
     /**
      * Subclasses may override to indicate that this Dictionary is not yet properly initialized.
      */
-
     public boolean isInitialized() {
         return true;
     }
+
+    /**
+     * Whether we think this suggestion should trigger an auto-commit. prevWord is the word
+     * before the suggestion, so that we can use n-gram frequencies.
+     * @param candidate The candidate suggestion, in whole (not only the first part).
+     * @return whether we should auto-commit or not.
+     */
+    public boolean shouldAutoCommit(final SuggestedWordInfo candidate) {
+        // If we don't have support for auto-commit, or if we don't know, we return false to
+        // avoid auto-committing stuff. Implementations of the Dictionary class that know to
+        // determine whether we should auto-commit will override this.
+        return false;
+    }
+
+    /**
+     * Not a true dictionary. A placeholder used to indicate suggestions that don't come from any
+     * real dictionary.
+     */
+    private static class PhonyDictionary extends Dictionary {
+        // This class is not publicly instantiable.
+        private PhonyDictionary(final String type) {
+            super(type);
+        }
+
+        @Override
+        public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
+                final String prevWord, final ProximityInfo proximityInfo,
+                final boolean blockOffensiveWords, final int[] additionalFeaturesOptions) {
+            return null;
+        }
+
+        @Override
+        public boolean isValidWord(String word) {
+            return false;
+        }
+    }
 }
diff --git a/java/src/com/android/inputmethod/latin/DictionaryCollection.java b/java/src/com/android/inputmethod/latin/DictionaryCollection.java
index d05bb1e..bf07514 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryCollection.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryCollection.java
@@ -58,18 +58,18 @@
     @Override
     public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
             final String prevWord, final ProximityInfo proximityInfo,
-            final boolean blockOffensiveWords) {
+            final boolean blockOffensiveWords, final int[] additionalFeaturesOptions) {
         final CopyOnWriteArrayList<Dictionary> dictionaries = mDictionaries;
         if (dictionaries.isEmpty()) return null;
         // To avoid creating unnecessary objects, we get the list out of the first
         // dictionary and add the rest to it if not null, hence the get(0)
         ArrayList<SuggestedWordInfo> suggestions = dictionaries.get(0).getSuggestions(composer,
-                prevWord, proximityInfo, blockOffensiveWords);
+                prevWord, proximityInfo, blockOffensiveWords, additionalFeaturesOptions);
         if (null == suggestions) suggestions = CollectionUtils.newArrayList();
         final int length = dictionaries.size();
         for (int i = 1; i < length; ++ i) {
             final ArrayList<SuggestedWordInfo> sugg = dictionaries.get(i).getSuggestions(composer,
-                    prevWord, proximityInfo, blockOffensiveWords);
+                    prevWord, proximityInfo, blockOffensiveWords, additionalFeaturesOptions);
             if (null != sugg) suggestions.addAll(sugg);
         }
         return suggestions;
diff --git a/java/src/com/android/inputmethod/latin/DictionaryWriter.java b/java/src/com/android/inputmethod/latin/DictionaryWriter.java
index 47151bf..5a453dd 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryWriter.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryWriter.java
@@ -20,15 +20,14 @@
 
 import com.android.inputmethod.keyboard.ProximityInfo;
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
-import com.android.inputmethod.latin.makedict.BinaryDictInputOutput;
+import com.android.inputmethod.latin.makedict.DictEncoder;
 import com.android.inputmethod.latin.makedict.FormatSpec;
 import com.android.inputmethod.latin.makedict.FusionDictionary;
-import com.android.inputmethod.latin.makedict.FusionDictionary.Node;
+import com.android.inputmethod.latin.makedict.FusionDictionary.PtNodeArray;
 import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
 import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
 import com.android.inputmethod.latin.utils.CollectionUtils;
 
-import java.io.FileOutputStream;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -51,7 +50,7 @@
     @Override
     public void clear() {
         final HashMap<String, String> attributes = CollectionUtils.newHashMap();
-        mFusionDictionary = new FusionDictionary(new Node(),
+        mFusionDictionary = new FusionDictionary(new PtNodeArray(),
                 new FusionDictionary.DictionaryOptions(attributes, false, false));
     }
 
@@ -75,7 +74,7 @@
 
     @Override
     public void addBigramWords(final String word0, final String word1, final int frequency,
-            final boolean isValid) {
+            final boolean isValid, final long lastModifiedTime) {
         mFusionDictionary.setBigram(word0, word1, frequency);
     }
 
@@ -85,15 +84,15 @@
     }
 
     @Override
-    protected void writeBinaryDictionary(final FileOutputStream out)
+    protected void writeDictionary(final DictEncoder dictEncoder)
             throws IOException, UnsupportedFormatException {
-        BinaryDictInputOutput.writeDictionaryBinary(out, mFusionDictionary, FORMAT_OPTIONS);
+        dictEncoder.writeDictionary(mFusionDictionary, FORMAT_OPTIONS);
     }
 
     @Override
     public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
             final String prevWord, final ProximityInfo proximityInfo,
-            boolean blockOffensiveWords) {
+            boolean blockOffensiveWords, final int[] additionalFeaturesOptions) {
         // This class doesn't support suggestion.
         return null;
     }
diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
index 3f11391..0774ce2 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
@@ -20,14 +20,26 @@
 import android.os.SystemClock;
 import android.util.Log;
 
+import com.android.inputmethod.annotations.UsedForTesting;
 import com.android.inputmethod.keyboard.ProximityInfo;
+import com.android.inputmethod.latin.makedict.DictEncoder;
+import com.android.inputmethod.latin.makedict.FormatSpec;
+import com.android.inputmethod.latin.makedict.FusionDictionary;
+import com.android.inputmethod.latin.makedict.FusionDictionary.PtNodeArray;
+import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
+import com.android.inputmethod.latin.makedict.Ver3DictEncoder;
+import com.android.inputmethod.latin.personalization.DynamicPersonalizationDictionaryWriter;
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
+import com.android.inputmethod.latin.utils.AsyncResultHolder;
 import com.android.inputmethod.latin.utils.CollectionUtils;
+import com.android.inputmethod.latin.utils.PrioritizedSerialExecutor;
 
 import java.io.File;
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.HashMap;
-import java.util.concurrent.locks.ReentrantReadWriteLock;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicReference;
 
 /**
  * Abstract base class for an expandable dictionary that can be created and updated dynamically
@@ -45,19 +57,30 @@
     /** Whether to print debug output to log */
     private static boolean DEBUG = false;
 
+    // TODO: Remove.
+    /** Whether to call binary dictionary dynamically updating methods. */
+    public static boolean ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE = true;
+
+    private static final int TIMEOUT_FOR_READ_OPS_IN_MILLISECONDS = 100;
+
     /**
      * The maximum length of a word in this dictionary.
      */
     protected static final int MAX_WORD_LENGTH = Constants.DICTIONARY_MAX_WORD_LENGTH;
 
+    private static final FormatSpec.FormatOptions FORMAT_OPTIONS =
+            new FormatSpec.FormatOptions(3 /* version */, true /* supportsDynamicUpdate */);
+
     /**
-     * A static map of locks, each of which controls access to a single binary dictionary file. They
-     * ensure that only one instance can update the same dictionary at the same time. The key for
-     * this map is the filename and the value is the shared dictionary controller associated with
-     * that filename.
+     * A static map of time recorders, each of which records the time of accesses to a single binary
+     * dictionary file. The key for this map is the filename and the value is the shared dictionary
+     * time recorder associated with that filename.
      */
-    private static final HashMap<String, DictionaryController> sSharedDictionaryControllers =
-            CollectionUtils.newHashMap();
+    private static volatile ConcurrentHashMap<String, DictionaryTimeRecorder>
+            sFilenameDictionaryTimeRecorderMap = CollectionUtils.newConcurrentHashMap();
+
+    private static volatile ConcurrentHashMap<String, PrioritizedSerialExecutor>
+            sFilenameExecutorMap = CollectionUtils.newConcurrentHashMap();
 
     /** The application context. */
     protected final Context mContext;
@@ -68,21 +91,34 @@
      */
     private BinaryDictionary mBinaryDictionary;
 
+    // TODO: Remove and handle dictionaries in native code.
     /** The in-memory dictionary used to generate the binary dictionary. */
-    private AbstractDictionaryWriter mDictionaryWriter;
+    protected AbstractDictionaryWriter mDictionaryWriter;
 
     /**
      * The name of this dictionary, used as the filename for storing the binary dictionary. Multiple
      * dictionary instances with the same filename is supported, with access controlled by
-     * DictionaryController.
+     * DictionaryTimeRecorder.
      */
     private final String mFilename;
 
-    /** Controls access to the shared binary dictionary file across multiple instances. */
-    private final DictionaryController mSharedDictionaryController;
+    /** Whether to support dynamically updating the dictionary */
+    private final boolean mIsUpdatable;
 
-    /** Controls access to the local binary dictionary for this instance. */
-    private final DictionaryController mLocalDictionaryController = new DictionaryController();
+    // TODO: remove, once dynamic operations is serialized
+    /** Records access to the shared binary dictionary file across multiple instances. */
+    private final DictionaryTimeRecorder mFilenameDictionaryTimeRecorder;
+
+    // TODO: remove, once dynamic operations is serialized
+    /** Records access to the local binary dictionary for this instance. */
+    private final DictionaryTimeRecorder mPerInstanceDictionaryTimeRecorder =
+            new DictionaryTimeRecorder();
+
+    /* A extension for a binary dictionary file. */
+    public static final String DICT_FILE_EXTENSION = ".dict";
+
+    private final AtomicReference<Runnable> mUnfinishedFlushingTask =
+            new AtomicReference<Runnable>();
 
     /**
      * Abstract method for loading the unigrams and bigrams of a given dictionary in a background
@@ -98,16 +134,45 @@
     protected abstract boolean hasContentChanged();
 
     /**
-     * Gets the shared dictionary controller for the given filename.
+     * Gets the dictionary time recorder for the given filename.
      */
-    private static synchronized DictionaryController getSharedDictionaryController(
+    private static DictionaryTimeRecorder getDictionaryTimeRecorder(
             String filename) {
-        DictionaryController controller = sSharedDictionaryControllers.get(filename);
-        if (controller == null) {
-            controller = new DictionaryController();
-            sSharedDictionaryControllers.put(filename, controller);
+        DictionaryTimeRecorder recorder = sFilenameDictionaryTimeRecorderMap.get(filename);
+        if (recorder == null) {
+            synchronized(sFilenameDictionaryTimeRecorderMap) {
+                recorder = new DictionaryTimeRecorder();
+                sFilenameDictionaryTimeRecorderMap.put(filename, recorder);
+            }
         }
-        return controller;
+        return recorder;
+    }
+
+    /**
+     * Gets the executor for the given filename.
+     */
+    private static PrioritizedSerialExecutor getExecutor(final String filename) {
+        PrioritizedSerialExecutor executor = sFilenameExecutorMap.get(filename);
+        if (executor == null) {
+            synchronized(sFilenameExecutorMap) {
+                executor = new PrioritizedSerialExecutor();
+                sFilenameExecutorMap.put(filename, executor);
+            }
+        }
+        return executor;
+    }
+
+    private static AbstractDictionaryWriter getDictionaryWriter(final Context context,
+            final String dictType, final boolean isDynamicPersonalizationDictionary) {
+        if (isDynamicPersonalizationDictionary) {
+            if (ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE) {
+                return null;
+            } else {
+                return new DynamicPersonalizationDictionaryWriter(context, dictType);
+            }
+        } else {
+            return new DictionaryWriter(context, dictType);
+        }
     }
 
     /**
@@ -117,19 +182,23 @@
      * @param filename The filename for this binary dictionary. Multiple dictionaries with the same
      *        filename is supported.
      * @param dictType the dictionary type, as a human-readable string
+     * @param isUpdatable whether to support dynamically updating the dictionary. Please note that
+     *        dynamic dictionary has negative effects on memory space and computation time.
      */
-    public ExpandableBinaryDictionary(
-            final Context context, final String filename, final String dictType) {
+    public ExpandableBinaryDictionary(final Context context, final String filename,
+            final String dictType, final boolean isUpdatable) {
         super(dictType);
         mFilename = filename;
         mContext = context;
+        mIsUpdatable = isUpdatable;
         mBinaryDictionary = null;
-        mSharedDictionaryController = getSharedDictionaryController(filename);
-        mDictionaryWriter = new DictionaryWriter(context, dictType);
+        mFilenameDictionaryTimeRecorder = getDictionaryTimeRecorder(filename);
+        // Currently, only dynamic personalization dictionary is updatable.
+        mDictionaryWriter = getDictionaryWriter(context, dictType, isUpdatable);
     }
 
     protected static String getFilenameWithLocale(final String name, final String localeStr) {
-        return name + "." + localeStr + ".dict";
+        return name + "." + localeStr + DICT_FILE_EXTENSION;
     }
 
     /**
@@ -137,17 +206,56 @@
      */
     @Override
     public void close() {
-        // Ensure that no other threads are accessing the local binary dictionary.
-        mLocalDictionaryController.writeLock().lock();
-        try {
-            if (mBinaryDictionary != null) {
-                mBinaryDictionary.close();
-                mBinaryDictionary = null;
+        getExecutor(mFilename).execute(new Runnable() {
+            @Override
+            public void run() {
+                if (mBinaryDictionary!= null) {
+                    mBinaryDictionary.close();
+                    mBinaryDictionary = null;
+                }
+                if (mDictionaryWriter != null) {
+                    mDictionaryWriter.close();
+                }
             }
-            mDictionaryWriter.close();
-        } finally {
-            mLocalDictionaryController.writeLock().unlock();
-        }
+        });
+    }
+
+    protected void closeBinaryDictionary() {
+        // Ensure that no other threads are accessing the local binary dictionary.
+        getExecutor(mFilename).execute(new Runnable() {
+            @Override
+            public void run() {
+                if (mBinaryDictionary != null) {
+                    mBinaryDictionary.close();
+                    mBinaryDictionary = null;
+                }
+            }
+        });
+    }
+
+    protected void clear() {
+        getExecutor(mFilename).execute(new Runnable() {
+            @Override
+            public void run() {
+                if (ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE && mDictionaryWriter == null) {
+                    mBinaryDictionary.close();
+                    final File file = new File(mContext.getFilesDir(), mFilename);
+                    final FusionDictionary dict = new FusionDictionary(new PtNodeArray(),
+                            new FusionDictionary.DictionaryOptions(new HashMap<String,String>(),
+                                    false, false));
+                    final DictEncoder dictEncoder = new Ver3DictEncoder(file);
+                    try {
+                        dictEncoder.writeDictionary(dict, FORMAT_OPTIONS);
+                    } catch (IOException e) {
+                        Log.e(TAG, "Exception in creating new dictionary file.", e);
+                    } catch (UnsupportedFormatException e) {
+                        Log.e(TAG, "Exception in creating new dictionary file.", e);
+                    }
+                } else {
+                    mDictionaryWriter.clear();
+                }
+            }
+        });
     }
 
     /**
@@ -159,86 +267,159 @@
     }
 
     /**
-     * Sets a word bigram in the dictionary. Used for loading a dictionary.
+     * Adds a word bigram in the dictionary. Used for loading a dictionary.
      */
-    protected void setBigram(final String prevWord, final String word, final int frequency) {
-        mDictionaryWriter.addBigramWords(prevWord, word, frequency, true /* isValid */);
+    protected void addBigram(final String prevWord, final String word, final int frequency,
+            final long lastModifiedTime) {
+        mDictionaryWriter.addBigramWords(prevWord, word, frequency, true /* isValid */,
+                lastModifiedTime);
     }
 
     /**
-     * Dynamically adds a word unigram to the dictionary.
+     * Dynamically adds a word unigram to the dictionary. May overwrite an existing entry.
      */
     protected void addWordDynamically(final String word, final String shortcutTarget,
             final int frequency, final boolean isNotAWord) {
-        mLocalDictionaryController.writeLock().lock();
-        try {
-            mDictionaryWriter.addUnigramWord(word, shortcutTarget, frequency, isNotAWord);
-        } finally {
-            mLocalDictionaryController.writeLock().unlock();
+        if (!mIsUpdatable) {
+            Log.w(TAG, "addWordDynamically is called for non-updatable dictionary: " + mFilename);
+            return;
         }
+
+        getExecutor(mFilename).execute(new Runnable() {
+            @Override
+            public void run() {
+                if (ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE) {
+                    mBinaryDictionary.addUnigramWord(word, frequency);
+                } else {
+                    // TODO: Remove.
+                    mDictionaryWriter.addUnigramWord(word, shortcutTarget, frequency, isNotAWord);
+                }
+            }
+        });
     }
 
     /**
-     * Dynamically sets a word bigram in the dictionary.
+     * Dynamically adds a word bigram in the dictionary. May overwrite an existing entry.
      */
-    protected void setBigramDynamically(final String prevWord, final String word,
-            final int frequency) {
-        mLocalDictionaryController.writeLock().lock();
-        try {
-            mDictionaryWriter.addBigramWords(prevWord, word, frequency, true /* isValid */);
-        } finally {
-            mLocalDictionaryController.writeLock().unlock();
+    protected void addBigramDynamically(final String word0, final String word1,
+            final int frequency, final boolean isValid) {
+        if (!mIsUpdatable) {
+            Log.w(TAG, "addBigramDynamically is called for non-updatable dictionary: "
+                    + mFilename);
+            return;
         }
+
+        getExecutor(mFilename).execute(new Runnable() {
+            @Override
+            public void run() {
+                if (ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE) {
+                    mBinaryDictionary.addBigramWords(word0, word1, frequency);
+                } else {
+                    // TODO: Remove.
+                    mDictionaryWriter.addBigramWords(word0, word1, frequency, isValid,
+                            0 /* lastTouchedTime */);
+                }
+            }
+        });
+    }
+
+    /**
+     * Dynamically remove a word bigram in the dictionary.
+     */
+    protected void removeBigramDynamically(final String word0, final String word1) {
+        if (!mIsUpdatable) {
+            Log.w(TAG, "removeBigramDynamically is called for non-updatable dictionary: "
+                    + mFilename);
+            return;
+        }
+
+        getExecutor(mFilename).execute(new Runnable() {
+            @Override
+            public void run() {
+                if (ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE) {
+                    mBinaryDictionary.removeBigramWords(word0, word1);
+                } else {
+                    // TODO: Remove.
+                    mDictionaryWriter.removeBigramWords(word0, word1);
+                }
+            }
+        });
+    }
+
+    @Override
+    public ArrayList<SuggestedWordInfo> getSuggestionsWithSessionId(final WordComposer composer,
+            final String prevWord, final ProximityInfo proximityInfo,
+            final boolean blockOffensiveWords, final int[] additionalFeaturesOptions,
+            final int sessionId) {
+        reloadDictionaryIfRequired();
+        final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList();
+        final AsyncResultHolder<ArrayList<SuggestedWordInfo>> holder =
+                new AsyncResultHolder<ArrayList<SuggestedWordInfo>>();
+        getExecutor(mFilename).executePrioritized(new Runnable() {
+            @Override
+            public void run() {
+                if (ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE) {
+                    if (mBinaryDictionary == null) {
+                        holder.set(null);
+                        return;
+                    }
+                    final ArrayList<SuggestedWordInfo> binarySuggestion =
+                            mBinaryDictionary.getSuggestionsWithSessionId(composer, prevWord,
+                                    proximityInfo, blockOffensiveWords, additionalFeaturesOptions,
+                                    sessionId);
+                    holder.set(binarySuggestion);
+                } else {
+                    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.getSuggestionsWithSessionId(composer, prevWord,
+                                        proximityInfo, blockOffensiveWords,
+                                        additionalFeaturesOptions, sessionId);
+                        if (inMemDictSuggestion == null) {
+                            holder.set(binarySuggestion);
+                        } else if (binarySuggestion == null) {
+                            holder.set(inMemDictSuggestion);
+                        } else {
+                            binarySuggestion.addAll(inMemDictSuggestion);
+                            holder.set(binarySuggestion);
+                        }
+                    } else {
+                        holder.set(inMemDictSuggestion);
+                    }
+                }
+            }
+        });
+        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) {
-        asyncReloadDictionaryIfRequired();
-        // Write lock because getSuggestions in native updates session status.
-        if (mLocalDictionaryController.writeLock().tryLock()) {
-            try {
-                final ArrayList<SuggestedWordInfo> inMemDictSuggestion =
-                        mDictionaryWriter.getSuggestions(composer, prevWord, proximityInfo,
-                                blockOffensiveWords);
-                if (mBinaryDictionary != null) {
-                    final ArrayList<SuggestedWordInfo> binarySuggestion =
-                            mBinaryDictionary.getSuggestions(composer, prevWord, proximityInfo,
-                                    blockOffensiveWords);
-                    if (inMemDictSuggestion == null) {
-                        return binarySuggestion;
-                    } else if (binarySuggestion == null) {
-                        return inMemDictSuggestion;
-                    } else {
-                        binarySuggestion.addAll(binarySuggestion);
-                        return binarySuggestion;
-                    }
-                } else {
-                    return inMemDictSuggestion;
-                }
-            } finally {
-                mLocalDictionaryController.writeLock().unlock();
-            }
-        }
-        return null;
+            final boolean blockOffensiveWords, final int[] additionalFeaturesOptions) {
+        return getSuggestionsWithSessionId(composer, prevWord, proximityInfo, blockOffensiveWords,
+                additionalFeaturesOptions, 0 /* sessionId */);
     }
 
     @Override
     public boolean isValidWord(final String word) {
-        asyncReloadDictionaryIfRequired();
+        reloadDictionaryIfRequired();
         return isValidWordInner(word);
     }
 
     protected boolean isValidWordInner(final String word) {
-        if (mLocalDictionaryController.readLock().tryLock()) {
-            try {
-                return isValidWordLocked(word);
-            } finally {
-                mLocalDictionaryController.readLock().unlock();
+        final AsyncResultHolder<Boolean> holder = new AsyncResultHolder<Boolean>();
+        getExecutor(mFilename).executePrioritized(new Runnable() {
+            @Override
+            public void run() {
+                holder.set(isValidWordLocked(word));
             }
-        }
-        return false;
+        });
+        return holder.get(false, TIMEOUT_FOR_READ_OPS_IN_MILLISECONDS);
     }
 
     protected boolean isValidWordLocked(final String word) {
@@ -256,8 +437,8 @@
      * dictionary exists, this method will generate one.
      */
     protected void loadDictionary() {
-        mLocalDictionaryController.mLastUpdateRequestTime = SystemClock.uptimeMillis();
-        asyncReloadDictionaryIfRequired();
+        mPerInstanceDictionaryTimeRecorder.mLastUpdateRequestTime = SystemClock.uptimeMillis();
+        reloadDictionaryIfRequired();
     }
 
     /**
@@ -267,8 +448,8 @@
     private void loadBinaryDictionary() {
         if (DEBUG) {
             Log.d(TAG, "Loading binary dictionary: " + mFilename + " request="
-                    + mSharedDictionaryController.mLastUpdateRequestTime + " update="
-                    + mSharedDictionaryController.mLastUpdateTime);
+                    + mFilenameDictionaryTimeRecorder.mLastUpdateRequestTime + " update="
+                    + mFilenameDictionaryTimeRecorder.mLastUpdateTime);
         }
 
         final File file = new File(mContext.getFilesDir(), mFilename);
@@ -277,22 +458,21 @@
 
         // Build the new binary dictionary
         final BinaryDictionary newBinaryDictionary = new BinaryDictionary(filename, 0, length,
-                true /* useFullEditDistance */, null, mDictType, false /* isUpdatable */);
+                true /* useFullEditDistance */, null, mDictType, mIsUpdatable);
 
-        if (mBinaryDictionary != null) {
-            // Ensure all threads accessing the current dictionary have finished before swapping in
-            // the new one.
-            final BinaryDictionary oldBinaryDictionary = mBinaryDictionary;
-            mLocalDictionaryController.writeLock().lock();
-            try {
+        // Ensure all threads accessing the current dictionary have finished before
+        // swapping in the new one.
+        // TODO: Ensure multi-thread assignment of mBinaryDictionary.
+        final BinaryDictionary oldBinaryDictionary = mBinaryDictionary;
+        getExecutor(mFilename).executePrioritized(new Runnable() {
+            @Override
+            public void run() {
                 mBinaryDictionary = newBinaryDictionary;
-            } finally {
-                mLocalDictionaryController.writeLock().unlock();
+                if (oldBinaryDictionary != null) {
+                    oldBinaryDictionary.close();
+                }
             }
-            oldBinaryDictionary.close();
-        } else {
-            mBinaryDictionary = newBinaryDictionary;
-        }
+        });
     }
 
     /**
@@ -302,19 +482,44 @@
     abstract protected boolean needsToReloadBeforeWriting();
 
     /**
-     * Generates and writes a new binary dictionary based on the contents of the fusion dictionary.
+     * Writes a new binary dictionary based on the contents of the fusion dictionary.
      */
-    private void generateBinaryDictionary() {
+    private void writeBinaryDictionary() {
         if (DEBUG) {
             Log.d(TAG, "Generating binary dictionary: " + mFilename + " request="
-                    + mSharedDictionaryController.mLastUpdateRequestTime + " update="
-                    + mSharedDictionaryController.mLastUpdateTime);
+                    + mFilenameDictionaryTimeRecorder.mLastUpdateRequestTime + " update="
+                    + mFilenameDictionaryTimeRecorder.mLastUpdateTime);
         }
         if (needsToReloadBeforeWriting()) {
             mDictionaryWriter.clear();
             loadDictionaryAsync();
+            mDictionaryWriter.write(mFilename);
+        } else {
+            if (ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE) {
+                if (mBinaryDictionary == null || !mBinaryDictionary.isValidDictionary()) {
+                    final File file = new File(mContext.getFilesDir(), mFilename);
+                    final FusionDictionary dict = new FusionDictionary(new PtNodeArray(),
+                            new FusionDictionary.DictionaryOptions(new HashMap<String,String>(),
+                                    false, false));
+                    final DictEncoder dictEncoder = new Ver3DictEncoder(file);
+                    try {
+                        dictEncoder.writeDictionary(dict, FORMAT_OPTIONS);
+                    } catch (IOException e) {
+                        Log.e(TAG, "Exception in creating new dictionary file.", e);
+                    } catch (UnsupportedFormatException e) {
+                        Log.e(TAG, "Exception in creating new dictionary file.", e);
+                    }
+                } else {
+                    if (mBinaryDictionary.needsToRunGC()) {
+                        mBinaryDictionary.flushWithGC();
+                    } else {
+                        mBinaryDictionary.flush();
+                    }
+                }
+            } else {
+                mDictionaryWriter.write(mFilename);
+            }
         }
-        mDictionaryWriter.write(mFilename);
     }
 
     /**
@@ -326,83 +531,74 @@
      */
     protected void setRequiresReload(final boolean requiresRebuild) {
         final long time = SystemClock.uptimeMillis();
-        mLocalDictionaryController.mLastUpdateRequestTime = time;
-        mSharedDictionaryController.mLastUpdateRequestTime = time;
+        mPerInstanceDictionaryTimeRecorder.mLastUpdateRequestTime = time;
+        mFilenameDictionaryTimeRecorder.mLastUpdateRequestTime = time;
         if (DEBUG) {
             Log.d(TAG, "Reload request: " + mFilename + ": request=" + time + " update="
-                    + mSharedDictionaryController.mLastUpdateTime);
+                    + mFilenameDictionaryTimeRecorder.mLastUpdateTime);
         }
     }
 
     /**
-     * Reloads the dictionary if required. Reload will occur asynchronously in a separate thread.
-     */
-    void asyncReloadDictionaryIfRequired() {
-        if (!isReloadRequired()) return;
-        if (DEBUG) {
-            Log.d(TAG, "Starting AsyncReloadDictionaryTask: " + mFilename);
-        }
-        new AsyncReloadDictionaryTask().start();
-    }
-
-    /**
      * Reloads the dictionary if required.
      */
-    protected final void syncReloadDictionaryIfRequired() {
+    public final void reloadDictionaryIfRequired() {
         if (!isReloadRequired()) return;
-        syncReloadDictionaryInternal();
+        reloadDictionary();
     }
 
     /**
      * Returns whether a dictionary reload is required.
      */
     private boolean isReloadRequired() {
-        return mBinaryDictionary == null || mLocalDictionaryController.isOutOfDate();
+        return mBinaryDictionary == null || mPerInstanceDictionaryTimeRecorder.isOutOfDate();
     }
 
     /**
      * Reloads the dictionary. Access is controlled on a per dictionary file basis and supports
      * concurrent calls from multiple instances that share the same dictionary file.
      */
-    private final void syncReloadDictionaryInternal() {
+    private final void reloadDictionary() {
         // Ensure that only one thread attempts to read or write to the shared binary dictionary
         // file at the same time.
-        mSharedDictionaryController.writeLock().lock();
-        try {
-            final long time = SystemClock.uptimeMillis();
-            final boolean dictionaryFileExists = dictionaryFileExists();
-            if (mSharedDictionaryController.isOutOfDate() || !dictionaryFileExists) {
-                // If the shared dictionary file does not exist or is out of date, the first
-                // instance that acquires the lock will generate a new one.
-                if (hasContentChanged() || !dictionaryFileExists) {
-                    // If the source content has changed or the dictionary does not exist, rebuild
-                    // the binary dictionary. Empty dictionaries are supported (in the case where
-                    // loadDictionaryAsync() adds nothing) in order to provide a uniform framework.
-                    mSharedDictionaryController.mLastUpdateTime = time;
-                    generateBinaryDictionary();
+        getExecutor(mFilename).execute(new Runnable() {
+            @Override
+            public void run() {
+                final long time = SystemClock.uptimeMillis();
+                final boolean dictionaryFileExists = dictionaryFileExists();
+                if (mFilenameDictionaryTimeRecorder.isOutOfDate() || !dictionaryFileExists) {
+                    // If the shared dictionary file does not exist or is out of date, the first
+                    // instance that acquires the lock will generate a new one.
+                    if (hasContentChanged() || !dictionaryFileExists) {
+                        // If the source content has changed or the dictionary does not exist,
+                        // rebuild the binary dictionary. Empty dictionaries are supported (in the
+                        // case where loadDictionaryAsync() adds nothing) in order to provide a
+                        // uniform framework.
+                        mFilenameDictionaryTimeRecorder.mLastUpdateTime = time;
+                        writeBinaryDictionary();
+                        loadBinaryDictionary();
+                    } else {
+                        // If not, the reload request was unnecessary so revert
+                        // LastUpdateRequestTime to LastUpdateTime.
+                        mFilenameDictionaryTimeRecorder.mLastUpdateRequestTime =
+                                mFilenameDictionaryTimeRecorder.mLastUpdateTime;
+                    }
+                } else if (mBinaryDictionary == null ||
+                        mPerInstanceDictionaryTimeRecorder.mLastUpdateTime
+                                < mFilenameDictionaryTimeRecorder.mLastUpdateTime) {
+                    // Otherwise, if the local dictionary is older than the shared dictionary, load
+                    // the shared dictionary.
                     loadBinaryDictionary();
-                } else {
-                    // If not, the reload request was unnecessary so revert LastUpdateRequestTime
-                    // to LastUpdateTime.
-                    mSharedDictionaryController.mLastUpdateRequestTime =
-                            mSharedDictionaryController.mLastUpdateTime;
                 }
-            } else if (mBinaryDictionary == null || mLocalDictionaryController.mLastUpdateTime
-                    < mSharedDictionaryController.mLastUpdateTime) {
-                // Otherwise, if the local dictionary is older than the shared dictionary, load the
-                // shared dictionary.
-                loadBinaryDictionary();
+                if (mBinaryDictionary != null && !mBinaryDictionary.isValidDictionary()) {
+                    // Binary dictionary is not valid. Regenerate the dictionary file.
+                    mFilenameDictionaryTimeRecorder.mLastUpdateTime = time;
+                    writeBinaryDictionary();
+                    loadBinaryDictionary();
+                }
+                mPerInstanceDictionaryTimeRecorder.mLastUpdateTime = time;
             }
-            if (mBinaryDictionary != null && !mBinaryDictionary.isValidDictionary()) {
-                // Binary dictionary is not valid. Regenerate the dictionary file.
-                mSharedDictionaryController.mLastUpdateTime = time;
-                generateBinaryDictionary();
-                loadBinaryDictionary();
-            }
-            mLocalDictionaryController.mLastUpdateTime = time;
-        } finally {
-            mSharedDictionaryController.writeLock().unlock();
-        }
+        });
     }
 
     // TODO: cache the file's existence so that we avoid doing a disk access each time.
@@ -412,21 +608,38 @@
     }
 
     /**
-     * Thread class for asynchronously reloading and rewriting the binary dictionary.
+     * Load the dictionary to memory.
      */
-    private class AsyncReloadDictionaryTask extends Thread {
-        @Override
-        public void run() {
-            syncReloadDictionaryInternal();
-        }
+    protected void asyncLoadDictionaryToMemory() {
+        getExecutor(mFilename).executePrioritized(new Runnable() {
+            @Override
+            public void run() {
+                if (!ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE) {
+                    loadDictionaryAsync();
+                }
+            }
+        });
     }
 
     /**
-     * Lock for controlling access to a given binary dictionary and for tracking whether the
-     * dictionary is out of date. Can be shared across multiple dictionary instances that access the
-     * same filename.
+     * Generate binary dictionary using DictionaryWriter.
      */
-    private static class DictionaryController extends ReentrantReadWriteLock {
+    protected void asyncFlashAllBinaryDictionary() {
+        final Runnable newTask = new Runnable() {
+            @Override
+            public void run() {
+                writeBinaryDictionary();
+            }
+        };
+        final Runnable oldTask = mUnfinishedFlushingTask.getAndSet(newTask);
+        getExecutor(mFilename).replaceAndExecute(oldTask, newTask);
+    }
+
+    /**
+     * Time recorder for tracking whether the dictionary is out of date.
+     * Can be shared across multiple dictionary instances that access the same filename.
+     */
+    private static class DictionaryTimeRecorder {
         private volatile long mLastUpdateTime = 0;
         private volatile long mLastUpdateRequestTime = 0;
 
@@ -434,4 +647,75 @@
             return (mLastUpdateRequestTime > mLastUpdateTime);
         }
     }
+
+    /**
+     * Dynamically adds a word unigram to the dictionary for testing with blocking-lock.
+     */
+    @UsedForTesting
+    protected void addWordDynamicallyForTests(final String word, final String shortcutTarget,
+            final int frequency, final boolean isNotAWord) {
+        getExecutor(mFilename).executePrioritized(new Runnable() {
+            @Override
+            public void run() {
+                addWordDynamically(word, shortcutTarget, frequency, isNotAWord);
+            }
+        });
+    }
+
+    /**
+     * Dynamically adds a word bigram in the dictionary for testing with blocking-lock.
+     */
+    @UsedForTesting
+    protected void addBigramDynamicallyForTests(final String word0, final String word1,
+            final int frequency, final boolean isValid) {
+        getExecutor(mFilename).executePrioritized(new Runnable() {
+            @Override
+            public void run() {
+                addBigramDynamically(word0, word1, frequency, isValid);
+            }
+        });
+    }
+
+    /**
+     * Dynamically remove a word bigram in the dictionary for testing with blocking-lock.
+     */
+    @UsedForTesting
+    protected void removeBigramDynamicallyForTests(final String word0, final String word1) {
+        getExecutor(mFilename).executePrioritized(new Runnable() {
+            @Override
+            public void run() {
+                removeBigramDynamically(word0, word1);
+            }
+        });
+    }
+
+    // TODO: Implement native binary methods once the dynamic dictionary implementation is done.
+    @UsedForTesting
+    public boolean isInDictionaryForTests(final String word) {
+        final AsyncResultHolder<Boolean> holder = new AsyncResultHolder<Boolean>();
+        getExecutor(mFilename).executePrioritized(new Runnable() {
+            @Override
+            public void run() {
+                if (mDictType == Dictionary.TYPE_USER_HISTORY) {
+                    if (ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE) {
+                        holder.set(mBinaryDictionary.isValidWord(word));
+                    } else {
+                        holder.set(((DynamicPersonalizationDictionaryWriter) mDictionaryWriter)
+                                .isInDictionaryForTests(word));
+                    }
+                }
+            }
+        });
+        return holder.get(false, TIMEOUT_FOR_READ_OPS_IN_MILLISECONDS);
+    }
+
+    @UsedForTesting
+    public void shutdownExecutorForTests() {
+        getExecutor(mFilename).shutdown();
+    }
+
+    @UsedForTesting
+    public boolean isTerminatedForTests() {
+        return getExecutor(mFilename).isTerminated();
+    }
 }
diff --git a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
index bd2d703..d491f98 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
@@ -16,10 +16,10 @@
 
 package com.android.inputmethod.latin;
 
-import android.content.Context;
 import android.text.TextUtils;
 import android.util.Log;
 
+import com.android.inputmethod.annotations.UsedForTesting;
 import com.android.inputmethod.keyboard.ProximityInfo;
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
 import com.android.inputmethod.latin.utils.CollectionUtils;
@@ -29,9 +29,10 @@
 import java.util.LinkedList;
 
 /**
- * Base class for an in-memory dictionary that can grow dynamically and can
+ * Class for an in-memory dictionary that can grow dynamically and can
  * be searched for suggestions and valid words.
  */
+// TODO: Remove after binary dictionary supports dynamic update.
 public class ExpandableDictionary extends Dictionary {
     private static final String TAG = ExpandableDictionary.class.getSimpleName();
     /**
@@ -39,23 +40,11 @@
      */
     private static final int FULL_WORD_SCORE_MULTIPLIER = 2;
 
-    // Bigram frequency is a fixed point number with 1 meaning 1.2 and 255 meaning 1.8.
-    protected static final int BIGRAM_MAX_FREQUENCY = 255;
-
-    private Context mContext;
     private char[] mWordBuilder = new char[Constants.DICTIONARY_MAX_WORD_LENGTH];
     private int mMaxDepth;
     private int mInputLength;
 
-    private boolean mRequiresReload;
-
-    private boolean mUpdatingDictionary;
-
-    // Use this lock before touching mUpdatingDictionary & mRequiresDownload
-    private Object mUpdatingLock = new Object();
-
     private static final class Node {
-        Node() {}
         char mCode;
         int mFrequency;
         boolean mTerminal;
@@ -157,46 +146,12 @@
 
     private int[][] mCodes;
 
-    public ExpandableDictionary(final Context context, final String dictType) {
+    public ExpandableDictionary(final String dictType) {
         super(dictType);
-        mContext = context;
         clearDictionary();
         mCodes = new int[Constants.DICTIONARY_MAX_WORD_LENGTH][];
     }
 
-    public void loadDictionary() {
-        synchronized (mUpdatingLock) {
-            startDictionaryLoadingTaskLocked();
-        }
-    }
-
-    public void startDictionaryLoadingTaskLocked() {
-        if (!mUpdatingDictionary) {
-            mUpdatingDictionary = true;
-            mRequiresReload = false;
-            new LoadDictionaryTask().start();
-        }
-    }
-
-    public void setRequiresReload(final boolean reload) {
-        synchronized (mUpdatingLock) {
-            mRequiresReload = reload;
-        }
-    }
-
-    public boolean getRequiresReload() {
-        return mRequiresReload;
-    }
-
-    /** Override to load your dictionary here, on a background thread. */
-    public void loadDictionaryAsync() {
-        // empty base implementation
-    }
-
-    public Context getContext() {
-        return mContext;
-    }
-
     public int getMaxWordLength() {
         return Constants.DICTIONARY_MAX_WORD_LENGTH;
     }
@@ -231,7 +186,7 @@
             childNode.mShortcutOnly = isShortcutOnly;
             children.add(childNode);
         }
-        if (wordLength == depth + 1 && shortcutTarget != null) {
+        if (wordLength == depth + 1) {
             // Terminate this word
             childNode.mTerminal = true;
             if (isShortcutOnly) {
@@ -255,8 +210,7 @@
     @Override
     public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
             final String prevWord, final ProximityInfo proximityInfo,
-            final boolean blockOffensiveWords) {
-        if (reloadDictionaryIfRequired()) return null;
+            final boolean blockOffensiveWords, final int[] additionalFeaturesOptions) {
         if (composer.size() > 1) {
             if (composer.size() >= Constants.DICTIONARY_MAX_WORD_LENGTH) {
                 return null;
@@ -272,17 +226,7 @@
         }
     }
 
-    // This reloads the dictionary if required, and returns whether it's currently updating its
-    // contents or not.
-    private boolean reloadDictionaryIfRequired() {
-        synchronized (mUpdatingLock) {
-            // If we need to update, start off a background task
-            if (mRequiresReload) startDictionaryLoadingTaskLocked();
-            return mUpdatingDictionary;
-        }
-    }
-
-    protected ArrayList<SuggestedWordInfo> getWordsInner(final WordComposer codes,
+    private ArrayList<SuggestedWordInfo> getWordsInner(final WordComposer codes,
             final String prevWordForBigrams, final ProximityInfo proximityInfo) {
         final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList();
         mInputLength = codes.size();
@@ -312,11 +256,6 @@
 
     @Override
     public synchronized boolean isValidWord(final String word) {
-        synchronized (mUpdatingLock) {
-            // If we need to update, start off a background task
-            if (mRequiresReload) startDictionaryLoadingTaskLocked();
-            if (mUpdatingDictionary) return false;
-        }
         final Node node = searchNode(mRoots, word, 0, word.length());
         // If node is null, we didn't find the word, so it's not valid.
         // If node.mShortcutOnly is true, then it exists as a shortcut but not as a word,
@@ -326,10 +265,10 @@
         return (node == null) ? false : !node.mShortcutOnly;
     }
 
-    protected boolean removeBigram(final String word1, final String word2) {
+    public boolean removeBigram(final String word0, final String word1) {
         // Refer to addOrSetBigram() about word1.toLowerCase()
-        final Node firstWord = searchWord(mRoots, word1.toLowerCase(), 0, null);
-        final Node secondWord = searchWord(mRoots, word2, 0, null);
+        final Node firstWord = searchWord(mRoots, word0.toLowerCase(), 0, null);
+        final Node secondWord = searchWord(mRoots, word1, 0, null);
         LinkedList<NextWord> bigrams = firstWord.mNGrams;
         NextWord bigramNode = null;
         if (bigrams == null || bigrams.size() == 0) {
@@ -351,16 +290,17 @@
     /**
      * Returns the word's frequency or -1 if not found
      */
-    protected int getWordFrequency(final String word) {
+    @UsedForTesting
+    public int getWordFrequency(final String word) {
         // Case-sensitive search
         final Node node = searchNode(mRoots, word, 0, word.length());
         return (node == null) ? -1 : node.mFrequency;
     }
 
-    protected NextWord getBigramWord(final String word1, final String word2) {
-        // Refer to addOrSetBigram() about word1.toLowerCase()
-        final Node firstWord = searchWord(mRoots, word1.toLowerCase(), 0, null);
-        final Node secondWord = searchWord(mRoots, word2, 0, null);
+    public NextWord getBigramWord(final String word0, final String word1) {
+        // Refer to addOrSetBigram() about word0.toLowerCase()
+        final Node firstWord = searchWord(mRoots, word0.toLowerCase(), 0, null);
+        final Node secondWord = searchWord(mRoots, word1, 0, null);
         LinkedList<NextWord> bigrams = firstWord.mNGrams;
         if (bigrams == null || bigrams.size() == 0) {
             return null;
@@ -403,7 +343,9 @@
             // the respective size of the typed word and the suggestion if it matters sometime
             // in the future.
             suggestions.add(new SuggestedWordInfo(new String(word, 0, depth + 1), finalFreq,
-                    SuggestedWordInfo.KIND_CORRECTION, mDictType));
+                    SuggestedWordInfo.KIND_CORRECTION, this /* sourceDict */,
+                    SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */,
+                    SuggestedWordInfo.NOT_A_CONFIDENCE /* autoCommitFirstWordConfidence */));
             if (suggestions.size() >= Suggest.MAX_SUGGESTIONS) return false;
         }
         if (null != node.mShortcutTargets) {
@@ -411,7 +353,9 @@
             for (int shortcutIndex = 0; shortcutIndex < length; ++shortcutIndex) {
                 final char[] shortcut = node.mShortcutTargets.get(shortcutIndex);
                 suggestions.add(new SuggestedWordInfo(new String(shortcut, 0, shortcut.length),
-                        finalFreq, SuggestedWordInfo.KIND_SHORTCUT, mDictType));
+                        finalFreq, SuggestedWordInfo.KIND_SHORTCUT, this /* sourceDict */,
+                        SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */,
+                        SuggestedWordInfo.NOT_A_CONFIDENCE /* autoCommitFirstWordConfidence */));
                 if (suggestions.size() > Suggest.MAX_SUGGESTIONS) return false;
             }
         }
@@ -438,7 +382,7 @@
      * @param suggestions the list in which to add suggestions
      */
     // TODO: Share this routine with the native code for BinaryDictionary
-    protected void getWordsRec(final NodeArray roots, final WordComposer codes, final char[] word,
+    private void getWordsRec(final NodeArray roots, final WordComposer codes, final char[] word,
             final int depth, final boolean completion, final int snr, final int inputIndex,
             final int skipPos, final ArrayList<SuggestedWordInfo> suggestions) {
         final int count = roots.mLength;
@@ -531,37 +475,41 @@
         }
     }
 
-    public int setBigramAndGetFrequency(final String word1, final String word2,
+    public int setBigramAndGetFrequency(final String word0, final String word1,
             final int frequency) {
-        return setBigramAndGetFrequency(word1, word2, frequency, null /* unused */);
+        return setBigramAndGetFrequency(word0, word1, frequency, null /* unused */);
     }
 
-    public int setBigramAndGetFrequency(final String word1, final String word2,
+    public int setBigramAndGetFrequency(final String word0, final String word1,
             final ForgettingCurveParams fcp) {
-        return setBigramAndGetFrequency(word1, word2, 0 /* unused */, fcp);
+        return setBigramAndGetFrequency(word0, word1, 0 /* unused */, fcp);
     }
 
     /**
      * Adds bigrams to the in-memory trie structure that is being used to retrieve any word
-     * @param word1 the first word of this bigram
-     * @param word2 the second word of this bigram
+     * @param word0 the first word of this bigram
+     * @param word1 the second word of this bigram
      * @param frequency frequency for this bigram
      * @param fcp an instance of ForgettingCurveParams to use for decay policy
      * @return returns the final bigram frequency
      */
-    private int setBigramAndGetFrequency(final String word1, final String word2,
+    private int setBigramAndGetFrequency(final String word0, final String word1,
             final int frequency, final ForgettingCurveParams fcp) {
+        if (TextUtils.isEmpty(word0)) {
+            Log.e(TAG, "Invalid bigram previous word: " + word0);
+            return frequency;
+        }
         // We don't want results to be different according to case of the looked up left hand side
         // word. We do want however to return the correct case for the right hand side.
         // So we want to squash the case of the left hand side, and preserve that of the right
         // hand side word.
-        final String word1Lower = word1.toLowerCase();
-        if (TextUtils.isEmpty(word1Lower) || TextUtils.isEmpty(word2)) {
-            Log.e(TAG, "Invalid bigram pair: " + word1 + ", " + word1Lower + ", " + word2);
+        final String word0Lower = word0.toLowerCase();
+        if (TextUtils.isEmpty(word0Lower) || TextUtils.isEmpty(word1)) {
+            Log.e(TAG, "Invalid bigram pair: " + word0 + ", " + word0Lower + ", " + word1);
             return frequency;
         }
-        final Node firstWord = searchWord(mRoots, word1Lower, 0, null);
-        final Node secondWord = searchWord(mRoots, word2, 0, null);
+        final Node firstWord = searchWord(mRoots, word0Lower, 0, null);
+        final Node secondWord = searchWord(mRoots, word1, 0, null);
         LinkedList<NextWord> bigrams = firstWord.mNGrams;
         if (bigrams == null || bigrams.size() == 0) {
             firstWord.mNGrams = CollectionUtils.newLinkedList();
@@ -657,7 +605,9 @@
             if (freq >= 0 && node == null) {
                 suggestions.add(new SuggestedWordInfo(new String(mLookedUpString, index,
                         Constants.DICTIONARY_MAX_WORD_LENGTH - index),
-                        freq, SuggestedWordInfo.KIND_CORRECTION, mDictType));
+                        freq, SuggestedWordInfo.KIND_CORRECTION, this /* sourceDict */,
+                        SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */,
+                        SuggestedWordInfo.NOT_A_CONFIDENCE /* autoCommitFirstWordConfidence */));
             }
         }
     }
@@ -695,21 +645,10 @@
         return null;
     }
 
-    protected void clearDictionary() {
+    public void clearDictionary() {
         mRoots = new NodeArray();
     }
 
-    private final class LoadDictionaryTask extends Thread {
-        LoadDictionaryTask() {}
-        @Override
-        public void run() {
-            loadDictionaryAsync();
-            synchronized (mUpdatingLock) {
-                mUpdatingDictionary = false;
-            }
-        }
-    }
-
     private static char toLowerCase(final char c) {
         char baseChar = c;
         if (c < BASE_CHARS.length) {
diff --git a/java/src/com/android/inputmethod/latin/InputPointers.java b/java/src/com/android/inputmethod/latin/InputPointers.java
index e96a46e..2e638aa 100644
--- a/java/src/com/android/inputmethod/latin/InputPointers.java
+++ b/java/src/com/android/inputmethod/latin/InputPointers.java
@@ -105,6 +105,17 @@
         mTimes.append(times, startPos, length);
     }
 
+    /**
+     * Shift to the left by elementCount, discarding elementCount pointers at the start.
+     * @param elementCount how many elements to shift.
+     */
+    public void shift(final int elementCount) {
+        mXCoordinates.shift(elementCount);
+        mYCoordinates.shift(elementCount);
+        mPointerIds.shift(elementCount);
+        mTimes.shift(elementCount);
+    }
+
     public void reset() {
         final int defaultCapacity = mDefaultCapacity;
         mXCoordinates.reset(defaultCapacity);
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 5c5b7b7..270dc4c 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;
@@ -73,9 +74,12 @@
 import com.android.inputmethod.keyboard.KeyboardId;
 import com.android.inputmethod.keyboard.KeyboardSwitcher;
 import com.android.inputmethod.keyboard.MainKeyboardView;
+import com.android.inputmethod.latin.Suggest.OnGetSuggestedWordsCallback;
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
 import com.android.inputmethod.latin.define.ProductionFlag;
-import com.android.inputmethod.latin.personalization.PersonalizationDictionaryHelper;
+import com.android.inputmethod.latin.personalization.PersonalizationDictionary;
+import com.android.inputmethod.latin.personalization.PersonalizationDictionarySessionRegister;
+import com.android.inputmethod.latin.personalization.PersonalizationHelper;
 import com.android.inputmethod.latin.personalization.PersonalizationPredictionDictionary;
 import com.android.inputmethod.latin.personalization.UserHistoryPredictionDictionary;
 import com.android.inputmethod.latin.settings.Settings;
@@ -83,6 +87,7 @@
 import com.android.inputmethod.latin.settings.SettingsValues;
 import com.android.inputmethod.latin.suggestions.SuggestionStripView;
 import com.android.inputmethod.latin.utils.ApplicationUtils;
+import com.android.inputmethod.latin.utils.AsyncResultHolder;
 import com.android.inputmethod.latin.utils.AutoCorrectionUtils;
 import com.android.inputmethod.latin.utils.CapsModeUtils;
 import com.android.inputmethod.latin.utils.CollectionUtils;
@@ -91,11 +96,11 @@
 import com.android.inputmethod.latin.utils.IntentUtils;
 import com.android.inputmethod.latin.utils.JniUtils;
 import com.android.inputmethod.latin.utils.LatinImeLoggerUtils;
-import com.android.inputmethod.latin.utils.PositionalInfoForUserDictPendingAddition;
 import com.android.inputmethod.latin.utils.RecapitalizeStatus;
 import com.android.inputmethod.latin.utils.StaticInnerHandlerWrapper;
 import com.android.inputmethod.latin.utils.TargetPackageInfoGetterTask;
 import com.android.inputmethod.latin.utils.TextRange;
+import com.android.inputmethod.latin.utils.UserHistoryForgettingCurveUtils;
 import com.android.inputmethod.research.ResearchLogger;
 
 import java.io.FileDescriptor;
@@ -123,6 +128,11 @@
 
     private static final int PENDING_IMS_CALLBACK_DURATION = 800;
 
+    private static final int PERIOD_FOR_AUDIO_AND_HAPTIC_FEEDBACK_IN_KEY_REPEAT = 2;
+
+    // TODO: Set this value appropriately.
+    private static final int GET_SUGGESTED_WORDS_TIMEOUT = 200;
+
     /**
      * The name of the scheme used by the Package Manager to warn of a new package installation,
      * replacement or removal.
@@ -171,11 +181,10 @@
     private UserBinaryDictionary mUserDictionary;
     private UserHistoryPredictionDictionary mUserHistoryPredictionDictionary;
     private PersonalizationPredictionDictionary mPersonalizationPredictionDictionary;
+    private PersonalizationDictionary mPersonalizationDictionary;
     private boolean mIsUserDictionaryAvailable;
 
     private LastComposedWord mLastComposedWord = LastComposedWord.NOT_A_COMPOSED_WORD;
-    private PositionalInfoForUserDictPendingAddition
-            mPositionalInfoForUserDictPendingAddition = null;
     private final WordComposer mWordComposer = new WordComposer();
     private final RichInputConnection mConnection = new RichInputConnection(this);
     private final RecapitalizeStatus mRecapitalizeStatus = new RecapitalizeStatus();
@@ -190,7 +199,10 @@
     private boolean mExpectingUpdateSelection;
     private int mDeleteCount;
     private long mLastKeyTime;
-    private TreeSet<Long> mCurrentlyPressedHardwareKeys = CollectionUtils.newTreeSet();
+    private final TreeSet<Long> mCurrentlyPressedHardwareKeys = CollectionUtils.newTreeSet();
+    // Personalization debugging params
+    private boolean mUseOnlyPersonalizationDictionaryForDebug = false;
+    private boolean mBoostPersonalizationDictionaryForDebug = false;
 
     // Member variables for remembering the current device orientation.
     private int mDisplayOrientation;
@@ -211,6 +223,7 @@
     private final boolean mIsHardwareAcceleratedDrawingEnabled;
 
     public final UIHandler mHandler = new UIHandler(this);
+    private InputUpdater mInputUpdater;
 
     public static final class UIHandler extends StaticInnerHandlerWrapper<LatinIME> {
         private static final int MSG_UPDATE_SHIFT_STATE = 0;
@@ -219,8 +232,14 @@
         private static final int MSG_SHOW_GESTURE_PREVIEW_AND_SUGGESTION_STRIP = 3;
         private static final int MSG_RESUME_SUGGESTIONS = 4;
         private static final int MSG_REOPEN_DICTIONARIES = 5;
+        private static final int MSG_ON_END_BATCH_INPUT = 6;
+        private static final int MSG_RESET_CACHES = 7;
 
+        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;
@@ -253,8 +272,18 @@
                 switcher.updateShiftState();
                 break;
             case MSG_SHOW_GESTURE_PREVIEW_AND_SUGGESTION_STRIP:
-                latinIme.showGesturePreviewAndSuggestionStrip((SuggestedWords)msg.obj,
-                        msg.arg1 == ARG1_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT);
+                if (msg.arg1 == ARG1_NOT_GESTURE_INPUT) {
+                    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);
+                }
                 break;
             case MSG_RESUME_SUGGESTIONS:
                 latinIme.restartSuggestionsOnWordTouchedByCursor();
@@ -266,6 +295,13 @@
                 // get any suggestions. Wait one frame.
                 postUpdateSuggestionStrip();
                 break;
+            case MSG_ON_END_BATCH_INPUT:
+                latinIme.onEndBatchInputAsyncInternal((SuggestedWords) msg.obj);
+                break;
+            case MSG_RESET_CACHES:
+                latinIme.retryResetCaches(msg.arg1 == 1 /* tryResumeSuggestions */,
+                        msg.arg2 /* remainingTries */);
+                break;
             }
         }
 
@@ -282,6 +318,12 @@
             sendMessageDelayed(obtainMessage(MSG_RESUME_SUGGESTIONS), mDelayUpdateSuggestions);
         }
 
+        public void postResetCaches(final boolean tryResumeSuggestions, final int remainingTries) {
+            removeMessages(MSG_RESET_CACHES);
+            sendMessage(obtainMessage(MSG_RESET_CACHES, tryResumeSuggestions ? 1 : 0,
+                    remainingTries, null));
+        }
+
         public void cancelUpdateSuggestionStrip() {
             removeMessages(MSG_UPDATE_SUGGESTION_STRIP);
         }
@@ -307,9 +349,29 @@
                 final boolean dismissGestureFloatingPreviewText) {
             removeMessages(MSG_SHOW_GESTURE_PREVIEW_AND_SUGGESTION_STRIP);
             final int arg1 = dismissGestureFloatingPreviewText
-                    ? ARG1_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT : 0;
-            obtainMessage(MSG_SHOW_GESTURE_PREVIEW_AND_SUGGESTION_STRIP, arg1, 0, suggestedWords)
-                    .sendToTarget();
+                    ? ARG1_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT
+                    : ARG1_SHOW_GESTURE_FLOATING_PREVIEW_TEXT;
+            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, 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) {
+            obtainMessage(MSG_ON_END_BATCH_INPUT, suggestedWords).sendToTarget();
         }
 
         public void startDoubleSpacePeriodTimer() {
@@ -472,6 +534,7 @@
         KeyboardSwitcher.init(this);
         AudioAndHapticFeedbackManager.init(this);
         AccessibilityUtils.init(this);
+        PersonalizationDictionarySessionRegister.init(this);
 
         super.onCreate();
 
@@ -503,6 +566,8 @@
         final IntentFilter newDictFilter = new IntentFilter();
         newDictFilter.addAction(DictionaryPackConstants.NEW_DICTIONARY_INTENT_ACTION);
         registerReceiver(mDictionaryPackInstallReceiver, newDictFilter);
+
+        mInputUpdater = new InputUpdater(this);
     }
 
     // Has to be package-visible for unit tests
@@ -558,10 +623,13 @@
 
         final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
 
-        mUserHistoryPredictionDictionary = PersonalizationDictionaryHelper
+        mUserHistoryPredictionDictionary = PersonalizationHelper
                 .getUserHistoryPredictionDictionary(this, localeStr, prefs);
         newSuggest.setUserHistoryPredictionDictionary(mUserHistoryPredictionDictionary);
-        mPersonalizationPredictionDictionary = PersonalizationDictionaryHelper
+        mPersonalizationDictionary = PersonalizationHelper
+                .getPersonalizationDictionary(this, localeStr, prefs);
+        newSuggest.setPersonalizationDictionary(mPersonalizationDictionary);
+        mPersonalizationPredictionDictionary = PersonalizationHelper
                 .getPersonalizationPredictionDictionary(this, localeStr, prefs);
         newSuggest.setPersonalizationPredictionDictionary(mPersonalizationPredictionDictionary);
 
@@ -633,8 +701,13 @@
             ResearchLogger.getInstance().onDestroy();
         }
         unregisterReceiver(mDictionaryPackInstallReceiver);
+        PersonalizationDictionarySessionRegister.onDestroy(this);
         LatinImeLogger.commit();
         LatinImeLogger.onDestroy();
+        if (mInputUpdater != null) {
+            mInputUpdater.onDestroy();
+            mInputUpdater = null;
+        }
         super.onDestroy();
     }
 
@@ -652,6 +725,7 @@
                 mOptionsDialog.dismiss();
             }
         }
+        PersonalizationDictionarySessionRegister.onConfigurationChanged(this, conf);
         super.onConfigurationChanged(conf);
     }
 
@@ -789,7 +863,6 @@
         // span, so we should reset our state unconditionally, even if restarting is true.
         mEnteredText = null;
         resetComposingState(true /* alsoResetLastComposedWord */);
-        if (isDifferentTextField) mHandler.postResumeSuggestions();
         mDeleteCount = 0;
         mSpaceState = SPACE_STATE_NONE;
         mRecapitalizeStatus.deactivate();
@@ -808,8 +881,16 @@
         }
         mSuggestedWords = SuggestedWords.EMPTY;
 
-        mConnection.resetCachesUponCursorMove(editorInfo.initialSelStart,
-                false /* shouldFinishComposition */);
+        // Sometimes, while rotating, for some reason the framework tells the app we are not
+        // connected to it and that means we can't refresh the cache. In this case, schedule a
+        // refresh later.
+        if (!mConnection.resetCachesUponCursorMoveAndReturnSuccess(editorInfo.initialSelStart,
+                false /* shouldFinishComposition */)) {
+            // We try resetting the caches up to 5 times before giving up.
+            mHandler.postResetCaches(isDifferentTextField, 5 /* remainingTries */);
+        } else {
+            if (isDifferentTextField) mHandler.postResumeSuggestions();
+        }
 
         if (isDifferentTextField) {
             mainKeyboardView.closing();
@@ -836,6 +917,9 @@
 
         mLastSelectionStart = editorInfo.initialSelStart;
         mLastSelectionEnd = editorInfo.initialSelEnd;
+        // In some cases (namely, after rotation of the device) editorInfo.initialSelStart is lying
+        // so we try using some heuristics to find out about these and fix them.
+        tryFixLyingCursorPosition();
 
         mHandler.cancelUpdateSuggestionStrip();
         mHandler.cancelDoubleSpacePeriodTimer();
@@ -850,22 +934,64 @@
                 currentSettingsValues.mGestureTrailEnabled,
                 currentSettingsValues.mGestureFloatingPreviewTextEnabled);
 
-        // If we have a user dictionary addition in progress, we should check now if we should
-        // replace the previously committed string with the word that has actually been added
-        // to the user dictionary.
-        if (null != mPositionalInfoForUserDictPendingAddition
-                && mPositionalInfoForUserDictPendingAddition.tryReplaceWithActualWord(
-                        mConnection, editorInfo, mLastSelectionEnd, currentLocale)) {
-            mPositionalInfoForUserDictPendingAddition = null;
-        }
-        // If tryReplaceWithActualWord returns false, we don't know what word was
-        // added to the user dictionary yet, so we keep the data and defer processing. The word will
-        // be replaced when the user dictionary reports back with the actual word, which ends
-        // up calling #onWordAddedToUserDictionary() in this class.
+        initPersonalizationDebugSettings(currentSettingsValues);
 
         if (TRACE) Debug.startMethodTracing("/data/trace/latinime");
     }
 
+    /**
+     * Try to get the text from the editor to expose lies the framework may have been
+     * telling us. Concretely, when the device rotates, the frameworks tells us about where the
+     * cursor used to be initially in the editor at the time it first received the focus; this
+     * may be completely different from the place it is upon rotation. Since we don't have any
+     * means to get the real value, try at least to ask the text view for some characters and
+     * detect the most damaging cases: when the cursor position is declared to be much smaller
+     * than it really is.
+     */
+    private void tryFixLyingCursorPosition() {
+        final CharSequence textBeforeCursor =
+                mConnection.getTextBeforeCursor(Constants.EDITOR_CONTENTS_CACHE_SIZE, 0);
+        if (null == textBeforeCursor) {
+            mLastSelectionStart = mLastSelectionEnd = NOT_A_CURSOR_POSITION;
+        } else {
+            final int textLength = textBeforeCursor.length();
+            if (textLength > mLastSelectionStart
+                    || (textLength < Constants.EDITOR_CONTENTS_CACHE_SIZE
+                            && mLastSelectionStart < Constants.EDITOR_CONTENTS_CACHE_SIZE)) {
+                mLastSelectionStart = textLength;
+                // We can't figure out the value of mLastSelectionEnd :(
+                // But at least if it's smaller than mLastSelectionStart something is wrong
+                if (mLastSelectionStart > mLastSelectionEnd) {
+                    mLastSelectionEnd = mLastSelectionStart;
+                }
+            }
+        }
+    }
+
+    // Initialization of personalization debug settings. This must be called inside
+    // onStartInputView.
+    private void initPersonalizationDebugSettings(SettingsValues currentSettingsValues) {
+        if (mUseOnlyPersonalizationDictionaryForDebug
+                != currentSettingsValues.mUseOnlyPersonalizationDictionaryForDebug) {
+            // Only for debug
+            initSuggest();
+            mUseOnlyPersonalizationDictionaryForDebug =
+                    currentSettingsValues.mUseOnlyPersonalizationDictionaryForDebug;
+        }
+
+        if (mBoostPersonalizationDictionaryForDebug !=
+                currentSettingsValues.mBoostPersonalizationDictionaryForDebug) {
+            // Only for debug
+            mBoostPersonalizationDictionaryForDebug =
+                    currentSettingsValues.mBoostPersonalizationDictionaryForDebug;
+            if (mBoostPersonalizationDictionaryForDebug) {
+                UserHistoryForgettingCurveUtils.boostMaxFreqForDebug();
+            } else {
+                UserHistoryForgettingCurveUtils.resetMaxFreqForDebug();
+            }
+        }
+    }
+
     // Callback for the TargetPackageInfoGetterTask
     @Override
     public void onTargetPackageInfoKnown(final PackageInfo info) {
@@ -996,7 +1122,7 @@
                 // argument as true. But in all cases where we don't reset the entire input state,
                 // we still want to tell the rich input connection about the new cursor position so
                 // that it can update its caches.
-                mConnection.resetCachesUponCursorMove(newSelStart,
+                mConnection.resetCachesUponCursorMoveAndReturnSuccess(newSelStart,
                         false /* shouldFinishComposition */);
             }
 
@@ -1135,11 +1261,13 @@
             return currentHeight;
         }
 
-        final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
-        if (mainKeyboardView == null) {
+        final View visibleKeyboardView = mKeyboardSwitcher.getVisibleKeyboardView();
+        if (visibleKeyboardView == null) {
             return 0;
         }
-        final int keyboardHeight = mainKeyboardView.getHeight();
+        // TODO: !!!!!!!!!!!!!!!!!!!! Handle different backing view heights between the main   !!!
+        // keyboard and the emoji keyboard. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+        final int keyboardHeight = visibleKeyboardView.getHeight();
         final int suggestionsHeight = mSuggestionStripView.getHeight();
         final int displayHeight = getResources().getDisplayMetrics().heightPixels;
         final Rect rect = new Rect();
@@ -1157,8 +1285,8 @@
     @Override
     public void onComputeInsets(final InputMethodService.Insets outInsets) {
         super.onComputeInsets(outInsets);
-        final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
-        if (mainKeyboardView == null || mSuggestionStripView == null) {
+        final View visibleKeyboardView = mKeyboardSwitcher.getVisibleKeyboardView();
+        if (visibleKeyboardView == null || mSuggestionStripView == null) {
             return;
         }
         final int adjustedBackingHeight = getAdjustedBackingViewHeight();
@@ -1173,13 +1301,16 @@
         final int extraHeight = extractHeight + backingHeight + suggestionsHeight;
         int visibleTopY = extraHeight;
         // Need to set touchable region only if input view is being shown
-        if (mainKeyboardView.isShown()) {
-            if (mSuggestionStripView.getVisibility() == View.VISIBLE) {
+        if (visibleKeyboardView.isShown()) {
+            // Note that the height of Emoji layout is the same as the height of the main keyboard
+            // and the suggestion strip
+            if (mKeyboardSwitcher.isShowingEmojiKeyboard()
+                    || mSuggestionStripView.getVisibility() == View.VISIBLE) {
                 visibleTopY -= suggestionsHeight;
             }
-            final int touchY = mainKeyboardView.isShowingMoreKeysPanel() ? 0 : visibleTopY;
-            final int touchWidth = mainKeyboardView.getWidth();
-            final int touchHeight = mainKeyboardView.getHeight() + extraHeight
+            final int touchY = mKeyboardSwitcher.isShowingMoreKeysPanel() ? 0 : visibleTopY;
+            final int touchWidth = visibleKeyboardView.getWidth();
+            final int touchHeight = visibleKeyboardView.getHeight() + extraHeight
                     // Extend touchable region below the keyboard.
                     + EXTENDED_TOUCHABLE_REGION_HEIGHT;
             outInsets.touchableInsets = InputMethodService.Insets.TOUCHABLE_INSETS_REGION;
@@ -1227,7 +1358,8 @@
         } else {
             setSuggestedWords(settingsValues.mSuggestPuncList, false);
         }
-        mConnection.resetCachesUponCursorMove(newCursorPosition, shouldFinishComposition);
+        mConnection.resetCachesUponCursorMoveAndReturnSuccess(newCursorPosition,
+                shouldFinishComposition);
     }
 
     private void resetComposingState(final boolean alsoResetLastComposedWord) {
@@ -1331,7 +1463,8 @@
                 || codePoint == Constants.CODE_CLOSING_PARENTHESIS
                 || codePoint == Constants.CODE_CLOSING_SQUARE_BRACKET
                 || codePoint == Constants.CODE_CLOSING_CURLY_BRACKET
-                || codePoint == Constants.CODE_CLOSING_ANGLE_BRACKET;
+                || codePoint == Constants.CODE_CLOSING_ANGLE_BRACKET
+                || codePoint == Constants.CODE_PLUS;
     }
 
     // Callback for the {@link SuggestionStripView}, to call when the "add to dictionary" hint is
@@ -1340,7 +1473,6 @@
     public void addWordToUserDictionary(final String word) {
         if (TextUtils.isEmpty(word)) {
             // Probably never supposed to happen, but just in case.
-            mPositionalInfoForUserDictPendingAddition = null;
             return;
         }
         final String wordToEdit;
@@ -1352,26 +1484,6 @@
         mUserDictionary.addWordToUserDictionary(wordToEdit);
     }
 
-    public void onWordAddedToUserDictionary(final String newSpelling) {
-        // If word was added but not by us, bail out
-        if (null == mPositionalInfoForUserDictPendingAddition) return;
-        if (mWordComposer.isComposingWord()) {
-            // We are late... give up and return
-            mPositionalInfoForUserDictPendingAddition = null;
-            return;
-        }
-        mPositionalInfoForUserDictPendingAddition.setActualWordBeingAdded(newSpelling);
-        if (mPositionalInfoForUserDictPendingAddition.tryReplaceWithActualWord(
-                mConnection, getCurrentInputEditorInfo(), mLastSelectionEnd,
-                mSubtypeSwitcher.getCurrentSubtypeLocale())) {
-            mPositionalInfoForUserDictPendingAddition = null;
-        }
-    }
-
-    private static boolean isAlphabet(final int code) {
-        return Character.isLetter(code);
-    }
-
     private void onSettingsKeyPressed() {
         if (isShowingOptionDialog()) return;
         showSubtypeSelectorAndSettings();
@@ -1479,7 +1591,7 @@
             break;
         case Constants.CODE_SHIFT:
             // Note: Calling back to the keyboard on Shift key is handled in
-            // {@link #onPressKey(int,boolean)} and {@link #onReleaseKey(int,boolean)}.
+            // {@link #onPressKey(int,int,boolean)} and {@link #onReleaseKey(int,boolean)}.
             final Keyboard currentKeyboard = switcher.getKeyboard();
             if (null != currentKeyboard && currentKeyboard.mId.isAlphabetKeyboard()) {
                 // TODO: Instead of checking for alphabetic keyboard here, separate keycodes for
@@ -1493,7 +1605,7 @@
             break;
         case Constants.CODE_SWITCH_ALPHA_SYMBOL:
             // Note: Calling back to the keyboard on symbol key is handled in
-            // {@link #onPressKey(int,boolean)} and {@link #onReleaseKey(int,boolean)}.
+            // {@link #onPressKey(int,int,boolean)} and {@link #onReleaseKey(int,boolean)}.
             break;
         case Constants.CODE_SETTINGS:
             onSettingsKeyPressed();
@@ -1511,7 +1623,8 @@
             handleLanguageSwitchKey();
             break;
         case Constants.CODE_EMOJI:
-            // TODO: Implement emoji keyboard switch.
+            // Note: Switching emoji keyboard is being handled in
+            // {@link KeyboardState#onCodeInput(int,int)}.
             break;
         case Constants.CODE_ENTER:
             final EditorInfo editorInfo = getCurrentInputEditorInfo();
@@ -1627,7 +1740,7 @@
 
     @Override
     public void onStartBatchInput() {
-        BatchInputUpdater.getInstance().onStartBatchInput(this);
+        mInputUpdater.onStartBatchInput();
         mHandler.cancelUpdateSuggestionStrip();
         mConnection.beginBatchEdit();
         final SettingsValues settingsValues = mSettings.getCurrent();
@@ -1667,46 +1780,41 @@
         mWordComposer.setCapitalizedModeAtStartComposingTime(getActualCapsMode());
     }
 
-    private static final class BatchInputUpdater implements Handler.Callback {
+    private static final class InputUpdater implements Handler.Callback {
         private final Handler mHandler;
-        private LatinIME mLatinIme;
+        private final LatinIME mLatinIme;
         private final Object mLock = new Object();
         private boolean mInBatchInput; // synchronized using {@link #mLock}.
 
-        private BatchInputUpdater() {
+        private InputUpdater(final LatinIME latinIme) {
             final HandlerThread handlerThread = new HandlerThread(
-                    BatchInputUpdater.class.getSimpleName());
+                    InputUpdater.class.getSimpleName());
             handlerThread.start();
             mHandler = new Handler(handlerThread.getLooper(), this);
-        }
-
-        // Initialization-on-demand holder
-        private static final class OnDemandInitializationHolder {
-            public static final BatchInputUpdater sInstance = new BatchInputUpdater();
-        }
-
-        public static BatchInputUpdater getInstance() {
-            return OnDemandInitializationHolder.sInstance;
+            mLatinIme = latinIme;
         }
 
         private static final int MSG_UPDATE_GESTURE_PREVIEW_AND_SUGGESTION_STRIP = 1;
+        private static final int MSG_GET_SUGGESTED_WORDS = 2;
 
         @Override
         public boolean handleMessage(final Message msg) {
             switch (msg.what) {
-            case MSG_UPDATE_GESTURE_PREVIEW_AND_SUGGESTION_STRIP:
-                updateBatchInput((InputPointers)msg.obj);
-                break;
+                case MSG_UPDATE_GESTURE_PREVIEW_AND_SUGGESTION_STRIP:
+                    updateBatchInput((InputPointers)msg.obj);
+                    break;
+                case MSG_GET_SUGGESTED_WORDS:
+                    mLatinIme.getSuggestedWords(msg.arg1, (OnGetSuggestedWordsCallback) msg.obj);
+                    break;
             }
             return true;
         }
 
         // Run in the UI thread.
-        public void onStartBatchInput(final LatinIME latinIme) {
+        public void onStartBatchInput() {
             synchronized (mLock) {
                 mHandler.removeMessages(MSG_UPDATE_GESTURE_PREVIEW_AND_SUGGESTION_STRIP);
                 mInBatchInput = true;
-                mLatinIme = latinIme;
                 mLatinIme.mHandler.showGesturePreviewAndSuggestionStrip(
                         SuggestedWords.EMPTY, false /* dismissGestureFloatingPreviewText */);
             }
@@ -1719,9 +1827,14 @@
                     // Batch input has ended or canceled while the message was being delivered.
                     return;
                 }
-                final SuggestedWords suggestedWords = getSuggestedWordsGestureLocked(batchPointers);
-                mLatinIme.mHandler.showGesturePreviewAndSuggestionStrip(
-                        suggestedWords, false /* dismissGestureFloatingPreviewText */);
+
+                getSuggestedWordsGestureLocked(batchPointers, new OnGetSuggestedWordsCallback() {
+                    @Override
+                    public void onGetSuggestedWords(final SuggestedWords suggestedWords) {
+                        mLatinIme.mHandler.showGesturePreviewAndSuggestionStrip(
+                                suggestedWords, false /* dismissGestureFloatingPreviewText */);
+                    }
+                });
             }
         }
 
@@ -1744,35 +1857,57 @@
         }
 
         // Run in the UI thread.
-        public SuggestedWords onEndBatchInput(final InputPointers batchPointers) {
-            synchronized (mLock) {
-                mInBatchInput = false;
-                final SuggestedWords suggestedWords = getSuggestedWordsGestureLocked(batchPointers);
-                mLatinIme.mHandler.showGesturePreviewAndSuggestionStrip(
-                        suggestedWords, true /* dismissGestureFloatingPreviewText */);
-                return suggestedWords;
+        public void onEndBatchInput(final InputPointers batchPointers) {
+            synchronized(mLock) {
+                getSuggestedWordsGestureLocked(batchPointers, new OnGetSuggestedWordsCallback() {
+                    @Override
+                    public void onGetSuggestedWords(final SuggestedWords suggestedWords) {
+                        mInBatchInput = false;
+                        mLatinIme.mHandler.showGesturePreviewAndSuggestionStrip(suggestedWords,
+                                true /* dismissGestureFloatingPreviewText */);
+                        mLatinIme.mHandler.onEndBatchInput(suggestedWords);
+                    }
+                });
             }
         }
 
         // {@link LatinIME#getSuggestedWords(int)} method calls with same session id have to
         // be synchronized.
-        private SuggestedWords getSuggestedWordsGestureLocked(final InputPointers batchPointers) {
+        private void getSuggestedWordsGestureLocked(final InputPointers batchPointers,
+                final OnGetSuggestedWordsCallback callback) {
             mLatinIme.mWordComposer.setBatchInputPointers(batchPointers);
-            final SuggestedWords suggestedWords =
-                    mLatinIme.getSuggestedWordsOrOlderSuggestions(Suggest.SESSION_GESTURE);
-            final int suggestionCount = suggestedWords.size();
-            if (suggestionCount <= 1) {
-                final String mostProbableSuggestion = (suggestionCount == 0) ? null
-                        : suggestedWords.getWord(0);
-                return mLatinIme.getOlderSuggestions(mostProbableSuggestion);
-            }
-            return suggestedWords;
+            mLatinIme.getSuggestedWordsOrOlderSuggestionsAsync(Suggest.SESSION_GESTURE,
+                    new OnGetSuggestedWordsCallback() {
+                @Override
+                public void onGetSuggestedWords(SuggestedWords suggestedWords) {
+                    final int suggestionCount = suggestedWords.size();
+                    if (suggestionCount <= 1) {
+                        final String mostProbableSuggestion = (suggestionCount == 0) ? null
+                                : suggestedWords.getWord(0);
+                        callback.onGetSuggestedWords(
+                                mLatinIme.getOlderSuggestions(mostProbableSuggestion));
+                    }
+                    callback.onGetSuggestedWords(suggestedWords);
+                }
+            });
+        }
+
+        public void getSuggestedWords(final int sessionId,
+                final OnGetSuggestedWordsCallback callback) {
+            mHandler.obtainMessage(MSG_GET_SUGGESTED_WORDS, sessionId, 0, callback).sendToTarget();
+        }
+
+        private void onDestroy() {
+            mHandler.removeMessages(MSG_GET_SUGGESTED_WORDS);
+            mHandler.removeMessages(MSG_UPDATE_GESTURE_PREVIEW_AND_SUGGESTION_STRIP);
+            mHandler.getLooper().quit();
         }
     }
 
+    // This method must run in UI Thread.
     private void showGesturePreviewAndSuggestionStrip(final SuggestedWords suggestedWords,
             final boolean dismissGestureFloatingPreviewText) {
-        showSuggestionStrip(suggestedWords, null);
+        showSuggestionStrip(suggestedWords);
         final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
         mainKeyboardView.showGestureFloatingPreviewText(suggestedWords);
         if (dismissGestureFloatingPreviewText) {
@@ -1782,24 +1917,48 @@
 
     @Override
     public void onUpdateBatchInput(final InputPointers batchPointers) {
-        BatchInputUpdater.getInstance().onUpdateBatchInput(batchPointers);
+        if (mSettings.getCurrent().mPhraseGestureEnabled) {
+            final SuggestedWordInfo candidate = mSuggestedWords.getAutoCommitCandidate();
+            if (null != candidate) {
+                if (candidate.mSourceDict.shouldAutoCommit(candidate)) {
+                    final String[] commitParts = candidate.mWord.split(" ", 2);
+                    batchPointers.shift(candidate.mIndexOfTouchPointOfSecondWord);
+                    promotePhantomSpace();
+                    mConnection.commitText(commitParts[0], 0);
+                    mSpaceState = SPACE_STATE_PHANTOM;
+                    mKeyboardSwitcher.updateShiftState();
+                    mWordComposer.setCapitalizedModeAtStartComposingTime(getActualCapsMode());
+                }
+            }
+        }
+        mInputUpdater.onUpdateBatchInput(batchPointers);
     }
 
-    @Override
-    public void onEndBatchInput(final InputPointers batchPointers) {
-        final SuggestedWords suggestedWords = BatchInputUpdater.getInstance().onEndBatchInput(
-                batchPointers);
+    // This method must run in UI Thread.
+    public void onEndBatchInputAsyncInternal(final SuggestedWords suggestedWords) {
         final String batchInputText = suggestedWords.isEmpty()
                 ? null : suggestedWords.getWord(0);
         if (TextUtils.isEmpty(batchInputText)) {
             return;
         }
-        mWordComposer.setBatchInputWord(batchInputText);
         mConnection.beginBatchEdit();
         if (SPACE_STATE_PHANTOM == mSpaceState) {
             promotePhantomSpace();
         }
-        mConnection.setComposingText(batchInputText, 1);
+        if (mSettings.getCurrent().mPhraseGestureEnabled) {
+            // Find the last space
+            final int indexOfLastSpace = batchInputText.lastIndexOf(Constants.CODE_SPACE) + 1;
+            if (0 != indexOfLastSpace) {
+                mConnection.commitText(batchInputText.substring(0, indexOfLastSpace), 1);
+                showSuggestionStrip(suggestedWords.getSuggestedWordsForLastWordOfPhraseGesture());
+            }
+            final String lastWord = batchInputText.substring(indexOfLastSpace);
+            mWordComposer.setBatchInputWord(lastWord);
+            mConnection.setComposingText(lastWord, 1);
+        } else {
+            mWordComposer.setBatchInputWord(batchInputText);
+            mConnection.setComposingText(batchInputText, 1);
+        }
         mExpectingUpdateSelection = true;
         mConnection.endBatchEdit();
         if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
@@ -1810,6 +1969,11 @@
         mKeyboardSwitcher.updateShiftState();
     }
 
+    @Override
+    public void onEndBatchInput(final InputPointers batchPointers) {
+        mInputUpdater.onEndBatchInput(batchPointers);
+    }
+
     private String specificTldProcessingOnTextInput(final String text) {
         if (text.length() <= 1 || text.charAt(0) != Constants.CODE_PERIOD
                 || !Character.isLetter(text.charAt(1))) {
@@ -1845,7 +2009,7 @@
 
     @Override
     public void onCancelBatchInput() {
-        BatchInputUpdater.getInstance().onCancelBatchInput();
+        mInputUpdater.onCancelBatchInput();
     }
 
     private void handleBackspace(final int spaceState) {
@@ -1861,23 +2025,23 @@
             // When we exit this if-clause, mWordComposer.isComposingWord() will return false.
         }
         if (mWordComposer.isComposingWord()) {
-            final int length = mWordComposer.size();
-            if (length > 0) {
-                if (mWordComposer.isBatchMode()) {
-                    if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
-                        final String word = mWordComposer.getTypedWord();
-                        ResearchLogger.latinIME_handleBackspace_batch(word, 1);
-                    }
-                    final String rejectedSuggestion = mWordComposer.getTypedWord();
-                    mWordComposer.reset();
-                    mWordComposer.setRejectedBatchModeSuggestion(rejectedSuggestion);
-                } else {
-                    mWordComposer.deleteLast();
+            if (mWordComposer.isBatchMode()) {
+                if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
+                    final String word = mWordComposer.getTypedWord();
+                    ResearchLogger.latinIME_handleBackspace_batch(word, 1);
                 }
-                mConnection.setComposingText(getTextWithUnderline(mWordComposer.getTypedWord()), 1);
-                mHandler.postUpdateSuggestionStrip();
+                final String rejectedSuggestion = mWordComposer.getTypedWord();
+                mWordComposer.reset();
+                mWordComposer.setRejectedBatchModeSuggestion(rejectedSuggestion);
             } else {
-                mConnection.deleteSurroundingText(1, 0);
+                mWordComposer.deleteLast();
+            }
+            mConnection.setComposingText(getTextWithUnderline(mWordComposer.getTypedWord()), 1);
+            mHandler.postUpdateSuggestionStrip();
+            if (!mWordComposer.isComposingWord()) {
+                // If we just removed the last character, auto-caps mode may have changed so we
+                // need to re-evaluate.
+                mKeyboardSwitcher.updateShiftState();
             }
         } else {
             final SettingsValues currentSettings = mSettings.getCurrent();
@@ -1892,8 +2056,7 @@
                 // Cancel multi-character input: remove the text we just entered.
                 // This is triggered on backspace after a key that inputs multiple characters,
                 // like the smiley key or the .com key.
-                final int length = mEnteredText.length();
-                mConnection.deleteSurroundingText(length, 0);
+                mConnection.deleteSurroundingText(mEnteredText.length(), 0);
                 if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
                     ResearchLogger.latinIME_handleBackspace_cancelTextInput(mEnteredText);
                 }
@@ -1939,6 +2102,8 @@
                     // This should never happen.
                     Log.e(TAG, "Backspace when we don't know the selection position");
                 }
+                final int lengthToDelete = Character.isSupplementaryCodePoint(
+                        mConnection.getCodePointBeforeCursor()) ? 2 : 1;
                 if (mAppWorkAroundsUtils.isBeforeJellyBean()) {
                     // Backward compatibility mode. Before Jelly bean, the keyboard would simulate
                     // a hardware keyboard event on pressing enter or delete. This is bad for many
@@ -1946,22 +2111,28 @@
                     // relying on this behavior so we continue to support it for older apps.
                     sendDownUpKeyEventForBackwardCompatibility(KeyEvent.KEYCODE_DEL);
                 } else {
-                    mConnection.deleteSurroundingText(1, 0);
+                    mConnection.deleteSurroundingText(lengthToDelete, 0);
                 }
                 if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
-                    ResearchLogger.latinIME_handleBackspace(1, true /* shouldUncommitLogUnit */);
+                    ResearchLogger.latinIME_handleBackspace(lengthToDelete,
+                            true /* shouldUncommitLogUnit */);
                 }
                 if (mDeleteCount > DELETE_ACCELERATE_AT) {
-                    mConnection.deleteSurroundingText(1, 0);
+                    final int lengthToDeleteAgain = Character.isSupplementaryCodePoint(
+                            mConnection.getCodePointBeforeCursor()) ? 2 : 1;
+                    mConnection.deleteSurroundingText(lengthToDeleteAgain, 0);
                     if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
-                        ResearchLogger.latinIME_handleBackspace(1,
+                        ResearchLogger.latinIME_handleBackspace(lengthToDeleteAgain,
                                 true /* shouldUncommitLogUnit */);
                     }
                 }
             }
-            if (currentSettings.isSuggestionsRequested(mDisplayOrientation)) {
+            if (currentSettings.isSuggestionsRequested(mDisplayOrientation)
+                    && currentSettings.mCurrentLanguageHasSpaces) {
                 restartSuggestionsOnWordBeforeCursorIfAtEndOfWord();
             }
+            // We just removed a character. We need to update the auto-caps state.
+            mKeyboardSwitcher.updateShiftState();
         }
     }
 
@@ -1986,6 +2157,9 @@
 
     private void handleCharacter(final int primaryCode, final int x,
             final int y, final int spaceState) {
+        // TODO: refactor this method to stop flipping isComposingWord around all the time, and
+        // make it shorter (possibly cut into several pieces). Also factor handleNonSpecialCharacter
+        // which has the same name as other handle* methods but is not the same.
         boolean isComposingWord = mWordComposer.isComposingWord();
 
         // TODO: remove isWordConnector() and use isUsuallyFollowedBySpace() instead.
@@ -2005,18 +2179,26 @@
             resetEntireInputState(mLastSelectionStart);
             isComposingWord = false;
         }
-        // NOTE: isCursorTouchingWord() is a blocking IPC call, so it often takes several
-        // dozen milliseconds. Avoid calling it as much as possible, since we are on the UI
-        // thread here.
-        if (!isComposingWord && (isAlphabet(primaryCode)
-                || currentSettings.isWordConnector(primaryCode))
+        // We want to find out whether to start composing a new word with this character. If so,
+        // we need to reset the composing state and switch isComposingWord. The order of the
+        // tests is important for good performance.
+        // We only start composing if we're not already composing.
+        if (!isComposingWord
+        // We only start composing if this is a word code point. Essentially that means it's a
+        // a letter or a word connector.
+                && currentSettings.isWordCodePoint(primaryCode)
+        // We never go into composing state if suggestions are not requested.
                 && currentSettings.isSuggestionsRequested(mDisplayOrientation) &&
-                !mConnection.isCursorTouchingWord(currentSettings)) {
+        // In languages with spaces, we only start composing a word when we are not already
+        // touching a word. In languages without spaces, the above conditions are sufficient.
+                (!mConnection.isCursorTouchingWord(currentSettings)
+                        || !currentSettings.mCurrentLanguageHasSpaces)) {
             // Reset entirely the composing state anyway, then start composing a new word unless
-            // the character is a single quote. The idea here is, single quote is not a
-            // separator and it should be treated as a normal character, except in the first
-            // position where it should not start composing a word.
-            isComposingWord = (Constants.CODE_SINGLE_QUOTE != primaryCode);
+            // the character is a single quote or a dash. The idea here is, single quote and dash
+            // are not separators and they should be treated as normal characters, except in the
+            // first position where they should not start composing a word.
+            isComposingWord = (Constants.CODE_SINGLE_QUOTE != primaryCode
+                    && Constants.CODE_DASH != primaryCode);
             // Here we don't need to reset the last composed word. It will be reset
             // when we commit this one, if we ever do; if on the other hand we backspace
             // it entirely and resume suggestions on the previous word, we'd like to still
@@ -2095,20 +2277,24 @@
         mKeyboardSwitcher.updateShiftState();
     }
 
-    // Returns true if we did an autocorrection, false otherwise.
+    // Returns true if we do an autocorrection, false otherwise.
     private boolean handleSeparator(final int primaryCode, final int x, final int y,
             final int spaceState) {
         boolean didAutoCorrect = false;
+        final SettingsValues currentSettings = mSettings.getCurrent();
+        // We avoid sending spaces in languages without spaces if we were composing.
+        final boolean shouldAvoidSendingCode = Constants.CODE_SPACE == primaryCode
+                && !currentSettings.mCurrentLanguageHasSpaces && mWordComposer.isComposingWord();
         if (mWordComposer.isCursorFrontOrMiddleOfComposingWord()) {
             // If we are in the middle of a recorrection, we need to commit the recorrection
             // first so that we can insert the separator at the current cursor position.
             resetEntireInputState(mLastSelectionStart);
         }
-        final SettingsValues currentSettings = mSettings.getCurrent();
-        if (mWordComposer.isComposingWord()) {
+        if (mWordComposer.isComposingWord()) { // May have changed since we stored wasComposing
             if (currentSettings.mCorrectionEnabled) {
-                // TODO: maybe cache Strings in an <String> sparse array or something
-                commitCurrentAutoCorrection(new String(new int[]{primaryCode}, 0, 1));
+                final String separator = shouldAvoidSendingCode ? LastComposedWord.NOT_A_SEPARATOR
+                        : new String(new int[] { primaryCode }, 0, 1);
+                commitCurrentAutoCorrection(separator);
                 didAutoCorrect = true;
             } else {
                 commitTyped(new String(new int[]{primaryCode}, 0, 1));
@@ -2125,7 +2311,10 @@
         if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
             ResearchLogger.latinIME_handleSeparator(primaryCode, mWordComposer.isComposingWord());
         }
-        sendKeyCodePoint(primaryCode);
+
+        if (!shouldAvoidSendingCode) {
+            sendKeyCodePoint(primaryCode);
+        }
 
         if (Constants.CODE_SPACE == primaryCode) {
             if (currentSettings.isSuggestionsRequested(mDisplayOrientation)) {
@@ -2255,34 +2444,60 @@
             return;
         }
 
-        final SuggestedWords suggestedWords =
-                getSuggestedWordsOrOlderSuggestions(Suggest.SESSION_TYPING);
-        final String typedWord = mWordComposer.getTypedWord();
-        showSuggestionStrip(suggestedWords, typedWord);
+        final AsyncResultHolder<SuggestedWords> holder = new AsyncResultHolder<SuggestedWords>();
+        getSuggestedWordsOrOlderSuggestionsAsync(Suggest.SESSION_TYPING,
+                new OnGetSuggestedWordsCallback() {
+                    @Override
+                    public void onGetSuggestedWords(final SuggestedWords suggestedWords) {
+                        holder.set(suggestedWords);
+                    }
+                }
+        );
+
+        // This line may cause the current thread to wait.
+        final SuggestedWords suggestedWords = holder.get(null, GET_SUGGESTED_WORDS_TIMEOUT);
+        if (suggestedWords != null) {
+            showSuggestionStrip(suggestedWords);
+        }
     }
 
-    private SuggestedWords getSuggestedWords(final int sessionId) {
+    private void getSuggestedWords(final int sessionId,
+            final OnGetSuggestedWordsCallback callback) {
         final Keyboard keyboard = mKeyboardSwitcher.getKeyboard();
         final Suggest suggest = mSuggest;
         if (keyboard == null || suggest == null) {
-            return SuggestedWords.EMPTY;
+            callback.onGetSuggestedWords(SuggestedWords.EMPTY);
+            return;
         }
         // Get the word on which we should search the bigrams. If we are composing a word, it's
         // whatever is *before* the half-committed word in the buffer, hence 2; if we aren't, we
         // should just skip whitespace if any, so 1.
-        // TODO: this is slow (2-way IPC) - we should probably cache this instead.
         final SettingsValues currentSettings = mSettings.getCurrent();
-        final String prevWord =
-                mConnection.getNthPreviousWord(currentSettings.mWordSeparators,
-                mWordComposer.isComposingWord() ? 2 : 1);
-        return suggest.getSuggestedWords(mWordComposer, prevWord, keyboard.getProximityInfo(),
-                currentSettings.mBlockPotentiallyOffensive,
-                currentSettings.mCorrectionEnabled, sessionId);
+        final int[] additionalFeaturesOptions = currentSettings.mAdditionalFeaturesSettingValues;
+        final String prevWord;
+        if (currentSettings.mCurrentLanguageHasSpaces) {
+            // If we are typing in a language with spaces we can just look up the previous
+            // word from textview.
+            prevWord = mConnection.getNthPreviousWord(currentSettings.mWordSeparators,
+                    mWordComposer.isComposingWord() ? 2 : 1);
+        } else {
+            prevWord = LastComposedWord.NOT_A_COMPOSED_WORD == mLastComposedWord ? null
+                    : mLastComposedWord.mCommittedWord;
+        }
+        suggest.getSuggestedWords(mWordComposer, prevWord, keyboard.getProximityInfo(),
+                currentSettings.mBlockPotentiallyOffensive, currentSettings.mCorrectionEnabled,
+                additionalFeaturesOptions, sessionId, callback);
     }
 
-    private SuggestedWords getSuggestedWordsOrOlderSuggestions(final int sessionId) {
-        return maybeRetrieveOlderSuggestions(mWordComposer.getTypedWord(),
-                getSuggestedWords(sessionId));
+    private void getSuggestedWordsOrOlderSuggestionsAsync(final int sessionId,
+            final OnGetSuggestedWordsCallback callback) {
+        mInputUpdater.getSuggestedWords(sessionId, new OnGetSuggestedWordsCallback() {
+            @Override
+            public void onGetSuggestedWords(SuggestedWords suggestedWords) {
+                callback.onGetSuggestedWords(maybeRetrieveOlderSuggestions(
+                        mWordComposer.getTypedWord(), suggestedWords));
+            }
+        });
     }
 
     private SuggestedWords maybeRetrieveOlderSuggestions(final String typedWord,
@@ -2322,25 +2537,42 @@
                 false /* isPrediction */);
     }
 
-    private void showSuggestionStrip(final SuggestedWords suggestedWords, final String typedWord) {
+    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 {
+            // 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;
         }
-        final String autoCorrection;
-        if (suggestedWords.mWillAutoCorrect) {
-            autoCorrection = suggestedWords.getWord(1);
-        } else {
-            autoCorrection = typedWord;
-        }
-        mWordComposer.setAutoCorrection(autoCorrection);
-        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 separatorString) {
+    private void commitCurrentAutoCorrection(final String separator) {
         // Complete any pending suggestions query first
         if (mHandler.hasPendingUpdateSuggestions()) {
             updateSuggestionStrip();
@@ -2356,16 +2588,16 @@
             }
             if (mSettings.isInternal()) {
                 LatinImeLoggerUtils.onAutoCorrection(
-                        typedWord, autoCorrection, separatorString, mWordComposer);
+                        typedWord, autoCorrection, separator, mWordComposer);
             }
             if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
                 final SuggestedWords suggestedWords = mSuggestedWords;
                 ResearchLogger.latinIme_commitCurrentAutoCorrection(typedWord, autoCorrection,
-                        separatorString, mWordComposer.isBatchMode(), suggestedWords);
+                        separator, mWordComposer.isBatchMode(), suggestedWords);
             }
             mExpectingUpdateSelection = true;
             commitChosenWord(autoCorrection, LastComposedWord.COMMIT_TYPE_DECIDED_WORD,
-                    separatorString);
+                    separator);
             if (!typedWord.equals(autoCorrection)) {
                 // This will make the correction flash for a short while as a visual clue
                 // to the user that auto-correction happened. It has no other effect; in particular
@@ -2440,7 +2672,7 @@
         if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
             ResearchLogger.latinIME_pickSuggestionManually(replacedWord, index, suggestion,
                     mWordComposer.isBatchMode(), suggestionInfo.mScore, suggestionInfo.mKind,
-                    suggestionInfo.mSourceDict);
+                    suggestionInfo.mSourceDict.mDictType);
         }
         mConnection.endBatchEdit();
         // Don't allow cancellation of manual pick
@@ -2535,6 +2767,13 @@
         return prevWord;
     }
 
+    private boolean isResumableWord(final String word, final SettingsValues settings) {
+        final int firstCodePoint = word.codePointAt(0);
+        return settings.isWordCodePoint(firstCodePoint)
+                && Constants.CODE_SINGLE_QUOTE != firstCodePoint
+                && Constants.CODE_DASH != firstCodePoint;
+    }
+
     /**
      * Check if the cursor is touching a word. If so, restart suggestions on this word, else
      * do nothing.
@@ -2544,6 +2783,11 @@
         // recorrection. This is a temporary, stopgap measure that will be removed later.
         // TODO: remove this.
         if (mAppWorkAroundsUtils.isBrokenByRecorrection()) return;
+        // A simple way to test for support from the TextView.
+        if (!isSuggestionsStripVisible()) return;
+        // Recorrection is not supported in languages without spaces because we don't know
+        // how to segment them yet.
+        if (!mSettings.getCurrent().mCurrentLanguageHasSpaces) return;
         // If the cursor is not touching a word, or if there is a selection, return right away.
         if (mLastSelectionStart != mLastSelectionEnd) return;
         // If we don't know the cursor location, return.
@@ -2553,12 +2797,14 @@
         final TextRange range = mConnection.getWordRangeAtCursor(currentSettings.mWordSeparators,
                 0 /* additionalPrecedingWordsCount */);
         if (null == range) return; // Happens if we don't have an input connection at all
+        if (range.length() <= 0) return; // Race condition. No text to resume on, so bail out.
         // If for some strange reason (editor bug or so) we measure the text before the cursor as
         // longer than what the entire text is supposed to be, the safe thing to do is bail out.
         final int numberOfCharsInWordBeforeCursor = range.getNumberOfCharsInWordBeforeCursor();
         if (numberOfCharsInWordBeforeCursor > mLastSelectionStart) return;
         final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList();
         final String typedWord = range.mWord.toString();
+        if (!isResumableWord(typedWord, currentSettings)) return;
         int i = 0;
         for (final SuggestionSpan span : range.getSuggestionSpansAtWord()) {
             for (final String s : span.getSuggestions()) {
@@ -2566,51 +2812,69 @@
                 if (!TextUtils.equals(s, typedWord)) {
                     suggestions.add(new SuggestedWordInfo(s,
                             SuggestionStripView.MAX_SUGGESTIONS - i,
-                            SuggestedWordInfo.KIND_RESUMED, Dictionary.TYPE_RESUMED));
+                            SuggestedWordInfo.KIND_RESUMED, Dictionary.DICTIONARY_RESUMED,
+                            SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */,
+                            SuggestedWordInfo.NOT_A_CONFIDENCE
+                                    /* autoCommitFirstWordConfidence */));
                 }
             }
         }
         mWordComposer.setComposingWord(typedWord, mKeyboardSwitcher.getKeyboard());
-        // TODO: this is in chars but the callee expects code points!
-        mWordComposer.setCursorPositionWithinWord(numberOfCharsInWordBeforeCursor);
+        mWordComposer.setCursorPositionWithinWord(
+                typedWord.codePointCount(0, numberOfCharsInWordBeforeCursor));
         mConnection.setComposingRegion(
                 mLastSelectionStart - numberOfCharsInWordBeforeCursor,
                 mLastSelectionEnd + range.getNumberOfCharsInWordAfterCursor());
-        final SuggestedWords suggestedWords;
         if (suggestions.isEmpty()) {
             // We come here if there weren't any suggestion spans on this word. We will try to
             // compute suggestions for it instead.
-            final SuggestedWords suggestedWordsIncludingTypedWord =
-                    getSuggestedWords(Suggest.SESSION_TYPING);
-            if (suggestedWordsIncludingTypedWord.size() > 1) {
-                // We were able to compute new suggestions for this word.
-                // Remove the typed word, since we don't want to display it in this case.
-                // The #getSuggestedWordsExcludingTypedWord() method sets willAutoCorrect to false.
-                suggestedWords =
-                        suggestedWordsIncludingTypedWord.getSuggestedWordsExcludingTypedWord();
-            } else {
-                // No saved suggestions, and we were unable to compute any good one either.
-                // Rather than displaying an empty suggestion strip, we'll display the original
-                // word alone in the middle.
-                // Since there is only one word, willAutoCorrect is false.
-                suggestedWords = suggestedWordsIncludingTypedWord;
-            }
+            mInputUpdater.getSuggestedWords(Suggest.SESSION_TYPING,
+                    new OnGetSuggestedWordsCallback() {
+                @Override
+                public void onGetSuggestedWords(
+                        final SuggestedWords suggestedWordsIncludingTypedWord) {
+                    final SuggestedWords suggestedWords;
+                    if (suggestedWordsIncludingTypedWord.size() > 1) {
+                        // We were able to compute new suggestions for this word.
+                        // Remove the typed word, since we don't want to display it in this case.
+                        // The #getSuggestedWordsExcludingTypedWord() method sets willAutoCorrect to
+                        // false.
+                        suggestedWords = suggestedWordsIncludingTypedWord
+                                .getSuggestedWordsExcludingTypedWord();
+                    } else {
+                        // No saved suggestions, and we were unable to compute any good one either.
+                        // Rather than displaying an empty suggestion strip, we'll display the
+                        // original word alone in the middle.
+                        // Since there is only one word, willAutoCorrect is false.
+                        suggestedWords = suggestedWordsIncludingTypedWord;
+                    }
+                    // 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
             // them, and make willAutoCorrect false.
-            suggestedWords = new SuggestedWords(suggestions,
+            final SuggestedWords suggestedWords = new SuggestedWords(suggestions,
                     true /* typedWordValid */, false /* willAutoCorrect */,
                     false /* isPunctuationSuggestions */, false /* isObsoleteSuggestions */,
                     false /* isPrediction */);
+            // We need to pass typedWord because mWordComposer.mTypedWord may differ from typedWord.
+            unsetIsAutoCorrectionIndicatorOnAndCallShowSuggestionStrip(suggestedWords, typedWord);
         }
+    }
 
+    public void unsetIsAutoCorrectionIndicatorOnAndCallShowSuggestionStrip(
+            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 where suggestedWords is affected. We also need to reset
-        // mIsAutoCorrectionIndicatorOn to avoid showSuggestionStrip touching the text to adapt it.
-        // TODO: remove mIsAutoCorrectionIndicator on (see comment on definition)
+        // We never want to auto-correct on a resumed suggestion. Please refer to the three places
+        // above in restartSuggestionsOnWordTouchedByCursor() where suggestedWords is affected.
+        // We also need to unset mIsAutoCorrectionIndicatorOn to avoid showSuggestionStrip touching
+        // the text to adapt it.
+        // TODO: remove mIsAutoCorrectionIndicatorOn (see comment on definition)
         mIsAutoCorrectionIndicatorOn = false;
-        showSuggestionStrip(suggestedWords, typedWord);
+        mHandler.showSuggestionStripWithTypedWord(suggestedWords, typedWord);
     }
 
     /**
@@ -2640,6 +2904,27 @@
         mHandler.postUpdateSuggestionStrip();
     }
 
+    /**
+     * Retry resetting caches in the rich input connection.
+     *
+     * When the editor can't be accessed we can't reset the caches, so we schedule a retry.
+     * This method handles the retry, and re-schedules a new retry if we still can't access.
+     * We only retry up to 5 times before giving up.
+     *
+     * @param tryResumeSuggestions Whether we should resume suggestions or not.
+     * @param remainingTries How many times we may try again before giving up.
+     */
+    private void retryResetCaches(final boolean tryResumeSuggestions, final int remainingTries) {
+        if (!mConnection.resetCachesUponCursorMoveAndReturnSuccess(mLastSelectionStart, false)) {
+            if (0 < remainingTries) {
+                mHandler.postResetCaches(tryResumeSuggestions, remainingTries - 1);
+            }
+            return;
+        }
+        tryFixLyingCursorPosition();
+        if (tryResumeSuggestions) mHandler.postResumeSuggestions();
+    }
+
     private void revertCommit() {
         final String previousWord = mLastComposedWord.mPrevWord;
         final String originallyTypedWord = mLastComposedWord.mTypedWord;
@@ -2666,7 +2951,18 @@
         if (!TextUtils.isEmpty(previousWord) && !TextUtils.isEmpty(committedWord)) {
             mUserHistoryPredictionDictionary.cancelAddingUserHistory(previousWord, committedWord);
         }
-        mConnection.commitText(originallyTypedWord + mLastComposedWord.mSeparatorString, 1);
+        final String stringToCommit = originallyTypedWord + mLastComposedWord.mSeparatorString;
+        if (mSettings.getCurrent().mCurrentLanguageHasSpaces) {
+            // For languages with spaces, we revert to the typed string, but the cursor is still
+            // after the separator so we don't resume suggestions. If the user wants to correct
+            // the word, they have to press backspace again.
+            mConnection.commitText(stringToCommit, 1);
+        } else {
+            // For languages without spaces, we revert the typed string but the cursor is flush
+            // with the typed word, so we need to resume suggestions right away.
+            mWordComposer.setComposingWord(stringToCommit, mKeyboardSwitcher.getKeyboard());
+            mConnection.setComposingText(stringToCommit, 1);
+        }
         if (mSettings.isInternal()) {
             LatinImeLoggerUtils.onSeparator(mLastComposedWord.mSeparatorString,
                     Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
@@ -2684,7 +2980,9 @@
 
     // This essentially inserts a space, and that's it.
     public void promotePhantomSpace() {
-        if (mSettings.getCurrent().shouldInsertSpacesAutomatically()
+        final SettingsValues currentSettings = mSettings.getCurrent();
+        if (currentSettings.shouldInsertSpacesAutomatically()
+                && currentSettings.mCurrentLanguageHasSpaces
                 && !mConnection.textBeforeCursorLooksLikeURL()) {
             if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
                 ResearchLogger.latinIME_promotePhantomSpace();
@@ -2710,30 +3008,43 @@
         }
     }
 
-    private void hapticAndAudioFeedback(final int code, final boolean isRepeatKey) {
+    private void hapticAndAudioFeedback(final int code, final int repeatCount) {
         final MainKeyboardView keyboardView = mKeyboardSwitcher.getMainKeyboardView();
         if (keyboardView != null && keyboardView.isInSlidingKeyInput()) {
             // No need to feedback while sliding input.
             return;
         }
-        if (isRepeatKey && code == Constants.CODE_DELETE && !mConnection.canDeleteCharacters()) {
-            // No need to feedback when repeating delete key will have no effect.
-            return;
+        if (repeatCount > 0) {
+            if (code == Constants.CODE_DELETE && !mConnection.canDeleteCharacters()) {
+                // No need to feedback when repeat delete key will have no effect.
+                return;
+            }
+            // TODO: Use event time that the last feedback has been generated instead of relying on
+            // a repeat count to thin out feedback.
+            if (repeatCount % PERIOD_FOR_AUDIO_AND_HAPTIC_FEEDBACK_IN_KEY_REPEAT == 0) {
+                return;
+            }
         }
-        AudioAndHapticFeedbackManager.getInstance().hapticAndAudioFeedback(code, keyboardView);
+        final AudioAndHapticFeedbackManager feedbackManager =
+                AudioAndHapticFeedbackManager.getInstance();
+        if (repeatCount == 0) {
+            // TODO: Reconsider how to perform haptic feedback when repeating key.
+            feedbackManager.performHapticFeedback(keyboardView);
+        }
+        feedbackManager.performAudioFeedback(code);
     }
 
     // Callback of the {@link KeyboardActionListener}. This is called when a key is depressed;
     // release matching call is {@link #onReleaseKey(int,boolean)} below.
     @Override
-    public void onPressKey(final int primaryCode, final boolean isRepeatKey,
+    public void onPressKey(final int primaryCode, final int repeatCount,
             final boolean isSinglePointer) {
         mKeyboardSwitcher.onPressKey(primaryCode, isSinglePointer);
-        hapticAndAudioFeedback(primaryCode, isRepeatKey);
+        hapticAndAudioFeedback(primaryCode, repeatCount);
     }
 
     // Callback of the {@link KeyboardActionListener}. This is called when a key is released;
-    // press matching call is {@link #onPressKey(int,boolean,boolean)} above.
+    // press matching call is {@link #onPressKey(int,int,boolean)} above.
     @Override
     public void onReleaseKey(final int primaryCode, final boolean withSliding) {
         mKeyboardSwitcher.onReleaseKey(primaryCode, withSliding);
@@ -2749,17 +3060,6 @@
                 break;
             }
         }
-
-        if (Constants.CODE_DELETE == primaryCode) {
-            // This is a stopgap solution to avoid leaving a high surrogate alone in a text view.
-            // In the future, we need to deprecate deteleSurroundingText() and have a surrogate
-            // pair-friendly way of deleting characters in InputConnection.
-            // TODO: use getCodePointBeforeCursor instead to improve performance
-            final CharSequence lastChar = mConnection.getTextBeforeCursor(1, 0);
-            if (!TextUtils.isEmpty(lastChar) && Character.isHighSurrogate(lastChar.charAt(0))) {
-                mConnection.deleteSurroundingText(1, 0);
-            }
-        }
     }
 
     // Hooks for hardware keyboard
@@ -2895,6 +3195,12 @@
         return mSuggest.hasMainDictionary();
     }
 
+    // DO NOT USE THIS for any other purpose than testing. This can break the keyboard badly.
+    @UsedForTesting
+    /* package for test */ void replaceMainDictionaryForTest(final Locale locale) {
+        mSuggest.resetMainDict(this, locale, null);
+    }
+
     public void debugDumpStateAndCrashWithException(final String context) {
         final StringBuilder s = new StringBuilder(mAppWorkAroundsUtils.toString());
         s.append("\nAttributes : ").append(mSettings.getCurrent().mInputAttributes)
diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java
index 35920f8..925381b 100644
--- a/java/src/com/android/inputmethod/latin/RichInputConnection.java
+++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java
@@ -73,9 +73,6 @@
      * This contains the currently composing text, as LatinIME thinks the TextView is seeing it.
      */
     private final StringBuilder mComposingText = new StringBuilder();
-    // A hint on how many characters to cache from the TextView. A good value of this is given by
-    // how many characters we need to be able to almost always find the caps mode.
-    private static final int DEFAULT_TEXT_CACHE_SIZE = 100;
 
     private final InputMethodService mParent;
     InputConnection mIC;
@@ -93,7 +90,8 @@
         r.token = 1;
         r.flags = 0;
         final ExtractedText et = mIC.getExtractedText(r, 0);
-        final CharSequence beforeCursor = getTextBeforeCursor(DEFAULT_TEXT_CACHE_SIZE, 0);
+        final CharSequence beforeCursor = getTextBeforeCursor(Constants.EDITOR_CONTENTS_CACHE_SIZE,
+                0);
         final StringBuilder internal = new StringBuilder().append(mCommittedTextBeforeComposingText)
                 .append(mComposingText);
         if (null == et || null == beforeCursor) return;
@@ -142,19 +140,56 @@
         if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
     }
 
-    public void resetCachesUponCursorMove(final int newCursorPosition,
+    /**
+     * Reset the cached text and retrieve it again from the editor.
+     *
+     * This should be called when the cursor moved. It's possible that we can't connect to
+     * the application when doing this; notably, this happens sometimes during rotation, probably
+     * because of a race condition in the framework. In this case, we just can't retrieve the
+     * data, so we empty the cache and note that we don't know the new cursor position, and we
+     * return false so that the caller knows about this and can retry later.
+     *
+     * @param newCursorPosition The new position of the cursor, as received from the system.
+     * @param shouldFinishComposition Whether we should finish the composition in progress.
+     * @return true if we were able to connect to the editor successfully, false otherwise. When
+     *   this method returns false, the caches could not be correctly refreshed so they were only
+     *   reset: the caller should try again later to return to normal operation.
+     */
+    public boolean resetCachesUponCursorMoveAndReturnSuccess(final int newCursorPosition,
             final boolean shouldFinishComposition) {
         mExpectedCursorPosition = newCursorPosition;
         mComposingText.setLength(0);
         mCommittedTextBeforeComposingText.setLength(0);
-        final CharSequence textBeforeCursor = getTextBeforeCursor(DEFAULT_TEXT_CACHE_SIZE, 0);
-        if (null != textBeforeCursor) mCommittedTextBeforeComposingText.append(textBeforeCursor);
+        mIC = mParent.getCurrentInputConnection();
+        // Call upon the inputconnection directly since our own method is using the cache, and
+        // we want to refresh it.
+        final CharSequence textBeforeCursor = null == mIC ? null :
+                mIC.getTextBeforeCursor(Constants.EDITOR_CONTENTS_CACHE_SIZE, 0);
+        if (null == textBeforeCursor) {
+            // For some reason the app thinks we are not connected to it. This looks like a
+            // framework bug... Fall back to ground state and return false.
+            mExpectedCursorPosition = INVALID_CURSOR_POSITION;
+            Log.e(TAG, "Unable to connect to the editor to retrieve text... will retry later");
+            return false;
+        }
+        mCommittedTextBeforeComposingText.append(textBeforeCursor);
+        final int lengthOfTextBeforeCursor = textBeforeCursor.length();
+        if (lengthOfTextBeforeCursor > newCursorPosition
+                || (lengthOfTextBeforeCursor < Constants.EDITOR_CONTENTS_CACHE_SIZE
+                        && newCursorPosition < Constants.EDITOR_CONTENTS_CACHE_SIZE)) {
+            // newCursorPosition may be lying -- when rotating the device (probably a framework
+            // bug). If we have less chars than we asked for, then we know how many chars we have,
+            // and if we got more than newCursorPosition says, then we know it was lying. In both
+            // cases the length is more reliable
+            mExpectedCursorPosition = lengthOfTextBeforeCursor;
+        }
         if (null != mIC && shouldFinishComposition) {
             mIC.finishComposingText();
             if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
                 ResearchLogger.richInputConnection_finishComposingText();
             }
         }
+        return true;
     }
 
     private void checkBatchEdit() {
@@ -169,7 +204,6 @@
         if (DEBUG_BATCH_NESTING) checkBatchEdit();
         if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
         mCommittedTextBeforeComposingText.append(mComposingText);
-        mExpectedCursorPosition += mComposingText.length();
         mComposingText.setLength(0);
         if (null != mIC) {
             mIC.finishComposingText();
@@ -234,8 +268,11 @@
         // getCapsMode should be updated to be able to return a "not enough info" result so that
         // we can get more context only when needed.
         if (TextUtils.isEmpty(mCommittedTextBeforeComposingText) && 0 != mExpectedCursorPosition) {
-            mCommittedTextBeforeComposingText.append(
-                    getTextBeforeCursor(DEFAULT_TEXT_CACHE_SIZE, 0));
+            final CharSequence textBeforeCursor = getTextBeforeCursor(
+                    Constants.EDITOR_CONTENTS_CACHE_SIZE, 0);
+            if (!TextUtils.isEmpty(textBeforeCursor)) {
+                mCommittedTextBeforeComposingText.append(textBeforeCursor);
+            }
         }
         // This never calls InputConnection#getCapsMode - in fact, it's a static method that
         // never blocks or initiates IPC.
@@ -363,7 +400,7 @@
         if (DEBUG_BATCH_NESTING) checkBatchEdit();
         if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
         final CharSequence textBeforeCursor =
-                getTextBeforeCursor(DEFAULT_TEXT_CACHE_SIZE + (end - start), 0);
+                getTextBeforeCursor(Constants.EDITOR_CONTENTS_CACHE_SIZE + (end - start), 0);
         mCommittedTextBeforeComposingText.setLength(0);
         if (!TextUtils.isEmpty(textBeforeCursor)) {
             final int indexOfStartOfComposingText =
@@ -405,7 +442,8 @@
         }
         mExpectedCursorPosition = start;
         mCommittedTextBeforeComposingText.setLength(0);
-        mCommittedTextBeforeComposingText.append(getTextBeforeCursor(DEFAULT_TEXT_CACHE_SIZE, 0));
+        mCommittedTextBeforeComposingText.append(
+                getTextBeforeCursor(Constants.EDITOR_CONTENTS_CACHE_SIZE, 0));
     }
 
     public void commitCorrection(final CorrectionInfo correctionInfo) {
@@ -524,9 +562,9 @@
         if (mIC == null || sep == null) {
             return null;
         }
-        final CharSequence before = mIC.getTextBeforeCursor(1000,
+        final CharSequence before = mIC.getTextBeforeCursor(Constants.EDITOR_CONTENTS_CACHE_SIZE,
                 InputConnection.GET_TEXT_WITH_STYLES);
-        final CharSequence after = mIC.getTextAfterCursor(1000,
+        final CharSequence after = mIC.getTextAfterCursor(Constants.EDITOR_CONTENTS_CACHE_SIZE,
                 InputConnection.GET_TEXT_WITH_STYLES);
         if (before == null || after == null) {
             return null;
@@ -569,8 +607,11 @@
             }
         }
 
-        return new TextRange(TextUtils.concat(before, after), startIndexInBefore,
-                before.length() + endIndexInAfter, before.length());
+        // We don't use TextUtils#concat because it copies all spans without respect to their
+        // nature. If the text includes a PARAGRAPH span and it has been split, then
+        // TextUtils#concat will crash when it tries to concat both sides of it.
+        return new TextRange(StringUtils.concatWithNonParagraphSuggestionSpansOnly(before, after),
+                startIndexInBefore, before.length() + endIndexInAfter, before.length());
     }
 
     public boolean isCursorTouchingWord(final SettingsValues settingsValues) {
diff --git a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
index be03d4a..cd9c89f 100644
--- a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
+++ b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
@@ -53,12 +53,25 @@
     private InputMethodInfo mShortcutInputMethodInfo;
     private InputMethodSubtype mShortcutSubtype;
     private InputMethodSubtype mNoLanguageSubtype;
+    private InputMethodSubtype mEmojiSubtype;
     private boolean mIsNetworkConnected;
 
     // Dummy no language QWERTY subtype. See {@link R.xml.method}.
     private static final InputMethodSubtype DUMMY_NO_LANGUAGE_SUBTYPE = new InputMethodSubtype(
-            R.string.subtype_no_language_qwerty, R.drawable.ic_subtype_keyboard, "zz", "keyboard",
-            "KeyboardLayoutSet=qwerty,AsciiCapable,EnabledWhenDefaultIsNotAsciiCapable",
+            R.string.subtype_no_language_qwerty, R.drawable.ic_ime_switcher_dark,
+            SubtypeLocaleUtils.NO_LANGUAGE, "keyboard", "KeyboardLayoutSet="
+                    + SubtypeLocaleUtils.QWERTY
+                    + "," + Constants.Subtype.ExtraValue.ASCII_CAPABLE
+                    + ",EnabledWhenDefaultIsNotAsciiCapable,"
+                    + Constants.Subtype.ExtraValue.EMOJI_CAPABLE,
+            false /* isAuxiliary */, false /* overridesImplicitlyEnabledSubtype */);
+    // Caveat: We probably should remove this when we add an Emoji subtype in {@link R.xml.method}.
+    // Dummy Emoji subtype. See {@link R.xml.method}.
+    private static final InputMethodSubtype DUMMY_EMOJI_SUBTYPE = new InputMethodSubtype(
+            R.string.subtype_emoji, R.drawable.ic_ime_switcher_dark,
+            SubtypeLocaleUtils.NO_LANGUAGE, "keyboard", "KeyboardLayoutSet="
+                    + SubtypeLocaleUtils.EMOJI + ","
+                    + Constants.Subtype.ExtraValue.EMOJI_CAPABLE,
             false /* isAuxiliary */, false /* overridesImplicitlyEnabledSubtype */);
 
     static final class NeedsToDisplayLanguage {
@@ -271,4 +284,17 @@
                 + DUMMY_NO_LANGUAGE_SUBTYPE);
         return DUMMY_NO_LANGUAGE_SUBTYPE;
     }
+
+    public InputMethodSubtype getEmojiSubtype() {
+        if (mEmojiSubtype == null) {
+            mEmojiSubtype = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
+                    SubtypeLocaleUtils.NO_LANGUAGE, SubtypeLocaleUtils.EMOJI);
+        }
+        if (mEmojiSubtype != null) {
+            return mEmojiSubtype;
+        }
+        Log.w(TAG, "Can't find Emoji subtype");
+        Log.w(TAG, "No input method subtype found; return dummy subtype: " + DUMMY_EMOJI_SUBTYPE);
+        return DUMMY_EMOJI_SUBTYPE;
+    }
 }
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index c2fdcb5..6c18c94 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -24,6 +24,7 @@
 import com.android.inputmethod.annotations.UsedForTesting;
 import com.android.inputmethod.keyboard.ProximityInfo;
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
+import com.android.inputmethod.latin.personalization.PersonalizationDictionary;
 import com.android.inputmethod.latin.personalization.PersonalizationPredictionDictionary;
 import com.android.inputmethod.latin.personalization.UserHistoryPredictionDictionary;
 import com.android.inputmethod.latin.settings.Settings;
@@ -47,8 +48,9 @@
 
     // Session id for
     // {@link #getSuggestedWords(WordComposer,String,ProximityInfo,boolean,int)}.
+    // We are sharing the same ID between typing and gesture to save RAM footprint.
     public static final int SESSION_TYPING = 0;
-    public static final int SESSION_GESTURE = 1;
+    public static final int SESSION_GESTURE = 0;
 
     // TODO: rename this to CORRECTION_OFF
     public static final int CORRECTION_NONE = 0;
@@ -107,7 +109,7 @@
     }
 
     private void addOrReplaceDictionaryInternal(final String key, final Dictionary dict) {
-        if (mOnlyDictionarySetForDebug != null && mOnlyDictionarySetForDebug.contains(key)) {
+        if (mOnlyDictionarySetForDebug != null && !mOnlyDictionarySetForDebug.contains(key)) {
             Log.w(TAG, "Ignore add " + key + " dictionary for debug.");
             return;
         }
@@ -200,28 +202,41 @@
                 personalizationPredictionDictionary);
     }
 
+    public void setPersonalizationDictionary(
+            final PersonalizationDictionary personalizationDictionary) {
+        addOrReplaceDictionaryInternal(Dictionary.TYPE_PERSONALIZATION,
+                personalizationDictionary);
+    }
+
     public void setAutoCorrectionThreshold(float threshold) {
         mAutoCorrectionThreshold = threshold;
     }
 
-    public SuggestedWords getSuggestedWords(final WordComposer wordComposer,
+    public interface OnGetSuggestedWordsCallback {
+        public void onGetSuggestedWords(final SuggestedWords suggestedWords);
+    }
+
+    public void getSuggestedWords(final WordComposer wordComposer,
             final String prevWordForBigram, final ProximityInfo proximityInfo,
             final boolean blockOffensiveWords, final boolean isCorrectionEnabled,
-            final int sessionId) {
+            final int[] additionalFeaturesOptions, final int sessionId,
+            final OnGetSuggestedWordsCallback callback) {
         LatinImeLogger.onStartSuggestion(prevWordForBigram);
         if (wordComposer.isBatchMode()) {
-            return getSuggestedWordsForBatchInput(
-                    wordComposer, prevWordForBigram, proximityInfo, blockOffensiveWords, sessionId);
+            getSuggestedWordsForBatchInput(wordComposer, prevWordForBigram, proximityInfo,
+                    blockOffensiveWords, additionalFeaturesOptions, sessionId, callback);
         } else {
-            return getSuggestedWordsForTypingInput(wordComposer, prevWordForBigram, proximityInfo,
-                    blockOffensiveWords, isCorrectionEnabled);
+            getSuggestedWordsForTypingInput(wordComposer, prevWordForBigram, proximityInfo,
+                    blockOffensiveWords, isCorrectionEnabled, additionalFeaturesOptions, callback);
         }
     }
 
-    // Retrieves suggestions for the typing input.
-    private SuggestedWords getSuggestedWordsForTypingInput(final WordComposer wordComposer,
+    // Retrieves suggestions for the typing input
+    // and calls the callback function with the suggestions.
+    private void getSuggestedWordsForTypingInput(final WordComposer wordComposer,
             final String prevWordForBigram, final ProximityInfo proximityInfo,
-            final boolean blockOffensiveWords, final boolean isCorrectionEnabled) {
+            final boolean blockOffensiveWords, final boolean isCorrectionEnabled,
+            final int[] additionalFeaturesOptions, final OnGetSuggestedWordsCallback callback) {
         final int trailingSingleQuotesCount = wordComposer.trailingSingleQuotesCount();
         final BoundedTreeSet suggestionsSet = new BoundedTreeSet(sSuggestedWordInfoComparator,
                 MAX_SUGGESTIONS);
@@ -244,8 +259,9 @@
 
         for (final String key : mDictionaries.keySet()) {
             final Dictionary dictionary = mDictionaries.get(key);
-            suggestionsSet.addAll(dictionary.getSuggestions(
-                    wordComposerForLookup, prevWordForBigram, proximityInfo, blockOffensiveWords));
+            suggestionsSet.addAll(dictionary.getSuggestions(wordComposerForLookup,
+                    prevWordForBigram, proximityInfo, blockOffensiveWords,
+                    additionalFeaturesOptions));
         }
 
         final String whitelistedWord;
@@ -303,13 +319,16 @@
 
         for (int i = 0; i < suggestionsCount; ++i) {
             final SuggestedWordInfo wordInfo = suggestionsContainer.get(i);
-            LatinImeLogger.onAddSuggestedWord(wordInfo.mWord.toString(), wordInfo.mSourceDict);
+            LatinImeLogger.onAddSuggestedWord(wordInfo.mWord.toString(),
+                    wordInfo.mSourceDict.mDictType);
         }
 
         if (!TextUtils.isEmpty(typedWord)) {
             suggestionsContainer.add(0, new SuggestedWordInfo(typedWord,
                     SuggestedWordInfo.MAX_SCORE, SuggestedWordInfo.KIND_TYPED,
-                    Dictionary.TYPE_USER_TYPED));
+                    Dictionary.DICTIONARY_USER_TYPED,
+                    SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */,
+                    SuggestedWordInfo.NOT_A_CONFIDENCE /* autoCommitFirstWordConfidence */));
         }
         SuggestedWordInfo.removeDups(suggestionsContainer);
 
@@ -320,7 +339,7 @@
             suggestionsList = suggestionsContainer;
         }
 
-        return new SuggestedWords(suggestionsList,
+        callback.onGetSuggestedWords(new SuggestedWords(suggestionsList,
                 // TODO: this first argument is lying. If this is a whitelisted word which is an
                 // actual word, it says typedWordValid = false, which looks wrong. We should either
                 // rename the attribute or change the value.
@@ -328,31 +347,28 @@
                 hasAutoCorrection, /* willAutoCorrect */
                 false /* isPunctuationSuggestions */,
                 false /* isObsoleteSuggestions */,
-                !wordComposer.isComposingWord() /* isPrediction */);
+                !wordComposer.isComposingWord() /* isPrediction */));
     }
 
-    // Retrieves suggestions for the batch input.
-    private SuggestedWords getSuggestedWordsForBatchInput(final WordComposer wordComposer,
+    // Retrieves suggestions for the batch input
+    // and calls the callback function with the suggestions.
+    private void getSuggestedWordsForBatchInput(final WordComposer wordComposer,
             final String prevWordForBigram, final ProximityInfo proximityInfo,
-            final boolean blockOffensiveWords, final int sessionId) {
+            final boolean blockOffensiveWords, final int[] additionalFeaturesOptions,
+            final int sessionId, final OnGetSuggestedWordsCallback callback) {
         final BoundedTreeSet suggestionsSet = new BoundedTreeSet(sSuggestedWordInfoComparator,
                 MAX_SUGGESTIONS);
 
         // 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, sessionId));
+                    prevWordForBigram, proximityInfo, blockOffensiveWords,
+                    additionalFeaturesOptions, sessionId));
         }
 
         for (SuggestedWordInfo wordInfo : suggestionsSet) {
-            LatinImeLogger.onAddSuggestedWord(wordInfo.mWord, wordInfo.mSourceDict);
+            LatinImeLogger.onAddSuggestedWord(wordInfo.mWord, wordInfo.mSourceDict.mDictType);
         }
 
         final ArrayList<SuggestedWordInfo> suggestionsContainer =
@@ -387,12 +403,12 @@
 
         // In the batch input mode, the most relevant suggested word should act as a "typed word"
         // (typedWordValid=true), not as an "auto correct word" (willAutoCorrect=false).
-        return new SuggestedWords(suggestionsContainer,
+        callback.onGetSuggestedWords(new SuggestedWords(suggestionsContainer,
                 true /* typedWordValid */,
                 false /* willAutoCorrect */,
                 false /* isPunctuationSuggestions */,
                 false /* isObsoleteSuggestions */,
-                false /* isPrediction */);
+                false /* isPrediction */));
     }
 
     private static ArrayList<SuggestedWordInfo> getSuggestionsInfoListWithDebugInfo(
@@ -438,7 +454,7 @@
     private static final SuggestedWordInfoComparator sSuggestedWordInfoComparator =
             new SuggestedWordInfoComparator();
 
-    private static SuggestedWordInfo getTransformedSuggestedWordInfo(
+    /* package for test */ static SuggestedWordInfo getTransformedSuggestedWordInfo(
             final SuggestedWordInfo wordInfo, final Locale locale, final boolean isAllUpperCase,
             final boolean isFirstCharCapitalized, final int trailingSingleQuotesCount) {
         final StringBuilder sb = new StringBuilder(wordInfo.mWord.length());
@@ -449,11 +465,17 @@
         } else {
             sb.append(wordInfo.mWord);
         }
-        for (int i = trailingSingleQuotesCount - 1; i >= 0; --i) {
+        // Appending quotes is here to help people quote words. However, it's not helpful
+        // when they type words with quotes toward the end like "it's" or "didn't", where
+        // it's more likely the user missed the last character (or didn't type it yet).
+        final int quotesToAppend = trailingSingleQuotesCount
+                - (-1 == wordInfo.mWord.indexOf(Constants.CODE_SINGLE_QUOTE) ? 0 : 1);
+        for (int i = quotesToAppend - 1; i >= 0; --i) {
             sb.appendCodePoint(Constants.CODE_SINGLE_QUOTE);
         }
         return new SuggestedWordInfo(sb.toString(), wordInfo.mScore, wordInfo.mKind,
-                wordInfo.mSourceDict);
+                wordInfo.mSourceDict, wordInfo.mIndexOfTouchPointOfSecondWord,
+                SuggestedWordInfo.NOT_A_CONFIDENCE /* autoCommitFirstWordConfidence */);
     }
 
     public void close() {
diff --git a/java/src/com/android/inputmethod/latin/SuggestedWords.java b/java/src/com/android/inputmethod/latin/SuggestedWords.java
index 22beaef..fed4cdb 100644
--- a/java/src/com/android/inputmethod/latin/SuggestedWords.java
+++ b/java/src/com/android/inputmethod/latin/SuggestedWords.java
@@ -113,7 +113,9 @@
             if (null == text) continue;
             final SuggestedWordInfo suggestedWordInfo = new SuggestedWordInfo(text.toString(),
                     SuggestedWordInfo.MAX_SCORE, SuggestedWordInfo.KIND_APP_DEFINED,
-                    Dictionary.TYPE_APPLICATION_DEFINED);
+                    Dictionary.DICTIONARY_APPLICATION_DEFINED,
+                    SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */,
+                    SuggestedWordInfo.NOT_A_CONFIDENCE /* autoCommitFirstWordConfidence */);
             result.add(suggestedWordInfo);
         }
         return result;
@@ -126,7 +128,9 @@
         final ArrayList<SuggestedWordInfo> suggestionsList = CollectionUtils.newArrayList();
         final HashSet<String> alreadySeen = CollectionUtils.newHashSet();
         suggestionsList.add(new SuggestedWordInfo(typedWord, SuggestedWordInfo.MAX_SCORE,
-                SuggestedWordInfo.KIND_TYPED, Dictionary.TYPE_USER_TYPED));
+                SuggestedWordInfo.KIND_TYPED, Dictionary.DICTIONARY_USER_TYPED,
+                SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */,
+                SuggestedWordInfo.NOT_A_CONFIDENCE /* autoCommitFirstWordConfidence */));
         alreadySeen.add(typedWord.toString());
         final int previousSize = previousSuggestions.size();
         for (int index = 1; index < previousSize; index++) {
@@ -141,7 +145,15 @@
         return suggestionsList;
     }
 
+    public SuggestedWordInfo getAutoCommitCandidate() {
+        if (mSuggestedWordInfoList.size() <= 0) return null;
+        final SuggestedWordInfo candidate = mSuggestedWordInfoList.get(0);
+        return candidate.isEligibleForAutoCommit() ? candidate : null;
+    }
+
     public static final class SuggestedWordInfo {
+        public static final int NOT_AN_INDEX = -1;
+        public static final int NOT_A_CONFIDENCE = -1;
         public static final int MAX_SCORE = Integer.MAX_VALUE;
         public static final int KIND_MASK_KIND = 0xFF; // Mask to get only the kind
         public static final int KIND_TYPED = 0; // What user typed
@@ -166,18 +178,40 @@
         public final int mScore;
         public final int mKind; // one of the KIND_* constants above
         public final int mCodePointCount;
-        public final String mSourceDict;
+        public final Dictionary mSourceDict;
+        // For auto-commit. This keeps track of the index inside the touch coordinates array
+        // passed to native code to get suggestions for a gesture that corresponds to the first
+        // letter of the second word.
+        public final int mIndexOfTouchPointOfSecondWord;
+        // For auto-commit. This is a measure of how confident we are that we can commit the
+        // first word of this suggestion.
+        public final int mAutoCommitFirstWordConfidence;
         private String mDebugString = "";
 
+        /**
+         * Create a new suggested word info.
+         * @param word The string to suggest.
+         * @param score A measure of how likely this suggestion is.
+         * @param kind The kind of suggestion, as one of the above KIND_* constants.
+         * @param sourceDict What instance of Dictionary produced this suggestion.
+         * @param indexOfTouchPointOfSecondWord See mIndexOfTouchPointOfSecondWord.
+         * @param autoCommitFirstWordConfidence See mAutoCommitFirstWordConfidence.
+         */
         public SuggestedWordInfo(final String word, final int score, final int kind,
-                final String sourceDict) {
+                final Dictionary sourceDict, final int indexOfTouchPointOfSecondWord,
+                final int autoCommitFirstWordConfidence) {
             mWord = word;
             mScore = score;
             mKind = kind;
             mSourceDict = sourceDict;
             mCodePointCount = StringUtils.codePointCount(mWord);
+            mIndexOfTouchPointOfSecondWord = indexOfTouchPointOfSecondWord;
+            mAutoCommitFirstWordConfidence = autoCommitFirstWordConfidence;
         }
 
+        public boolean isEligibleForAutoCommit() {
+            return (KIND_CORRECTION == mKind && NOT_AN_INDEX != mIndexOfTouchPointOfSecondWord);
+        }
 
         public void setDebugString(final String str) {
             if (null == str) throw new NullPointerException("Debug info is null");
@@ -242,4 +276,24 @@
                 false /* willAutoCorrect */, mIsPunctuationSuggestions, mIsObsoleteSuggestions,
                 mIsPrediction);
     }
+
+    // Creates a new SuggestedWordInfo from the currently suggested words that removes all but the
+    // last word of all suggestions, separated by a space. This is necessary because when we commit
+    // a multiple-word suggestion, the IME only retains the last word as the composing word, and
+    // we should only suggest replacements for this last word.
+    // TODO: make this work with languages without spaces.
+    public SuggestedWords getSuggestedWordsForLastWordOfPhraseGesture() {
+        final ArrayList<SuggestedWordInfo> newSuggestions = CollectionUtils.newArrayList();
+        for (int i = 0; i < mSuggestedWordInfoList.size(); ++i) {
+            final SuggestedWordInfo info = mSuggestedWordInfoList.get(i);
+            final int indexOfLastSpace = info.mWord.lastIndexOf(Constants.CODE_SPACE) + 1;
+            final String lastWord = info.mWord.substring(indexOfLastSpace);
+            newSuggestions.add(new SuggestedWordInfo(lastWord, info.mScore, info.mKind,
+                    info.mSourceDict, SuggestedWordInfo.NOT_AN_INDEX,
+                    SuggestedWordInfo.NOT_A_CONFIDENCE));
+        }
+        return new SuggestedWords(newSuggestions, mTypedWordValid,
+                mWillAutoCorrect, mIsPunctuationSuggestions, mIsObsoleteSuggestions,
+                mIsPrediction);
+    }
 }
diff --git a/java/src/com/android/inputmethod/latin/SynchronouslyLoadedContactsBinaryDictionary.java b/java/src/com/android/inputmethod/latin/SynchronouslyLoadedContactsBinaryDictionary.java
index 92f96c0..3213c92 100644
--- a/java/src/com/android/inputmethod/latin/SynchronouslyLoadedContactsBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/SynchronouslyLoadedContactsBinaryDictionary.java
@@ -34,14 +34,15 @@
     @Override
     public synchronized ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer codes,
             final String prevWordForBigrams, final ProximityInfo proximityInfo,
-            final boolean blockOffensiveWords) {
-        syncReloadDictionaryIfRequired();
-        return super.getSuggestions(codes, prevWordForBigrams, proximityInfo, blockOffensiveWords);
+            final boolean blockOffensiveWords, final int[] additionalFeaturesOptions) {
+        reloadDictionaryIfRequired();
+        return super.getSuggestions(codes, prevWordForBigrams, proximityInfo, blockOffensiveWords,
+                additionalFeaturesOptions);
     }
 
     @Override
     public synchronized boolean isValidWord(final String word) {
-        syncReloadDictionaryIfRequired();
+        reloadDictionaryIfRequired();
         return isValidWordInner(word);
     }
 
diff --git a/java/src/com/android/inputmethod/latin/SynchronouslyLoadedUserBinaryDictionary.java b/java/src/com/android/inputmethod/latin/SynchronouslyLoadedUserBinaryDictionary.java
index 33fe896..6405b5e 100644
--- a/java/src/com/android/inputmethod/latin/SynchronouslyLoadedUserBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/SynchronouslyLoadedUserBinaryDictionary.java
@@ -37,14 +37,15 @@
     @Override
     public synchronized ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer codes,
             final String prevWordForBigrams, final ProximityInfo proximityInfo,
-            final boolean blockOffensiveWords) {
-        syncReloadDictionaryIfRequired();
-        return super.getSuggestions(codes, prevWordForBigrams, proximityInfo, blockOffensiveWords);
+            final boolean blockOffensiveWords, final int[] additionalFeaturesOptions) {
+        reloadDictionaryIfRequired();
+        return super.getSuggestions(codes, prevWordForBigrams, proximityInfo, blockOffensiveWords,
+                additionalFeaturesOptions);
     }
 
     @Override
     public synchronized boolean isValidWord(final String word) {
-        syncReloadDictionaryIfRequired();
+        reloadDictionaryIfRequired();
         return isValidWordInner(word);
     }
 }
diff --git a/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java b/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
index ed6fefa..864a173 100644
--- a/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
@@ -22,10 +22,12 @@
 import android.content.Context;
 import android.database.ContentObserver;
 import android.database.Cursor;
+import android.database.sqlite.SQLiteException;
 import android.net.Uri;
 import android.os.Build;
 import android.provider.UserDictionary.Words;
 import android.text.TextUtils;
+import android.util.Log;
 
 import com.android.inputmethod.compat.UserDictionaryCompatUtils;
 import com.android.inputmethod.latin.utils.LocaleUtils;
@@ -39,6 +41,7 @@
  * dictionary file to use it from native code.
  */
 public class UserBinaryDictionary extends ExpandableBinaryDictionary {
+    private static final String TAG = ExpandableBinaryDictionary.class.getSimpleName();
 
     // The user dictionary provider uses an empty string to mean "all languages".
     private static final String USER_DICTIONARY_ALL_LANGUAGES = "";
@@ -75,7 +78,8 @@
 
     public UserBinaryDictionary(final Context context, final String locale,
             final boolean alsoUseMoreRestrictiveLocales) {
-        super(context, getFilenameWithLocale(NAME, locale), Dictionary.TYPE_USER);
+        super(context, getFilenameWithLocale(NAME, locale), Dictionary.TYPE_USER,
+                false /* isUpdatable */);
         if (null == locale) throw new NullPointerException(); // Catch the error earlier
         if (SubtypeLocaleUtils.NO_LANGUAGE.equals(locale)) {
             // If we don't have a locale, insert into the "all locales" user dictionary.
@@ -102,14 +106,6 @@
             @Override
             public void onChange(final boolean self, final Uri uri) {
                 setRequiresReload(true);
-                // We want to report back to Latin IME in case the user just entered the word.
-                // If the user changed the word in the dialog box, then we want to replace
-                // what was entered in the text field.
-                if (null == uri || !(context instanceof LatinIME)) return;
-                final long changedRowId = ContentUris.parseId(uri);
-                if (-1 == changedRowId) return; // Unknown content... Not sure why we're here
-                final String changedWord = getChangedWordForUri(uri);
-                ((LatinIME)context).onWordAddedToUserDictionary(changedWord);
             }
         };
         cres.registerContentObserver(Words.CONTENT_URI, true, mObserver);
@@ -117,19 +113,6 @@
         loadDictionary();
     }
 
-    private String getChangedWordForUri(final Uri uri) {
-        final Cursor cursor = mContext.getContentResolver().query(uri,
-                PROJECTION_QUERY, null, null, null);
-        if (cursor == null) return null;
-        try {
-            if (!cursor.moveToFirst()) return null;
-            final int indexWord = cursor.getColumnIndex(Words.WORD);
-            return cursor.getString(indexWord);
-        } finally {
-            cursor.close();
-        }
-    }
-
     @Override
     public synchronized void close() {
         if (mObserver != null) {
@@ -188,12 +171,19 @@
         } else {
             requestArguments = localeElements;
         }
-        final Cursor cursor = mContext.getContentResolver().query(
-                Words.CONTENT_URI, PROJECTION_QUERY, request.toString(), requestArguments, null);
+        Cursor cursor = null;
         try {
+            cursor = mContext.getContentResolver().query(
+                Words.CONTENT_URI, PROJECTION_QUERY, request.toString(), requestArguments, null);
             addWords(cursor);
+        } catch (final SQLiteException e) {
+            Log.e(TAG, "SQLiteException in the remote User dictionary process.", e);
         } finally {
-            if (null != cursor) cursor.close();
+            try {
+                if (null != cursor) cursor.close();
+            } catch (final SQLiteException e) {
+                Log.e(TAG, "SQLiteException in the remote User dictionary process.", e);
+            }
         }
     }
 
diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java
index a09ca60..039dadc 100644
--- a/java/src/com/android/inputmethod/latin/WordComposer.java
+++ b/java/src/com/android/inputmethod/latin/WordComposer.java
@@ -272,8 +272,8 @@
         final int x, y;
         final Key key;
         if (keyboard != null && (key = keyboard.getKey(codePoint)) != null) {
-            x = key.mX + key.mWidth / 2;
-            y = key.mY + key.mHeight / 2;
+            x = key.getX() + key.getWidth() / 2;
+            y = key.getY() + key.getHeight() / 2;
         } else {
             x = Constants.NOT_A_COORDINATE;
             y = Constants.NOT_A_COORDINATE;
diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderUtils.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderUtils.java
new file mode 100644
index 0000000..665c7a2
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderUtils.java
@@ -0,0 +1,624 @@
+/*
+ * 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.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.PtNodeArray;
+import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.TreeMap;
+
+/**
+ * Decodes binary files for a FusionDictionary.
+ *
+ * All the methods in this class are static.
+ *
+ * TODO: Remove calls from classes except Ver3DictDecoder
+ * TODO: Move this file to makedict/internal.
+ * TODO: Rename this class to DictDecoderUtils.
+ */
+public final class BinaryDictDecoderUtils {
+
+    private static final boolean DBG = MakedictLog.DBG;
+
+    private BinaryDictDecoderUtils() {
+        // This utility class is not publicly instantiable.
+    }
+
+    private static final int MAX_JUMPS = 12;
+
+    @UsedForTesting
+    public interface DictBuffer {
+        public int readUnsignedByte();
+        public int readUnsignedShort();
+        public int readUnsignedInt24();
+        public int readInt();
+        public int position();
+        public void position(int newPosition);
+        public void put(final byte b);
+        public int limit();
+        @UsedForTesting
+        public int capacity();
+    }
+
+    public static final class ByteBufferDictBuffer implements DictBuffer {
+        private ByteBuffer mBuffer;
+
+        public ByteBufferDictBuffer(final ByteBuffer buffer) {
+            mBuffer = buffer;
+        }
+
+        @Override
+        public int readUnsignedByte() {
+            return mBuffer.get() & 0xFF;
+        }
+
+        @Override
+        public int readUnsignedShort() {
+            return mBuffer.getShort() & 0xFFFF;
+        }
+
+        @Override
+        public int readUnsignedInt24() {
+            final int retval = readUnsignedByte();
+            return (retval << 16) + readUnsignedShort();
+        }
+
+        @Override
+        public int readInt() {
+            return mBuffer.getInt();
+        }
+
+        @Override
+        public int position() {
+            return mBuffer.position();
+        }
+
+        @Override
+        public void position(int newPos) {
+            mBuffer.position(newPos);
+        }
+
+        @Override
+        public void put(final byte b) {
+            mBuffer.put(b);
+        }
+
+        @Override
+        public int limit() {
+            return mBuffer.limit();
+        }
+
+        @Override
+        public int capacity() {
+            return mBuffer.capacity();
+        }
+    }
+
+    /**
+     * A class grouping utility function for our specific character encoding.
+     */
+    static final class CharEncoding {
+        private static final int MINIMAL_ONE_BYTE_CHARACTER_VALUE = 0x20;
+        private static final int MAXIMAL_ONE_BYTE_CHARACTER_VALUE = 0xFF;
+
+        /**
+         * Helper method to find out whether this code fits on one byte
+         */
+        private static boolean fitsOnOneByte(final int character) {
+            return character >= MINIMAL_ONE_BYTE_CHARACTER_VALUE
+                    && character <= MAXIMAL_ONE_BYTE_CHARACTER_VALUE;
+        }
+
+        /**
+         * Compute the size of a character given its character code.
+         *
+         * Char format is:
+         * 1 byte = bbbbbbbb match
+         * case 000xxxxx: xxxxx << 16 + next byte << 8 + next byte
+         * else: if 00011111 (= 0x1F) : this is the terminator. This is a relevant choice because
+         *       unicode code points range from 0 to 0x10FFFF, so any 3-byte value starting with
+         *       00011111 would be outside unicode.
+         * else: iso-latin-1 code
+         * This allows for the whole unicode range to be encoded, including chars outside of
+         * the BMP. Also everything in the iso-latin-1 charset is only 1 byte, except control
+         * characters which should never happen anyway (and still work, but take 3 bytes).
+         *
+         * @param character the character code.
+         * @return the size in binary encoded-form, either 1 or 3 bytes.
+         */
+        static int getCharSize(final int character) {
+            // See char encoding in FusionDictionary.java
+            if (fitsOnOneByte(character)) return 1;
+            if (FormatSpec.INVALID_CHARACTER == character) return 1;
+            return 3;
+        }
+
+        /**
+         * Compute the byte size of a character array.
+         */
+        static int getCharArraySize(final int[] chars) {
+            int size = 0;
+            for (int character : chars) size += getCharSize(character);
+            return size;
+        }
+
+        /**
+         * Writes a char array to a byte buffer.
+         *
+         * @param codePoints the code point array to write.
+         * @param buffer the byte buffer to write to.
+         * @param index the index in buffer to write the character array to.
+         * @return the index after the last character.
+         */
+        static int writeCharArray(final int[] codePoints, final byte[] buffer, int index) {
+            for (int codePoint : codePoints) {
+                if (1 == getCharSize(codePoint)) {
+                    buffer[index++] = (byte)codePoint;
+                } else {
+                    buffer[index++] = (byte)(0xFF & (codePoint >> 16));
+                    buffer[index++] = (byte)(0xFF & (codePoint >> 8));
+                    buffer[index++] = (byte)(0xFF & codePoint);
+                }
+            }
+            return index;
+        }
+
+        /**
+         * Writes a string with our character format to a byte buffer.
+         *
+         * This will also write the terminator byte.
+         *
+         * @param buffer the byte buffer to write to.
+         * @param origin the offset to write from.
+         * @param word the string to write.
+         * @return the size written, in bytes.
+         */
+        static int writeString(final byte[] buffer, final int origin,
+                final String word) {
+            final int length = word.length();
+            int index = origin;
+            for (int i = 0; i < length; i = word.offsetByCodePoints(i, 1)) {
+                final int codePoint = word.codePointAt(i);
+                if (1 == getCharSize(codePoint)) {
+                    buffer[index++] = (byte)codePoint;
+                } else {
+                    buffer[index++] = (byte)(0xFF & (codePoint >> 16));
+                    buffer[index++] = (byte)(0xFF & (codePoint >> 8));
+                    buffer[index++] = (byte)(0xFF & codePoint);
+                }
+            }
+            buffer[index++] = FormatSpec.PTNODE_CHARACTERS_TERMINATOR;
+            return index - origin;
+        }
+
+        /**
+         * Writes a string with our character format to a ByteArrayOutputStream.
+         *
+         * This will also write the terminator byte.
+         *
+         * @param buffer the ByteArrayOutputStream to write to.
+         * @param word the string to write.
+         */
+        static void writeString(final ByteArrayOutputStream buffer, final String word) {
+            final int length = word.length();
+            for (int i = 0; i < length; i = word.offsetByCodePoints(i, 1)) {
+                final int codePoint = word.codePointAt(i);
+                if (1 == getCharSize(codePoint)) {
+                    buffer.write((byte) codePoint);
+                } else {
+                    buffer.write((byte) (0xFF & (codePoint >> 16)));
+                    buffer.write((byte) (0xFF & (codePoint >> 8)));
+                    buffer.write((byte) (0xFF & codePoint));
+                }
+            }
+            buffer.write(FormatSpec.PTNODE_CHARACTERS_TERMINATOR);
+        }
+
+        /**
+         * Reads a string from a DictBuffer. This is the converse of the above method.
+         */
+        static String readString(final DictBuffer dictBuffer) {
+            final StringBuilder s = new StringBuilder();
+            int character = readChar(dictBuffer);
+            while (character != FormatSpec.INVALID_CHARACTER) {
+                s.appendCodePoint(character);
+                character = readChar(dictBuffer);
+            }
+            return s.toString();
+        }
+
+        /**
+         * Reads a character from the buffer.
+         *
+         * This follows the character format documented earlier in this source file.
+         *
+         * @param dictBuffer the buffer, positioned over an encoded character.
+         * @return the character code.
+         */
+        static int readChar(final DictBuffer dictBuffer) {
+            int character = dictBuffer.readUnsignedByte();
+            if (!fitsOnOneByte(character)) {
+                if (FormatSpec.PTNODE_CHARACTERS_TERMINATOR == character) {
+                    return FormatSpec.INVALID_CHARACTER;
+                }
+                character <<= 16;
+                character += dictBuffer.readUnsignedShort();
+            }
+            return character;
+        }
+    }
+
+    // Input methods: Read a binary dictionary to memory.
+    // readDictionaryBinary is the public entry point for them.
+
+    static int readSInt24(final DictBuffer dictBuffer) {
+        final int retval = dictBuffer.readUnsignedInt24();
+        final int sign = ((retval & FormatSpec.MSB24) != 0) ? -1 : 1;
+        return sign * (retval & FormatSpec.SINT24_MAX);
+    }
+
+    static int readChildrenAddress(final DictBuffer dictBuffer,
+            final int optionFlags, final FormatOptions options) {
+        if (options.mSupportsDynamicUpdate) {
+            final int address = dictBuffer.readUnsignedInt24();
+            if (address == 0) return FormatSpec.NO_CHILDREN_ADDRESS;
+            if ((address & FormatSpec.MSB24) != 0) {
+                return -(address & FormatSpec.SINT24_MAX);
+            } else {
+                return address;
+            }
+        }
+        int address;
+        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;
+        }
+    }
+
+    static int readParentAddress(final DictBuffer dictBuffer,
+            final FormatOptions formatOptions) {
+        if (BinaryDictIOUtils.supportsDynamicUpdate(formatOptions)) {
+            final int parentAddress = dictBuffer.readUnsignedInt24();
+            final int sign = ((parentAddress & FormatSpec.MSB24) != 0) ? -1 : 1;
+            return sign * (parentAddress & FormatSpec.SINT24_MAX);
+        } else {
+            return FormatSpec.NO_PARENT_ADDRESS;
+        }
+    }
+
+    /**
+     * Reads and returns the PtNode count out of a buffer and forwards the pointer.
+     */
+    /* package */ static int readPtNodeCount(final DictBuffer dictBuffer) {
+        final int msb = dictBuffer.readUnsignedByte();
+        if (FormatSpec.MAX_PTNODES_FOR_ONE_BYTE_PTNODE_COUNT >= msb) {
+            return msb;
+        } else {
+            return ((FormatSpec.MAX_PTNODES_FOR_ONE_BYTE_PTNODE_COUNT & msb) << 8)
+                    + dictBuffer.readUnsignedByte();
+        }
+    }
+
+    /**
+     * Finds, as a string, the word at the position passed as an argument.
+     *
+     * @param dictDecoder the dict decoder.
+     * @param headerSize the size of the header.
+     * @param pos the position to seek.
+     * @param formatOptions file format options.
+     * @return the word with its frequency, as a weighted string.
+     */
+    /* package for tests */ static WeightedString getWordAtPosition(final DictDecoder dictDecoder,
+            final int headerSize, final int pos, final FormatOptions formatOptions) {
+        final WeightedString result;
+        final int originalPos = dictDecoder.getPosition();
+        dictDecoder.setPosition(pos);
+
+        if (BinaryDictIOUtils.supportsDynamicUpdate(formatOptions)) {
+            result = getWordAtPositionWithParentAddress(dictDecoder, pos, formatOptions);
+        } else {
+            result = getWordAtPositionWithoutParentAddress(dictDecoder, headerSize, pos,
+                    formatOptions);
+        }
+
+        dictDecoder.setPosition(originalPos);
+        return result;
+    }
+
+    @SuppressWarnings("unused")
+    private static WeightedString getWordAtPositionWithParentAddress(final DictDecoder dictDecoder,
+            final int pos, final FormatOptions options) {
+        int currentPos = pos;
+        int frequency = Integer.MIN_VALUE;
+        final StringBuilder builder = new StringBuilder();
+        // the length of the path from the root to the leaf is limited by MAX_WORD_LENGTH
+        for (int count = 0; count < FormatSpec.MAX_WORD_LENGTH; ++count) {
+            PtNodeInfo currentInfo;
+            int loopCounter = 0;
+            do {
+                dictDecoder.setPosition(currentPos);
+                currentInfo = dictDecoder.readPtNode(currentPos, options);
+                if (BinaryDictIOUtils.isMovedPtNode(currentInfo.mFlags, options)) {
+                    currentPos = currentInfo.mParentAddress + currentInfo.mOriginalAddress;
+                }
+                if (DBG && loopCounter++ > MAX_JUMPS) {
+                    MakedictLog.d("Too many jumps - probably a bug");
+                }
+            } while (BinaryDictIOUtils.isMovedPtNode(currentInfo.mFlags, options));
+            if (Integer.MIN_VALUE == frequency) frequency = currentInfo.mFrequency;
+            builder.insert(0,
+                    new String(currentInfo.mCharacters, 0, currentInfo.mCharacters.length));
+            if (currentInfo.mParentAddress == FormatSpec.NO_PARENT_ADDRESS) break;
+            currentPos = currentInfo.mParentAddress + currentInfo.mOriginalAddress;
+        }
+        return new WeightedString(builder.toString(), frequency);
+    }
+
+    private static WeightedString getWordAtPositionWithoutParentAddress(
+            final DictDecoder dictDecoder, final int headerSize, final int pos,
+            final FormatOptions options) {
+        dictDecoder.setPosition(headerSize);
+        final int count = dictDecoder.readPtNodeCount();
+        int groupPos = headerSize + BinaryDictIOUtils.getPtNodeCountSize(count);
+        final StringBuilder builder = new StringBuilder();
+        WeightedString result = null;
+
+        PtNodeInfo last = null;
+        for (int i = count - 1; i >= 0; --i) {
+            PtNodeInfo info = dictDecoder.readPtNode(groupPos, options);
+            groupPos = info.mEndAddress;
+            if (info.mOriginalAddress == pos) {
+                builder.append(new String(info.mCharacters, 0, info.mCharacters.length));
+                result = new WeightedString(builder.toString(), info.mFrequency);
+                break; // and return
+            }
+            if (BinaryDictIOUtils.hasChildrenAddress(info.mChildrenAddress)) {
+                if (info.mChildrenAddress > pos) {
+                    if (null == last) continue;
+                    builder.append(new String(last.mCharacters, 0, last.mCharacters.length));
+                    dictDecoder.setPosition(last.mChildrenAddress);
+                    i = dictDecoder.readPtNodeCount();
+                    groupPos = last.mChildrenAddress + BinaryDictIOUtils.getPtNodeCountSize(i);
+                    last = null;
+                    continue;
+                }
+                last = info;
+            }
+            if (0 == i && BinaryDictIOUtils.hasChildrenAddress(last.mChildrenAddress)) {
+                builder.append(new String(last.mCharacters, 0, last.mCharacters.length));
+                dictDecoder.setPosition(last.mChildrenAddress);
+                i = dictDecoder.readPtNodeCount();
+                groupPos = last.mChildrenAddress + BinaryDictIOUtils.getPtNodeCountSize(i);
+                last = null;
+                continue;
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Reads a single node array from a buffer.
+     *
+     * This methods reads the file at the current position. A node array is fully expected to start
+     * at the current position.
+     * This will recursively read other node arrays into the structure, populating the reverse
+     * maps on the fly and using them to keep track of already read nodes.
+     *
+     * @param dictDecoder the dict decoder, correctly positioned at the start of a node array.
+     * @param headerSize the size, in bytes, of the file header.
+     * @param reverseNodeArrayMap a mapping from addresses to already read node arrays.
+     * @param reversePtNodeMap a mapping from addresses to already read PtNodes.
+     * @param options file format options.
+     * @return the read node array with all his children already read.
+     */
+    private static PtNodeArray readNodeArray(final DictDecoder dictDecoder,
+            final int headerSize, final Map<Integer, PtNodeArray> reverseNodeArrayMap,
+            final Map<Integer, PtNode> reversePtNodeMap, final FormatOptions options)
+            throws IOException {
+        final ArrayList<PtNode> nodeArrayContents = new ArrayList<PtNode>();
+        final int nodeArrayOriginPos = dictDecoder.getPosition();
+
+        do { // Scan the linked-list node.
+            final int nodeArrayHeadPos = dictDecoder.getPosition();
+            final int count = dictDecoder.readPtNodeCount();
+            int groupOffsetPos = nodeArrayHeadPos + BinaryDictIOUtils.getPtNodeCountSize(count);
+            for (int i = count; i > 0; --i) { // Scan the array of PtNode.
+                PtNodeInfo info = dictDecoder.readPtNode(groupOffsetPos, options);
+                if (BinaryDictIOUtils.isMovedPtNode(info.mFlags, options)) continue;
+                ArrayList<WeightedString> shortcutTargets = info.mShortcutTargets;
+                ArrayList<WeightedString> bigrams = null;
+                if (null != info.mBigrams) {
+                    bigrams = new ArrayList<WeightedString>();
+                    for (PendingAttribute bigram : info.mBigrams) {
+                        final WeightedString word = getWordAtPosition(dictDecoder, headerSize,
+                                bigram.mAddress, options);
+                        final int reconstructedFrequency =
+                                BinaryDictIOUtils.reconstructBigramFrequency(word.mFrequency,
+                                        bigram.mFrequency);
+                        bigrams.add(new WeightedString(word.mWord, reconstructedFrequency));
+                    }
+                }
+                if (BinaryDictIOUtils.hasChildrenAddress(info.mChildrenAddress)) {
+                    PtNodeArray children = reverseNodeArrayMap.get(info.mChildrenAddress);
+                    if (null == children) {
+                        final int currentPosition = dictDecoder.getPosition();
+                        dictDecoder.setPosition(info.mChildrenAddress);
+                        children = readNodeArray(dictDecoder, headerSize, reverseNodeArrayMap,
+                                reversePtNodeMap, options);
+                        dictDecoder.setPosition(currentPosition);
+                    }
+                    nodeArrayContents.add(
+                            new PtNode(info.mCharacters, shortcutTargets, bigrams,
+                                    info.mFrequency,
+                                    0 != (info.mFlags & FormatSpec.FLAG_IS_NOT_A_WORD),
+                                    0 != (info.mFlags & FormatSpec.FLAG_IS_BLACKLISTED), children));
+                } else {
+                    nodeArrayContents.add(
+                            new PtNode(info.mCharacters, shortcutTargets, bigrams,
+                                    info.mFrequency,
+                                    0 != (info.mFlags & FormatSpec.FLAG_IS_NOT_A_WORD),
+                                    0 != (info.mFlags & FormatSpec.FLAG_IS_BLACKLISTED)));
+                }
+                groupOffsetPos = info.mEndAddress;
+            }
+
+            // reach the end of the array.
+            if (options.mSupportsDynamicUpdate) {
+                final boolean hasValidForwardLink = dictDecoder.readAndFollowForwardLink();
+                if (!hasValidForwardLink) break;
+            }
+        } while (options.mSupportsDynamicUpdate && dictDecoder.hasNextPtNodeArray());
+
+        final PtNodeArray nodeArray = new PtNodeArray(nodeArrayContents);
+        nodeArray.mCachedAddressBeforeUpdate = nodeArrayOriginPos;
+        nodeArray.mCachedAddressAfterUpdate = nodeArrayOriginPos;
+        reverseNodeArrayMap.put(nodeArray.mCachedAddressAfterUpdate, nodeArray);
+        return nodeArray;
+    }
+
+    /**
+     * Helper function to get the binary format version from the header.
+     * @throws IOException
+     */
+    private static int getFormatVersion(final DictBuffer dictBuffer)
+            throws IOException {
+        final int magic = dictBuffer.readInt();
+        if (FormatSpec.MAGIC_NUMBER == magic) return dictBuffer.readUnsignedShort();
+        return FormatSpec.NOT_A_VERSION_NUMBER;
+    }
+
+    /**
+     * Helper function to get and validate the binary format version.
+     * @throws UnsupportedFormatException
+     * @throws IOException
+     */
+    static int checkFormatVersion(final DictBuffer dictBuffer)
+            throws IOException, UnsupportedFormatException {
+        final int version = getFormatVersion(dictBuffer);
+        if (version < FormatSpec.MINIMUM_SUPPORTED_VERSION
+                || version > FormatSpec.MAXIMUM_SUPPORTED_VERSION) {
+            throw new UnsupportedFormatException("This file has version " + version
+                    + ", but this implementation does not support versions above "
+                    + FormatSpec.MAXIMUM_SUPPORTED_VERSION);
+        }
+        return version;
+    }
+
+    /**
+     * Reads a buffer and returns the memory representation of the dictionary.
+     *
+     * This high-level method takes a buffer and reads its contents, populating a
+     * FusionDictionary structure. The optional dict argument is an existing dictionary to
+     * which words from the buffer should be added. If it is null, a new dictionary is created.
+     *
+     * @param dictDecoder the dict decoder.
+     * @param dict an optional dictionary to add words to, or null.
+     * @return the created (or merged) dictionary.
+     */
+    @UsedForTesting
+    /* package */ static FusionDictionary readDictionaryBinary(final DictDecoder dictDecoder,
+            final FusionDictionary dict) throws IOException, UnsupportedFormatException {
+        // Read header
+        final FileHeader fileHeader = dictDecoder.readHeader();
+
+        Map<Integer, PtNodeArray> reverseNodeArrayMapping = new TreeMap<Integer, PtNodeArray>();
+        Map<Integer, PtNode> reversePtNodeMapping = new TreeMap<Integer, PtNode>();
+        final PtNodeArray root = readNodeArray(dictDecoder, fileHeader.mHeaderSize,
+                reverseNodeArrayMapping, reversePtNodeMapping, fileHeader.mFormatOptions);
+
+        FusionDictionary newDict = new FusionDictionary(root, fileHeader.mDictionaryOptions);
+        if (null != dict) {
+            for (final Word w : dict) {
+                if (w.mIsBlacklistEntry) {
+                    newDict.addBlacklistEntry(w.mWord, w.mShortcutTargets, w.mIsNotAWord);
+                } else {
+                    newDict.add(w.mWord, w.mFrequency, w.mShortcutTargets, w.mIsNotAWord);
+                }
+            }
+            for (final Word w : dict) {
+                // By construction a binary dictionary may not have bigrams pointing to
+                // words that are not also registered as unigrams so we don't have to avoid
+                // them explicitly here.
+                for (final WeightedString bigram : w.mBigrams) {
+                    newDict.setBigram(w.mWord, bigram.mWord, bigram.mFrequency);
+                }
+            }
+        }
+
+        return newDict;
+    }
+
+    /**
+     * Helper method to pass a file name instead of a File object to isBinaryDictionary.
+     */
+    public static boolean isBinaryDictionary(final String filename) {
+        final File file = new File(filename);
+        return isBinaryDictionary(file);
+    }
+
+    /**
+     * Basic test to find out whether the file is a binary dictionary or not.
+     *
+     * Concretely this only tests the magic number.
+     *
+     * @param file The file to test.
+     * @return true if it's a binary dictionary, false otherwise
+     */
+    public static boolean isBinaryDictionary(final File file) {
+        FileInputStream inStream = null;
+        try {
+            inStream = new FileInputStream(file);
+            final ByteBuffer buffer = inStream.getChannel().map(
+                    FileChannel.MapMode.READ_ONLY, 0, file.length());
+            final int version = getFormatVersion(new ByteBufferDictBuffer(buffer));
+            return (version >= FormatSpec.MINIMUM_SUPPORTED_VERSION
+                    && version <= FormatSpec.MAXIMUM_SUPPORTED_VERSION);
+        } catch (FileNotFoundException e) {
+            return false;
+        } catch (IOException e) {
+            return false;
+        } finally {
+            if (inStream != null) {
+                try {
+                    inStream.close();
+                } catch (IOException e) {
+                    // do nothing
+                }
+            }
+        }
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictEncoderUtils.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictEncoderUtils.java
new file mode 100644
index 0000000..4dba8e5
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictEncoderUtils.java
@@ -0,0 +1,936 @@
+/*
+ * 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.latin.makedict.BinaryDictDecoderUtils.CharEncoding;
+import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions;
+import com.android.inputmethod.latin.makedict.FusionDictionary.PtNode;
+import com.android.inputmethod.latin.makedict.FusionDictionary.DictionaryOptions;
+import com.android.inputmethod.latin.makedict.FusionDictionary.PtNodeArray;
+import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+
+/**
+ * Encodes binary files for a FusionDictionary.
+ *
+ * All the methods in this class are static.
+ *
+ * TODO: Rename this class to DictEncoderUtils.
+ */
+public class BinaryDictEncoderUtils {
+
+    private static final boolean DBG = MakedictLog.DBG;
+
+    private BinaryDictEncoderUtils() {
+        // This utility class is not publicly instantiable.
+    }
+
+    // Arbitrary limit to how much passes we consider address size compression should
+    // terminate in. At the time of this writing, our largest dictionary completes
+    // compression in five passes.
+    // If the number of passes exceeds this number, makedict bails with an exception on
+    // suspicion that a bug might be causing an infinite loop.
+    private static final int MAX_PASSES = 24;
+
+    /**
+     * Compute the binary size of the character array.
+     *
+     * If only one character, this is the size of this character. If many, it's the sum of their
+     * sizes + 1 byte for the terminator.
+     *
+     * @param characters the character array
+     * @return the size of the char array, including the terminator if any
+     */
+    static int getPtNodeCharactersSize(final int[] characters) {
+        int size = CharEncoding.getCharArraySize(characters);
+        if (characters.length > 1) size += FormatSpec.PTNODE_TERMINATOR_SIZE;
+        return size;
+    }
+
+    /**
+     * Compute the binary size of the character array in a PtNode
+     *
+     * If only one character, this is the size of this character. If many, it's the sum of their
+     * sizes + 1 byte for the terminator.
+     *
+     * @param ptNode the PtNode
+     * @return the size of the char array, including the terminator if any
+     */
+    private static int getPtNodeCharactersSize(final PtNode ptNode) {
+        return getPtNodeCharactersSize(ptNode.mChars);
+    }
+
+    /**
+     * Compute the binary size of the PtNode count for a node array.
+     * @param nodeArray the nodeArray
+     * @return the size of the PtNode count, either 1 or 2 bytes.
+     */
+    private static int getPtNodeCountSize(final PtNodeArray nodeArray) {
+        return BinaryDictIOUtils.getPtNodeCountSize(nodeArray.mData.size());
+    }
+
+    /**
+     * Compute the size of a shortcut in bytes.
+     */
+    private static int getShortcutSize(final WeightedString shortcut) {
+        int size = FormatSpec.PTNODE_ATTRIBUTE_FLAGS_SIZE;
+        final String word = shortcut.mWord;
+        final int length = word.length();
+        for (int i = 0; i < length; i = word.offsetByCodePoints(i, 1)) {
+            final int codePoint = word.codePointAt(i);
+            size += CharEncoding.getCharSize(codePoint);
+        }
+        size += FormatSpec.PTNODE_TERMINATOR_SIZE;
+        return size;
+    }
+
+    /**
+     * Compute the size of a shortcut list in bytes.
+     *
+     * This is known in advance and does not change according to position in the file
+     * like address lists do.
+     */
+    static int getShortcutListSize(final ArrayList<WeightedString> shortcutList) {
+        if (null == shortcutList || shortcutList.isEmpty()) return 0;
+        int size = FormatSpec.PTNODE_SHORTCUT_LIST_SIZE_SIZE;
+        for (final WeightedString shortcut : shortcutList) {
+            size += getShortcutSize(shortcut);
+        }
+        return size;
+    }
+
+    /**
+     * Compute the maximum size of a PtNode, assuming 3-byte addresses for everything.
+     *
+     * @param ptNode the PtNode to compute the size of.
+     * @param options file format options.
+     * @return the maximum size of the PtNode.
+     */
+    private static int getPtNodeMaximumSize(final PtNode ptNode, final FormatOptions options) {
+        int size = getNodeHeaderSize(ptNode, options);
+        if (ptNode.isTerminal()) {
+            // If terminal, one byte for the frequency or four bytes for the terminal id.
+            if (options.mHasTerminalId) {
+                size += FormatSpec.PTNODE_TERMINAL_ID_SIZE;
+            } else {
+                size += FormatSpec.PTNODE_FREQUENCY_SIZE;
+            }
+        }
+        size += FormatSpec.PTNODE_MAX_ADDRESS_SIZE; // For children address
+        size += getShortcutListSize(ptNode.mShortcutTargets);
+        if (null != ptNode.mBigrams) {
+            size += (FormatSpec.PTNODE_ATTRIBUTE_FLAGS_SIZE
+                    + FormatSpec.PTNODE_ATTRIBUTE_MAX_ADDRESS_SIZE)
+                    * ptNode.mBigrams.size();
+        }
+        return size;
+    }
+
+    /**
+     * Compute the maximum size of each PtNode of a PtNode array, assuming 3-byte addresses for
+     * everything, and caches it in the `mCachedSize' member of the nodes; deduce the size of
+     * the containing node array, and cache it it its 'mCachedSize' member.
+     *
+     * @param ptNodeArray the node array to compute the maximum size of.
+     * @param options file format options.
+     */
+    private static void calculatePtNodeArrayMaximumSize(final PtNodeArray ptNodeArray,
+            final FormatOptions options) {
+        int size = getPtNodeCountSize(ptNodeArray);
+        for (PtNode node : ptNodeArray.mData) {
+            final int nodeSize = getPtNodeMaximumSize(node, options);
+            node.mCachedSize = nodeSize;
+            size += nodeSize;
+        }
+        if (options.mSupportsDynamicUpdate) {
+            size += FormatSpec.FORWARD_LINK_ADDRESS_SIZE;
+        }
+        ptNodeArray.mCachedSize = size;
+    }
+
+    /**
+     * Compute the size of the header (flag + [parent address] + characters size) of a PtNode.
+     *
+     * @param ptNode the PtNode of which to compute the size of the header
+     * @param options file format options.
+     */
+    private static int getNodeHeaderSize(final PtNode ptNode, final FormatOptions options) {
+        if (BinaryDictIOUtils.supportsDynamicUpdate(options)) {
+            return FormatSpec.PTNODE_FLAGS_SIZE + FormatSpec.PARENT_ADDRESS_SIZE
+                    + getPtNodeCharactersSize(ptNode);
+        } else {
+            return FormatSpec.PTNODE_FLAGS_SIZE + getPtNodeCharactersSize(ptNode);
+        }
+    }
+
+    /**
+     * Compute the size, in bytes, that an address will occupy.
+     *
+     * This can be used either for children addresses (which are always positive) or for
+     * attribute, which may be positive or negative but
+     * store their sign bit separately.
+     *
+     * @param address the address
+     * @return the byte size.
+     */
+    static int getByteSize(final int address) {
+        assert(address <= FormatSpec.UINT24_MAX);
+        if (!BinaryDictIOUtils.hasChildrenAddress(address)) {
+            return 0;
+        } else if (Math.abs(address) <= FormatSpec.UINT8_MAX) {
+            return 1;
+        } else if (Math.abs(address) <= FormatSpec.UINT16_MAX) {
+            return 2;
+        } else {
+            return 3;
+        }
+    }
+
+    static int writeUIntToBuffer(final byte[] buffer, int position, final int value,
+            final int size) {
+        switch(size) {
+            case 4:
+                buffer[position++] = (byte) ((value >> 24) & 0xFF);
+                /* fall through */
+            case 3:
+                buffer[position++] = (byte) ((value >> 16) & 0xFF);
+                /* fall through */
+            case 2:
+                buffer[position++] = (byte) ((value >> 8) & 0xFF);
+                /* fall through */
+            case 1:
+                buffer[position++] = (byte) (value & 0xFF);
+                break;
+            default:
+                /* nop */
+        }
+        return position;
+    }
+
+    // End utility methods
+
+    // This method is responsible for finding a nice ordering of the nodes that favors run-time
+    // cache performance and dictionary size.
+    /* package for tests */ static ArrayList<PtNodeArray> flattenTree(
+            final PtNodeArray rootNodeArray) {
+        final int treeSize = FusionDictionary.countPtNodes(rootNodeArray);
+        MakedictLog.i("Counted nodes : " + treeSize);
+        final ArrayList<PtNodeArray> flatTree = new ArrayList<PtNodeArray>(treeSize);
+        return flattenTreeInner(flatTree, rootNodeArray);
+    }
+
+    private static ArrayList<PtNodeArray> flattenTreeInner(final ArrayList<PtNodeArray> list,
+            final PtNodeArray ptNodeArray) {
+        // Removing the node is necessary if the tails are merged, because we would then
+        // add the same node several times when we only want it once. A number of places in
+        // the code also depends on any node being only once in the list.
+        // Merging tails can only be done if there are no attributes. Searching for attributes
+        // in LatinIME code depends on a total breadth-first ordering, which merging tails
+        // breaks. If there are no attributes, it should be fine (and reduce the file size)
+        // to merge tails, and removing the node from the list would be necessary. However,
+        // we don't merge tails because breaking the breadth-first ordering would result in
+        // extreme overhead at bigram lookup time (it would make the search function O(n) instead
+        // of the current O(log(n)), where n=number of nodes in the dictionary which is pretty
+        // high).
+        // If no nodes are ever merged, we can't have the same node twice in the list, hence
+        // searching for duplicates in unnecessary. It is also very performance consuming,
+        // since `list' is an ArrayList so it's an O(n) operation that runs on all nodes, making
+        // this simple list.remove operation O(n*n) overall. On Android this overhead is very
+        // high.
+        // For future reference, the code to remove duplicate is a simple : list.remove(node);
+        list.add(ptNodeArray);
+        final ArrayList<PtNode> branches = ptNodeArray.mData;
+        final int nodeSize = branches.size();
+        for (PtNode ptNode : branches) {
+            if (null != ptNode.mChildren) flattenTreeInner(list, ptNode.mChildren);
+        }
+        return list;
+    }
+
+    /**
+     * Get the offset from a position inside a current node array to a target node array, during
+     * update.
+     *
+     * If the current node array is before the target node array, the target node array has not
+     * been updated yet, so we should return the offset from the old position of the current node
+     * array to the old position of the target node array. If on the other hand the target is
+     * before the current node array, it already has been updated, so we should return the offset
+     * from the new position in the current node array to the new position in the target node
+     * array.
+     *
+     * @param currentNodeArray node array containing the PtNode where the offset will be written
+     * @param offsetFromStartOfCurrentNodeArray offset, in bytes, from the start of currentNodeArray
+     * @param targetNodeArray the target node array to get the offset to
+     * @return the offset to the target node array
+     */
+    private static int getOffsetToTargetNodeArrayDuringUpdate(final PtNodeArray currentNodeArray,
+            final int offsetFromStartOfCurrentNodeArray, final PtNodeArray targetNodeArray) {
+        final boolean isTargetBeforeCurrent = (targetNodeArray.mCachedAddressBeforeUpdate
+                < currentNodeArray.mCachedAddressBeforeUpdate);
+        if (isTargetBeforeCurrent) {
+            return targetNodeArray.mCachedAddressAfterUpdate
+                    - (currentNodeArray.mCachedAddressAfterUpdate
+                            + offsetFromStartOfCurrentNodeArray);
+        } else {
+            return targetNodeArray.mCachedAddressBeforeUpdate
+                    - (currentNodeArray.mCachedAddressBeforeUpdate
+                            + offsetFromStartOfCurrentNodeArray);
+        }
+    }
+
+    /**
+     * Get the offset from a position inside a current node array to a target PtNode, during
+     * update.
+     *
+     * @param currentNodeArray node array containing the PtNode where the offset will be written
+     * @param offsetFromStartOfCurrentNodeArray offset, in bytes, from the start of currentNodeArray
+     * @param targetPtNode the target PtNode to get the offset to
+     * @return the offset to the target PtNode
+     */
+    // TODO: is there any way to factorize this method with the one above?
+    private static int getOffsetToTargetPtNodeDuringUpdate(final PtNodeArray currentNodeArray,
+            final int offsetFromStartOfCurrentNodeArray, final PtNode targetPtNode) {
+        final int oldOffsetBasePoint = currentNodeArray.mCachedAddressBeforeUpdate
+                + offsetFromStartOfCurrentNodeArray;
+        final boolean isTargetBeforeCurrent = (targetPtNode.mCachedAddressBeforeUpdate
+                < oldOffsetBasePoint);
+        // If the target is before the current node array, then its address has already been
+        // updated. We can use the AfterUpdate member, and compare it to our own member after
+        // update. Otherwise, the AfterUpdate member is not updated yet, so we need to use the
+        // BeforeUpdate member, and of course we have to compare this to our own address before
+        // update.
+        if (isTargetBeforeCurrent) {
+            final int newOffsetBasePoint = currentNodeArray.mCachedAddressAfterUpdate
+                    + offsetFromStartOfCurrentNodeArray;
+            return targetPtNode.mCachedAddressAfterUpdate - newOffsetBasePoint;
+        } else {
+            return targetPtNode.mCachedAddressBeforeUpdate - oldOffsetBasePoint;
+        }
+    }
+
+    /**
+     * Computes the actual node array size, based on the cached addresses of the children nodes.
+     *
+     * Each node array stores its tentative address. During dictionary address computing, these
+     * are not final, but they can be used to compute the node array size (the node array size
+     * depends on the address of the children because the number of bytes necessary to store an
+     * address depends on its numeric value. The return value indicates whether the node array
+     * contents (as in, any of the addresses stored in the cache fields) have changed with
+     * respect to their previous value.
+     *
+     * @param ptNodeArray the node array to compute the size of.
+     * @param dict the dictionary in which the word/attributes are to be found.
+     * @param formatOptions file format options.
+     * @return false if none of the cached addresses inside the node array changed, true otherwise.
+     */
+    private static boolean computeActualPtNodeArraySize(final PtNodeArray ptNodeArray,
+            final FusionDictionary dict, final FormatOptions formatOptions) {
+        boolean changed = false;
+        int size = getPtNodeCountSize(ptNodeArray);
+        for (PtNode ptNode : ptNodeArray.mData) {
+            ptNode.mCachedAddressAfterUpdate = ptNodeArray.mCachedAddressAfterUpdate + size;
+            if (ptNode.mCachedAddressAfterUpdate != ptNode.mCachedAddressBeforeUpdate) {
+                changed = true;
+            }
+            int nodeSize = getNodeHeaderSize(ptNode, formatOptions);
+            if (ptNode.isTerminal()) {
+                if (formatOptions.mHasTerminalId) {
+                    nodeSize += FormatSpec.PTNODE_TERMINAL_ID_SIZE;
+                } else {
+                    nodeSize += FormatSpec.PTNODE_FREQUENCY_SIZE;
+                }
+            }
+            if (formatOptions.mSupportsDynamicUpdate) {
+                nodeSize += FormatSpec.SIGNED_CHILDREN_ADDRESS_SIZE;
+            } else if (null != ptNode.mChildren) {
+                nodeSize += getByteSize(getOffsetToTargetNodeArrayDuringUpdate(ptNodeArray,
+                        nodeSize + size, ptNode.mChildren));
+            }
+            nodeSize += getShortcutListSize(ptNode.mShortcutTargets);
+            if (null != ptNode.mBigrams) {
+                for (WeightedString bigram : ptNode.mBigrams) {
+                    final int offset = getOffsetToTargetPtNodeDuringUpdate(ptNodeArray,
+                            nodeSize + size + FormatSpec.PTNODE_ATTRIBUTE_FLAGS_SIZE,
+                            FusionDictionary.findWordInTree(dict.mRootNodeArray, bigram.mWord));
+                    nodeSize += getByteSize(offset) + FormatSpec.PTNODE_ATTRIBUTE_FLAGS_SIZE;
+                }
+            }
+            ptNode.mCachedSize = nodeSize;
+            size += nodeSize;
+        }
+        if (formatOptions.mSupportsDynamicUpdate) {
+            size += FormatSpec.FORWARD_LINK_ADDRESS_SIZE;
+        }
+        if (ptNodeArray.mCachedSize != size) {
+            ptNodeArray.mCachedSize = size;
+            changed = true;
+        }
+        return changed;
+    }
+
+    /**
+     * Initializes the cached addresses of node arrays and their containing nodes from their size.
+     *
+     * @param flatNodes the list of node arrays.
+     * @param formatOptions file format options.
+     * @return the byte size of the entire stack.
+     */
+    private static int initializePtNodeArraysCachedAddresses(final ArrayList<PtNodeArray> flatNodes,
+            final FormatOptions formatOptions) {
+        int nodeArrayOffset = 0;
+        for (final PtNodeArray nodeArray : flatNodes) {
+            nodeArray.mCachedAddressBeforeUpdate = nodeArrayOffset;
+            int nodeCountSize = getPtNodeCountSize(nodeArray);
+            int nodeffset = 0;
+            for (final PtNode ptNode : nodeArray.mData) {
+                ptNode.mCachedAddressBeforeUpdate = ptNode.mCachedAddressAfterUpdate =
+                        nodeCountSize + nodeArrayOffset + nodeffset;
+                nodeffset += ptNode.mCachedSize;
+            }
+            final int nodeSize = nodeCountSize + nodeffset
+                    + (formatOptions.mSupportsDynamicUpdate
+                            ? FormatSpec.FORWARD_LINK_ADDRESS_SIZE : 0);
+            nodeArrayOffset += nodeArray.mCachedSize;
+        }
+        return nodeArrayOffset;
+    }
+
+    /**
+     * Updates the cached addresses of node arrays after recomputing their new positions.
+     *
+     * @param flatNodes the list of node arrays.
+     */
+    private static void updatePtNodeArraysCachedAddresses(final ArrayList<PtNodeArray> flatNodes) {
+        for (final PtNodeArray nodeArray : flatNodes) {
+            nodeArray.mCachedAddressBeforeUpdate = nodeArray.mCachedAddressAfterUpdate;
+            for (final PtNode ptNode : nodeArray.mData) {
+                ptNode.mCachedAddressBeforeUpdate = ptNode.mCachedAddressAfterUpdate;
+            }
+        }
+    }
+
+    /**
+     * Compute the cached parent addresses after all has been updated.
+     *
+     * The parent addresses are used by some binary formats at write-to-disk time. Not all formats
+     * need them. In particular, version 2 does not need them, and version 3 does.
+     *
+     * @param flatNodes the flat array of node arrays to fill in
+     */
+    private static void computeParentAddresses(final ArrayList<PtNodeArray> flatNodes) {
+        for (final PtNodeArray nodeArray : flatNodes) {
+            for (final PtNode ptNode : nodeArray.mData) {
+                if (null != ptNode.mChildren) {
+                    // Assign my address to children's parent address
+                    // Here BeforeUpdate and AfterUpdate addresses have the same value, so it
+                    // does not matter which we use.
+                    ptNode.mChildren.mCachedParentAddress = ptNode.mCachedAddressAfterUpdate
+                            - ptNode.mChildren.mCachedAddressAfterUpdate;
+                }
+            }
+        }
+    }
+
+    /**
+     * Compute the addresses and sizes of an ordered list of PtNode arrays.
+     *
+     * This method takes a list of PtNode arrays and will update their cached address and size
+     * values so that they can be written into a file. It determines the smallest size each of the
+     * PtNode arrays can be given the addresses of its children and attributes, and store that into
+     * each PtNode.
+     * The order of the PtNode is given by the order of the array. This method makes no effort
+     * to find a good order; it only mechanically computes the size this order results in.
+     *
+     * @param dict the dictionary
+     * @param flatNodes the ordered list of PtNode arrays
+     * @param formatOptions file format options.
+     * @return the same array it was passed. The nodes have been updated for address and size.
+     */
+    /* package */ static ArrayList<PtNodeArray> computeAddresses(final FusionDictionary dict,
+            final ArrayList<PtNodeArray> flatNodes, final FormatOptions formatOptions) {
+        // First get the worst possible sizes and offsets
+        for (final PtNodeArray n : flatNodes) calculatePtNodeArrayMaximumSize(n, formatOptions);
+        final int offset = initializePtNodeArraysCachedAddresses(flatNodes, formatOptions);
+
+        MakedictLog.i("Compressing the array addresses. Original size : " + offset);
+        MakedictLog.i("(Recursively seen size : " + offset + ")");
+
+        int passes = 0;
+        boolean changesDone = false;
+        do {
+            changesDone = false;
+            int ptNodeArrayStartOffset = 0;
+            for (final PtNodeArray ptNodeArray : flatNodes) {
+                ptNodeArray.mCachedAddressAfterUpdate = ptNodeArrayStartOffset;
+                final int oldNodeArraySize = ptNodeArray.mCachedSize;
+                final boolean changed =
+                        computeActualPtNodeArraySize(ptNodeArray, dict, formatOptions);
+                final int newNodeArraySize = ptNodeArray.mCachedSize;
+                if (oldNodeArraySize < newNodeArraySize) {
+                    throw new RuntimeException("Increased size ?!");
+                }
+                ptNodeArrayStartOffset += newNodeArraySize;
+                changesDone |= changed;
+            }
+            updatePtNodeArraysCachedAddresses(flatNodes);
+            ++passes;
+            if (passes > MAX_PASSES) throw new RuntimeException("Too many passes - probably a bug");
+        } while (changesDone);
+
+        if (formatOptions.mSupportsDynamicUpdate) {
+            computeParentAddresses(flatNodes);
+        }
+        final PtNodeArray lastPtNodeArray = flatNodes.get(flatNodes.size() - 1);
+        MakedictLog.i("Compression complete in " + passes + " passes.");
+        MakedictLog.i("After address compression : "
+                + (lastPtNodeArray.mCachedAddressAfterUpdate + lastPtNodeArray.mCachedSize));
+
+        return flatNodes;
+    }
+
+    /**
+     * Sanity-checking method.
+     *
+     * This method checks a list of PtNode arrays for juxtaposition, that is, it will do
+     * nothing if each node array's cached address is actually the previous node array's address
+     * plus the previous node's size.
+     * If this is not the case, it will throw an exception.
+     *
+     * @param arrays the list of node arrays to check
+     */
+    /* package */ static void checkFlatPtNodeArrayList(final ArrayList<PtNodeArray> arrays) {
+        int offset = 0;
+        int index = 0;
+        for (final PtNodeArray ptNodeArray : arrays) {
+            // BeforeUpdate and AfterUpdate addresses are the same here, so it does not matter
+            // which we use.
+            if (ptNodeArray.mCachedAddressAfterUpdate != offset) {
+                throw new RuntimeException("Wrong address for node " + index
+                        + " : expected " + offset + ", got " +
+                        ptNodeArray.mCachedAddressAfterUpdate);
+            }
+            ++index;
+            offset += ptNodeArray.mCachedSize;
+        }
+    }
+
+    /**
+     * Helper method to write a children position to a file.
+     *
+     * @param buffer the buffer to write to.
+     * @param index the index in the buffer to write the address to.
+     * @param position the position to write.
+     * @return the size in bytes the address actually took.
+     */
+    /* package */ static int writeChildrenPosition(final byte[] buffer, int index,
+            final int position) {
+        switch (getByteSize(position)) {
+        case 1:
+            buffer[index++] = (byte)position;
+            return 1;
+        case 2:
+            buffer[index++] = (byte)(0xFF & (position >> 8));
+            buffer[index++] = (byte)(0xFF & position);
+            return 2;
+        case 3:
+            buffer[index++] = (byte)(0xFF & (position >> 16));
+            buffer[index++] = (byte)(0xFF & (position >> 8));
+            buffer[index++] = (byte)(0xFF & position);
+            return 3;
+        case 0:
+            return 0;
+        default:
+            throw new RuntimeException("Position " + position + " has a strange size");
+        }
+    }
+
+    /**
+     * Helper method to write a signed children position to a file.
+     *
+     * @param buffer the buffer to write to.
+     * @param index the index in the buffer to write the address to.
+     * @param position the position to write.
+     * @return the size in bytes the address actually took.
+     */
+    /* package */ static int writeSignedChildrenPosition(final byte[] buffer, int index,
+            final int position) {
+        if (!BinaryDictIOUtils.hasChildrenAddress(position)) {
+            buffer[index] = buffer[index + 1] = buffer[index + 2] = 0;
+        } else {
+            final int absPosition = Math.abs(position);
+            buffer[index++] =
+                    (byte)((position < 0 ? FormatSpec.MSB8 : 0) | (0xFF & (absPosition >> 16)));
+            buffer[index++] = (byte)(0xFF & (absPosition >> 8));
+            buffer[index++] = (byte)(0xFF & absPosition);
+        }
+        return 3;
+    }
+
+    /**
+     * Makes the flag value for a PtNode.
+     *
+     * @param hasMultipleChars whether the PtNode has multiple chars.
+     * @param isTerminal whether the PtNode is terminal.
+     * @param childrenAddressSize the size of a children address.
+     * @param hasShortcuts whether the PtNode has shortcuts.
+     * @param hasBigrams whether the PtNode has bigrams.
+     * @param isNotAWord whether the PtNode is not a word.
+     * @param isBlackListEntry whether the PtNode is a blacklist entry.
+     * @param formatOptions file format options.
+     * @return the flags
+     */
+    static int makePtNodeFlags(final boolean hasMultipleChars, final boolean isTerminal,
+            final int childrenAddressSize, final boolean hasShortcuts, final boolean hasBigrams,
+            final boolean isNotAWord, final boolean isBlackListEntry,
+            final FormatOptions formatOptions) {
+        byte flags = 0;
+        if (hasMultipleChars) flags |= FormatSpec.FLAG_HAS_MULTIPLE_CHARS;
+        if (isTerminal) flags |= FormatSpec.FLAG_IS_TERMINAL;
+        if (formatOptions.mSupportsDynamicUpdate) {
+            flags |= FormatSpec.FLAG_IS_NOT_MOVED;
+        } else if (true) {
+            switch (childrenAddressSize) {
+                case 1:
+                    flags |= FormatSpec.FLAG_CHILDREN_ADDRESS_TYPE_ONEBYTE;
+                    break;
+                case 2:
+                    flags |= FormatSpec.FLAG_CHILDREN_ADDRESS_TYPE_TWOBYTES;
+                    break;
+                case 3:
+                    flags |= FormatSpec.FLAG_CHILDREN_ADDRESS_TYPE_THREEBYTES;
+                    break;
+                case 0:
+                    flags |= FormatSpec.FLAG_CHILDREN_ADDRESS_TYPE_NOADDRESS;
+                    break;
+                default:
+                    throw new RuntimeException("Node with a strange address");
+            }
+        }
+        if (hasShortcuts) flags |= FormatSpec.FLAG_HAS_SHORTCUT_TARGETS;
+        if (hasBigrams) flags |= FormatSpec.FLAG_HAS_BIGRAMS;
+        if (isNotAWord) flags |= FormatSpec.FLAG_IS_NOT_A_WORD;
+        if (isBlackListEntry) flags |= FormatSpec.FLAG_IS_BLACKLISTED;
+        return flags;
+    }
+
+    /* package */ static byte makePtNodeFlags(final PtNode node, final int ptNodeAddress,
+            final int childrenOffset, final FormatOptions formatOptions) {
+        return (byte) makePtNodeFlags(node.mChars.length > 1, node.mFrequency >= 0,
+                getByteSize(childrenOffset),
+                node.mShortcutTargets != null && !node.mShortcutTargets.isEmpty(),
+                node.mBigrams != null, node.mIsNotAWord, node.mIsBlacklistEntry, formatOptions);
+    }
+
+    /**
+     * Makes the flag value for a bigram.
+     *
+     * @param more whether there are more bigrams after this one.
+     * @param offset the offset of the bigram.
+     * @param bigramFrequency the frequency of the bigram, 0..255.
+     * @param unigramFrequency the unigram frequency of the same word, 0..255.
+     * @param word the second bigram, for debugging purposes
+     * @return the flags
+     */
+    /* package */ static final int makeBigramFlags(final boolean more, final int offset,
+            int bigramFrequency, final int unigramFrequency, final String word) {
+        int bigramFlags = (more ? FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_HAS_NEXT : 0)
+                + (offset < 0 ? FormatSpec.FLAG_BIGRAM_ATTR_OFFSET_NEGATIVE : 0);
+        switch (getByteSize(offset)) {
+        case 1:
+            bigramFlags |= FormatSpec.FLAG_BIGRAM_ATTR_ADDRESS_TYPE_ONEBYTE;
+            break;
+        case 2:
+            bigramFlags |= FormatSpec.FLAG_BIGRAM_ATTR_ADDRESS_TYPE_TWOBYTES;
+            break;
+        case 3:
+            bigramFlags |= FormatSpec.FLAG_BIGRAM_ATTR_ADDRESS_TYPE_THREEBYTES;
+            break;
+        default:
+            throw new RuntimeException("Strange offset size");
+        }
+        if (unigramFrequency > bigramFrequency) {
+            MakedictLog.e("Unigram freq is superior to bigram freq for \"" + word
+                    + "\". Bigram freq is " + bigramFrequency + ", unigram freq for "
+                    + word + " is " + unigramFrequency);
+            bigramFrequency = unigramFrequency;
+        }
+        // We compute the difference between 255 (which means probability = 1) and the
+        // unigram score. We split this into a number of discrete steps.
+        // Now, the steps are numbered 0~15; 0 represents an increase of 1 step while 15
+        // represents an increase of 16 steps: a value of 15 will be interpreted as the median
+        // value of the 16th step. In all justice, if the bigram frequency is low enough to be
+        // rounded below the first step (which means it is less than half a step higher than the
+        // unigram frequency) then the unigram frequency itself is the best approximation of the
+        // bigram freq that we could possibly supply, hence we should *not* include this bigram
+        // in the file at all.
+        // until this is done, we'll write 0 and slightly overestimate this case.
+        // In other words, 0 means "between 0.5 step and 1.5 step", 1 means "between 1.5 step
+        // and 2.5 steps", and 15 means "between 15.5 steps and 16.5 steps". So we want to
+        // divide our range [unigramFreq..MAX_TERMINAL_FREQUENCY] in 16.5 steps to get the
+        // step size. Then we compute the start of the first step (the one where value 0 starts)
+        // by adding half-a-step to the unigramFrequency. From there, we compute the integer
+        // number of steps to the bigramFrequency. One last thing: we want our steps to include
+        // their lower bound and exclude their higher bound so we need to have the first step
+        // start at exactly 1 unit higher than floor(unigramFreq + half a step).
+        // Note : to reconstruct the score, the dictionary reader will need to divide
+        // MAX_TERMINAL_FREQUENCY - unigramFreq by 16.5 likewise to get the value of the step,
+        // and add (discretizedFrequency + 0.5 + 0.5) times this value to get the best
+        // approximation. (0.5 to get the first step start, and 0.5 to get the middle of the
+        // step pointed by the discretized frequency.
+        final float stepSize =
+                (FormatSpec.MAX_TERMINAL_FREQUENCY - unigramFrequency)
+                / (1.5f + FormatSpec.MAX_BIGRAM_FREQUENCY);
+        final float firstStepStart = 1 + unigramFrequency + (stepSize / 2.0f);
+        final int discretizedFrequency = (int)((bigramFrequency - firstStepStart) / stepSize);
+        // If the bigram freq is less than half-a-step higher than the unigram freq, we get -1
+        // here. The best approximation would be the unigram freq itself, so we should not
+        // include this bigram in the dictionary. For now, register as 0, and live with the
+        // small over-estimation that we get in this case. TODO: actually remove this bigram
+        // if discretizedFrequency < 0.
+        final int finalBigramFrequency = discretizedFrequency > 0 ? discretizedFrequency : 0;
+        bigramFlags += finalBigramFrequency & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_FREQUENCY;
+        return bigramFlags;
+    }
+
+    /**
+     * Makes the 2-byte value for options flags.
+     */
+    private static final int makeOptionsValue(final FusionDictionary dictionary,
+            final FormatOptions formatOptions) {
+        final DictionaryOptions options = dictionary.mOptions;
+        final boolean hasBigrams = dictionary.hasBigrams();
+        return (options.mFrenchLigatureProcessing ? FormatSpec.FRENCH_LIGATURE_PROCESSING_FLAG : 0)
+                + (options.mGermanUmlautProcessing ? FormatSpec.GERMAN_UMLAUT_PROCESSING_FLAG : 0)
+                + (hasBigrams ? FormatSpec.CONTAINS_BIGRAMS_FLAG : 0)
+                + (formatOptions.mSupportsDynamicUpdate ? FormatSpec.SUPPORTS_DYNAMIC_UPDATE : 0);
+    }
+
+    /**
+     * Makes the flag value for a shortcut.
+     *
+     * @param more whether there are more attributes after this one.
+     * @param frequency the frequency of the attribute, 0..15
+     * @return the flags
+     */
+    static final int makeShortcutFlags(final boolean more, final int frequency) {
+        return (more ? FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_HAS_NEXT : 0)
+                + (frequency & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_FREQUENCY);
+    }
+
+    /* package */ static final int writeParentAddress(final byte[] buffer, final int index,
+            final int address, final FormatOptions formatOptions) {
+        if (BinaryDictIOUtils.supportsDynamicUpdate(formatOptions)) {
+            if (address == FormatSpec.NO_PARENT_ADDRESS) {
+                buffer[index] = buffer[index + 1] = buffer[index + 2] = 0;
+            } else {
+                final int absAddress = Math.abs(address);
+                assert(absAddress <= FormatSpec.SINT24_MAX);
+                buffer[index] = (byte)((address < 0 ? FormatSpec.MSB8 : 0)
+                        | ((absAddress >> 16) & 0xFF));
+                buffer[index + 1] = (byte)((absAddress >> 8) & 0xFF);
+                buffer[index + 2] = (byte)(absAddress & 0xFF);
+            }
+            return index + 3;
+        } else {
+            return index;
+        }
+    }
+
+    /* package */ static final int getChildrenPosition(final PtNode ptNode,
+            final FormatOptions formatOptions) {
+        int positionOfChildrenPosField = ptNode.mCachedAddressAfterUpdate
+                + getNodeHeaderSize(ptNode, formatOptions);
+        if (ptNode.isTerminal()) {
+            // A terminal node has either the terminal id or the frequency.
+            // If positionOfChildrenPosField is incorrect, we may crash when jumping to the children
+            // position.
+            if (formatOptions.mHasTerminalId) {
+                positionOfChildrenPosField += FormatSpec.PTNODE_TERMINAL_ID_SIZE;
+            } else {
+                positionOfChildrenPosField += FormatSpec.PTNODE_FREQUENCY_SIZE;
+            }
+        }
+        return null == ptNode.mChildren ? FormatSpec.NO_CHILDREN_ADDRESS
+                : ptNode.mChildren.mCachedAddressAfterUpdate - positionOfChildrenPosField;
+    }
+
+    /**
+     * Write a PtNodeArray. The PtNodeArray is expected to have its final position cached.
+     *
+     * @param dict the dictionary the node array is a part of (for relative offsets).
+     * @param dictEncoder the dictionary encoder.
+     * @param ptNodeArray the node array to write.
+     * @param formatOptions file format options.
+     */
+    @SuppressWarnings("unused")
+    /* package */ static void writePlacedPtNodeArray(final FusionDictionary dict,
+            final DictEncoder dictEncoder, final PtNodeArray ptNodeArray,
+            final FormatOptions formatOptions) {
+        // TODO: Make the code in common with BinaryDictIOUtils#writePtNode
+        dictEncoder.setPosition(ptNodeArray.mCachedAddressAfterUpdate);
+
+        final int ptNodeCount = ptNodeArray.mData.size();
+        dictEncoder.writePtNodeCount(ptNodeCount);
+        final int parentPosition =
+                (ptNodeArray.mCachedParentAddress == FormatSpec.NO_PARENT_ADDRESS)
+                ? FormatSpec.NO_PARENT_ADDRESS
+                : ptNodeArray.mCachedParentAddress + ptNodeArray.mCachedAddressAfterUpdate;
+        for (int i = 0; i < ptNodeCount; ++i) {
+            final PtNode ptNode = ptNodeArray.mData.get(i);
+            if (dictEncoder.getPosition() != ptNode.mCachedAddressAfterUpdate) {
+                throw new RuntimeException("Bug: write index is not the same as the cached address "
+                        + "of the node : " + dictEncoder.getPosition() + " <> "
+                        + ptNode.mCachedAddressAfterUpdate);
+            }
+            // Sanity checks.
+            if (DBG && ptNode.mFrequency > FormatSpec.MAX_TERMINAL_FREQUENCY) {
+                throw new RuntimeException("A node has a frequency > "
+                        + FormatSpec.MAX_TERMINAL_FREQUENCY
+                        + " : " + ptNode.mFrequency);
+            }
+            dictEncoder.writePtNode(ptNode, parentPosition, formatOptions, dict);
+        }
+        if (formatOptions.mSupportsDynamicUpdate) {
+            dictEncoder.writeForwardLinkAddress(FormatSpec.NO_FORWARD_LINK_ADDRESS);
+        }
+        if (dictEncoder.getPosition() != ptNodeArray.mCachedAddressAfterUpdate
+                + ptNodeArray.mCachedSize) {
+            throw new RuntimeException("Not the same size : written "
+                     + (dictEncoder.getPosition() - ptNodeArray.mCachedAddressAfterUpdate)
+                     + " bytes from a node that should have " + ptNodeArray.mCachedSize + " bytes");
+        }
+    }
+
+    /**
+     * Dumps a collection of useful statistics about a list of PtNode arrays.
+     *
+     * This prints purely informative stuff, like the total estimated file size, the
+     * number of PtNode arrays, of PtNodes, the repartition of each address size, etc
+     *
+     * @param ptNodeArrays the list of PtNode arrays.
+     */
+    /* package */ static void showStatistics(ArrayList<PtNodeArray> ptNodeArrays) {
+        int firstTerminalAddress = Integer.MAX_VALUE;
+        int lastTerminalAddress = Integer.MIN_VALUE;
+        int size = 0;
+        int ptNodes = 0;
+        int maxNodes = 0;
+        int maxRuns = 0;
+        for (final PtNodeArray ptNodeArray : ptNodeArrays) {
+            if (maxNodes < ptNodeArray.mData.size()) maxNodes = ptNodeArray.mData.size();
+            for (final PtNode ptNode : ptNodeArray.mData) {
+                ++ptNodes;
+                if (ptNode.mChars.length > maxRuns) maxRuns = ptNode.mChars.length;
+                if (ptNode.mFrequency >= 0) {
+                    if (ptNodeArray.mCachedAddressAfterUpdate < firstTerminalAddress)
+                        firstTerminalAddress = ptNodeArray.mCachedAddressAfterUpdate;
+                    if (ptNodeArray.mCachedAddressAfterUpdate > lastTerminalAddress)
+                        lastTerminalAddress = ptNodeArray.mCachedAddressAfterUpdate;
+                }
+            }
+            if (ptNodeArray.mCachedAddressAfterUpdate + ptNodeArray.mCachedSize > size) {
+                size = ptNodeArray.mCachedAddressAfterUpdate + ptNodeArray.mCachedSize;
+            }
+        }
+        final int[] ptNodeCounts = new int[maxNodes + 1];
+        final int[] runCounts = new int[maxRuns + 1];
+        for (final PtNodeArray ptNodeArray : ptNodeArrays) {
+            ++ptNodeCounts[ptNodeArray.mData.size()];
+            for (final PtNode ptNode : ptNodeArray.mData) {
+                ++runCounts[ptNode.mChars.length];
+            }
+        }
+
+        MakedictLog.i("Statistics:\n"
+                + "  total file size " + size + "\n"
+                + "  " + ptNodeArrays.size() + " node arrays\n"
+                + "  " + ptNodes + " PtNodes (" + ((float)ptNodes / ptNodeArrays.size())
+                        + " PtNodes per node)\n"
+                + "  first terminal at " + firstTerminalAddress + "\n"
+                + "  last terminal at " + lastTerminalAddress + "\n"
+                + "  PtNode stats : max = " + maxNodes);
+        for (int i = 0; i < ptNodeCounts.length; ++i) {
+            MakedictLog.i("    " + i + " : " + ptNodeCounts[i]);
+        }
+        MakedictLog.i("  Character run stats : max = " + maxRuns);
+        for (int i = 0; i < runCounts.length; ++i) {
+            MakedictLog.i("    " + i + " : " + runCounts[i]);
+        }
+    }
+
+    /**
+     * Writes a file header to an output stream.
+     *
+     * @param destination the stream to write the file header to.
+     * @param dict the dictionary to write.
+     * @param formatOptions file format options.
+     */
+    /* package */ static void writeDictionaryHeader(final OutputStream destination,
+            final FusionDictionary dict, final FormatOptions formatOptions)
+                    throws IOException, UnsupportedFormatException {
+        final int version = formatOptions.mVersion;
+        if (version < FormatSpec.MINIMUM_SUPPORTED_VERSION
+                || version > FormatSpec.MAXIMUM_SUPPORTED_VERSION) {
+            throw new UnsupportedFormatException("Requested file format version " + version
+                    + ", but this implementation only supports versions "
+                    + FormatSpec.MINIMUM_SUPPORTED_VERSION + " through "
+                    + FormatSpec.MAXIMUM_SUPPORTED_VERSION);
+        }
+
+        ByteArrayOutputStream headerBuffer = new ByteArrayOutputStream(256);
+
+        // The magic number in big-endian order.
+        // Magic number for all versions.
+        headerBuffer.write((byte) (0xFF & (FormatSpec.MAGIC_NUMBER >> 24)));
+        headerBuffer.write((byte) (0xFF & (FormatSpec.MAGIC_NUMBER >> 16)));
+        headerBuffer.write((byte) (0xFF & (FormatSpec.MAGIC_NUMBER >> 8)));
+        headerBuffer.write((byte) (0xFF & FormatSpec.MAGIC_NUMBER));
+        // Dictionary version.
+        headerBuffer.write((byte) (0xFF & (version >> 8)));
+        headerBuffer.write((byte) (0xFF & version));
+
+        // Options flags
+        final int options = makeOptionsValue(dict, formatOptions);
+        headerBuffer.write((byte) (0xFF & (options >> 8)));
+        headerBuffer.write((byte) (0xFF & options));
+        final int headerSizeOffset = headerBuffer.size();
+        // Placeholder to be written later with header size.
+        for (int i = 0; i < 4; ++i) {
+            headerBuffer.write(0);
+        }
+        // Write out the options.
+        for (final String key : dict.mOptions.mAttributes.keySet()) {
+            final String value = dict.mOptions.mAttributes.get(key);
+            CharEncoding.writeString(headerBuffer, key);
+            CharEncoding.writeString(headerBuffer, value);
+        }
+        final int size = headerBuffer.size();
+        final byte[] bytes = headerBuffer.toByteArray();
+        // Write out the header size.
+        bytes[headerSizeOffset] = (byte) (0xFF & (size >> 24));
+        bytes[headerSizeOffset + 1] = (byte) (0xFF & (size >> 16));
+        bytes[headerSizeOffset + 2] = (byte) (0xFF & (size >> 8));
+        bytes[headerSizeOffset + 3] = (byte) (0xFF & (size >> 0));
+        destination.write(bytes);
+
+        headerBuffer.close();
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java
index 167c691..a282f59 100644
--- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java
+++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java
@@ -18,56 +18,52 @@
 
 import com.android.inputmethod.annotations.UsedForTesting;
 import com.android.inputmethod.latin.Constants;
-import com.android.inputmethod.latin.makedict.BinaryDictInputOutput.CharEncoding;
-import com.android.inputmethod.latin.makedict.BinaryDictInputOutput.FusionDictionaryBufferInterface;
+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.CharGroup;
+import com.android.inputmethod.latin.makedict.FusionDictionary.PtNode;
 import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
+import com.android.inputmethod.latin.utils.ByteArrayDictBuffer;
 
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.OutputStream;
-import java.nio.channels.FileChannel;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Iterator;
 import java.util.Map;
 import java.util.Stack;
 
 public final class BinaryDictIOUtils {
     private static final boolean DBG = false;
-    private static final int MSB24 = 0x800000;
-    private static final int SINT24_MAX = 0x7FFFFF;
-    private static final int MAX_JUMPS = 10000;
 
     private BinaryDictIOUtils() {
         // This utility class is not publicly instantiable.
     }
 
     private static final class Position {
-        public static final int NOT_READ_GROUPCOUNT = -1;
+        public static final int NOT_READ_PTNODE_COUNT = -1;
 
         public int mAddress;
-        public int mNumOfCharGroup;
+        public int mNumOfPtNode;
         public int mPosition;
         public int mLength;
 
         public Position(int address, int length) {
             mAddress = address;
             mLength = length;
-            mNumOfCharGroup = NOT_READ_GROUPCOUNT;
+            mNumOfPtNode = NOT_READ_PTNODE_COUNT;
         }
     }
 
     /**
-     * Tours all node without recursive call.
+     * Retrieves all node arrays without recursive call.
      */
-    private static void readUnigramsAndBigramsBinaryInner(
-            final FusionDictionaryBufferInterface buffer, final int headerSize,
-            final Map<Integer, String> words, final Map<Integer, Integer> frequencies,
+    private static void readUnigramsAndBigramsBinaryInner(final DictDecoder dictDecoder,
+            final int headerSize, final Map<Integer, String> words,
+            final Map<Integer, Integer> frequencies,
             final Map<Integer, ArrayList<PendingAttribute>> bigrams,
             final FormatOptions formatOptions) {
         int[] pushedChars = new int[FormatSpec.MAX_WORD_LENGTH + 1];
@@ -82,47 +78,47 @@
             Position p = stack.peek();
 
             if (DBG) {
-                MakedictLog.d("read: address=" + p.mAddress + ", numOfCharGroup=" +
-                        p.mNumOfCharGroup + ", position=" + p.mPosition + ", length=" + p.mLength);
+                MakedictLog.d("read: address=" + p.mAddress + ", numOfPtNode=" +
+                        p.mNumOfPtNode + ", position=" + p.mPosition + ", length=" + p.mLength);
             }
 
-            if (buffer.position() != p.mAddress) buffer.position(p.mAddress);
+            if (dictDecoder.getPosition() != p.mAddress) dictDecoder.setPosition(p.mAddress);
             if (index != p.mLength) index = p.mLength;
 
-            if (p.mNumOfCharGroup == Position.NOT_READ_GROUPCOUNT) {
-                p.mNumOfCharGroup = BinaryDictInputOutput.readCharGroupCount(buffer);
-                p.mAddress += BinaryDictInputOutput.getGroupCountSize(p.mNumOfCharGroup);
+            if (p.mNumOfPtNode == Position.NOT_READ_PTNODE_COUNT) {
+                p.mNumOfPtNode = dictDecoder.readPtNodeCount();
+                p.mAddress += getPtNodeCountSize(p.mNumOfPtNode);
                 p.mPosition = 0;
             }
-            if (p.mNumOfCharGroup == 0) {
+            if (p.mNumOfPtNode == 0) {
                 stack.pop();
                 continue;
             }
-            CharGroupInfo info = BinaryDictInputOutput.readCharGroup(buffer,
-                    p.mAddress - headerSize, formatOptions);
+            PtNodeInfo info = dictDecoder.readPtNode(p.mAddress, formatOptions);
             for (int i = 0; i < info.mCharacters.length; ++i) {
                 pushedChars[index++] = info.mCharacters[i];
             }
             p.mPosition++;
 
-            final boolean isMovedGroup = BinaryDictInputOutput.isMovedGroup(info.mFlags,
+            final boolean isMovedPtNode = isMovedPtNode(info.mFlags,
                     formatOptions);
-            final boolean isDeletedGroup = BinaryDictInputOutput.isDeletedGroup(info.mFlags,
+            final boolean isDeletedPtNode = isDeletedPtNode(info.mFlags,
                     formatOptions);
-            if (!isMovedGroup && !isDeletedGroup
-                    && info.mFrequency != FusionDictionary.CharGroup.NOT_A_TERMINAL) {// found word
+            if (!isMovedPtNode && !isDeletedPtNode
+                    && info.mFrequency != FusionDictionary.PtNode.NOT_A_TERMINAL) {// found word
                 words.put(info.mOriginalAddress, new String(pushedChars, 0, index));
                 frequencies.put(info.mOriginalAddress, info.mFrequency);
                 if (info.mBigrams != null) bigrams.put(info.mOriginalAddress, info.mBigrams);
             }
 
-            if (p.mPosition == p.mNumOfCharGroup) {
+            if (p.mPosition == p.mNumOfPtNode) {
                 if (formatOptions.mSupportsDynamicUpdate) {
-                    final int forwardLinkAddress = buffer.readUnsignedInt24();
-                    if (forwardLinkAddress != FormatSpec.NO_FORWARD_LINK_ADDRESS) {
-                        // the node has a forward link.
-                        p.mNumOfCharGroup = Position.NOT_READ_GROUPCOUNT;
-                        p.mAddress = forwardLinkAddress;
+                    final boolean hasValidForwardLinkAddress =
+                            dictDecoder.readAndFollowForwardLink();
+                    if (hasValidForwardLinkAddress && dictDecoder.hasNextPtNodeArray()) {
+                        // The node array has a forward link.
+                        p.mNumOfPtNode = Position.NOT_READ_PTNODE_COUNT;
+                        p.mAddress = dictDecoder.getPosition();
                     } else {
                         stack.pop();
                     }
@@ -130,12 +126,12 @@
                     stack.pop();
                 }
             } else {
-                // the node has more groups.
-                p.mAddress = buffer.position();
+                // The Ptnode array has more PtNodes.
+                p.mAddress = dictDecoder.getPosition();
             }
 
-            if (!isMovedGroup && BinaryDictInputOutput.hasChildrenAddress(info.mChildrenAddress)) {
-                Position childrenPos = new Position(info.mChildrenAddress + headerSize, index);
+            if (!isMovedPtNode && hasChildrenAddress(info.mChildrenAddress)) {
+                final Position childrenPos = new Position(info.mChildrenAddress, index);
                 stack.push(childrenPos);
             }
         }
@@ -143,61 +139,59 @@
 
     /**
      * Reads unigrams and bigrams from the binary file.
-     * Doesn't make the memory representation of the dictionary.
+     * Doesn't store a full memory representation of the dictionary.
      *
-     * @param buffer the buffer to read.
+     * @param dictDecoder the dict decoder.
      * @param words the map to store the address as a key and the word as a value.
      * @param frequencies the map to store the address as a key and the frequency as a value.
      * @param bigrams the map to store the address as a key and the list of address as a value.
-     * @throws IOException
-     * @throws UnsupportedFormatException
+     * @throws IOException if the file can't be read.
+     * @throws UnsupportedFormatException if the format of the file is not recognized.
      */
-    public static void readUnigramsAndBigramsBinary(final FusionDictionaryBufferInterface buffer,
+    /* package */ static void readUnigramsAndBigramsBinary(final DictDecoder dictDecoder,
             final Map<Integer, String> words, final Map<Integer, Integer> frequencies,
             final Map<Integer, ArrayList<PendingAttribute>> bigrams) throws IOException,
             UnsupportedFormatException {
         // Read header
-        final FileHeader header = BinaryDictInputOutput.readHeader(buffer);
-        readUnigramsAndBigramsBinaryInner(buffer, header.mHeaderSize, words, frequencies, bigrams,
-                header.mFormatOptions);
+        final FileHeader header = dictDecoder.readHeader();
+        readUnigramsAndBigramsBinaryInner(dictDecoder, header.mHeaderSize, words,
+                frequencies, bigrams, header.mFormatOptions);
     }
 
     /**
-     * Gets the address of the last CharGroup of the exact matching word in the dictionary.
+     * Gets the address of the last PtNode of the exact matching word in the dictionary.
      * If no match is found, returns NOT_VALID_WORD.
      *
-     * @param buffer the buffer to read.
+     * @param dictDecoder the dict decoder.
      * @param word the word we search for.
      * @return the address of the terminal node.
-     * @throws IOException
-     * @throws UnsupportedFormatException
+     * @throws IOException if the file can't be read.
+     * @throws UnsupportedFormatException if the format of the file is not recognized.
      */
     @UsedForTesting
-    public static int getTerminalPosition(final FusionDictionaryBufferInterface buffer,
+    /* package */ static int getTerminalPosition(final DictDecoder dictDecoder,
             final String word) throws IOException, UnsupportedFormatException {
         if (word == null) return FormatSpec.NOT_VALID_WORD;
-        if (buffer.position() != 0) buffer.position(0);
+        dictDecoder.setPosition(0);
 
-        final FileHeader header = BinaryDictInputOutput.readHeader(buffer);
+        final FileHeader header = dictDecoder.readHeader();
         int wordPos = 0;
         final int wordLen = word.codePointCount(0, word.length());
         for (int depth = 0; depth < Constants.DICTIONARY_MAX_WORD_LENGTH; ++depth) {
             if (wordPos >= wordLen) return FormatSpec.NOT_VALID_WORD;
 
             do {
-                final int charGroupCount = BinaryDictInputOutput.readCharGroupCount(buffer);
-                boolean foundNextCharGroup = false;
-                for (int i = 0; i < charGroupCount; ++i) {
-                    final int charGroupPos = buffer.position();
-                    final CharGroupInfo currentInfo = BinaryDictInputOutput.readCharGroup(buffer,
-                            buffer.position(), header.mFormatOptions);
-                    final boolean isMovedGroup =
-                            BinaryDictInputOutput.isMovedGroup(currentInfo.mFlags,
-                                    header.mFormatOptions);
-                    final boolean isDeletedGroup =
-                            BinaryDictInputOutput.isDeletedGroup(currentInfo.mFlags,
-                                    header.mFormatOptions);
-                    if (isMovedGroup) continue;
+                final int ptNodeCount = dictDecoder.readPtNodeCount();
+                boolean foundNextPtNode = false;
+                for (int i = 0; i < ptNodeCount; ++i) {
+                    final int ptNodePos = dictDecoder.getPosition();
+                    final PtNodeInfo currentInfo = dictDecoder.readPtNode(ptNodePos,
+                            header.mFormatOptions);
+                    final boolean isMovedNode = isMovedPtNode(currentInfo.mFlags,
+                            header.mFormatOptions);
+                    final boolean isDeletedNode = isDeletedPtNode(currentInfo.mFlags,
+                            header.mFormatOptions);
+                    if (isMovedNode) continue;
                     boolean same = true;
                     for (int p = 0, j = word.offsetByCodePoints(0, wordPos);
                             p < currentInfo.mCharacters.length;
@@ -210,86 +204,60 @@
                     }
 
                     if (same) {
-                        // found the group matches the word.
+                        // found the PtNode matches the word.
                         if (wordPos + currentInfo.mCharacters.length == wordLen) {
-                            if (currentInfo.mFrequency == CharGroup.NOT_A_TERMINAL
-                                    || isDeletedGroup) {
+                            if (currentInfo.mFrequency == PtNode.NOT_A_TERMINAL
+                                    || isDeletedNode) {
                                 return FormatSpec.NOT_VALID_WORD;
                             } else {
-                                return charGroupPos;
+                                return ptNodePos;
                             }
                         }
                         wordPos += currentInfo.mCharacters.length;
                         if (currentInfo.mChildrenAddress == FormatSpec.NO_CHILDREN_ADDRESS) {
                             return FormatSpec.NOT_VALID_WORD;
                         }
-                        foundNextCharGroup = true;
-                        buffer.position(currentInfo.mChildrenAddress);
+                        foundNextPtNode = true;
+                        dictDecoder.setPosition(currentInfo.mChildrenAddress);
                         break;
                     }
                 }
 
-                // If we found the next char group, it is under the file pointer.
-                // But if not, we are at the end of this node so we expect to have
+                // If we found the next PtNode, it is under the file pointer.
+                // But if not, we are at the end of this node array so we expect to have
                 // a forward link address that we need to consult and possibly resume
-                // search on the next node in the linked list.
-                if (foundNextCharGroup) break;
+                // search on the next node array in the linked list.
+                if (foundNextPtNode) break;
                 if (!header.mFormatOptions.mSupportsDynamicUpdate) {
                     return FormatSpec.NOT_VALID_WORD;
                 }
 
-                final int forwardLinkAddress = buffer.readUnsignedInt24();
-                if (forwardLinkAddress == FormatSpec.NO_FORWARD_LINK_ADDRESS) {
+                final boolean hasValidForwardLinkAddress =
+                        dictDecoder.readAndFollowForwardLink();
+                if (!hasValidForwardLinkAddress || !dictDecoder.hasNextPtNodeArray()) {
                     return FormatSpec.NOT_VALID_WORD;
                 }
-                buffer.position(forwardLinkAddress);
             } while(true);
         }
         return FormatSpec.NOT_VALID_WORD;
     }
 
-    private static int markAsDeleted(final int flags) {
-        return (flags & (~FormatSpec.MASK_GROUP_ADDRESS_TYPE)) | FormatSpec.FLAG_IS_DELETED;
-    }
-
-    /**
-     * Delete the word from the binary file.
-     *
-     * @param buffer the buffer to write.
-     * @param word the word we delete
-     * @throws IOException
-     * @throws UnsupportedFormatException
-     */
-    @UsedForTesting
-    public static void deleteWord(final FusionDictionaryBufferInterface buffer,
-            final String word) throws IOException, UnsupportedFormatException {
-        buffer.position(0);
-        final FileHeader header = BinaryDictInputOutput.readHeader(buffer);
-        final int wordPosition = getTerminalPosition(buffer, word);
-        if (wordPosition == FormatSpec.NOT_VALID_WORD) return;
-
-        buffer.position(wordPosition);
-        final int flags = buffer.readUnsignedByte();
-        buffer.position(wordPosition);
-        buffer.put((byte)markAsDeleted(flags));
-    }
-
     /**
      * @return the size written, in bytes. Always 3 bytes.
      */
-    private static int writeSInt24ToBuffer(final FusionDictionaryBufferInterface buffer,
+    static int writeSInt24ToBuffer(final DictBuffer dictBuffer,
             final int value) {
         final int absValue = Math.abs(value);
-        buffer.put((byte)(((value < 0 ? 0x80 : 0) | (absValue >> 16)) & 0xFF));
-        buffer.put((byte)((absValue >> 8) & 0xFF));
-        buffer.put((byte)(absValue & 0xFF));
+        dictBuffer.put((byte)(((value < 0 ? 0x80 : 0) | (absValue >> 16)) & 0xFF));
+        dictBuffer.put((byte)((absValue >> 8) & 0xFF));
+        dictBuffer.put((byte)(absValue & 0xFF));
         return 3;
     }
 
     /**
      * @return the size written, in bytes. Always 3 bytes.
      */
-    private static int writeSInt24ToStream(final OutputStream destination, final int value)
+    static int writeSInt24ToStream(final OutputStream destination, final int value)
             throws IOException {
         final int absValue = Math.abs(value);
         destination.write((byte)(((value < 0 ? 0x80 : 0) | (absValue >> 16)) & 0xFF));
@@ -303,7 +271,7 @@
      */
     private static int writeVariableAddress(final OutputStream destination, final int value)
             throws IOException {
-        switch (BinaryDictInputOutput.getByteSize(value)) {
+        switch (BinaryDictEncoderUtils.getByteSize(value)) {
         case 1:
             destination.write((byte)value);
             break;
@@ -317,111 +285,52 @@
             destination.write((byte)(0xFF & value));
             break;
         }
-        return BinaryDictInputOutput.getByteSize(value);
+        return BinaryDictEncoderUtils.getByteSize(value);
     }
 
-    /**
-     * Update a parent address in a CharGroup that is referred to by groupOriginAddress.
-     *
-     * @param buffer the buffer to write.
-     * @param groupOriginAddress the address of the group.
-     * @param newParentAddress the absolute address of the parent.
-     * @param formatOptions file format options.
-     */
-    public static void updateParentAddress(final FusionDictionaryBufferInterface buffer,
-            final int groupOriginAddress, final int newParentAddress,
-            final FormatOptions formatOptions) {
-        final int originalPosition = buffer.position();
-        buffer.position(groupOriginAddress);
-        if (!formatOptions.mSupportsDynamicUpdate) {
-            throw new RuntimeException("this file format does not support parent addresses");
-        }
-        final int flags = buffer.readUnsignedByte();
-        if (BinaryDictInputOutput.isMovedGroup(flags, formatOptions)) {
-            // if the group is moved, the parent address is stored in the destination group.
-            // We are guaranteed to process the destination group later, so there is no need to
-            // update anything here.
-            buffer.position(originalPosition);
-            return;
-        }
-        if (DBG) {
-            MakedictLog.d("update parent address flags=" + flags + ", " + groupOriginAddress);
-        }
-        final int parentOffset = newParentAddress - groupOriginAddress;
-        writeSInt24ToBuffer(buffer, parentOffset);
-        buffer.position(originalPosition);
-    }
-
-    private static void skipCharGroup(final FusionDictionaryBufferInterface buffer,
-            final FormatOptions formatOptions) {
-        final int flags = buffer.readUnsignedByte();
-        BinaryDictInputOutput.readParentAddress(buffer, formatOptions);
-        skipString(buffer, (flags & FormatSpec.FLAG_HAS_MULTIPLE_CHARS) != 0);
-        BinaryDictInputOutput.readChildrenAddress(buffer, flags, formatOptions);
-        if ((flags & FormatSpec.FLAG_IS_TERMINAL) != 0) buffer.readUnsignedByte();
+    static void skipPtNode(final DictBuffer dictBuffer, final FormatOptions formatOptions) {
+        final int flags = dictBuffer.readUnsignedByte();
+        BinaryDictDecoderUtils.readParentAddress(dictBuffer, formatOptions);
+        skipString(dictBuffer, (flags & FormatSpec.FLAG_HAS_MULTIPLE_CHARS) != 0);
+        BinaryDictDecoderUtils.readChildrenAddress(dictBuffer, flags, formatOptions);
+        if ((flags & FormatSpec.FLAG_IS_TERMINAL) != 0) dictBuffer.readUnsignedByte();
         if ((flags & FormatSpec.FLAG_HAS_SHORTCUT_TARGETS) != 0) {
-            final int shortcutsSize = buffer.readUnsignedShort();
-            buffer.position(buffer.position() + shortcutsSize
-                    - FormatSpec.GROUP_SHORTCUT_LIST_SIZE_SIZE);
+            final int shortcutsSize = dictBuffer.readUnsignedShort();
+            dictBuffer.position(dictBuffer.position() + shortcutsSize
+                    - FormatSpec.PTNODE_SHORTCUT_LIST_SIZE_SIZE);
         }
         if ((flags & FormatSpec.FLAG_HAS_BIGRAMS) != 0) {
             int bigramCount = 0;
-            while (bigramCount++ < FormatSpec.MAX_BIGRAMS_IN_A_GROUP) {
-                final int bigramFlags = buffer.readUnsignedByte();
-                switch (bigramFlags & FormatSpec.MASK_ATTRIBUTE_ADDRESS_TYPE) {
-                    case FormatSpec.FLAG_ATTRIBUTE_ADDRESS_TYPE_ONEBYTE:
-                        buffer.readUnsignedByte();
+            while (bigramCount++ < FormatSpec.MAX_BIGRAMS_IN_A_PTNODE) {
+                final int bigramFlags = dictBuffer.readUnsignedByte();
+                switch (bigramFlags & FormatSpec.MASK_BIGRAM_ATTR_ADDRESS_TYPE) {
+                    case FormatSpec.FLAG_BIGRAM_ATTR_ADDRESS_TYPE_ONEBYTE:
+                        dictBuffer.readUnsignedByte();
                         break;
-                    case FormatSpec.FLAG_ATTRIBUTE_ADDRESS_TYPE_TWOBYTES:
-                        buffer.readUnsignedShort();
+                    case FormatSpec.FLAG_BIGRAM_ATTR_ADDRESS_TYPE_TWOBYTES:
+                        dictBuffer.readUnsignedShort();
                         break;
-                    case FormatSpec.FLAG_ATTRIBUTE_ADDRESS_TYPE_THREEBYTES:
-                        buffer.readUnsignedInt24();
+                    case FormatSpec.FLAG_BIGRAM_ATTR_ADDRESS_TYPE_THREEBYTES:
+                        dictBuffer.readUnsignedInt24();
                         break;
                 }
-                if ((bigramFlags & FormatSpec.FLAG_ATTRIBUTE_HAS_NEXT) == 0) break;
+                if ((bigramFlags & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_HAS_NEXT) == 0) break;
             }
-            if (bigramCount >= FormatSpec.MAX_BIGRAMS_IN_A_GROUP) {
-                throw new RuntimeException("Too many bigrams in a group.");
+            if (bigramCount >= FormatSpec.MAX_BIGRAMS_IN_A_PTNODE) {
+                throw new RuntimeException("Too many bigrams in a PtNode.");
             }
         }
     }
 
-    /**
-     * Update parent addresses in a Node that is referred to by nodeOriginAddress.
-     *
-     * @param buffer the buffer to be modified.
-     * @param nodeOriginAddress the address of a modified Node.
-     * @param newParentAddress the address to be written.
-     * @param formatOptions file format options.
-     */
-    public static void updateParentAddresses(final FusionDictionaryBufferInterface buffer,
-            final int nodeOriginAddress, final int newParentAddress,
-            final FormatOptions formatOptions) {
-        final int originalPosition = buffer.position();
-        buffer.position(nodeOriginAddress);
-        do {
-            final int count = BinaryDictInputOutput.readCharGroupCount(buffer);
-            for (int i = 0; i < count; ++i) {
-                updateParentAddress(buffer, buffer.position(), newParentAddress, formatOptions);
-                skipCharGroup(buffer, formatOptions);
-            }
-            final int forwardLinkAddress = buffer.readUnsignedInt24();
-            buffer.position(forwardLinkAddress);
-        } while (formatOptions.mSupportsDynamicUpdate
-                && buffer.position() != FormatSpec.NO_FORWARD_LINK_ADDRESS);
-        buffer.position(originalPosition);
-    }
-
-    private static void skipString(final FusionDictionaryBufferInterface buffer,
+    static void skipString(final DictBuffer dictBuffer,
             final boolean hasMultipleChars) {
         if (hasMultipleChars) {
-            int character = CharEncoding.readChar(buffer);
+            int character = CharEncoding.readChar(dictBuffer);
             while (character != FormatSpec.INVALID_CHARACTER) {
-                character = CharEncoding.readChar(buffer);
+                character = CharEncoding.readChar(dictBuffer);
             }
         } else {
-            CharEncoding.readChar(buffer);
+            CharEncoding.readChar(dictBuffer);
         }
     }
 
@@ -449,46 +358,24 @@
                 size += 3;
             }
         }
-        destination.write((byte)FormatSpec.GROUP_CHARACTERS_TERMINATOR);
-        size += FormatSpec.GROUP_TERMINATOR_SIZE;
+        destination.write((byte)FormatSpec.PTNODE_CHARACTERS_TERMINATOR);
+        size += FormatSpec.PTNODE_TERMINATOR_SIZE;
         return size;
     }
 
     /**
-     * Update a children address in a CharGroup that is addressed by groupOriginAddress.
-     *
-     * @param buffer the buffer to write.
-     * @param groupOriginAddress the address of the group.
-     * @param newChildrenAddress the absolute address of the child.
-     * @param formatOptions file format options.
-     */
-    public static void updateChildrenAddress(final FusionDictionaryBufferInterface buffer,
-            final int groupOriginAddress, final int newChildrenAddress,
-            final FormatOptions formatOptions) {
-        final int originalPosition = buffer.position();
-        buffer.position(groupOriginAddress);
-        final int flags = buffer.readUnsignedByte();
-        final int parentAddress = BinaryDictInputOutput.readParentAddress(buffer, formatOptions);
-        skipString(buffer, (flags & FormatSpec.FLAG_HAS_MULTIPLE_CHARS) != 0);
-        if ((flags & FormatSpec.FLAG_IS_TERMINAL) != 0) buffer.readUnsignedByte();
-        final int childrenOffset = newChildrenAddress == FormatSpec.NO_CHILDREN_ADDRESS
-                ? FormatSpec.NO_CHILDREN_ADDRESS : newChildrenAddress - buffer.position();
-        writeSInt24ToBuffer(buffer, childrenOffset);
-        buffer.position(originalPosition);
-    }
-
-    /**
-     * Write a char group to an output stream.
-     * A char group is an in-memory representation of a node in trie.
-     * A char group info is an on-disk representation of a node.
+     * Write a PtNode to an output stream from a PtNodeInfo.
+     * A PtNode is an in-memory representation of a node in the patricia trie.
+     * A PtNode info is a container for low-level information about how the
+     * PtNode is stored in the binary format.
      *
      * @param destination the stream to write.
-     * @param info the char group info to be written.
+     * @param info the PtNode info to be written.
      * @return the size written, in bytes.
      */
-    public static int writeCharGroup(final OutputStream destination, final CharGroupInfo info)
+    private static int writePtNode(final OutputStream destination, final PtNodeInfo info)
             throws IOException {
-        int size = FormatSpec.GROUP_FLAGS_SIZE;
+        int size = FormatSpec.PTNODE_FLAGS_SIZE;
         destination.write((byte)info.mFlags);
         final int parentOffset = info.mParentAddress == FormatSpec.NO_PARENT_ADDRESS ?
                 FormatSpec.NO_PARENT_ADDRESS : info.mParentAddress - info.mOriginalAddress;
@@ -503,7 +390,7 @@
             }
         }
         if (info.mCharacters.length > 1) {
-            destination.write((byte)FormatSpec.GROUP_CHARACTERS_TERMINATOR);
+            destination.write((byte)FormatSpec.PTNODE_CHARACTERS_TERMINATOR);
             size++;
         }
 
@@ -513,7 +400,7 @@
         }
 
         if (DBG) {
-            MakedictLog.d("writeCharGroup origin=" + info.mOriginalAddress + ", size=" + size
+            MakedictLog.d("writePtNode origin=" + info.mOriginalAddress + ", size=" + size
                     + ", child=" + info.mChildrenAddress + ", characters ="
                     + new String(info.mCharacters, 0, info.mCharacters.length));
         }
@@ -524,14 +411,14 @@
 
         if (info.mShortcutTargets != null && info.mShortcutTargets.size() > 0) {
             final int shortcutListSize =
-                    BinaryDictInputOutput.getShortcutListSize(info.mShortcutTargets);
+                    BinaryDictEncoderUtils.getShortcutListSize(info.mShortcutTargets);
             destination.write((byte)(shortcutListSize >> 8));
             destination.write((byte)(shortcutListSize & 0xFF));
             size += 2;
             final Iterator<WeightedString> shortcutIterator = info.mShortcutTargets.iterator();
             while (shortcutIterator.hasNext()) {
                 final WeightedString target = shortcutIterator.next();
-                destination.write((byte)BinaryDictInputOutput.makeShortcutFlags(
+                destination.write((byte)BinaryDictEncoderUtils.makeShortcutFlags(
                         shortcutIterator.hasNext(), target.mFrequency));
                 size++;
                 size += writeString(destination, target.mWord);
@@ -540,28 +427,28 @@
 
         if (info.mBigrams != null) {
             // TODO: Consolidate this code with the code that computes the size of the bigram list
-            //        in BinaryDictionaryInputOutput#computeActualNodeSize
+            //        in BinaryDictEncoderUtils#computeActualNodeArraySize
             for (int i = 0; i < info.mBigrams.size(); ++i) {
 
                 final int bigramFrequency = info.mBigrams.get(i).mFrequency;
                 int bigramFlags = (i < info.mBigrams.size() - 1)
-                        ? FormatSpec.FLAG_ATTRIBUTE_HAS_NEXT : 0;
+                        ? FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_HAS_NEXT : 0;
                 size++;
                 final int bigramOffset = info.mBigrams.get(i).mAddress - (info.mOriginalAddress
                         + size);
-                bigramFlags |= (bigramOffset < 0) ? FormatSpec.FLAG_ATTRIBUTE_OFFSET_NEGATIVE : 0;
-                switch (BinaryDictInputOutput.getByteSize(bigramOffset)) {
+                bigramFlags |= (bigramOffset < 0) ? FormatSpec.FLAG_BIGRAM_ATTR_OFFSET_NEGATIVE : 0;
+                switch (BinaryDictEncoderUtils.getByteSize(bigramOffset)) {
                 case 1:
-                    bigramFlags |= FormatSpec.FLAG_ATTRIBUTE_ADDRESS_TYPE_ONEBYTE;
+                    bigramFlags |= FormatSpec.FLAG_BIGRAM_ATTR_ADDRESS_TYPE_ONEBYTE;
                     break;
                 case 2:
-                    bigramFlags |= FormatSpec.FLAG_ATTRIBUTE_ADDRESS_TYPE_TWOBYTES;
+                    bigramFlags |= FormatSpec.FLAG_BIGRAM_ATTR_ADDRESS_TYPE_TWOBYTES;
                     break;
                 case 3:
-                    bigramFlags |= FormatSpec.FLAG_ATTRIBUTE_ADDRESS_TYPE_THREEBYTES;
+                    bigramFlags |= FormatSpec.FLAG_BIGRAM_ATTR_ADDRESS_TYPE_THREEBYTES;
                     break;
                 }
-                bigramFlags |= bigramFrequency & FormatSpec.FLAG_ATTRIBUTE_FREQUENCY;
+                bigramFlags |= bigramFrequency & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_FREQUENCY;
                 destination.write((byte)bigramFlags);
                 size += writeVariableAddress(destination, Math.abs(bigramOffset));
             }
@@ -569,82 +456,40 @@
         return size;
     }
 
-    @SuppressWarnings("unused")
-    private static void updateForwardLink(final FusionDictionaryBufferInterface buffer,
-            final int nodeOriginAddress, final int newNodeAddress,
-            final FormatOptions formatOptions) {
-        buffer.position(nodeOriginAddress);
-        int jumpCount = 0;
-        while (jumpCount++ < MAX_JUMPS) {
-            final int count = BinaryDictInputOutput.readCharGroupCount(buffer);
-            for (int i = 0; i < count; ++i) skipCharGroup(buffer, formatOptions);
-            final int forwardLinkAddress = buffer.readUnsignedInt24();
-            if (forwardLinkAddress == FormatSpec.NO_FORWARD_LINK_ADDRESS) {
-                buffer.position(buffer.position() - FormatSpec.FORWARD_LINK_ADDRESS_SIZE);
-                writeSInt24ToBuffer(buffer, newNodeAddress);
-                return;
-            }
-            buffer.position(forwardLinkAddress);
-        }
-        if (DBG && jumpCount >= MAX_JUMPS) {
-            throw new RuntimeException("too many jumps, probably a bug.");
-        }
-    }
-
     /**
-     * Helper method to move a char group to the tail of the file.
+     * Compute the size of the PtNode.
      */
-    private static int moveCharGroup(final OutputStream destination,
-            final FusionDictionaryBufferInterface buffer, final CharGroupInfo info,
-            final int nodeOriginAddress, final int oldGroupAddress,
-            final FormatOptions formatOptions) throws IOException {
-        updateParentAddress(buffer, oldGroupAddress, buffer.limit() + 1, formatOptions);
-        buffer.position(oldGroupAddress);
-        final int currentFlags = buffer.readUnsignedByte();
-        buffer.position(oldGroupAddress);
-        buffer.put((byte)(FormatSpec.FLAG_IS_MOVED | (currentFlags
-                & (~FormatSpec.MASK_MOVE_AND_DELETE_FLAG))));
-        int size = FormatSpec.GROUP_FLAGS_SIZE;
-        updateForwardLink(buffer, nodeOriginAddress, buffer.limit(), formatOptions);
-        size += writeNode(destination, new CharGroupInfo[] { info });
-        return size;
-    }
-
-    /**
-     * Compute the size of the char group.
-     */
-    private static int computeGroupSize(final CharGroupInfo info,
-            final FormatOptions formatOptions) {
-        int size = FormatSpec.GROUP_FLAGS_SIZE + FormatSpec.PARENT_ADDRESS_SIZE
-                + BinaryDictInputOutput.getGroupCharactersSize(info.mCharacters)
-                + BinaryDictInputOutput.getChildrenAddressSize(info.mFlags, formatOptions);
+    static int computePtNodeSize(final PtNodeInfo info, final FormatOptions formatOptions) {
+        int size = FormatSpec.PTNODE_FLAGS_SIZE + FormatSpec.PARENT_ADDRESS_SIZE
+                + BinaryDictEncoderUtils.getPtNodeCharactersSize(info.mCharacters)
+                + getChildrenAddressSize(info.mFlags, formatOptions);
         if ((info.mFlags & FormatSpec.FLAG_IS_TERMINAL) != 0) {
-            size += FormatSpec.GROUP_FREQUENCY_SIZE;
+            size += FormatSpec.PTNODE_FREQUENCY_SIZE;
         }
         if (info.mShortcutTargets != null && !info.mShortcutTargets.isEmpty()) {
-            size += BinaryDictInputOutput.getShortcutListSize(info.mShortcutTargets);
+            size += BinaryDictEncoderUtils.getShortcutListSize(info.mShortcutTargets);
         }
         if (info.mBigrams != null) {
             for (final PendingAttribute attr : info.mBigrams) {
-                size += FormatSpec.GROUP_FLAGS_SIZE;
-                size += BinaryDictInputOutput.getByteSize(attr.mAddress);
+                size += FormatSpec.PTNODE_FLAGS_SIZE;
+                size += BinaryDictEncoderUtils.getByteSize(attr.mAddress);
             }
         }
         return size;
     }
 
     /**
-     * Write a node to the stream.
+     * Write a node array to the stream.
      *
      * @param destination the stream to write.
-     * @param infos groups to be written.
+     * @param infos an array of PtNodeInfo to be written.
      * @return the size written, in bytes.
      * @throws IOException
      */
-    private static int writeNode(final OutputStream destination, final CharGroupInfo[] infos)
+    static int writeNodes(final OutputStream destination, final PtNodeInfo[] infos)
             throws IOException {
-        int size = BinaryDictInputOutput.getGroupCountSize(infos.length);
-        switch (BinaryDictInputOutput.getGroupCountSize(infos.length)) {
+        int size = getPtNodeCountSize(infos.length);
+        switch (getPtNodeCountSize(infos.length)) {
             case 1:
                 destination.write((byte)infos.length);
                 break;
@@ -653,335 +498,13 @@
                 destination.write((byte)(infos.length & 0xFF));
                 break;
             default:
-                throw new RuntimeException("Invalid group count size.");
+                throw new RuntimeException("Invalid node count size.");
         }
-        for (final CharGroupInfo info : infos) size += writeCharGroup(destination, info);
+        for (final PtNodeInfo info : infos) size += writePtNode(destination, info);
         writeSInt24ToStream(destination, FormatSpec.NO_FORWARD_LINK_ADDRESS);
         return size + FormatSpec.FORWARD_LINK_ADDRESS_SIZE;
     }
 
-    /**
-     * Move a group that is referred to by oldGroupOrigin to the tail of the file.
-     * And set the children address to the byte after the group.
-     *
-     * @param nodeOrigin the address of the tail of the file.
-     * @param characters
-     * @param length
-     * @param flags
-     * @param frequency
-     * @param parentAddress
-     * @param shortcutTargets
-     * @param bigrams
-     * @param destination the stream representing the tail of the file.
-     * @param buffer the buffer representing the (constant-size) body of the file.
-     * @param oldNodeOrigin
-     * @param oldGroupOrigin
-     * @param formatOptions
-     * @return the size written, in bytes.
-     * @throws IOException
-     */
-    private static int moveGroup(final int nodeOrigin, final int[] characters, final int length,
-            final int flags, final int frequency, final int parentAddress,
-            final ArrayList<WeightedString> shortcutTargets,
-            final ArrayList<PendingAttribute> bigrams, final OutputStream destination,
-            final FusionDictionaryBufferInterface buffer, final int oldNodeOrigin,
-            final int oldGroupOrigin, final FormatOptions formatOptions) throws IOException {
-        int size = 0;
-        final int newGroupOrigin = nodeOrigin + 1;
-        final int[] writtenCharacters = Arrays.copyOfRange(characters, 0, length);
-        final CharGroupInfo tmpInfo = new CharGroupInfo(newGroupOrigin, -1 /* endAddress */,
-                flags, writtenCharacters, frequency, parentAddress, FormatSpec.NO_CHILDREN_ADDRESS,
-                shortcutTargets, bigrams);
-        size = computeGroupSize(tmpInfo, formatOptions);
-        final CharGroupInfo newInfo = new CharGroupInfo(newGroupOrigin, newGroupOrigin + size,
-                flags, writtenCharacters, frequency, parentAddress,
-                nodeOrigin + 1 + size + FormatSpec.FORWARD_LINK_ADDRESS_SIZE, shortcutTargets,
-                bigrams);
-        moveCharGroup(destination, buffer, newInfo, oldNodeOrigin, oldGroupOrigin, formatOptions);
-        return 1 + size + FormatSpec.FORWARD_LINK_ADDRESS_SIZE;
-    }
-
-    /**
-     * Insert a word into a binary dictionary.
-     *
-     * @param buffer
-     * @param destination
-     * @param word
-     * @param frequency
-     * @param bigramStrings
-     * @param shortcuts
-     * @throws IOException
-     * @throws UnsupportedFormatException
-     */
-    // TODO: Support batch insertion.
-    // TODO: Remove @UsedForTesting once UserHistoryDictionary is implemented by BinaryDictionary.
-    @UsedForTesting
-    public static void insertWord(final FusionDictionaryBufferInterface buffer,
-            final OutputStream destination, final String word, final int frequency,
-            final ArrayList<WeightedString> bigramStrings,
-            final ArrayList<WeightedString> shortcuts, final boolean isNotAWord,
-            final boolean isBlackListEntry)
-                    throws IOException, UnsupportedFormatException {
-        final ArrayList<PendingAttribute> bigrams = new ArrayList<PendingAttribute>();
-        if (bigramStrings != null) {
-            for (final WeightedString bigram : bigramStrings) {
-                int position = getTerminalPosition(buffer, bigram.mWord);
-                if (position == FormatSpec.NOT_VALID_WORD) {
-                    // TODO: figure out what is the correct thing to do here.
-                } else {
-                    bigrams.add(new PendingAttribute(bigram.mFrequency, position));
-                }
-            }
-        }
-
-        final boolean isTerminal = true;
-        final boolean hasBigrams = !bigrams.isEmpty();
-        final boolean hasShortcuts = shortcuts != null && !shortcuts.isEmpty();
-
-        // find the insert position of the word.
-        if (buffer.position() != 0) buffer.position(0);
-        final FileHeader header = BinaryDictInputOutput.readHeader(buffer);
-
-        int wordPos = 0, address = buffer.position(), nodeOriginAddress = buffer.position();
-        final int[] codePoints = FusionDictionary.getCodePoints(word);
-        final int wordLen = codePoints.length;
-
-        for (int depth = 0; depth < Constants.DICTIONARY_MAX_WORD_LENGTH; ++depth) {
-            if (wordPos >= wordLen) break;
-            nodeOriginAddress = buffer.position();
-            int nodeParentAddress = -1;
-            final int charGroupCount = BinaryDictInputOutput.readCharGroupCount(buffer);
-            boolean foundNextGroup = false;
-
-            for (int i = 0; i < charGroupCount; ++i) {
-                address = buffer.position();
-                final CharGroupInfo currentInfo = BinaryDictInputOutput.readCharGroup(buffer,
-                        buffer.position(), header.mFormatOptions);
-                final boolean isMovedGroup = BinaryDictInputOutput.isMovedGroup(currentInfo.mFlags,
-                        header.mFormatOptions);
-                if (isMovedGroup) continue;
-                nodeParentAddress = (currentInfo.mParentAddress == FormatSpec.NO_PARENT_ADDRESS)
-                        ? FormatSpec.NO_PARENT_ADDRESS : currentInfo.mParentAddress + address;
-                boolean matched = true;
-                for (int p = 0; p < currentInfo.mCharacters.length; ++p) {
-                    if (wordPos + p >= wordLen) {
-                        /*
-                         * splitting
-                         * before
-                         *  abcd - ef
-                         *
-                         * insert "abc"
-                         *
-                         * after
-                         *  abc - d - ef
-                         */
-                        final int newNodeAddress = buffer.limit();
-                        final int flags = BinaryDictInputOutput.makeCharGroupFlags(p > 1,
-                                isTerminal, 0, hasShortcuts, hasBigrams, false /* isNotAWord */,
-                                false /* isBlackListEntry */, header.mFormatOptions);
-                        int written = moveGroup(newNodeAddress, currentInfo.mCharacters, p, flags,
-                                frequency, nodeParentAddress, shortcuts, bigrams, destination,
-                                buffer, nodeOriginAddress, address, header.mFormatOptions);
-
-                        final int[] characters2 = Arrays.copyOfRange(currentInfo.mCharacters, p,
-                                currentInfo.mCharacters.length);
-                        if (currentInfo.mChildrenAddress != FormatSpec.NO_CHILDREN_ADDRESS) {
-                            updateParentAddresses(buffer, currentInfo.mChildrenAddress,
-                                    newNodeAddress + written + 1, header.mFormatOptions);
-                        }
-                        final CharGroupInfo newInfo2 = new CharGroupInfo(
-                                newNodeAddress + written + 1, -1 /* endAddress */,
-                                currentInfo.mFlags, characters2, currentInfo.mFrequency,
-                                newNodeAddress + 1, currentInfo.mChildrenAddress,
-                                currentInfo.mShortcutTargets, currentInfo.mBigrams);
-                        writeNode(destination, new CharGroupInfo[] { newInfo2 });
-                        return;
-                    } else if (codePoints[wordPos + p] != currentInfo.mCharacters[p]) {
-                        if (p > 0) {
-                            /*
-                             * splitting
-                             * before
-                             *   ab - cd
-                             *
-                             * insert "ac"
-                             *
-                             * after
-                             *   a - b - cd
-                             *     |
-                             *     - c
-                             */
-
-                            final int newNodeAddress = buffer.limit();
-                            final int childrenAddress = currentInfo.mChildrenAddress;
-
-                            // move prefix
-                            final int prefixFlags = BinaryDictInputOutput.makeCharGroupFlags(p > 1,
-                                    false /* isTerminal */, 0 /* childrenAddressSize*/,
-                                    false /* hasShortcut */, false /* hasBigrams */,
-                                    false /* isNotAWord */, false /* isBlackListEntry */,
-                                    header.mFormatOptions);
-                            int written = moveGroup(newNodeAddress, currentInfo.mCharacters, p,
-                                    prefixFlags, -1 /* frequency */, nodeParentAddress, null, null,
-                                    destination, buffer, nodeOriginAddress, address,
-                                    header.mFormatOptions);
-
-                            final int[] suffixCharacters = Arrays.copyOfRange(
-                                    currentInfo.mCharacters, p, currentInfo.mCharacters.length);
-                            if (currentInfo.mChildrenAddress != FormatSpec.NO_CHILDREN_ADDRESS) {
-                                updateParentAddresses(buffer, currentInfo.mChildrenAddress,
-                                        newNodeAddress + written + 1, header.mFormatOptions);
-                            }
-                            final int suffixFlags = BinaryDictInputOutput.makeCharGroupFlags(
-                                    suffixCharacters.length > 1,
-                                    (currentInfo.mFlags & FormatSpec.FLAG_IS_TERMINAL) != 0,
-                                    0 /* childrenAddressSize */,
-                                    (currentInfo.mFlags & FormatSpec.FLAG_HAS_SHORTCUT_TARGETS)
-                                            != 0,
-                                    (currentInfo.mFlags & FormatSpec.FLAG_HAS_BIGRAMS) != 0,
-                                    isNotAWord, isBlackListEntry, header.mFormatOptions);
-                            final CharGroupInfo suffixInfo = new CharGroupInfo(
-                                    newNodeAddress + written + 1, -1 /* endAddress */, suffixFlags,
-                                    suffixCharacters, currentInfo.mFrequency, newNodeAddress + 1,
-                                    currentInfo.mChildrenAddress, currentInfo.mShortcutTargets,
-                                    currentInfo.mBigrams);
-                            written += computeGroupSize(suffixInfo, header.mFormatOptions) + 1;
-
-                            final int[] newCharacters = Arrays.copyOfRange(codePoints, wordPos + p,
-                                    codePoints.length);
-                            final int flags = BinaryDictInputOutput.makeCharGroupFlags(
-                                    newCharacters.length > 1, isTerminal,
-                                    0 /* childrenAddressSize */, hasShortcuts, hasBigrams,
-                                    isNotAWord, isBlackListEntry, header.mFormatOptions);
-                            final CharGroupInfo newInfo = new CharGroupInfo(
-                                    newNodeAddress + written, -1 /* endAddress */, flags,
-                                    newCharacters, frequency, newNodeAddress + 1,
-                                    FormatSpec.NO_CHILDREN_ADDRESS, shortcuts, bigrams);
-                            writeNode(destination, new CharGroupInfo[] { suffixInfo, newInfo });
-                            return;
-                        }
-                        matched = false;
-                        break;
-                    }
-                }
-
-                if (matched) {
-                    if (wordPos + currentInfo.mCharacters.length == wordLen) {
-                        // the word exists in the dictionary.
-                        // only update group.
-                        final int newNodeAddress = buffer.limit();
-                        final boolean hasMultipleChars = currentInfo.mCharacters.length > 1;
-                        final int flags = BinaryDictInputOutput.makeCharGroupFlags(hasMultipleChars,
-                                isTerminal, 0 /* childrenAddressSize */, hasShortcuts, hasBigrams,
-                                isNotAWord, isBlackListEntry, header.mFormatOptions);
-                        final CharGroupInfo newInfo = new CharGroupInfo(newNodeAddress + 1,
-                                -1 /* endAddress */, flags, currentInfo.mCharacters, frequency,
-                                nodeParentAddress, currentInfo.mChildrenAddress, shortcuts,
-                                bigrams);
-                        moveCharGroup(destination, buffer, newInfo, nodeOriginAddress, address,
-                                header.mFormatOptions);
-                        return;
-                    }
-                    wordPos += currentInfo.mCharacters.length;
-                    if (currentInfo.mChildrenAddress == FormatSpec.NO_CHILDREN_ADDRESS) {
-                        /*
-                         * found the prefix of the word.
-                         * make new node and link to the node from this group.
-                         *
-                         * before
-                         * ab - cd
-                         *
-                         * insert "abcde"
-                         *
-                         * after
-                         * ab - cd - e
-                         */
-                        final int newNodeAddress = buffer.limit();
-                        updateChildrenAddress(buffer, address, newNodeAddress,
-                                header.mFormatOptions);
-                        final int newGroupAddress = newNodeAddress + 1;
-                        final boolean hasMultipleChars = (wordLen - wordPos) > 1;
-                        final int flags = BinaryDictInputOutput.makeCharGroupFlags(hasMultipleChars,
-                                isTerminal, 0 /* childrenAddressSize */, hasShortcuts, hasBigrams,
-                                isNotAWord, isBlackListEntry, header.mFormatOptions);
-                        final int[] characters = Arrays.copyOfRange(codePoints, wordPos, wordLen);
-                        final CharGroupInfo newInfo = new CharGroupInfo(newGroupAddress, -1, flags,
-                                characters, frequency, address, FormatSpec.NO_CHILDREN_ADDRESS,
-                                shortcuts, bigrams);
-                        writeNode(destination, new CharGroupInfo[] { newInfo });
-                        return;
-                    }
-                    buffer.position(currentInfo.mChildrenAddress);
-                    foundNextGroup = true;
-                    break;
-                }
-            }
-
-            if (foundNextGroup) continue;
-
-            // reached the end of the array.
-            final int linkAddressPosition = buffer.position();
-            int nextLink = buffer.readUnsignedInt24();
-            if ((nextLink & MSB24) != 0) {
-                nextLink = -(nextLink & SINT24_MAX);
-            }
-            if (nextLink == FormatSpec.NO_FORWARD_LINK_ADDRESS) {
-                /*
-                 * expand this node.
-                 *
-                 * before
-                 * ab - cd
-                 *
-                 * insert "abef"
-                 *
-                 * after
-                 * ab - cd
-                 *    |
-                 *    - ef
-                 */
-
-                // change the forward link address.
-                final int newNodeAddress = buffer.limit();
-                buffer.position(linkAddressPosition);
-                writeSInt24ToBuffer(buffer, newNodeAddress);
-
-                final int[] characters = Arrays.copyOfRange(codePoints, wordPos, wordLen);
-                final int flags = BinaryDictInputOutput.makeCharGroupFlags(characters.length > 1,
-                        isTerminal, 0 /* childrenAddressSize */, hasShortcuts, hasBigrams,
-                        isNotAWord, isBlackListEntry, header.mFormatOptions);
-                final CharGroupInfo newInfo = new CharGroupInfo(newNodeAddress + 1,
-                        -1 /* endAddress */, flags, characters, frequency, nodeParentAddress,
-                        FormatSpec.NO_CHILDREN_ADDRESS, shortcuts, bigrams);
-                writeNode(destination, new CharGroupInfo[]{ newInfo });
-                return;
-            } else {
-                depth--;
-                buffer.position(nextLink);
-            }
-        }
-    }
-
-    /**
-     * Find a word from the buffer.
-     *
-     * @param buffer the buffer representing the body of the dictionary file.
-     * @param word the word searched
-     * @return the found group
-     * @throws IOException
-     * @throws UnsupportedFormatException
-     */
-    @UsedForTesting
-    public static CharGroupInfo findWordFromBuffer(final FusionDictionaryBufferInterface buffer,
-            final String word) throws IOException, UnsupportedFormatException {
-        int position = getTerminalPosition(buffer, word);
-        if (position != FormatSpec.NOT_VALID_WORD) {
-            buffer.position(0);
-            final FileHeader header = BinaryDictInputOutput.readHeader(buffer);
-            buffer.position(position);
-            return BinaryDictInputOutput.readCharGroup(buffer, position, header.mFormatOptions);
-        }
-        return null;
-    }
-
     private static final int HEADER_READING_BUFFER_SIZE = 16384;
     /**
      * Convenience method to read the header of a binary file.
@@ -992,20 +515,27 @@
      * @param offset The offset in the file where to start reading the data.
      * @param length The length of the data file.
      */
-    public static FileHeader getDictionaryFileHeader(
+    private static FileHeader getDictionaryFileHeader(
             final File file, final long offset, final long length)
             throws FileNotFoundException, IOException, UnsupportedFormatException {
         final byte[] buffer = new byte[HEADER_READING_BUFFER_SIZE];
-        final FileInputStream inStream = new FileInputStream(file);
-        try {
-            inStream.read(buffer);
-            final BinaryDictInputOutput.ByteBufferWrapper wrapper =
-                    new BinaryDictInputOutput.ByteBufferWrapper(inStream.getChannel().map(
-                            FileChannel.MapMode.READ_ONLY, offset, length));
-            return BinaryDictInputOutput.readHeader(wrapper);
-        } finally {
-            inStream.close();
-        }
+        final DictDecoder dictDecoder = FormatSpec.getDictDecoder(file,
+                new DictDecoder.DictionaryBufferFactory() {
+                    @Override
+                    public DictBuffer getDictionaryBuffer(File file)
+                            throws FileNotFoundException, IOException {
+                        final FileInputStream inStream = new FileInputStream(file);
+                        try {
+                            inStream.skip(offset);
+                            inStream.read(buffer);
+                            return new ByteArrayDictBuffer(buffer);
+                        } finally {
+                            inStream.close();
+                        }
+                    }
+                }
+        );
+        return dictDecoder.readHeader();
     }
 
     public static FileHeader getDictionaryFileHeaderOrNull(final File file, final long offset,
@@ -1019,4 +549,83 @@
             return null;
         }
     }
+
+    /**
+     * Helper method to hide the actual value of the no children address.
+     */
+    public static boolean hasChildrenAddress(final int address) {
+        return FormatSpec.NO_CHILDREN_ADDRESS != address;
+    }
+
+    /**
+     * Helper method to check whether the node is moved.
+     */
+    public static boolean isMovedPtNode(final int flags, final FormatOptions options) {
+        return options.mSupportsDynamicUpdate
+                && ((flags & FormatSpec.MASK_CHILDREN_ADDRESS_TYPE) == FormatSpec.FLAG_IS_MOVED);
+    }
+
+    /**
+     * Helper method to check whether the dictionary can be updated dynamically.
+     */
+    public static boolean supportsDynamicUpdate(final FormatOptions options) {
+        return options.mVersion >= FormatSpec.FIRST_VERSION_WITH_DYNAMIC_UPDATE
+                && options.mSupportsDynamicUpdate;
+    }
+
+    /**
+     * Helper method to check whether the node is deleted.
+     */
+    public static boolean isDeletedPtNode(final int flags, final FormatOptions formatOptions) {
+        return formatOptions.mSupportsDynamicUpdate
+                && ((flags & FormatSpec.MASK_CHILDREN_ADDRESS_TYPE) == FormatSpec.FLAG_IS_DELETED);
+    }
+
+    /**
+     * Compute the binary size of the node count
+     * @param count the node count
+     * @return the size of the node count, either 1 or 2 bytes.
+     */
+    public static int getPtNodeCountSize(final int count) {
+        if (FormatSpec.MAX_PTNODES_FOR_ONE_BYTE_PTNODE_COUNT >= count) {
+            return 1;
+        } else if (FormatSpec.MAX_PTNODES_IN_A_PT_NODE_ARRAY >= count) {
+            return 2;
+        } else {
+            throw new RuntimeException("Can't have more than "
+                    + FormatSpec.MAX_PTNODES_IN_A_PT_NODE_ARRAY + " PtNode in a PtNodeArray (found "
+                    + count + ")");
+        }
+    }
+
+    static int getChildrenAddressSize(final int optionFlags,
+            final FormatOptions formatOptions) {
+        if (formatOptions.mSupportsDynamicUpdate) return FormatSpec.SIGNED_CHILDREN_ADDRESS_SIZE;
+        switch (optionFlags & FormatSpec.MASK_CHILDREN_ADDRESS_TYPE) {
+            case FormatSpec.FLAG_CHILDREN_ADDRESS_TYPE_ONEBYTE:
+                return 1;
+            case FormatSpec.FLAG_CHILDREN_ADDRESS_TYPE_TWOBYTES:
+                return 2;
+            case FormatSpec.FLAG_CHILDREN_ADDRESS_TYPE_THREEBYTES:
+                return 3;
+            case FormatSpec.FLAG_CHILDREN_ADDRESS_TYPE_NOADDRESS:
+            default:
+                return 0;
+        }
+    }
+
+    /**
+     * Calculate bigram frequency from compressed value
+     *
+     * @param unigramFrequency
+     * @param bigramFrequency compressed frequency
+     * @return approximate bigram frequency
+     */
+    public static int reconstructBigramFrequency(final int unigramFrequency,
+            final int bigramFrequency) {
+        final float stepSize = (FormatSpec.MAX_TERMINAL_FREQUENCY - unigramFrequency)
+                / (1.5f + FormatSpec.MAX_BIGRAM_FREQUENCY);
+        final float resultFreqFloat = unigramFrequency + stepSize * (bigramFrequency + 1.0f);
+        return (int)resultFreqFloat;
+    }
 }
diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java
deleted file mode 100644
index 1b187d8..0000000
--- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java
+++ /dev/null
@@ -1,1838 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.latin.makedict;
-
-import com.android.inputmethod.annotations.UsedForTesting;
-import com.android.inputmethod.latin.makedict.FormatSpec.FileHeader;
-import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions;
-import com.android.inputmethod.latin.makedict.FusionDictionary.CharGroup;
-import com.android.inputmethod.latin.makedict.FusionDictionary.DictionaryOptions;
-import com.android.inputmethod.latin.makedict.FusionDictionary.Node;
-import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
-
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.nio.ByteBuffer;
-import java.nio.channels.FileChannel;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.TreeMap;
-
-/**
- * Reads and writes XML files for a FusionDictionary.
- *
- * All the methods in this class are static.
- */
-public final class BinaryDictInputOutput {
-
-    private static final boolean DBG = MakedictLog.DBG;
-
-    // Arbitrary limit to how much passes we consider address size compression should
-    // terminate in. At the time of this writing, our largest dictionary completes
-    // compression in five passes.
-    // If the number of passes exceeds this number, makedict bails with an exception on
-    // suspicion that a bug might be causing an infinite loop.
-    private static final int MAX_PASSES = 24;
-    private static final int MAX_JUMPS = 12;
-
-    @UsedForTesting
-    public interface FusionDictionaryBufferInterface {
-        public int readUnsignedByte();
-        public int readUnsignedShort();
-        public int readUnsignedInt24();
-        public int readInt();
-        public int position();
-        public void position(int newPosition);
-        public void put(final byte b);
-        public int limit();
-        public int capacity();
-    }
-
-    public static final class ByteBufferWrapper implements FusionDictionaryBufferInterface {
-        private ByteBuffer mBuffer;
-
-        public ByteBufferWrapper(final ByteBuffer buffer) {
-            mBuffer = buffer;
-        }
-
-        @Override
-        public int readUnsignedByte() {
-            return mBuffer.get() & 0xFF;
-        }
-
-        @Override
-        public int readUnsignedShort() {
-            return mBuffer.getShort() & 0xFFFF;
-        }
-
-        @Override
-        public int readUnsignedInt24() {
-            final int retval = readUnsignedByte();
-            return (retval << 16) + readUnsignedShort();
-        }
-
-        @Override
-        public int readInt() {
-            return mBuffer.getInt();
-        }
-
-        @Override
-        public int position() {
-            return mBuffer.position();
-        }
-
-        @Override
-        public void position(int newPos) {
-            mBuffer.position(newPos);
-        }
-
-        @Override
-        public void put(final byte b) {
-            mBuffer.put(b);
-        }
-
-        @Override
-        public int limit() {
-            return mBuffer.limit();
-        }
-
-        @Override
-        public int capacity() {
-            return mBuffer.capacity();
-        }
-    }
-
-    /**
-     * A class grouping utility function for our specific character encoding.
-     */
-    static final class CharEncoding {
-        private static final int MINIMAL_ONE_BYTE_CHARACTER_VALUE = 0x20;
-        private static final int MAXIMAL_ONE_BYTE_CHARACTER_VALUE = 0xFF;
-
-        /**
-         * Helper method to find out whether this code fits on one byte
-         */
-        private static boolean fitsOnOneByte(final int character) {
-            return character >= MINIMAL_ONE_BYTE_CHARACTER_VALUE
-                    && character <= MAXIMAL_ONE_BYTE_CHARACTER_VALUE;
-        }
-
-        /**
-         * Compute the size of a character given its character code.
-         *
-         * Char format is:
-         * 1 byte = bbbbbbbb match
-         * case 000xxxxx: xxxxx << 16 + next byte << 8 + next byte
-         * else: if 00011111 (= 0x1F) : this is the terminator. This is a relevant choice because
-         *       unicode code points range from 0 to 0x10FFFF, so any 3-byte value starting with
-         *       00011111 would be outside unicode.
-         * else: iso-latin-1 code
-         * This allows for the whole unicode range to be encoded, including chars outside of
-         * the BMP. Also everything in the iso-latin-1 charset is only 1 byte, except control
-         * characters which should never happen anyway (and still work, but take 3 bytes).
-         *
-         * @param character the character code.
-         * @return the size in binary encoded-form, either 1 or 3 bytes.
-         */
-        static int getCharSize(final int character) {
-            // See char encoding in FusionDictionary.java
-            if (fitsOnOneByte(character)) return 1;
-            if (FormatSpec.INVALID_CHARACTER == character) return 1;
-            return 3;
-        }
-
-        /**
-         * Compute the byte size of a character array.
-         */
-        private static int getCharArraySize(final int[] chars) {
-            int size = 0;
-            for (int character : chars) size += getCharSize(character);
-            return size;
-        }
-
-        /**
-         * Writes a char array to a byte buffer.
-         *
-         * @param codePoints the code point array to write.
-         * @param buffer the byte buffer to write to.
-         * @param index the index in buffer to write the character array to.
-         * @return the index after the last character.
-         */
-        private static int writeCharArray(final int[] codePoints, final byte[] buffer, int index) {
-            for (int codePoint : codePoints) {
-                if (1 == getCharSize(codePoint)) {
-                    buffer[index++] = (byte)codePoint;
-                } else {
-                    buffer[index++] = (byte)(0xFF & (codePoint >> 16));
-                    buffer[index++] = (byte)(0xFF & (codePoint >> 8));
-                    buffer[index++] = (byte)(0xFF & codePoint);
-                }
-            }
-            return index;
-        }
-
-        /**
-         * Writes a string with our character format to a byte buffer.
-         *
-         * This will also write the terminator byte.
-         *
-         * @param buffer the byte buffer to write to.
-         * @param origin the offset to write from.
-         * @param word the string to write.
-         * @return the size written, in bytes.
-         */
-        private static int writeString(final byte[] buffer, final int origin,
-                final String word) {
-            final int length = word.length();
-            int index = origin;
-            for (int i = 0; i < length; i = word.offsetByCodePoints(i, 1)) {
-                final int codePoint = word.codePointAt(i);
-                if (1 == getCharSize(codePoint)) {
-                    buffer[index++] = (byte)codePoint;
-                } else {
-                    buffer[index++] = (byte)(0xFF & (codePoint >> 16));
-                    buffer[index++] = (byte)(0xFF & (codePoint >> 8));
-                    buffer[index++] = (byte)(0xFF & codePoint);
-                }
-            }
-            buffer[index++] = FormatSpec.GROUP_CHARACTERS_TERMINATOR;
-            return index - origin;
-        }
-
-        /**
-         * Writes a string with our character format to a ByteArrayOutputStream.
-         *
-         * This will also write the terminator byte.
-         *
-         * @param buffer the ByteArrayOutputStream to write to.
-         * @param word the string to write.
-         */
-        private static void writeString(final ByteArrayOutputStream buffer, final String word) {
-            final int length = word.length();
-            for (int i = 0; i < length; i = word.offsetByCodePoints(i, 1)) {
-                final int codePoint = word.codePointAt(i);
-                if (1 == getCharSize(codePoint)) {
-                    buffer.write((byte) codePoint);
-                } else {
-                    buffer.write((byte) (0xFF & (codePoint >> 16)));
-                    buffer.write((byte) (0xFF & (codePoint >> 8)));
-                    buffer.write((byte) (0xFF & codePoint));
-                }
-            }
-            buffer.write(FormatSpec.GROUP_CHARACTERS_TERMINATOR);
-        }
-
-        /**
-         * Reads a string from a buffer. This is the converse of the above method.
-         */
-        private static String readString(final FusionDictionaryBufferInterface buffer) {
-            final StringBuilder s = new StringBuilder();
-            int character = readChar(buffer);
-            while (character != FormatSpec.INVALID_CHARACTER) {
-                s.appendCodePoint(character);
-                character = readChar(buffer);
-            }
-            return s.toString();
-        }
-
-        /**
-         * Reads a character from the buffer.
-         *
-         * This follows the character format documented earlier in this source file.
-         *
-         * @param buffer the buffer, positioned over an encoded character.
-         * @return the character code.
-         */
-        static int readChar(final FusionDictionaryBufferInterface buffer) {
-            int character = buffer.readUnsignedByte();
-            if (!fitsOnOneByte(character)) {
-                if (FormatSpec.GROUP_CHARACTERS_TERMINATOR == character) {
-                    return FormatSpec.INVALID_CHARACTER;
-                }
-                character <<= 16;
-                character += buffer.readUnsignedShort();
-            }
-            return character;
-        }
-    }
-
-    /**
-     * Compute the binary size of the character array.
-     *
-     * If only one character, this is the size of this character. If many, it's the sum of their
-     * sizes + 1 byte for the terminator.
-     *
-     * @param characters the character array
-     * @return the size of the char array, including the terminator if any
-     */
-    static int getGroupCharactersSize(final int[] characters) {
-        int size = CharEncoding.getCharArraySize(characters);
-        if (characters.length > 1) size += FormatSpec.GROUP_TERMINATOR_SIZE;
-        return size;
-    }
-
-    /**
-     * Compute the binary size of the character array in a group
-     *
-     * If only one character, this is the size of this character. If many, it's the sum of their
-     * sizes + 1 byte for the terminator.
-     *
-     * @param group the group
-     * @return the size of the char array, including the terminator if any
-     */
-    private static int getGroupCharactersSize(final CharGroup group) {
-        return getGroupCharactersSize(group.mChars);
-    }
-
-    /**
-     * Compute the binary size of the group count
-     * @param count the group count
-     * @return the size of the group count, either 1 or 2 bytes.
-     */
-    public static int getGroupCountSize(final int count) {
-        if (FormatSpec.MAX_CHARGROUPS_FOR_ONE_BYTE_CHARGROUP_COUNT >= count) {
-            return 1;
-        } else if (FormatSpec.MAX_CHARGROUPS_IN_A_NODE >= count) {
-            return 2;
-        } else {
-            throw new RuntimeException("Can't have more than "
-                    + FormatSpec.MAX_CHARGROUPS_IN_A_NODE + " groups in a node (found " + count
-                    + ")");
-        }
-    }
-
-    /**
-     * Compute the binary size of the group count for a node
-     * @param node the node
-     * @return the size of the group count, either 1 or 2 bytes.
-     */
-    private static int getGroupCountSize(final Node node) {
-        return getGroupCountSize(node.mData.size());
-    }
-
-    /**
-     * Compute the size of a shortcut in bytes.
-     */
-    private static int getShortcutSize(final WeightedString shortcut) {
-        int size = FormatSpec.GROUP_ATTRIBUTE_FLAGS_SIZE;
-        final String word = shortcut.mWord;
-        final int length = word.length();
-        for (int i = 0; i < length; i = word.offsetByCodePoints(i, 1)) {
-            final int codePoint = word.codePointAt(i);
-            size += CharEncoding.getCharSize(codePoint);
-        }
-        size += FormatSpec.GROUP_TERMINATOR_SIZE;
-        return size;
-    }
-
-    /**
-     * Compute the size of a shortcut list in bytes.
-     *
-     * This is known in advance and does not change according to position in the file
-     * like address lists do.
-     */
-    static int getShortcutListSize(final ArrayList<WeightedString> shortcutList) {
-        if (null == shortcutList) return 0;
-        int size = FormatSpec.GROUP_SHORTCUT_LIST_SIZE_SIZE;
-        for (final WeightedString shortcut : shortcutList) {
-            size += getShortcutSize(shortcut);
-        }
-        return size;
-    }
-
-    /**
-     * Compute the maximum size of a CharGroup, assuming 3-byte addresses for everything.
-     *
-     * @param group the CharGroup to compute the size of.
-     * @param options file format options.
-     * @return the maximum size of the group.
-     */
-    private static int getCharGroupMaximumSize(final CharGroup group, final FormatOptions options) {
-        int size = getGroupHeaderSize(group, options);
-        // If terminal, one byte for the frequency
-        if (group.isTerminal()) size += FormatSpec.GROUP_FREQUENCY_SIZE;
-        size += FormatSpec.GROUP_MAX_ADDRESS_SIZE; // For children address
-        size += getShortcutListSize(group.mShortcutTargets);
-        if (null != group.mBigrams) {
-            size += (FormatSpec.GROUP_ATTRIBUTE_FLAGS_SIZE
-                    + FormatSpec.GROUP_ATTRIBUTE_MAX_ADDRESS_SIZE)
-                    * group.mBigrams.size();
-        }
-        return size;
-    }
-
-    /**
-     * Compute the maximum size of a node, assuming 3-byte addresses for everything, and caches
-     * it in the 'actualSize' member of the node.
-     *
-     * @param node the node to compute the maximum size of.
-     * @param options file format options.
-     */
-    private static void calculateNodeMaximumSize(final Node node, final FormatOptions options) {
-        int size = getGroupCountSize(node);
-        for (CharGroup g : node.mData) {
-            final int groupSize = getCharGroupMaximumSize(g, options);
-            g.mCachedSize = groupSize;
-            size += groupSize;
-        }
-        if (options.mSupportsDynamicUpdate) {
-            size += FormatSpec.FORWARD_LINK_ADDRESS_SIZE;
-        }
-        node.mCachedSize = size;
-    }
-
-    /**
-     * Helper method to hide the actual value of the no children address.
-     */
-    public static boolean hasChildrenAddress(final int address) {
-        return FormatSpec.NO_CHILDREN_ADDRESS != address;
-    }
-
-    /**
-     * Helper method to check whether the group is moved.
-     */
-    public static boolean isMovedGroup(final int flags, final FormatOptions options) {
-        return options.mSupportsDynamicUpdate
-                && ((flags & FormatSpec.MASK_GROUP_ADDRESS_TYPE) == FormatSpec.FLAG_IS_MOVED);
-    }
-
-    /**
-     * Helper method to check whether the group is deleted.
-     */
-    public static boolean isDeletedGroup(final int flags, final FormatOptions formatOptions) {
-        return formatOptions.mSupportsDynamicUpdate
-                && ((flags & FormatSpec.MASK_GROUP_ADDRESS_TYPE) == FormatSpec.FLAG_IS_DELETED);
-    }
-
-    /**
-     * Helper method to check whether the dictionary can be updated dynamically.
-     */
-    public static boolean supportsDynamicUpdate(final FormatOptions options) {
-        return options.mVersion >= FormatSpec.FIRST_VERSION_WITH_DYNAMIC_UPDATE
-                && options.mSupportsDynamicUpdate;
-    }
-
-    /**
-     * Compute the size of the header (flag + [parent address] + characters size) of a CharGroup.
-     *
-     * @param group the group of which to compute the size of the header
-     * @param options file format options.
-     */
-    private static int getGroupHeaderSize(final CharGroup group, final FormatOptions options) {
-        if (supportsDynamicUpdate(options)) {
-            return FormatSpec.GROUP_FLAGS_SIZE + FormatSpec.PARENT_ADDRESS_SIZE
-                    + getGroupCharactersSize(group);
-        } else {
-            return FormatSpec.GROUP_FLAGS_SIZE + getGroupCharactersSize(group);
-        }
-    }
-
-    private static final int UINT8_MAX = 0xFF;
-    private static final int UINT16_MAX = 0xFFFF;
-    private static final int UINT24_MAX = 0xFFFFFF;
-
-    /**
-     * Compute the size, in bytes, that an address will occupy.
-     *
-     * This can be used either for children addresses (which are always positive) or for
-     * attribute, which may be positive or negative but
-     * store their sign bit separately.
-     *
-     * @param address the address
-     * @return the byte size.
-     */
-    static int getByteSize(final int address) {
-        assert(address <= UINT24_MAX);
-        if (!hasChildrenAddress(address)) {
-            return 0;
-        } else if (Math.abs(address) <= UINT8_MAX) {
-            return 1;
-        } else if (Math.abs(address) <= UINT16_MAX) {
-            return 2;
-        } else {
-            return 3;
-        }
-    }
-
-    private static final int SINT24_MAX = 0x7FFFFF;
-    private static final int MSB8 = 0x80;
-    private static final int MSB24 = 0x800000;
-
-    // End utility methods.
-
-    // This method is responsible for finding a nice ordering of the nodes that favors run-time
-    // cache performance and dictionary size.
-    /* package for tests */ static ArrayList<Node> flattenTree(final Node root) {
-        final int treeSize = FusionDictionary.countCharGroups(root);
-        MakedictLog.i("Counted nodes : " + treeSize);
-        final ArrayList<Node> flatTree = new ArrayList<Node>(treeSize);
-        return flattenTreeInner(flatTree, root);
-    }
-
-    private static ArrayList<Node> flattenTreeInner(final ArrayList<Node> list, final Node node) {
-        // Removing the node is necessary if the tails are merged, because we would then
-        // add the same node several times when we only want it once. A number of places in
-        // the code also depends on any node being only once in the list.
-        // Merging tails can only be done if there are no attributes. Searching for attributes
-        // in LatinIME code depends on a total breadth-first ordering, which merging tails
-        // breaks. If there are no attributes, it should be fine (and reduce the file size)
-        // to merge tails, and removing the node from the list would be necessary. However,
-        // we don't merge tails because breaking the breadth-first ordering would result in
-        // extreme overhead at bigram lookup time (it would make the search function O(n) instead
-        // of the current O(log(n)), where n=number of nodes in the dictionary which is pretty
-        // high).
-        // If no nodes are ever merged, we can't have the same node twice in the list, hence
-        // searching for duplicates in unnecessary. It is also very performance consuming,
-        // since `list' is an ArrayList so it's an O(n) operation that runs on all nodes, making
-        // this simple list.remove operation O(n*n) overall. On Android this overhead is very
-        // high.
-        // For future reference, the code to remove duplicate is a simple : list.remove(node);
-        list.add(node);
-        final ArrayList<CharGroup> branches = node.mData;
-        final int nodeSize = branches.size();
-        for (CharGroup group : branches) {
-            if (null != group.mChildren) flattenTreeInner(list, group.mChildren);
-        }
-        return list;
-    }
-
-    /**
-     * Get the offset from a position inside a current node to a target node, during update.
-     *
-     * If the current node is before the target node, the target node has not been updated yet,
-     * so we should return the offset from the old position of the current node to the old position
-     * of the target node. If on the other hand the target is before the current node, it already
-     * has been updated, so we should return the offset from the new position in the current node
-     * to the new position in the target node.
-     * @param currentNode the node containing the CharGroup where the offset will be written
-     * @param offsetFromStartOfCurrentNode the offset, in bytes, from the start of currentNode
-     * @param targetNode the target node to get the offset to
-     * @return the offset to the target node
-     */
-    private static int getOffsetToTargetNodeDuringUpdate(final Node currentNode,
-            final int offsetFromStartOfCurrentNode, final Node targetNode) {
-        final boolean isTargetBeforeCurrent = (targetNode.mCachedAddressBeforeUpdate
-                < currentNode.mCachedAddressBeforeUpdate);
-        if (isTargetBeforeCurrent) {
-            return targetNode.mCachedAddressAfterUpdate
-                    - (currentNode.mCachedAddressAfterUpdate + offsetFromStartOfCurrentNode);
-        } else {
-            return targetNode.mCachedAddressBeforeUpdate
-                    - (currentNode.mCachedAddressBeforeUpdate + offsetFromStartOfCurrentNode);
-        }
-    }
-
-    /**
-     * Get the offset from a position inside a current node to a target CharGroup, during update.
-     * @param currentNode the node containing the CharGroup where the offset will be written
-     * @param offsetFromStartOfCurrentNode the offset, in bytes, from the start of currentNode
-     * @param targetCharGroup the target CharGroup to get the offset to
-     * @return the offset to the target CharGroup
-     */
-    // TODO: is there any way to factorize this method with the one above?
-    private static int getOffsetToTargetCharGroupDuringUpdate(final Node currentNode,
-            final int offsetFromStartOfCurrentNode, final CharGroup targetCharGroup) {
-        final int oldOffsetBasePoint = currentNode.mCachedAddressBeforeUpdate
-                + offsetFromStartOfCurrentNode;
-        final boolean isTargetBeforeCurrent = (targetCharGroup.mCachedAddressBeforeUpdate
-                < oldOffsetBasePoint);
-        // If the target is before the current node, then its address has already been updated.
-        // We can use the AfterUpdate member, and compare it to our own member after update.
-        // Otherwise, the AfterUpdate member is not updated yet, so we need to use the BeforeUpdate
-        // member, and of course we have to compare this to our own address before update.
-        if (isTargetBeforeCurrent) {
-            final int newOffsetBasePoint = currentNode.mCachedAddressAfterUpdate
-                    + offsetFromStartOfCurrentNode;
-            return targetCharGroup.mCachedAddressAfterUpdate - newOffsetBasePoint;
-        } else {
-            return targetCharGroup.mCachedAddressBeforeUpdate - oldOffsetBasePoint;
-        }
-    }
-
-    /**
-     * Computes the actual node size, based on the cached addresses of the children nodes.
-     *
-     * Each node stores its tentative address. During dictionary address computing, these
-     * are not final, but they can be used to compute the node size (the node size depends
-     * on the address of the children because the number of bytes necessary to store an
-     * address depends on its numeric value. The return value indicates whether the node
-     * contents (as in, any of the addresses stored in the cache fields) have changed with
-     * respect to their previous value.
-     *
-     * @param node the node to compute the size of.
-     * @param dict the dictionary in which the word/attributes are to be found.
-     * @param formatOptions file format options.
-     * @return false if none of the cached addresses inside the node changed, true otherwise.
-     */
-    private static boolean computeActualNodeSize(final Node node, final FusionDictionary dict,
-            final FormatOptions formatOptions) {
-        boolean changed = false;
-        int size = getGroupCountSize(node);
-        for (CharGroup group : node.mData) {
-            group.mCachedAddressAfterUpdate = node.mCachedAddressAfterUpdate + size;
-            if (group.mCachedAddressAfterUpdate != group.mCachedAddressBeforeUpdate) {
-                changed = true;
-            }
-            int groupSize = getGroupHeaderSize(group, formatOptions);
-            if (group.isTerminal()) groupSize += FormatSpec.GROUP_FREQUENCY_SIZE;
-            if (null == group.mChildren && formatOptions.mSupportsDynamicUpdate) {
-                groupSize += FormatSpec.SIGNED_CHILDREN_ADDRESS_SIZE;
-            } else if (null != group.mChildren) {
-                if (formatOptions.mSupportsDynamicUpdate) {
-                    groupSize += FormatSpec.SIGNED_CHILDREN_ADDRESS_SIZE;
-                } else {
-                    groupSize += getByteSize(getOffsetToTargetNodeDuringUpdate(node,
-                            groupSize + size, group.mChildren));
-                }
-            }
-            groupSize += getShortcutListSize(group.mShortcutTargets);
-            if (null != group.mBigrams) {
-                for (WeightedString bigram : group.mBigrams) {
-                    final int offset = getOffsetToTargetCharGroupDuringUpdate(node,
-                            groupSize + size + FormatSpec.GROUP_FLAGS_SIZE,
-                            FusionDictionary.findWordInTree(dict.mRoot, bigram.mWord));
-                    groupSize += getByteSize(offset) + FormatSpec.GROUP_FLAGS_SIZE;
-                }
-            }
-            group.mCachedSize = groupSize;
-            size += groupSize;
-        }
-        if (formatOptions.mSupportsDynamicUpdate) {
-            size += FormatSpec.FORWARD_LINK_ADDRESS_SIZE;
-        }
-        if (node.mCachedSize != size) {
-            node.mCachedSize = size;
-            changed = true;
-        }
-        return changed;
-    }
-
-    /**
-     * Initializes the cached addresses of nodes from their size.
-     *
-     * @param flatNodes the array of nodes.
-     * @param formatOptions file format options.
-     * @return the byte size of the entire stack.
-     */
-    private static int initializeNodesCachedAddresses(final ArrayList<Node> flatNodes,
-            final FormatOptions formatOptions) {
-        int nodeOffset = 0;
-        for (final Node n : flatNodes) {
-            n.mCachedAddressBeforeUpdate = nodeOffset;
-            int groupCountSize = getGroupCountSize(n);
-            int groupOffset = 0;
-            for (final CharGroup g : n.mData) {
-                g.mCachedAddressBeforeUpdate = g.mCachedAddressAfterUpdate =
-                        groupCountSize + nodeOffset + groupOffset;
-                groupOffset += g.mCachedSize;
-            }
-            final int nodeSize = groupCountSize + groupOffset
-                    + (formatOptions.mSupportsDynamicUpdate
-                            ? FormatSpec.FORWARD_LINK_ADDRESS_SIZE : 0);
-            nodeOffset += n.mCachedSize;
-        }
-        return nodeOffset;
-    }
-
-    /**
-     * Updates the cached addresses of nodes after recomputing their new positions.
-     *
-     * @param flatNodes the array of nodes.
-     */
-    private static void updateNodeCachedAddresses(final ArrayList<Node> flatNodes) {
-        for (final Node n : flatNodes) {
-            n.mCachedAddressBeforeUpdate = n.mCachedAddressAfterUpdate;
-            for (final CharGroup g : n.mData) {
-                g.mCachedAddressBeforeUpdate = g.mCachedAddressAfterUpdate;
-            }
-        }
-    }
-
-    /**
-     * Compute the cached parent addresses after all has been updated.
-     *
-     * The parent addresses are used by some binary formats at write-to-disk time. Not all formats
-     * need them. In particular, version 2 does not need them, and version 3 does.
-     *
-     * @param flatNodes the flat array of nodes to fill in
-     */
-    private static void computeParentAddresses(final ArrayList<Node> flatNodes) {
-        for (final Node node : flatNodes) {
-            for (final CharGroup group : node.mData) {
-                if (null != group.mChildren) {
-                    // Assign my address to children's parent address
-                    // Here BeforeUpdate and AfterUpdate addresses have the same value, so it
-                    // does not matter which we use.
-                    group.mChildren.mCachedParentAddress = group.mCachedAddressAfterUpdate
-                            - group.mChildren.mCachedAddressAfterUpdate;
-                }
-            }
-        }
-    }
-
-    /**
-     * Compute the addresses and sizes of an ordered node array.
-     *
-     * This method takes a node array and will update its cached address and size values
-     * so that they can be written into a file. It determines the smallest size each of the
-     * nodes can be given the addresses of its children and attributes, and store that into
-     * each node.
-     * The order of the node is given by the order of the array. This method makes no effort
-     * to find a good order; it only mechanically computes the size this order results in.
-     *
-     * @param dict the dictionary
-     * @param flatNodes the ordered array of nodes
-     * @param formatOptions file format options.
-     * @return the same array it was passed. The nodes have been updated for address and size.
-     */
-    private static ArrayList<Node> computeAddresses(final FusionDictionary dict,
-            final ArrayList<Node> flatNodes, final FormatOptions formatOptions) {
-        // First get the worst possible sizes and offsets
-        for (final Node n : flatNodes) calculateNodeMaximumSize(n, formatOptions);
-        final int offset = initializeNodesCachedAddresses(flatNodes, formatOptions);
-
-        MakedictLog.i("Compressing the array addresses. Original size : " + offset);
-        MakedictLog.i("(Recursively seen size : " + offset + ")");
-
-        int passes = 0;
-        boolean changesDone = false;
-        do {
-            changesDone = false;
-            int nodeStartOffset = 0;
-            for (final Node n : flatNodes) {
-                n.mCachedAddressAfterUpdate = nodeStartOffset;
-                final int oldNodeSize = n.mCachedSize;
-                final boolean changed = computeActualNodeSize(n, dict, formatOptions);
-                final int newNodeSize = n.mCachedSize;
-                if (oldNodeSize < newNodeSize) throw new RuntimeException("Increased size ?!");
-                nodeStartOffset += newNodeSize;
-                changesDone |= changed;
-            }
-            updateNodeCachedAddresses(flatNodes);
-            ++passes;
-            if (passes > MAX_PASSES) throw new RuntimeException("Too many passes - probably a bug");
-        } while (changesDone);
-
-        if (formatOptions.mSupportsDynamicUpdate) {
-            computeParentAddresses(flatNodes);
-        }
-        final Node lastNode = flatNodes.get(flatNodes.size() - 1);
-        MakedictLog.i("Compression complete in " + passes + " passes.");
-        MakedictLog.i("After address compression : "
-                + (lastNode.mCachedAddressAfterUpdate + lastNode.mCachedSize));
-
-        return flatNodes;
-    }
-
-    /**
-     * Sanity-checking method.
-     *
-     * This method checks an array of node for juxtaposition, that is, it will do
-     * nothing if each node's cached address is actually the previous node's address
-     * plus the previous node's size.
-     * If this is not the case, it will throw an exception.
-     *
-     * @param array the array node to check
-     */
-    private static void checkFlatNodeArray(final ArrayList<Node> array) {
-        int offset = 0;
-        int index = 0;
-        for (final Node n : array) {
-            // BeforeUpdate and AfterUpdate addresses are the same here, so it does not matter
-            // which we use.
-            if (n.mCachedAddressAfterUpdate != offset) {
-                throw new RuntimeException("Wrong address for node " + index
-                        + " : expected " + offset + ", got " + n.mCachedAddressAfterUpdate);
-            }
-            ++index;
-            offset += n.mCachedSize;
-        }
-    }
-
-    /**
-     * Helper method to write a variable-size address to a file.
-     *
-     * @param buffer the buffer to write to.
-     * @param index the index in the buffer to write the address to.
-     * @param address the address to write.
-     * @return the size in bytes the address actually took.
-     */
-    private static int writeVariableAddress(final byte[] buffer, int index, final int address) {
-        switch (getByteSize(address)) {
-        case 1:
-            buffer[index++] = (byte)address;
-            return 1;
-        case 2:
-            buffer[index++] = (byte)(0xFF & (address >> 8));
-            buffer[index++] = (byte)(0xFF & address);
-            return 2;
-        case 3:
-            buffer[index++] = (byte)(0xFF & (address >> 16));
-            buffer[index++] = (byte)(0xFF & (address >> 8));
-            buffer[index++] = (byte)(0xFF & address);
-            return 3;
-        case 0:
-            return 0;
-        default:
-            throw new RuntimeException("Address " + address + " has a strange size");
-        }
-    }
-
-    /**
-     * Helper method to write a variable-size signed address to a file.
-     *
-     * @param buffer the buffer to write to.
-     * @param index the index in the buffer to write the address to.
-     * @param address the address to write.
-     * @return the size in bytes the address actually took.
-     */
-    private static int writeVariableSignedAddress(final byte[] buffer, int index,
-            final int address) {
-        if (!hasChildrenAddress(address)) {
-            buffer[index] = buffer[index + 1] = buffer[index + 2] = 0;
-        } else {
-            final int absAddress = Math.abs(address);
-            buffer[index++] = (byte)((address < 0 ? MSB8 : 0) | (0xFF & (absAddress >> 16)));
-            buffer[index++] = (byte)(0xFF & (absAddress >> 8));
-            buffer[index++] = (byte)(0xFF & absAddress);
-        }
-        return 3;
-    }
-
-    /**
-     * Makes the flag value for a char group.
-     *
-     * @param hasMultipleChars whether the group has multiple chars.
-     * @param isTerminal whether the group is terminal.
-     * @param childrenAddressSize the size of a children address.
-     * @param hasShortcuts whether the group has shortcuts.
-     * @param hasBigrams whether the group has bigrams.
-     * @param isNotAWord whether the group is not a word.
-     * @param isBlackListEntry whether the group is a blacklist entry.
-     * @param formatOptions file format options.
-     * @return the flags
-     */
-    static int makeCharGroupFlags(final boolean hasMultipleChars, final boolean isTerminal,
-            final int childrenAddressSize, final boolean hasShortcuts, final boolean hasBigrams,
-            final boolean isNotAWord, final boolean isBlackListEntry,
-            final FormatOptions formatOptions) {
-        byte flags = 0;
-        if (hasMultipleChars) flags |= FormatSpec.FLAG_HAS_MULTIPLE_CHARS;
-        if (isTerminal) flags |= FormatSpec.FLAG_IS_TERMINAL;
-        if (formatOptions.mSupportsDynamicUpdate) {
-            flags |= FormatSpec.FLAG_IS_NOT_MOVED;
-        } else if (true) {
-            switch (childrenAddressSize) {
-                case 1:
-                    flags |= FormatSpec.FLAG_GROUP_ADDRESS_TYPE_ONEBYTE;
-                    break;
-                case 2:
-                    flags |= FormatSpec.FLAG_GROUP_ADDRESS_TYPE_TWOBYTES;
-                    break;
-                case 3:
-                    flags |= FormatSpec.FLAG_GROUP_ADDRESS_TYPE_THREEBYTES;
-                    break;
-                case 0:
-                    flags |= FormatSpec.FLAG_GROUP_ADDRESS_TYPE_NOADDRESS;
-                    break;
-                default:
-                    throw new RuntimeException("Node with a strange address");
-            }
-        }
-        if (hasShortcuts) flags |= FormatSpec.FLAG_HAS_SHORTCUT_TARGETS;
-        if (hasBigrams) flags |= FormatSpec.FLAG_HAS_BIGRAMS;
-        if (isNotAWord) flags |= FormatSpec.FLAG_IS_NOT_A_WORD;
-        if (isBlackListEntry) flags |= FormatSpec.FLAG_IS_BLACKLISTED;
-        return flags;
-    }
-
-    private static byte makeCharGroupFlags(final CharGroup group, final int groupAddress,
-            final int childrenOffset, final FormatOptions formatOptions) {
-        return (byte) makeCharGroupFlags(group.mChars.length > 1, group.mFrequency >= 0,
-                getByteSize(childrenOffset), group.mShortcutTargets != null, group.mBigrams != null,
-                group.mIsNotAWord, group.mIsBlacklistEntry, formatOptions);
-    }
-
-    /**
-     * Makes the flag value for a bigram.
-     *
-     * @param more whether there are more bigrams after this one.
-     * @param offset the offset of the bigram.
-     * @param bigramFrequency the frequency of the bigram, 0..255.
-     * @param unigramFrequency the unigram frequency of the same word, 0..255.
-     * @param word the second bigram, for debugging purposes
-     * @return the flags
-     */
-    private static final int makeBigramFlags(final boolean more, final int offset,
-            int bigramFrequency, final int unigramFrequency, final String word) {
-        int bigramFlags = (more ? FormatSpec.FLAG_ATTRIBUTE_HAS_NEXT : 0)
-                + (offset < 0 ? FormatSpec.FLAG_ATTRIBUTE_OFFSET_NEGATIVE : 0);
-        switch (getByteSize(offset)) {
-        case 1:
-            bigramFlags |= FormatSpec.FLAG_ATTRIBUTE_ADDRESS_TYPE_ONEBYTE;
-            break;
-        case 2:
-            bigramFlags |= FormatSpec.FLAG_ATTRIBUTE_ADDRESS_TYPE_TWOBYTES;
-            break;
-        case 3:
-            bigramFlags |= FormatSpec.FLAG_ATTRIBUTE_ADDRESS_TYPE_THREEBYTES;
-            break;
-        default:
-            throw new RuntimeException("Strange offset size");
-        }
-        if (unigramFrequency > bigramFrequency) {
-            MakedictLog.e("Unigram freq is superior to bigram freq for \"" + word
-                    + "\". Bigram freq is " + bigramFrequency + ", unigram freq for "
-                    + word + " is " + unigramFrequency);
-            bigramFrequency = unigramFrequency;
-        }
-        // We compute the difference between 255 (which means probability = 1) and the
-        // unigram score. We split this into a number of discrete steps.
-        // Now, the steps are numbered 0~15; 0 represents an increase of 1 step while 15
-        // represents an increase of 16 steps: a value of 15 will be interpreted as the median
-        // value of the 16th step. In all justice, if the bigram frequency is low enough to be
-        // rounded below the first step (which means it is less than half a step higher than the
-        // unigram frequency) then the unigram frequency itself is the best approximation of the
-        // bigram freq that we could possibly supply, hence we should *not* include this bigram
-        // in the file at all.
-        // until this is done, we'll write 0 and slightly overestimate this case.
-        // In other words, 0 means "between 0.5 step and 1.5 step", 1 means "between 1.5 step
-        // and 2.5 steps", and 15 means "between 15.5 steps and 16.5 steps". So we want to
-        // divide our range [unigramFreq..MAX_TERMINAL_FREQUENCY] in 16.5 steps to get the
-        // step size. Then we compute the start of the first step (the one where value 0 starts)
-        // by adding half-a-step to the unigramFrequency. From there, we compute the integer
-        // number of steps to the bigramFrequency. One last thing: we want our steps to include
-        // their lower bound and exclude their higher bound so we need to have the first step
-        // start at exactly 1 unit higher than floor(unigramFreq + half a step).
-        // Note : to reconstruct the score, the dictionary reader will need to divide
-        // MAX_TERMINAL_FREQUENCY - unigramFreq by 16.5 likewise to get the value of the step,
-        // and add (discretizedFrequency + 0.5 + 0.5) times this value to get the best
-        // approximation. (0.5 to get the first step start, and 0.5 to get the middle of the
-        // step pointed by the discretized frequency.
-        final float stepSize =
-                (FormatSpec.MAX_TERMINAL_FREQUENCY - unigramFrequency)
-                / (1.5f + FormatSpec.MAX_BIGRAM_FREQUENCY);
-        final float firstStepStart = 1 + unigramFrequency + (stepSize / 2.0f);
-        final int discretizedFrequency = (int)((bigramFrequency - firstStepStart) / stepSize);
-        // If the bigram freq is less than half-a-step higher than the unigram freq, we get -1
-        // here. The best approximation would be the unigram freq itself, so we should not
-        // include this bigram in the dictionary. For now, register as 0, and live with the
-        // small over-estimation that we get in this case. TODO: actually remove this bigram
-        // if discretizedFrequency < 0.
-        final int finalBigramFrequency = discretizedFrequency > 0 ? discretizedFrequency : 0;
-        bigramFlags += finalBigramFrequency & FormatSpec.FLAG_ATTRIBUTE_FREQUENCY;
-        return bigramFlags;
-    }
-
-    /**
-     * Makes the 2-byte value for options flags.
-     */
-    private static final int makeOptionsValue(final FusionDictionary dictionary,
-            final FormatOptions formatOptions) {
-        final DictionaryOptions options = dictionary.mOptions;
-        final boolean hasBigrams = dictionary.hasBigrams();
-        return (options.mFrenchLigatureProcessing ? FormatSpec.FRENCH_LIGATURE_PROCESSING_FLAG : 0)
-                + (options.mGermanUmlautProcessing ? FormatSpec.GERMAN_UMLAUT_PROCESSING_FLAG : 0)
-                + (hasBigrams ? FormatSpec.CONTAINS_BIGRAMS_FLAG : 0)
-                + (formatOptions.mSupportsDynamicUpdate ? FormatSpec.SUPPORTS_DYNAMIC_UPDATE : 0);
-    }
-
-    /**
-     * Makes the flag value for a shortcut.
-     *
-     * @param more whether there are more attributes after this one.
-     * @param frequency the frequency of the attribute, 0..15
-     * @return the flags
-     */
-    static final int makeShortcutFlags(final boolean more, final int frequency) {
-        return (more ? FormatSpec.FLAG_ATTRIBUTE_HAS_NEXT : 0)
-                + (frequency & FormatSpec.FLAG_ATTRIBUTE_FREQUENCY);
-    }
-
-    private static final int writeParentAddress(final byte[] buffer, final int index,
-            final int address, final FormatOptions formatOptions) {
-        if (supportsDynamicUpdate(formatOptions)) {
-            if (address == FormatSpec.NO_PARENT_ADDRESS) {
-                buffer[index] = buffer[index + 1] = buffer[index + 2] = 0;
-            } else {
-                final int absAddress = Math.abs(address);
-                assert(absAddress <= SINT24_MAX);
-                buffer[index] = (byte)((address < 0 ? MSB8 : 0)
-                        | ((absAddress >> 16) & 0xFF));
-                buffer[index + 1] = (byte)((absAddress >> 8) & 0xFF);
-                buffer[index + 2] = (byte)(absAddress & 0xFF);
-            }
-            return index + 3;
-        } else {
-            return index;
-        }
-    }
-
-    /**
-     * Write a node to memory. The node is expected to have its final position cached.
-     *
-     * This can be an empty map, but the more is inside the faster the lookups will be. It can
-     * be carried on as long as nodes do not move.
-     *
-     * @param dict the dictionary the node is a part of (for relative offsets).
-     * @param buffer the memory buffer to write to.
-     * @param node the node to write.
-     * @param formatOptions file format options.
-     * @return the address of the END of the node.
-     */
-    @SuppressWarnings("unused")
-    private static int writePlacedNode(final FusionDictionary dict, byte[] buffer,
-            final Node node, final FormatOptions formatOptions) {
-        // TODO: Make the code in common with BinaryDictIOUtils#writeCharGroup
-        int index = node.mCachedAddressAfterUpdate;
-
-        final int groupCount = node.mData.size();
-        final int countSize = getGroupCountSize(node);
-        final int parentAddress = node.mCachedParentAddress;
-        if (1 == countSize) {
-            buffer[index++] = (byte)groupCount;
-        } else if (2 == countSize) {
-            // We need to signal 2-byte size by setting the top bit of the MSB to 1, so
-            // we | 0x80 to do this.
-            buffer[index++] = (byte)((groupCount >> 8) | 0x80);
-            buffer[index++] = (byte)(groupCount & 0xFF);
-        } else {
-            throw new RuntimeException("Strange size from getGroupCountSize : " + countSize);
-        }
-        int groupAddress = index;
-        for (int i = 0; i < groupCount; ++i) {
-            final CharGroup group = node.mData.get(i);
-            if (index != group.mCachedAddressAfterUpdate) {
-                throw new RuntimeException("Bug: write index is not the same as the cached address "
-                        + "of the group : " + index + " <> " + group.mCachedAddressAfterUpdate);
-            }
-            groupAddress += getGroupHeaderSize(group, formatOptions);
-            // Sanity checks.
-            if (DBG && group.mFrequency > FormatSpec.MAX_TERMINAL_FREQUENCY) {
-                throw new RuntimeException("A node has a frequency > "
-                        + FormatSpec.MAX_TERMINAL_FREQUENCY
-                        + " : " + group.mFrequency);
-            }
-            if (group.mFrequency >= 0) groupAddress += FormatSpec.GROUP_FREQUENCY_SIZE;
-            final int childrenOffset = null == group.mChildren
-                    ? FormatSpec.NO_CHILDREN_ADDRESS
-                            : group.mChildren.mCachedAddressAfterUpdate - groupAddress;
-            buffer[index++] =
-                    makeCharGroupFlags(group, groupAddress, childrenOffset, formatOptions);
-
-            if (parentAddress == FormatSpec.NO_PARENT_ADDRESS) {
-                index = writeParentAddress(buffer, index, parentAddress, formatOptions);
-            } else {
-                index = writeParentAddress(buffer, index, parentAddress
-                        + (node.mCachedAddressAfterUpdate - group.mCachedAddressAfterUpdate),
-                        formatOptions);
-            }
-
-            index = CharEncoding.writeCharArray(group.mChars, buffer, index);
-            if (group.hasSeveralChars()) {
-                buffer[index++] = FormatSpec.GROUP_CHARACTERS_TERMINATOR;
-            }
-            if (group.mFrequency >= 0) {
-                buffer[index++] = (byte) group.mFrequency;
-            }
-
-            final int shift;
-            if (formatOptions.mSupportsDynamicUpdate) {
-                shift = writeVariableSignedAddress(buffer, index, childrenOffset);
-            } else {
-                shift = writeVariableAddress(buffer, index, childrenOffset);
-            }
-            index += shift;
-            groupAddress += shift;
-
-            // Write shortcuts
-            if (null != group.mShortcutTargets) {
-                final int indexOfShortcutByteSize = index;
-                index += FormatSpec.GROUP_SHORTCUT_LIST_SIZE_SIZE;
-                groupAddress += FormatSpec.GROUP_SHORTCUT_LIST_SIZE_SIZE;
-                final Iterator<WeightedString> shortcutIterator = group.mShortcutTargets.iterator();
-                while (shortcutIterator.hasNext()) {
-                    final WeightedString target = shortcutIterator.next();
-                    ++groupAddress;
-                    int shortcutFlags = makeShortcutFlags(shortcutIterator.hasNext(),
-                            target.mFrequency);
-                    buffer[index++] = (byte)shortcutFlags;
-                    final int shortcutShift = CharEncoding.writeString(buffer, index, target.mWord);
-                    index += shortcutShift;
-                    groupAddress += shortcutShift;
-                }
-                final int shortcutByteSize = index - indexOfShortcutByteSize;
-                if (shortcutByteSize > 0xFFFF) {
-                    throw new RuntimeException("Shortcut list too large");
-                }
-                buffer[indexOfShortcutByteSize] = (byte)(shortcutByteSize >> 8);
-                buffer[indexOfShortcutByteSize + 1] = (byte)(shortcutByteSize & 0xFF);
-            }
-            // Write bigrams
-            if (null != group.mBigrams) {
-                final Iterator<WeightedString> bigramIterator = group.mBigrams.iterator();
-                while (bigramIterator.hasNext()) {
-                    final WeightedString bigram = bigramIterator.next();
-                    final CharGroup target =
-                            FusionDictionary.findWordInTree(dict.mRoot, bigram.mWord);
-                    final int addressOfBigram = target.mCachedAddressAfterUpdate;
-                    final int unigramFrequencyForThisWord = target.mFrequency;
-                    ++groupAddress;
-                    final int offset = addressOfBigram - groupAddress;
-                    int bigramFlags = makeBigramFlags(bigramIterator.hasNext(), offset,
-                            bigram.mFrequency, unigramFrequencyForThisWord, bigram.mWord);
-                    buffer[index++] = (byte)bigramFlags;
-                    final int bigramShift = writeVariableAddress(buffer, index, Math.abs(offset));
-                    index += bigramShift;
-                    groupAddress += bigramShift;
-                }
-            }
-
-        }
-        if (formatOptions.mSupportsDynamicUpdate) {
-            buffer[index] = buffer[index + 1] = buffer[index + 2]
-                    = FormatSpec.NO_FORWARD_LINK_ADDRESS;
-            index += FormatSpec.FORWARD_LINK_ADDRESS_SIZE;
-        }
-        if (index != node.mCachedAddressAfterUpdate + node.mCachedSize) throw new RuntimeException(
-                "Not the same size : written "
-                + (index - node.mCachedAddressAfterUpdate) + " bytes from a node that should have "
-                + node.mCachedSize + " bytes");
-        return index;
-    }
-
-    /**
-     * Dumps a collection of useful statistics about a node array.
-     *
-     * This prints purely informative stuff, like the total estimated file size, the
-     * number of nodes, of character groups, the repartition of each address size, etc
-     *
-     * @param nodes the node array.
-     */
-    private static void showStatistics(ArrayList<Node> nodes) {
-        int firstTerminalAddress = Integer.MAX_VALUE;
-        int lastTerminalAddress = Integer.MIN_VALUE;
-        int size = 0;
-        int charGroups = 0;
-        int maxGroups = 0;
-        int maxRuns = 0;
-        for (final Node n : nodes) {
-            if (maxGroups < n.mData.size()) maxGroups = n.mData.size();
-            for (final CharGroup cg : n.mData) {
-                ++charGroups;
-                if (cg.mChars.length > maxRuns) maxRuns = cg.mChars.length;
-                if (cg.mFrequency >= 0) {
-                    if (n.mCachedAddressAfterUpdate < firstTerminalAddress)
-                        firstTerminalAddress = n.mCachedAddressAfterUpdate;
-                    if (n.mCachedAddressAfterUpdate > lastTerminalAddress)
-                        lastTerminalAddress = n.mCachedAddressAfterUpdate;
-                }
-            }
-            if (n.mCachedAddressAfterUpdate + n.mCachedSize > size) {
-                size = n.mCachedAddressAfterUpdate + n.mCachedSize;
-            }
-        }
-        final int[] groupCounts = new int[maxGroups + 1];
-        final int[] runCounts = new int[maxRuns + 1];
-        for (final Node n : nodes) {
-            ++groupCounts[n.mData.size()];
-            for (final CharGroup cg : n.mData) {
-                ++runCounts[cg.mChars.length];
-            }
-        }
-
-        MakedictLog.i("Statistics:\n"
-                + "  total file size " + size + "\n"
-                + "  " + nodes.size() + " nodes\n"
-                + "  " + charGroups + " groups (" + ((float)charGroups / nodes.size())
-                        + " groups per node)\n"
-                + "  first terminal at " + firstTerminalAddress + "\n"
-                + "  last terminal at " + lastTerminalAddress + "\n"
-                + "  Group stats : max = " + maxGroups);
-        for (int i = 0; i < groupCounts.length; ++i) {
-            MakedictLog.i("    " + i + " : " + groupCounts[i]);
-        }
-        MakedictLog.i("  Character run stats : max = " + maxRuns);
-        for (int i = 0; i < runCounts.length; ++i) {
-            MakedictLog.i("    " + i + " : " + runCounts[i]);
-        }
-    }
-
-    /**
-     * Dumps a FusionDictionary to a file.
-     *
-     * This is the public entry point to write a dictionary to a file.
-     *
-     * @param destination the stream to write the binary data to.
-     * @param dict the dictionary to write.
-     * @param formatOptions file format options.
-     */
-    public static void writeDictionaryBinary(final OutputStream destination,
-            final FusionDictionary dict, final FormatOptions formatOptions)
-            throws IOException, UnsupportedFormatException {
-
-        // Addresses are limited to 3 bytes, but since addresses can be relative to each node, the
-        // structure itself is not limited to 16MB. However, if it is over 16MB deciding the order
-        // of the nodes becomes a quite complicated problem, because though the dictionary itself
-        // does not have a size limit, each node must still be within 16MB of all its children and
-        // parents. As long as this is ensured, the dictionary file may grow to any size.
-
-        final int version = formatOptions.mVersion;
-        if (version < FormatSpec.MINIMUM_SUPPORTED_VERSION
-                || version > FormatSpec.MAXIMUM_SUPPORTED_VERSION) {
-            throw new UnsupportedFormatException("Requested file format version " + version
-                    + ", but this implementation only supports versions "
-                    + FormatSpec.MINIMUM_SUPPORTED_VERSION + " through "
-                    + FormatSpec.MAXIMUM_SUPPORTED_VERSION);
-        }
-
-        ByteArrayOutputStream headerBuffer = new ByteArrayOutputStream(256);
-
-        // The magic number in big-endian order.
-        if (version >= FormatSpec.FIRST_VERSION_WITH_HEADER_SIZE) {
-            // Magic number for version 2+.
-            headerBuffer.write((byte) (0xFF & (FormatSpec.VERSION_2_MAGIC_NUMBER >> 24)));
-            headerBuffer.write((byte) (0xFF & (FormatSpec.VERSION_2_MAGIC_NUMBER >> 16)));
-            headerBuffer.write((byte) (0xFF & (FormatSpec.VERSION_2_MAGIC_NUMBER >> 8)));
-            headerBuffer.write((byte) (0xFF & FormatSpec.VERSION_2_MAGIC_NUMBER));
-            // Dictionary version.
-            headerBuffer.write((byte) (0xFF & (version >> 8)));
-            headerBuffer.write((byte) (0xFF & version));
-        } else {
-            // Magic number for version 1.
-            headerBuffer.write((byte) (0xFF & (FormatSpec.VERSION_1_MAGIC_NUMBER >> 8)));
-            headerBuffer.write((byte) (0xFF & FormatSpec.VERSION_1_MAGIC_NUMBER));
-            // Dictionary version.
-            headerBuffer.write((byte) (0xFF & version));
-        }
-        // Options flags
-        final int options = makeOptionsValue(dict, formatOptions);
-        headerBuffer.write((byte) (0xFF & (options >> 8)));
-        headerBuffer.write((byte) (0xFF & options));
-        if (version >= FormatSpec.FIRST_VERSION_WITH_HEADER_SIZE) {
-            final int headerSizeOffset = headerBuffer.size();
-            // Placeholder to be written later with header size.
-            for (int i = 0; i < 4; ++i) {
-                headerBuffer.write(0);
-            }
-            // Write out the options.
-            for (final String key : dict.mOptions.mAttributes.keySet()) {
-                final String value = dict.mOptions.mAttributes.get(key);
-                CharEncoding.writeString(headerBuffer, key);
-                CharEncoding.writeString(headerBuffer, value);
-            }
-            final int size = headerBuffer.size();
-            final byte[] bytes = headerBuffer.toByteArray();
-            // Write out the header size.
-            bytes[headerSizeOffset] = (byte) (0xFF & (size >> 24));
-            bytes[headerSizeOffset + 1] = (byte) (0xFF & (size >> 16));
-            bytes[headerSizeOffset + 2] = (byte) (0xFF & (size >> 8));
-            bytes[headerSizeOffset + 3] = (byte) (0xFF & (size >> 0));
-            destination.write(bytes);
-        } else {
-            headerBuffer.writeTo(destination);
-        }
-
-        headerBuffer.close();
-
-        // Leave the choice of the optimal node order to the flattenTree function.
-        MakedictLog.i("Flattening the tree...");
-        ArrayList<Node> flatNodes = flattenTree(dict.mRoot);
-
-        MakedictLog.i("Computing addresses...");
-        computeAddresses(dict, flatNodes, formatOptions);
-        MakedictLog.i("Checking array...");
-        if (DBG) checkFlatNodeArray(flatNodes);
-
-        // Create a buffer that matches the final dictionary size.
-        final Node lastNode = flatNodes.get(flatNodes.size() - 1);
-        final int bufferSize = lastNode.mCachedAddressAfterUpdate + lastNode.mCachedSize;
-        final byte[] buffer = new byte[bufferSize];
-        int index = 0;
-
-        MakedictLog.i("Writing file...");
-        int dataEndOffset = 0;
-        for (Node n : flatNodes) {
-            dataEndOffset = writePlacedNode(dict, buffer, n, formatOptions);
-        }
-
-        if (DBG) showStatistics(flatNodes);
-
-        destination.write(buffer, 0, dataEndOffset);
-
-        destination.close();
-        MakedictLog.i("Done");
-    }
-
-
-    // Input methods: Read a binary dictionary to memory.
-    // readDictionaryBinary is the public entry point for them.
-
-    static int getChildrenAddressSize(final int optionFlags,
-            final FormatOptions formatOptions) {
-        if (formatOptions.mSupportsDynamicUpdate) return FormatSpec.SIGNED_CHILDREN_ADDRESS_SIZE;
-        switch (optionFlags & FormatSpec.MASK_GROUP_ADDRESS_TYPE) {
-            case FormatSpec.FLAG_GROUP_ADDRESS_TYPE_ONEBYTE:
-                return 1;
-            case FormatSpec.FLAG_GROUP_ADDRESS_TYPE_TWOBYTES:
-                return 2;
-            case FormatSpec.FLAG_GROUP_ADDRESS_TYPE_THREEBYTES:
-                return 3;
-            case FormatSpec.FLAG_GROUP_ADDRESS_TYPE_NOADDRESS:
-            default:
-                return 0;
-        }
-    }
-
-    static int readChildrenAddress(final FusionDictionaryBufferInterface buffer,
-            final int optionFlags, final FormatOptions options) {
-        if (options.mSupportsDynamicUpdate) {
-            final int address = buffer.readUnsignedInt24();
-            if (address == 0) return FormatSpec.NO_CHILDREN_ADDRESS;
-            if ((address & MSB24) != 0) {
-                return -(address & SINT24_MAX);
-            } else {
-                return address;
-            }
-        }
-        int address;
-        switch (optionFlags & FormatSpec.MASK_GROUP_ADDRESS_TYPE) {
-            case FormatSpec.FLAG_GROUP_ADDRESS_TYPE_ONEBYTE:
-                return buffer.readUnsignedByte();
-            case FormatSpec.FLAG_GROUP_ADDRESS_TYPE_TWOBYTES:
-                return buffer.readUnsignedShort();
-            case FormatSpec.FLAG_GROUP_ADDRESS_TYPE_THREEBYTES:
-                return buffer.readUnsignedInt24();
-            case FormatSpec.FLAG_GROUP_ADDRESS_TYPE_NOADDRESS:
-            default:
-                return FormatSpec.NO_CHILDREN_ADDRESS;
-        }
-    }
-
-    static int readParentAddress(final FusionDictionaryBufferInterface buffer,
-            final FormatOptions formatOptions) {
-        if (supportsDynamicUpdate(formatOptions)) {
-            final int parentAddress = buffer.readUnsignedInt24();
-            final int sign = ((parentAddress & MSB24) != 0) ? -1 : 1;
-            return sign * (parentAddress & SINT24_MAX);
-        } else {
-            return FormatSpec.NO_PARENT_ADDRESS;
-        }
-    }
-
-    private static final int[] CHARACTER_BUFFER = new int[FormatSpec.MAX_WORD_LENGTH];
-    public static CharGroupInfo readCharGroup(final FusionDictionaryBufferInterface buffer,
-            final int originalGroupAddress, final FormatOptions options) {
-        int addressPointer = originalGroupAddress;
-        final int flags = buffer.readUnsignedByte();
-        ++addressPointer;
-
-        final int parentAddress = readParentAddress(buffer, options);
-        if (supportsDynamicUpdate(options)) {
-            addressPointer += 3;
-        }
-
-        final int characters[];
-        if (0 != (flags & FormatSpec.FLAG_HAS_MULTIPLE_CHARS)) {
-            int index = 0;
-            int character = CharEncoding.readChar(buffer);
-            addressPointer += CharEncoding.getCharSize(character);
-            while (-1 != 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 CHARACTER_BUFFER.
-                CHARACTER_BUFFER[index++] = character;
-                character = CharEncoding.readChar(buffer);
-                addressPointer += CharEncoding.getCharSize(character);
-            }
-            characters = Arrays.copyOfRange(CHARACTER_BUFFER, 0, index);
-        } else {
-            final int character = CharEncoding.readChar(buffer);
-            addressPointer += CharEncoding.getCharSize(character);
-            characters = new int[] { character };
-        }
-        final int frequency;
-        if (0 != (FormatSpec.FLAG_IS_TERMINAL & flags)) {
-            ++addressPointer;
-            frequency = buffer.readUnsignedByte();
-        } else {
-            frequency = CharGroup.NOT_A_TERMINAL;
-        }
-        int childrenAddress = readChildrenAddress(buffer, flags, options);
-        if (childrenAddress != FormatSpec.NO_CHILDREN_ADDRESS) {
-            childrenAddress += addressPointer;
-        }
-        addressPointer += getChildrenAddressSize(flags, options);
-        ArrayList<WeightedString> shortcutTargets = null;
-        if (0 != (flags & FormatSpec.FLAG_HAS_SHORTCUT_TARGETS)) {
-            final int pointerBefore = buffer.position();
-            shortcutTargets = new ArrayList<WeightedString>();
-            buffer.readUnsignedShort(); // Skip the size
-            while (true) {
-                final int targetFlags = buffer.readUnsignedByte();
-                final String word = CharEncoding.readString(buffer);
-                shortcutTargets.add(new WeightedString(word,
-                        targetFlags & FormatSpec.FLAG_ATTRIBUTE_FREQUENCY));
-                if (0 == (targetFlags & FormatSpec.FLAG_ATTRIBUTE_HAS_NEXT)) break;
-            }
-            addressPointer += buffer.position() - pointerBefore;
-        }
-        ArrayList<PendingAttribute> bigrams = null;
-        if (0 != (flags & FormatSpec.FLAG_HAS_BIGRAMS)) {
-            bigrams = new ArrayList<PendingAttribute>();
-            int bigramCount = 0;
-            while (bigramCount++ < FormatSpec.MAX_BIGRAMS_IN_A_GROUP) {
-                final int bigramFlags = buffer.readUnsignedByte();
-                ++addressPointer;
-                final int sign = 0 == (bigramFlags & FormatSpec.FLAG_ATTRIBUTE_OFFSET_NEGATIVE)
-                        ? 1 : -1;
-                int bigramAddress = addressPointer;
-                switch (bigramFlags & FormatSpec.MASK_ATTRIBUTE_ADDRESS_TYPE) {
-                case FormatSpec.FLAG_ATTRIBUTE_ADDRESS_TYPE_ONEBYTE:
-                    bigramAddress += sign * buffer.readUnsignedByte();
-                    addressPointer += 1;
-                    break;
-                case FormatSpec.FLAG_ATTRIBUTE_ADDRESS_TYPE_TWOBYTES:
-                    bigramAddress += sign * buffer.readUnsignedShort();
-                    addressPointer += 2;
-                    break;
-                case FormatSpec.FLAG_ATTRIBUTE_ADDRESS_TYPE_THREEBYTES:
-                    final int offset = (buffer.readUnsignedByte() << 16)
-                            + buffer.readUnsignedShort();
-                    bigramAddress += sign * offset;
-                    addressPointer += 3;
-                    break;
-                default:
-                    throw new RuntimeException("Has bigrams with no address");
-                }
-                bigrams.add(new PendingAttribute(bigramFlags & FormatSpec.FLAG_ATTRIBUTE_FREQUENCY,
-                        bigramAddress));
-                if (0 == (bigramFlags & FormatSpec.FLAG_ATTRIBUTE_HAS_NEXT)) break;
-            }
-            if (bigramCount >= FormatSpec.MAX_BIGRAMS_IN_A_GROUP) {
-                MakedictLog.d("too many bigrams in a group.");
-            }
-        }
-        return new CharGroupInfo(originalGroupAddress, addressPointer, flags, characters, frequency,
-                parentAddress, childrenAddress, shortcutTargets, bigrams);
-    }
-
-    /**
-     * Reads and returns the char group count out of a buffer and forwards the pointer.
-     */
-    public static int readCharGroupCount(final FusionDictionaryBufferInterface buffer) {
-        final int msb = buffer.readUnsignedByte();
-        if (FormatSpec.MAX_CHARGROUPS_FOR_ONE_BYTE_CHARGROUP_COUNT >= msb) {
-            return msb;
-        } else {
-            return ((FormatSpec.MAX_CHARGROUPS_FOR_ONE_BYTE_CHARGROUP_COUNT & msb) << 8)
-                    + buffer.readUnsignedByte();
-        }
-    }
-
-    // The word cache here is a stopgap bandaid to help the catastrophic performance
-    // of this method. Since it performs direct, unbuffered random access to the file and
-    // may be called hundreds of thousands of times, the resulting performance is not
-    // reasonable without some kind of cache. Thus:
-    private static TreeMap<Integer, WeightedString> wordCache =
-            new TreeMap<Integer, WeightedString>();
-    /**
-     * Finds, as a string, the word at the address passed as an argument.
-     *
-     * @param buffer the buffer to read from.
-     * @param headerSize the size of the header.
-     * @param address the address to seek.
-     * @param formatOptions file format options.
-     * @return the word with its frequency, as a weighted string.
-     */
-    /* package for tests */ static WeightedString getWordAtAddress(
-            final FusionDictionaryBufferInterface buffer, final int headerSize, final int address,
-            final FormatOptions formatOptions) {
-        final WeightedString cachedString = wordCache.get(address);
-        if (null != cachedString) return cachedString;
-
-        final WeightedString result;
-        final int originalPointer = buffer.position();
-        buffer.position(address);
-
-        if (supportsDynamicUpdate(formatOptions)) {
-            result = getWordAtAddressWithParentAddress(buffer, headerSize, address, formatOptions);
-        } else {
-            result = getWordAtAddressWithoutParentAddress(buffer, headerSize, address,
-                    formatOptions);
-        }
-
-        wordCache.put(address, result);
-        buffer.position(originalPointer);
-        return result;
-    }
-
-    // TODO: static!? This will behave erratically when used in multi-threaded code.
-    // We need to fix this
-    private static int[] sGetWordBuffer = new int[FormatSpec.MAX_WORD_LENGTH];
-    @SuppressWarnings("unused")
-    private static WeightedString getWordAtAddressWithParentAddress(
-            final FusionDictionaryBufferInterface buffer, final int headerSize, final int address,
-            final FormatOptions options) {
-        int currentAddress = address;
-        int index = FormatSpec.MAX_WORD_LENGTH - 1;
-        int frequency = Integer.MIN_VALUE;
-        // the length of the path from the root to the leaf is limited by MAX_WORD_LENGTH
-        for (int count = 0; count < FormatSpec.MAX_WORD_LENGTH; ++count) {
-            CharGroupInfo currentInfo;
-            int loopCounter = 0;
-            do {
-                buffer.position(currentAddress + headerSize);
-                currentInfo = readCharGroup(buffer, currentAddress, options);
-                if (isMovedGroup(currentInfo.mFlags, options)) {
-                    currentAddress = currentInfo.mParentAddress + currentInfo.mOriginalAddress;
-                }
-                if (DBG && loopCounter++ > MAX_JUMPS) {
-                    MakedictLog.d("Too many jumps - probably a bug");
-                }
-            } while (isMovedGroup(currentInfo.mFlags, options));
-            if (Integer.MIN_VALUE == frequency) frequency = currentInfo.mFrequency;
-            for (int i = 0; i < currentInfo.mCharacters.length; ++i) {
-                sGetWordBuffer[index--] =
-                        currentInfo.mCharacters[currentInfo.mCharacters.length - i - 1];
-            }
-            if (currentInfo.mParentAddress == FormatSpec.NO_PARENT_ADDRESS) break;
-            currentAddress = currentInfo.mParentAddress + currentInfo.mOriginalAddress;
-        }
-
-        return new WeightedString(
-                new String(sGetWordBuffer, index + 1, FormatSpec.MAX_WORD_LENGTH - index - 1),
-                        frequency);
-    }
-
-    private static WeightedString getWordAtAddressWithoutParentAddress(
-            final FusionDictionaryBufferInterface buffer, final int headerSize, final int address,
-            final FormatOptions options) {
-        buffer.position(headerSize);
-        final int count = readCharGroupCount(buffer);
-        int groupOffset = getGroupCountSize(count);
-        final StringBuilder builder = new StringBuilder();
-        WeightedString result = null;
-
-        CharGroupInfo last = null;
-        for (int i = count - 1; i >= 0; --i) {
-            CharGroupInfo info = readCharGroup(buffer, groupOffset, options);
-            groupOffset = info.mEndAddress;
-            if (info.mOriginalAddress == address) {
-                builder.append(new String(info.mCharacters, 0, info.mCharacters.length));
-                result = new WeightedString(builder.toString(), info.mFrequency);
-                break; // and return
-            }
-            if (hasChildrenAddress(info.mChildrenAddress)) {
-                if (info.mChildrenAddress > address) {
-                    if (null == last) continue;
-                    builder.append(new String(last.mCharacters, 0, last.mCharacters.length));
-                    buffer.position(last.mChildrenAddress + headerSize);
-                    i = readCharGroupCount(buffer);
-                    groupOffset = last.mChildrenAddress + getGroupCountSize(i);
-                    last = null;
-                    continue;
-                }
-                last = info;
-            }
-            if (0 == i && hasChildrenAddress(last.mChildrenAddress)) {
-                builder.append(new String(last.mCharacters, 0, last.mCharacters.length));
-                buffer.position(last.mChildrenAddress + headerSize);
-                i = readCharGroupCount(buffer);
-                groupOffset = last.mChildrenAddress + getGroupCountSize(i);
-                last = null;
-                continue;
-            }
-        }
-        return result;
-    }
-
-    /**
-     * Reads a single node from a buffer.
-     *
-     * This methods reads the file at the current position. A node is fully expected to start at
-     * the current position.
-     * This will recursively read other nodes into the structure, populating the reverse
-     * maps on the fly and using them to keep track of already read nodes.
-     *
-     * @param buffer the buffer, correctly positioned at the start of a node.
-     * @param headerSize the size, in bytes, of the file header.
-     * @param reverseNodeMap a mapping from addresses to already read nodes.
-     * @param reverseGroupMap a mapping from addresses to already read character groups.
-     * @param options file format options.
-     * @return the read node with all his children already read.
-     */
-    private static Node readNode(final FusionDictionaryBufferInterface buffer, final int headerSize,
-            final Map<Integer, Node> reverseNodeMap, final Map<Integer, CharGroup> reverseGroupMap,
-            final FormatOptions options)
-            throws IOException {
-        final ArrayList<CharGroup> nodeContents = new ArrayList<CharGroup>();
-        final int nodeOrigin = buffer.position() - headerSize;
-
-        do { // Scan the linked-list node.
-            final int nodeHeadPosition = buffer.position() - headerSize;
-            final int count = readCharGroupCount(buffer);
-            int groupOffset = nodeHeadPosition + getGroupCountSize(count);
-            for (int i = count; i > 0; --i) { // Scan the array of CharGroup.
-                CharGroupInfo info = readCharGroup(buffer, groupOffset, options);
-                if (isMovedGroup(info.mFlags, options)) continue;
-                ArrayList<WeightedString> shortcutTargets = info.mShortcutTargets;
-                ArrayList<WeightedString> bigrams = null;
-                if (null != info.mBigrams) {
-                    bigrams = new ArrayList<WeightedString>();
-                    for (PendingAttribute bigram : info.mBigrams) {
-                        final WeightedString word = getWordAtAddress(
-                                buffer, headerSize, bigram.mAddress, options);
-                        final int reconstructedFrequency =
-                                reconstructBigramFrequency(word.mFrequency, bigram.mFrequency);
-                        bigrams.add(new WeightedString(word.mWord, reconstructedFrequency));
-                    }
-                }
-                if (hasChildrenAddress(info.mChildrenAddress)) {
-                    Node children = reverseNodeMap.get(info.mChildrenAddress);
-                    if (null == children) {
-                        final int currentPosition = buffer.position();
-                        buffer.position(info.mChildrenAddress + headerSize);
-                        children = readNode(
-                                buffer, headerSize, reverseNodeMap, reverseGroupMap, options);
-                        buffer.position(currentPosition);
-                    }
-                    nodeContents.add(
-                            new CharGroup(info.mCharacters, shortcutTargets, bigrams,
-                                    info.mFrequency,
-                                    0 != (info.mFlags & FormatSpec.FLAG_IS_NOT_A_WORD),
-                                    0 != (info.mFlags & FormatSpec.FLAG_IS_BLACKLISTED), children));
-                } else {
-                    nodeContents.add(
-                            new CharGroup(info.mCharacters, shortcutTargets, bigrams,
-                                    info.mFrequency,
-                                    0 != (info.mFlags & FormatSpec.FLAG_IS_NOT_A_WORD),
-                                    0 != (info.mFlags & FormatSpec.FLAG_IS_BLACKLISTED)));
-                }
-                groupOffset = info.mEndAddress;
-            }
-
-            // reach the end of the array.
-            if (options.mSupportsDynamicUpdate) {
-                final int nextAddress = buffer.readUnsignedInt24();
-                if (nextAddress >= 0 && nextAddress < buffer.limit()) {
-                    buffer.position(nextAddress);
-                } else {
-                    break;
-                }
-            }
-        } while (options.mSupportsDynamicUpdate &&
-                buffer.position() != FormatSpec.NO_FORWARD_LINK_ADDRESS);
-
-        final Node node = new Node(nodeContents);
-        node.mCachedAddressBeforeUpdate = nodeOrigin;
-        node.mCachedAddressAfterUpdate = nodeOrigin;
-        reverseNodeMap.put(node.mCachedAddressAfterUpdate, node);
-        return node;
-    }
-
-    /**
-     * Helper function to get the binary format version from the header.
-     * @throws IOException
-     */
-    private static int getFormatVersion(final FusionDictionaryBufferInterface buffer)
-            throws IOException {
-        final int magic_v1 = buffer.readUnsignedShort();
-        if (FormatSpec.VERSION_1_MAGIC_NUMBER == magic_v1) return buffer.readUnsignedByte();
-        final int magic_v2 = (magic_v1 << 16) + buffer.readUnsignedShort();
-        if (FormatSpec.VERSION_2_MAGIC_NUMBER == magic_v2) return buffer.readUnsignedShort();
-        return FormatSpec.NOT_A_VERSION_NUMBER;
-    }
-
-    /**
-     * Helper function to get and validate the binary format version.
-     * @throws UnsupportedFormatException
-     * @throws IOException
-     */
-    private static int checkFormatVersion(final FusionDictionaryBufferInterface buffer)
-            throws IOException, UnsupportedFormatException {
-        final int version = getFormatVersion(buffer);
-        if (version < FormatSpec.MINIMUM_SUPPORTED_VERSION
-                || version > FormatSpec.MAXIMUM_SUPPORTED_VERSION) {
-            throw new UnsupportedFormatException("This file has version " + version
-                    + ", but this implementation does not support versions above "
-                    + FormatSpec.MAXIMUM_SUPPORTED_VERSION);
-        }
-        return version;
-    }
-
-    /**
-     * Reads a header from a buffer.
-     * @param buffer the buffer to read.
-     * @throws IOException
-     * @throws UnsupportedFormatException
-     */
-    public static FileHeader readHeader(final FusionDictionaryBufferInterface buffer)
-            throws IOException, UnsupportedFormatException {
-        final int version = checkFormatVersion(buffer);
-        final int optionsFlags = buffer.readUnsignedShort();
-
-        final HashMap<String, String> attributes = new HashMap<String, String>();
-        final int headerSize;
-        if (version < FormatSpec.FIRST_VERSION_WITH_HEADER_SIZE) {
-            headerSize = buffer.position();
-        } else {
-            headerSize = buffer.readInt();
-            populateOptions(buffer, headerSize, attributes);
-            buffer.position(headerSize);
-        }
-
-        if (headerSize < 0) {
-            throw new UnsupportedFormatException("header size can't be negative.");
-        }
-
-        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 options from a buffer and populate a map with their contents.
-     *
-     * The buffer is read at the current position, so the caller must take care the pointer
-     * is in the right place before calling this.
-     */
-    public static void populateOptions(final FusionDictionaryBufferInterface buffer,
-            final int headerSize, final HashMap<String, String> options) {
-        while (buffer.position() < headerSize) {
-            final String key = CharEncoding.readString(buffer);
-            final String value = CharEncoding.readString(buffer);
-            options.put(key, value);
-        }
-    }
-
-    /**
-     * Reads a buffer and returns the memory representation of the dictionary.
-     *
-     * This high-level method takes a buffer and reads its contents, populating a
-     * FusionDictionary structure. The optional dict argument is an existing dictionary to
-     * which words from the buffer should be added. If it is null, a new dictionary is created.
-     *
-     * @param buffer the buffer to read.
-     * @param dict an optional dictionary to add words to, or null.
-     * @return the created (or merged) dictionary.
-     */
-    @UsedForTesting
-    public static FusionDictionary readDictionaryBinary(
-            final FusionDictionaryBufferInterface buffer, final FusionDictionary dict)
-                    throws IOException, UnsupportedFormatException {
-        // clear cache
-        wordCache.clear();
-
-        // Read header
-        final FileHeader header = readHeader(buffer);
-
-        Map<Integer, Node> reverseNodeMapping = new TreeMap<Integer, Node>();
-        Map<Integer, CharGroup> reverseGroupMapping = new TreeMap<Integer, CharGroup>();
-        final Node root = readNode(buffer, header.mHeaderSize, reverseNodeMapping,
-                reverseGroupMapping, header.mFormatOptions);
-
-        FusionDictionary newDict = new FusionDictionary(root, header.mDictionaryOptions);
-        if (null != dict) {
-            for (final Word w : dict) {
-                if (w.mIsBlacklistEntry) {
-                    newDict.addBlacklistEntry(w.mWord, w.mShortcutTargets, w.mIsNotAWord);
-                } else {
-                    newDict.add(w.mWord, w.mFrequency, w.mShortcutTargets, w.mIsNotAWord);
-                }
-            }
-            for (final Word w : dict) {
-                // By construction a binary dictionary may not have bigrams pointing to
-                // words that are not also registered as unigrams so we don't have to avoid
-                // them explicitly here.
-                for (final WeightedString bigram : w.mBigrams) {
-                    newDict.setBigram(w.mWord, bigram.mWord, bigram.mFrequency);
-                }
-            }
-        }
-
-        return newDict;
-    }
-
-    /**
-     * Helper method to pass a file name instead of a File object to isBinaryDictionary.
-     */
-    public static boolean isBinaryDictionary(final String filename) {
-        final File file = new File(filename);
-        return isBinaryDictionary(file);
-    }
-
-    /**
-     * Basic test to find out whether the file is a binary dictionary or not.
-     *
-     * Concretely this only tests the magic number.
-     *
-     * @param file The file to test.
-     * @return true if it's a binary dictionary, false otherwise
-     */
-    public static boolean isBinaryDictionary(final File file) {
-        FileInputStream inStream = null;
-        try {
-            inStream = new FileInputStream(file);
-            final ByteBuffer buffer = inStream.getChannel().map(
-                    FileChannel.MapMode.READ_ONLY, 0, file.length());
-            final int version = getFormatVersion(new ByteBufferWrapper(buffer));
-            return (version >= FormatSpec.MINIMUM_SUPPORTED_VERSION
-                    && version <= FormatSpec.MAXIMUM_SUPPORTED_VERSION);
-        } catch (FileNotFoundException e) {
-            return false;
-        } catch (IOException e) {
-            return false;
-        } finally {
-            if (inStream != null) {
-                try {
-                    inStream.close();
-                } catch (IOException e) {
-                    // do nothing
-                }
-            }
-        }
-    }
-
-    /**
-     * Calculate bigram frequency from compressed value
-     *
-     * @see #makeBigramFlags
-     *
-     * @param unigramFrequency
-     * @param bigramFrequency compressed frequency
-     * @return approximate bigram frequency
-     */
-    public static int reconstructBigramFrequency(final int unigramFrequency,
-            final int bigramFrequency) {
-        final float stepSize = (FormatSpec.MAX_TERMINAL_FREQUENCY - unigramFrequency)
-                / (1.5f + FormatSpec.MAX_BIGRAM_FREQUENCY);
-        final float resultFreqFloat = unigramFrequency + stepSize * (bigramFrequency + 1.0f);
-        return (int)resultFreqFloat;
-    }
-}
diff --git a/java/src/com/android/inputmethod/latin/makedict/DictDecoder.java b/java/src/com/android/inputmethod/latin/makedict/DictDecoder.java
new file mode 100644
index 0000000..3796a46
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/makedict/DictDecoder.java
@@ -0,0 +1,394 @@
+/*
+ * 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.WeightedString;
+import com.android.inputmethod.latin.utils.ByteArrayDictBuffer;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.TreeMap;
+
+/**
+ * The base class of binary dictionary decoders.
+ */
+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.
+     * @param ptNodePos the position of PtNode.
+     * @param formatOptions the format options.
+     * @return PtNodeInfo.
+     */
+    public abstract PtNodeInfo readPtNode(final int ptNodePos, final FormatOptions formatOptions);
+
+    /**
+     * Reads a buffer and returns the memory representation of the dictionary.
+     *
+     * This high-level method takes a buffer and reads its contents, populating a
+     * FusionDictionary structure. The optional dict argument is an existing dictionary to
+     * which words from the buffer should be added. If it is null, a new dictionary is created.
+     *
+     * @param dict an optional dictionary to add words to, or null.
+     * @param deleteDictIfBroken a flag indicating whether this method should remove the broken
+     * dictionary or not.
+     * @return the created (or merged) dictionary.
+     */
+    @UsedForTesting
+    public abstract FusionDictionary readDictionaryBinary(final FusionDictionary dict,
+            final boolean deleteDictIfBroken)
+                    throws FileNotFoundException, IOException, UnsupportedFormatException;
+
+    /**
+     * Gets the address of the last PtNode of the exact matching word in the dictionary.
+     * If no match is found, returns NOT_VALID_WORD.
+     *
+     * @param word the word we search for.
+     * @return the address of the terminal node.
+     * @throws IOException if the file can't be read.
+     * @throws UnsupportedFormatException if the format of the file is not recognized.
+     */
+    @UsedForTesting
+    public int getTerminalPosition(final String word)
+            throws IOException, UnsupportedFormatException {
+        if (!isDictBufferOpen()) {
+            openDictBuffer();
+        }
+        return BinaryDictIOUtils.getTerminalPosition(this, word);
+    }
+
+    /**
+     * Reads unigrams and bigrams from the binary file.
+     * Doesn't store a full memory representation of the dictionary.
+     *
+     * @param words the map to store the address as a key and the word as a value.
+     * @param frequencies the map to store the address as a key and the frequency as a value.
+     * @param bigrams the map to store the address as a key and the list of address as a value.
+     * @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 {
+        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 abstract void setPosition(final int newPos);
+
+    /**
+     * Gets the position of the buffer.
+     *
+     * @return the position
+     */
+    public abstract int getPosition();
+
+    /**
+     * Reads and returns the PtNode count out of a buffer and forwards the pointer.
+     */
+    public abstract int readPtNodeCount();
+
+    /**
+     * Reads the forward link and advances the position.
+     *
+     * @return true if this method moves the file pointer, false otherwise.
+     */
+    public abstract boolean readAndFollowForwardLink();
+    public abstract boolean hasNextPtNodeArray();
+
+    /**
+     * Opens the dictionary file and makes DictBuffer.
+     */
+    @UsedForTesting
+    public abstract void openDictBuffer() throws FileNotFoundException, IOException;
+    @UsedForTesting
+    public abstract boolean isDictBufferOpen();
+
+    // 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 = 0x03000000;
+    public static final int MASK_DICTBUFFER = 0x0F000000;
+
+    public interface DictionaryBufferFactory {
+        public DictBuffer getDictionaryBuffer(final File file)
+                throws FileNotFoundException, IOException;
+    }
+
+    /**
+     * Creates DictionaryBuffer using a ByteBuffer
+     *
+     * This class uses less memory than DictionaryBufferFromByteArrayFactory,
+     * but doesn't perform as fast.
+     * When operating on a big dictionary, this class is preferred.
+     */
+    public static final class DictionaryBufferFromReadOnlyByteBufferFactory
+            implements DictionaryBufferFactory {
+        @Override
+        public DictBuffer getDictionaryBuffer(final File file)
+                throws FileNotFoundException, IOException {
+            FileInputStream inStream = null;
+            ByteBuffer buffer = null;
+            try {
+                inStream = new FileInputStream(file);
+                buffer = inStream.getChannel().map(FileChannel.MapMode.READ_ONLY,
+                        0, file.length());
+            } finally {
+                if (inStream != null) {
+                    inStream.close();
+                }
+            }
+            if (buffer != null) {
+                return new BinaryDictDecoderUtils.ByteBufferDictBuffer(buffer);
+            }
+            return null;
+        }
+    }
+
+    /**
+     * Creates DictionaryBuffer using a byte array
+     *
+     * This class performs faster than other classes, but consumes more memory.
+     * When operating on a small dictionary, this class is preferred.
+     */
+    public static final class DictionaryBufferFromByteArrayFactory
+            implements DictionaryBufferFactory {
+        @Override
+        public DictBuffer getDictionaryBuffer(final File file)
+                throws FileNotFoundException, IOException {
+            FileInputStream inStream = null;
+            try {
+                inStream = new FileInputStream(file);
+                final byte[] array = new byte[(int) file.length()];
+                inStream.read(array);
+                return new ByteArrayDictBuffer(array);
+            } finally {
+                if (inStream != null) {
+                    inStream.close();
+                }
+            }
+        }
+    }
+
+    /**
+     * Creates DictionaryBuffer using a writable ByteBuffer and a RandomAccessFile.
+     *
+     * This class doesn't perform as fast as other classes,
+     * but this class is the only option available for destructive operations (insert or delete)
+     * on a dictionary.
+     */
+    @UsedForTesting
+    public static final class DictionaryBufferFromWritableByteBufferFactory
+            implements DictionaryBufferFactory {
+        @Override
+        public DictBuffer getDictionaryBuffer(final File file)
+                throws FileNotFoundException, IOException {
+            RandomAccessFile raFile = null;
+            ByteBuffer buffer = null;
+            try {
+                raFile = new RandomAccessFile(file, "rw");
+                buffer = raFile.getChannel().map(FileChannel.MapMode.READ_WRITE, 0, file.length());
+            } finally {
+                if (raFile != null) {
+                    raFile.close();
+                }
+            }
+            if (buffer != null) {
+                return new BinaryDictDecoderUtils.ByteBufferDictBuffer(buffer);
+            }
+            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/DictEncoder.java b/java/src/com/android/inputmethod/latin/makedict/DictEncoder.java
new file mode 100644
index 0000000..ea5d492
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/makedict/DictEncoder.java
@@ -0,0 +1,38 @@
+/*
+ * 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.latin.makedict.FormatSpec.FormatOptions;
+import com.android.inputmethod.latin.makedict.FusionDictionary.PtNode;
+
+import java.io.IOException;
+
+/**
+ * An interface of binary dictionary encoder.
+ */
+public interface DictEncoder {
+    public void writeDictionary(final FusionDictionary dict, final FormatOptions formatOptions)
+            throws IOException, UnsupportedFormatException;
+
+    public void setPosition(final int position);
+    public int getPosition();
+    public void writePtNodeCount(final int ptNodeCount);
+    public void writeForwardLinkAddress(final int forwardLinkAddress);
+
+    public void writePtNode(final PtNode ptNode, final int parentPosition,
+            final FormatOptions formatOptions, final FusionDictionary dict);
+}
diff --git a/java/src/com/android/inputmethod/latin/makedict/DynamicBinaryDictIOUtils.java b/java/src/com/android/inputmethod/latin/makedict/DynamicBinaryDictIOUtils.java
new file mode 100644
index 0000000..bf3d191
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/makedict/DynamicBinaryDictIOUtils.java
@@ -0,0 +1,502 @@
+/*
+ * 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.Constants;
+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 java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * The utility class to help dynamic updates on the binary dictionary.
+ *
+ * All the methods in this class are static.
+ */
+@UsedForTesting
+public final class DynamicBinaryDictIOUtils {
+    private static final boolean DBG = false;
+    private static final int MAX_JUMPS = 10000;
+
+    private DynamicBinaryDictIOUtils() {
+        // This utility class is not publicly instantiable.
+    }
+
+    private static int markAsDeleted(final int flags) {
+        return (flags & (~FormatSpec.MASK_CHILDREN_ADDRESS_TYPE)) | FormatSpec.FLAG_IS_DELETED;
+    }
+
+    /**
+     * Delete the word from the binary file.
+     *
+     * @param dictDecoder the dict decoder.
+     * @param word the word we delete
+     * @throws IOException
+     * @throws UnsupportedFormatException
+     */
+    @UsedForTesting
+    public static void deleteWord(final Ver3DictDecoder dictDecoder, final String word)
+            throws IOException, UnsupportedFormatException {
+        final DictBuffer dictBuffer = dictDecoder.getDictBuffer();
+        dictBuffer.position(0);
+        final FileHeader header = dictDecoder.readHeader();
+        final int wordPosition = dictDecoder.getTerminalPosition(word);
+        if (wordPosition == FormatSpec.NOT_VALID_WORD) return;
+
+        dictBuffer.position(wordPosition);
+        final int flags = dictBuffer.readUnsignedByte();
+        dictBuffer.position(wordPosition);
+        dictBuffer.put((byte)markAsDeleted(flags));
+    }
+
+    /**
+     * Update a parent address in a PtNode that is referred to by ptNodeOriginAddress.
+     *
+     * @param dictBuffer the DictBuffer to write.
+     * @param ptNodeOriginAddress the address of the PtNode.
+     * @param newParentAddress the absolute address of the parent.
+     * @param formatOptions file format options.
+     */
+    public static void updateParentAddress(final DictBuffer dictBuffer,
+            final int ptNodeOriginAddress, final int newParentAddress,
+            final FormatOptions formatOptions) {
+        final int originalPosition = dictBuffer.position();
+        dictBuffer.position(ptNodeOriginAddress);
+        if (!formatOptions.mSupportsDynamicUpdate) {
+            throw new RuntimeException("this file format does not support parent addresses");
+        }
+        final int flags = dictBuffer.readUnsignedByte();
+        if (BinaryDictIOUtils.isMovedPtNode(flags, formatOptions)) {
+            // If the node is moved, the parent address is stored in the destination node.
+            // We are guaranteed to process the destination node later, so there is no need to
+            // update anything here.
+            dictBuffer.position(originalPosition);
+            return;
+        }
+        if (DBG) {
+            MakedictLog.d("update parent address flags=" + flags + ", " + ptNodeOriginAddress);
+        }
+        final int parentOffset = newParentAddress - ptNodeOriginAddress;
+        BinaryDictIOUtils.writeSInt24ToBuffer(dictBuffer, parentOffset);
+        dictBuffer.position(originalPosition);
+    }
+
+    /**
+     * Update parent addresses in a node array stored at ptNodeOriginAddress.
+     *
+     * @param dictBuffer the DictBuffer to be modified.
+     * @param ptNodeOriginAddress the address of the node array to update.
+     * @param newParentAddress the address to be written.
+     * @param formatOptions file format options.
+     */
+    public static void updateParentAddresses(final DictBuffer dictBuffer,
+            final int ptNodeOriginAddress, final int newParentAddress,
+            final FormatOptions formatOptions) {
+        final int originalPosition = dictBuffer.position();
+        dictBuffer.position(ptNodeOriginAddress);
+        do {
+            final int count = BinaryDictDecoderUtils.readPtNodeCount(dictBuffer);
+            for (int i = 0; i < count; ++i) {
+                updateParentAddress(dictBuffer, dictBuffer.position(), newParentAddress,
+                        formatOptions);
+                BinaryDictIOUtils.skipPtNode(dictBuffer, formatOptions);
+            }
+            final int forwardLinkAddress = dictBuffer.readUnsignedInt24();
+            dictBuffer.position(forwardLinkAddress);
+        } while (formatOptions.mSupportsDynamicUpdate
+                && dictBuffer.position() != FormatSpec.NO_FORWARD_LINK_ADDRESS);
+        dictBuffer.position(originalPosition);
+    }
+
+    /**
+     * Update a children address in a PtNode that is addressed by ptNodeOriginAddress.
+     *
+     * @param dictBuffer the DictBuffer to write.
+     * @param ptNodeOriginAddress the address of the PtNode.
+     * @param newChildrenAddress the absolute address of the child.
+     * @param formatOptions file format options.
+     */
+    public static void updateChildrenAddress(final DictBuffer dictBuffer,
+            final int ptNodeOriginAddress, final int newChildrenAddress,
+            final FormatOptions formatOptions) {
+        final int originalPosition = dictBuffer.position();
+        dictBuffer.position(ptNodeOriginAddress);
+        final int flags = dictBuffer.readUnsignedByte();
+        final int parentAddress = BinaryDictDecoderUtils.readParentAddress(dictBuffer,
+                formatOptions);
+        BinaryDictIOUtils.skipString(dictBuffer, (flags & FormatSpec.FLAG_HAS_MULTIPLE_CHARS) != 0);
+        if ((flags & FormatSpec.FLAG_IS_TERMINAL) != 0) dictBuffer.readUnsignedByte();
+        final int childrenOffset = newChildrenAddress == FormatSpec.NO_CHILDREN_ADDRESS
+                ? FormatSpec.NO_CHILDREN_ADDRESS : newChildrenAddress - dictBuffer.position();
+        BinaryDictIOUtils.writeSInt24ToBuffer(dictBuffer, childrenOffset);
+        dictBuffer.position(originalPosition);
+    }
+
+    /**
+     * Helper method to move a PtNode to the tail of the file.
+     */
+    private static int movePtNode(final OutputStream destination,
+            final DictBuffer dictBuffer, final PtNodeInfo info,
+            final int nodeArrayOriginAddress, final int oldNodeAddress,
+            final FormatOptions formatOptions) throws IOException {
+        updateParentAddress(dictBuffer, oldNodeAddress, dictBuffer.limit() + 1, formatOptions);
+        dictBuffer.position(oldNodeAddress);
+        final int currentFlags = dictBuffer.readUnsignedByte();
+        dictBuffer.position(oldNodeAddress);
+        dictBuffer.put((byte)(FormatSpec.FLAG_IS_MOVED | (currentFlags
+                & (~FormatSpec.MASK_MOVE_AND_DELETE_FLAG))));
+        int size = FormatSpec.PTNODE_FLAGS_SIZE;
+        updateForwardLink(dictBuffer, nodeArrayOriginAddress, dictBuffer.limit(), formatOptions);
+        size += BinaryDictIOUtils.writeNodes(destination, new PtNodeInfo[] { info });
+        return size;
+    }
+
+    @SuppressWarnings("unused")
+    private static void updateForwardLink(final DictBuffer dictBuffer,
+            final int nodeArrayOriginAddress, final int newNodeArrayAddress,
+            final FormatOptions formatOptions) {
+        dictBuffer.position(nodeArrayOriginAddress);
+        int jumpCount = 0;
+        while (jumpCount++ < MAX_JUMPS) {
+            final int count = BinaryDictDecoderUtils.readPtNodeCount(dictBuffer);
+            for (int i = 0; i < count; ++i) {
+                BinaryDictIOUtils.skipPtNode(dictBuffer, formatOptions);
+            }
+            final int forwardLinkAddress = dictBuffer.readUnsignedInt24();
+            if (forwardLinkAddress == FormatSpec.NO_FORWARD_LINK_ADDRESS) {
+                dictBuffer.position(dictBuffer.position() - FormatSpec.FORWARD_LINK_ADDRESS_SIZE);
+                BinaryDictIOUtils.writeSInt24ToBuffer(dictBuffer, newNodeArrayAddress);
+                return;
+            }
+            dictBuffer.position(forwardLinkAddress);
+        }
+        if (DBG && jumpCount >= MAX_JUMPS) {
+            throw new RuntimeException("too many jumps, probably a bug.");
+        }
+    }
+
+    /**
+     * Move a PtNode that is referred to by oldPtNodeOrigin to the tail of the file, and set the
+     * children address to the byte after the PtNode.
+     *
+     * @param fileEndAddress the address of the tail of the file.
+     * @param codePoints the characters to put inside the PtNode.
+     * @param length how many code points to read from codePoints.
+     * @param flags the flags for this PtNode.
+     * @param frequency the frequency of this terminal.
+     * @param parentAddress the address of the parent PtNode of this PtNode.
+     * @param shortcutTargets the shortcut targets for this PtNode.
+     * @param bigrams the bigrams for this PtNode.
+     * @param destination the stream representing the tail of the file.
+     * @param dictBuffer the DictBuffer representing the (constant-size) body of the file.
+     * @param oldPtNodeArrayOrigin the origin of the old PtNode array this PtNode was a part of.
+     * @param oldPtNodeOrigin the old origin where this PtNode used to be stored.
+     * @param formatOptions format options for this dictionary.
+     * @return the size written, in bytes.
+     * @throws IOException if the file can't be accessed
+     */
+    private static int movePtNode(final int fileEndAddress, final int[] codePoints,
+            final int length, final int flags, final int frequency, final int parentAddress,
+            final ArrayList<WeightedString> shortcutTargets,
+            final ArrayList<PendingAttribute> bigrams, final OutputStream destination,
+            final DictBuffer dictBuffer, final int oldPtNodeArrayOrigin,
+            final int oldPtNodeOrigin, final FormatOptions formatOptions) throws IOException {
+        int size = 0;
+        final int newPtNodeOrigin = fileEndAddress + 1;
+        final int[] writtenCharacters = Arrays.copyOfRange(codePoints, 0, length);
+        final PtNodeInfo tmpInfo = new PtNodeInfo(newPtNodeOrigin, -1 /* endAddress */,
+                flags, writtenCharacters, frequency, parentAddress, FormatSpec.NO_CHILDREN_ADDRESS,
+                shortcutTargets, bigrams);
+        size = BinaryDictIOUtils.computePtNodeSize(tmpInfo, formatOptions);
+        final PtNodeInfo newInfo = new PtNodeInfo(newPtNodeOrigin, newPtNodeOrigin + size,
+                flags, writtenCharacters, frequency, parentAddress,
+                fileEndAddress + 1 + size + FormatSpec.FORWARD_LINK_ADDRESS_SIZE, shortcutTargets,
+                bigrams);
+        movePtNode(destination, dictBuffer, newInfo, oldPtNodeArrayOrigin, oldPtNodeOrigin,
+                formatOptions);
+        return 1 + size + FormatSpec.FORWARD_LINK_ADDRESS_SIZE;
+    }
+
+    /**
+     * Insert a word into a binary dictionary.
+     *
+     * @param dictDecoder the dict decoder.
+     * @param destination a stream to the underlying file, with the pointer at the end of the file.
+     * @param word the word to insert.
+     * @param frequency the frequency of the new word.
+     * @param bigramStrings bigram list, or null if none.
+     * @param shortcuts shortcut list, or null if none.
+     * @param isBlackListEntry whether this should be a blacklist entry.
+     * @throws IOException if the file can't be accessed.
+     * @throws UnsupportedFormatException if the existing dictionary is in an unexpected format.
+     */
+    // TODO: Support batch insertion.
+    // TODO: Remove @UsedForTesting once UserHistoryDictionary is implemented by BinaryDictionary.
+    @UsedForTesting
+    public static void insertWord(final Ver3DictDecoder dictDecoder,
+            final OutputStream destination, final String word, final int frequency,
+            final ArrayList<WeightedString> bigramStrings,
+            final ArrayList<WeightedString> shortcuts, final boolean isNotAWord,
+            final boolean isBlackListEntry)
+                    throws IOException, UnsupportedFormatException {
+        final ArrayList<PendingAttribute> bigrams = new ArrayList<PendingAttribute>();
+        final DictBuffer dictBuffer = dictDecoder.getDictBuffer();
+        if (bigramStrings != null) {
+            for (final WeightedString bigram : bigramStrings) {
+                int position = dictDecoder.getTerminalPosition(bigram.mWord);
+                if (position == FormatSpec.NOT_VALID_WORD) {
+                    // TODO: figure out what is the correct thing to do here.
+                } else {
+                    bigrams.add(new PendingAttribute(bigram.mFrequency, position));
+                }
+            }
+        }
+
+        final boolean isTerminal = true;
+        final boolean hasBigrams = !bigrams.isEmpty();
+        final boolean hasShortcuts = shortcuts != null && !shortcuts.isEmpty();
+
+        // find the insert position of the word.
+        if (dictBuffer.position() != 0) dictBuffer.position(0);
+        final FileHeader fileHeader = dictDecoder.readHeader();
+
+        int wordPos = 0, address = dictBuffer.position(), nodeOriginAddress = dictBuffer.position();
+        final int[] codePoints = FusionDictionary.getCodePoints(word);
+        final int wordLen = codePoints.length;
+
+        for (int depth = 0; depth < Constants.DICTIONARY_MAX_WORD_LENGTH; ++depth) {
+            if (wordPos >= wordLen) break;
+            nodeOriginAddress = dictBuffer.position();
+            int nodeParentAddress = -1;
+            final int ptNodeCount = BinaryDictDecoderUtils.readPtNodeCount(dictBuffer);
+            boolean foundNextNode = false;
+
+            for (int i = 0; i < ptNodeCount; ++i) {
+                address = dictBuffer.position();
+                final PtNodeInfo currentInfo = dictDecoder.readPtNode(address,
+                        fileHeader.mFormatOptions);
+                final boolean isMovedNode = BinaryDictIOUtils.isMovedPtNode(currentInfo.mFlags,
+                        fileHeader.mFormatOptions);
+                if (isMovedNode) continue;
+                nodeParentAddress = (currentInfo.mParentAddress == FormatSpec.NO_PARENT_ADDRESS)
+                        ? FormatSpec.NO_PARENT_ADDRESS : currentInfo.mParentAddress + address;
+                boolean matched = true;
+                for (int p = 0; p < currentInfo.mCharacters.length; ++p) {
+                    if (wordPos + p >= wordLen) {
+                        /*
+                         * splitting
+                         * before
+                         *  abcd - ef
+                         *
+                         * insert "abc"
+                         *
+                         * after
+                         *  abc - d - ef
+                         */
+                        final int newNodeAddress = dictBuffer.limit();
+                        final int flags = BinaryDictEncoderUtils.makePtNodeFlags(p > 1,
+                                isTerminal, 0, hasShortcuts, hasBigrams, false /* isNotAWord */,
+                                false /* isBlackListEntry */, fileHeader.mFormatOptions);
+                        int written = movePtNode(newNodeAddress, currentInfo.mCharacters, p, flags,
+                                frequency, nodeParentAddress, shortcuts, bigrams, destination,
+                                dictBuffer, nodeOriginAddress, address, fileHeader.mFormatOptions);
+
+                        final int[] characters2 = Arrays.copyOfRange(currentInfo.mCharacters, p,
+                                currentInfo.mCharacters.length);
+                        if (currentInfo.mChildrenAddress != FormatSpec.NO_CHILDREN_ADDRESS) {
+                            updateParentAddresses(dictBuffer, currentInfo.mChildrenAddress,
+                                    newNodeAddress + written + 1, fileHeader.mFormatOptions);
+                        }
+                        final PtNodeInfo newInfo2 = new PtNodeInfo(
+                                newNodeAddress + written + 1, -1 /* endAddress */,
+                                currentInfo.mFlags, characters2, currentInfo.mFrequency,
+                                newNodeAddress + 1, currentInfo.mChildrenAddress,
+                                currentInfo.mShortcutTargets, currentInfo.mBigrams);
+                        BinaryDictIOUtils.writeNodes(destination, new PtNodeInfo[] { newInfo2 });
+                        return;
+                    } else if (codePoints[wordPos + p] != currentInfo.mCharacters[p]) {
+                        if (p > 0) {
+                            /*
+                             * splitting
+                             * before
+                             *   ab - cd
+                             *
+                             * insert "ac"
+                             *
+                             * after
+                             *   a - b - cd
+                             *     |
+                             *     - c
+                             */
+
+                            final int newNodeAddress = dictBuffer.limit();
+                            final int childrenAddress = currentInfo.mChildrenAddress;
+
+                            // move prefix
+                            final int prefixFlags = BinaryDictEncoderUtils.makePtNodeFlags(p > 1,
+                                    false /* isTerminal */, 0 /* childrenAddressSize*/,
+                                    false /* hasShortcut */, false /* hasBigrams */,
+                                    false /* isNotAWord */, false /* isBlackListEntry */,
+                                    fileHeader.mFormatOptions);
+                            int written = movePtNode(newNodeAddress, currentInfo.mCharacters, p,
+                                    prefixFlags, -1 /* frequency */, nodeParentAddress, null, null,
+                                    destination, dictBuffer, nodeOriginAddress, address,
+                                    fileHeader.mFormatOptions);
+
+                            final int[] suffixCharacters = Arrays.copyOfRange(
+                                    currentInfo.mCharacters, p, currentInfo.mCharacters.length);
+                            if (currentInfo.mChildrenAddress != FormatSpec.NO_CHILDREN_ADDRESS) {
+                                updateParentAddresses(dictBuffer, currentInfo.mChildrenAddress,
+                                        newNodeAddress + written + 1, fileHeader.mFormatOptions);
+                            }
+                            final int suffixFlags = BinaryDictEncoderUtils.makePtNodeFlags(
+                                    suffixCharacters.length > 1,
+                                    (currentInfo.mFlags & FormatSpec.FLAG_IS_TERMINAL) != 0,
+                                    0 /* childrenAddressSize */,
+                                    (currentInfo.mFlags & FormatSpec.FLAG_HAS_SHORTCUT_TARGETS)
+                                            != 0,
+                                    (currentInfo.mFlags & FormatSpec.FLAG_HAS_BIGRAMS) != 0,
+                                    isNotAWord, isBlackListEntry, fileHeader.mFormatOptions);
+                            final PtNodeInfo suffixInfo = new PtNodeInfo(
+                                    newNodeAddress + written + 1, -1 /* endAddress */, suffixFlags,
+                                    suffixCharacters, currentInfo.mFrequency, newNodeAddress + 1,
+                                    currentInfo.mChildrenAddress, currentInfo.mShortcutTargets,
+                                    currentInfo.mBigrams);
+                            written += BinaryDictIOUtils.computePtNodeSize(suffixInfo,
+                                    fileHeader.mFormatOptions) + 1;
+
+                            final int[] newCharacters = Arrays.copyOfRange(codePoints, wordPos + p,
+                                    codePoints.length);
+                            final int flags = BinaryDictEncoderUtils.makePtNodeFlags(
+                                    newCharacters.length > 1, isTerminal,
+                                    0 /* childrenAddressSize */, hasShortcuts, hasBigrams,
+                                    isNotAWord, isBlackListEntry, fileHeader.mFormatOptions);
+                            final PtNodeInfo newInfo = new PtNodeInfo(
+                                    newNodeAddress + written, -1 /* endAddress */, flags,
+                                    newCharacters, frequency, newNodeAddress + 1,
+                                    FormatSpec.NO_CHILDREN_ADDRESS, shortcuts, bigrams);
+                            BinaryDictIOUtils.writeNodes(destination,
+                                    new PtNodeInfo[] { suffixInfo, newInfo });
+                            return;
+                        }
+                        matched = false;
+                        break;
+                    }
+                }
+
+                if (matched) {
+                    if (wordPos + currentInfo.mCharacters.length == wordLen) {
+                        // the word exists in the dictionary.
+                        // only update the PtNode.
+                        final int newNodeAddress = dictBuffer.limit();
+                        final boolean hasMultipleChars = currentInfo.mCharacters.length > 1;
+                        final int flags = BinaryDictEncoderUtils.makePtNodeFlags(hasMultipleChars,
+                                isTerminal, 0 /* childrenAddressSize */, hasShortcuts, hasBigrams,
+                                isNotAWord, isBlackListEntry, fileHeader.mFormatOptions);
+                        final PtNodeInfo newInfo = new PtNodeInfo(newNodeAddress + 1,
+                                -1 /* endAddress */, flags, currentInfo.mCharacters, frequency,
+                                nodeParentAddress, currentInfo.mChildrenAddress, shortcuts,
+                                bigrams);
+                        movePtNode(destination, dictBuffer, newInfo, nodeOriginAddress, address,
+                                fileHeader.mFormatOptions);
+                        return;
+                    }
+                    wordPos += currentInfo.mCharacters.length;
+                    if (currentInfo.mChildrenAddress == FormatSpec.NO_CHILDREN_ADDRESS) {
+                        /*
+                         * found the prefix of the word.
+                         * make new PtNode and link to the PtNode from this PtNode.
+                         *
+                         * before
+                         * ab - cd
+                         *
+                         * insert "abcde"
+                         *
+                         * after
+                         * ab - cd - e
+                         */
+                        final int newNodeArrayAddress = dictBuffer.limit();
+                        updateChildrenAddress(dictBuffer, address, newNodeArrayAddress,
+                                fileHeader.mFormatOptions);
+                        final int newNodeAddress = newNodeArrayAddress + 1;
+                        final boolean hasMultipleChars = (wordLen - wordPos) > 1;
+                        final int flags = BinaryDictEncoderUtils.makePtNodeFlags(hasMultipleChars,
+                                isTerminal, 0 /* childrenAddressSize */, hasShortcuts, hasBigrams,
+                                isNotAWord, isBlackListEntry, fileHeader.mFormatOptions);
+                        final int[] characters = Arrays.copyOfRange(codePoints, wordPos, wordLen);
+                        final PtNodeInfo newInfo = new PtNodeInfo(newNodeAddress, -1, flags,
+                                characters, frequency, address, FormatSpec.NO_CHILDREN_ADDRESS,
+                                shortcuts, bigrams);
+                        BinaryDictIOUtils.writeNodes(destination, new PtNodeInfo[] { newInfo });
+                        return;
+                    }
+                    dictBuffer.position(currentInfo.mChildrenAddress);
+                    foundNextNode = true;
+                    break;
+                }
+            }
+
+            if (foundNextNode) continue;
+
+            // reached the end of the array.
+            final int linkAddressPosition = dictBuffer.position();
+            int nextLink = dictBuffer.readUnsignedInt24();
+            if ((nextLink & FormatSpec.MSB24) != 0) {
+                nextLink = -(nextLink & FormatSpec.SINT24_MAX);
+            }
+            if (nextLink == FormatSpec.NO_FORWARD_LINK_ADDRESS) {
+                /*
+                 * expand this node.
+                 *
+                 * before
+                 * ab - cd
+                 *
+                 * insert "abef"
+                 *
+                 * after
+                 * ab - cd
+                 *    |
+                 *    - ef
+                 */
+
+                // change the forward link address.
+                final int newNodeAddress = dictBuffer.limit();
+                dictBuffer.position(linkAddressPosition);
+                BinaryDictIOUtils.writeSInt24ToBuffer(dictBuffer, newNodeAddress);
+
+                final int[] characters = Arrays.copyOfRange(codePoints, wordPos, wordLen);
+                final int flags = BinaryDictEncoderUtils.makePtNodeFlags(characters.length > 1,
+                        isTerminal, 0 /* childrenAddressSize */, hasShortcuts, hasBigrams,
+                        isNotAWord, isBlackListEntry, fileHeader.mFormatOptions);
+                final PtNodeInfo newInfo = new PtNodeInfo(newNodeAddress + 1,
+                        -1 /* endAddress */, flags, characters, frequency, nodeParentAddress,
+                        FormatSpec.NO_CHILDREN_ADDRESS, shortcuts, bigrams);
+                BinaryDictIOUtils.writeNodes(destination, new PtNodeInfo[]{ newInfo });
+                return;
+            } else {
+                depth--;
+                dictBuffer.position(nextLink);
+            }
+        }
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
index 46266aa..51b89a0 100644
--- a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
+++ b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
@@ -18,29 +18,66 @@
 
 import com.android.inputmethod.annotations.UsedForTesting;
 import com.android.inputmethod.latin.Constants;
+import com.android.inputmethod.latin.makedict.DictDecoder.DictionaryBufferFactory;
 import com.android.inputmethod.latin.makedict.FusionDictionary.DictionaryOptions;
 
+import java.io.File;
+
 /**
  * Dictionary File Format Specification.
  */
 public final class FormatSpec {
 
     /*
-     * Array of Node(FusionDictionary.Node) layout is as follows:
+     * File header layout is as follows:
      *
-     * g |
-     * r | the number of groups, 1 or 2 bytes.
-     * o | 1 byte = bbbbbbbb match
-     * u |   case 1xxxxxxx => xxxxxxx << 8 + next byte
-     * p |   otherwise => bbbbbbbb
-     * c |
-     * ount
+     * v |
+     * e | MAGIC_NUMBER + version of the file format, 2 bytes.
+     * r |
+     * sion
      *
-     * g |
-     * r | sequence of groups,
-     * o | the layout of each group is described below.
-     * u |
-     * ps
+     * o |
+     * p | not used                                4 bits
+     * t | has bigrams ?                           1 bit, 1 = yes, 0 = no : CONTAINS_BIGRAMS_FLAG
+     * i | FRENCH_LIGATURE_PROCESSING_FLAG
+     * o | supports dynamic updates ?              1 bit, 1 = yes, 0 = no : SUPPORTS_DYNAMIC_UPDATE
+     * n | GERMAN_UMLAUT_PROCESSING_FLAG
+     * f |
+     * lags
+     *
+     * h |
+     * e | size of the file header, 4bytes
+     * a |   including the size of the magic number, the option flags and the header size
+     * d |
+     * ersize
+     *
+     *   | attributes list
+     *
+     * attributes list is:
+     * <key>   = | string of characters at the char format described below, with the terminator used
+     *           | to signal the end of the string.
+     * <value> = | string of characters at the char format described below, with the terminator used
+     *           | to signal the end of the string.
+     * if the size of already read < headersize, goto key.
+     *
+     */
+
+    /*
+     * Node array (FusionDictionary.PtNodeArray) layout is as follows:
+     *
+     * n |
+     * o | the number of PtNodes, 1 or 2 bytes.
+     * d | 1 byte = bbbbbbbb match
+     * e |   case 1xxxxxxx => xxxxxxx << 8 + next byte
+     * c |   otherwise => bbbbbbbb
+     * o |
+     * unt
+     *
+     * n |
+     * o | sequence of PtNodes,
+     * d | the layout of each PtNode is described below.
+     * e |
+     * s
      *
      * f |
      * o | IF SUPPORTS_DYNAMIC_UPDATE (defined in the file header)
@@ -52,19 +89,19 @@
      * linkaddress
      */
 
-    /* Node(CharGroup) layout is as follows:
+    /* Node (FusionDictionary.PtNode) layout is as follows:
      *   | IF !SUPPORTS_DYNAMIC_UPDATE
-     *   |   addressType                         xx     : mask with MASK_GROUP_ADDRESS_TYPE
-     *   |                           2 bits, 00 = no children : FLAG_GROUP_ADDRESS_TYPE_NOADDRESS
-     * f |                                   01 = 1 byte      : FLAG_GROUP_ADDRESS_TYPE_ONEBYTE
-     * l |                                   10 = 2 bytes     : FLAG_GROUP_ADDRESS_TYPE_TWOBYTES
-     * a |                                   11 = 3 bytes     : FLAG_GROUP_ADDRESS_TYPE_THREEBYTES
+     *   |   addressType                    xx               : mask with MASK_CHILDREN_ADDRESS_TYPE
+     *   |                          2 bits, 00 = no children : FLAG_CHILDREN_ADDRESS_TYPE_NOADDRESS
+     * f |                                  01 = 1 byte      : FLAG_CHILDREN_ADDRESS_TYPE_ONEBYTE
+     * l |                                  10 = 2 bytes     : FLAG_CHILDREN_ADDRESS_TYPE_TWOBYTES
+     * a |                                  11 = 3 bytes     : FLAG_CHILDREN_ADDRESS_TYPE_THREEBYTES
      * g | ELSE
-     * s |   is moved ?              2 bits, 11 = no          : FLAG_IS_NOT_MOVED
-     *   |                              This must be the same as FLAG_GROUP_ADDRESS_TYPE_THREEBYTES
-     *   |                                   01 = yes         : FLAG_IS_MOVED
+     * s |   is moved ?             2 bits, 11 = no          : FLAG_IS_NOT_MOVED
+     *   |                            This must be the same as FLAG_CHILDREN_ADDRESS_TYPE_THREEBYTES
+     *   |                                  01 = yes         : FLAG_IS_MOVED
      *   |                        the new address is stored in the same place as the parent address
-     *   |   is deleted?                     10 = yes         : FLAG_IS_DELETED
+     *   |   is deleted?                    10 = yes         : FLAG_IS_DELETED
      *   | has several chars ?         1 bit, 1 = yes, 0 = no   : FLAG_HAS_MULTIPLE_CHARS
      *   | has a terminal ?            1 bit, 1 = yes, 0 = no   : FLAG_IS_TERMINAL
      *   | has shortcut targets ?      1 bit, 1 = yes, 0 = no   : FLAG_HAS_SHORTCUT_TARGETS
@@ -78,11 +115,13 @@
      * e | 1 byte = bbbbbbbb match
      * n |   case 1xxxxxxx => -((0xxxxxxx << 16) + (next byte << 8) + next byte)
      * t |   otherwise => (bbbbbbbb << 16) + (next byte << 8) + next byte
-     * a |
-     * ddress
+     * a | This address is relative to the head of the PtNode.
+     * d | If the node doesn't have a parent, this field is set to 0.
+     * d |
+     * ress
      *
      * c | IF FLAG_HAS_MULTIPLE_CHARS
-     * h |   char, char, char, char    n * (1 or 3 bytes) : use CharGroupInfo for i/o helpers
+     * h |   char, char, char, char    n * (1 or 3 bytes) : use PtNodeInfo for i/o helpers
      * a |   end                       1 byte, = 0
      * r | ELSE
      * s |   char                      1 or 3 bytes
@@ -93,17 +132,23 @@
      * e |   frequency                 1 byte
      * q |
      *
-     * c | IF 00 = FLAG_GROUP_ADDRESS_TYPE_NOADDRESS = addressType
-     * h |   // nothing
-     * i | ELSIF 01 = FLAG_GROUP_ADDRESS_TYPE_ONEBYTE == addressType
-     * l |   children address, 1 byte
-     * d | ELSIF 10 = FLAG_GROUP_ADDRESS_TYPE_TWOBYTES == addressType
-     * r |   children address, 2 bytes
-     * e | ELSE // 11 = FLAG_GROUP_ADDRESS_TYPE_THREEBYTES = addressType
-     * n |   children address, 3 bytes
-     * A | END
-     * d
-     * dress
+     * c | IF SUPPORTS_DYNAMIC_UPDATE
+     * h |   children address, 3 bytes
+     * i |   1 byte = bbbbbbbb match
+     * l |     case 1xxxxxxx => -((0xxxxxxx << 16) + (next byte << 8) + next byte)
+     * d |     otherwise => (bbbbbbbb<<16) + (next byte << 8) + next byte
+     * r |   if this node doesn't have children, this field is set to 0.
+     * e |     (see BinaryDictEncoderUtils#writeVariableSignedAddress)
+     * n | ELSIF 00 = FLAG_CHILDREN_ADDRESS_TYPE_NOADDRESS == addressType
+     * a |   // nothing
+     * d | ELSIF 01 = FLAG_CHILDREN_ADDRESS_TYPE_ONEBYTE == addressType
+     * d |   children address, 1 byte
+     * r | ELSIF 10 = FLAG_CHILDREN_ADDRESS_TYPE_TWOBYTES == addressType
+     * e |   children address, 2 bytes
+     * s | ELSE // 11 = FLAG_CHILDREN_ADDRESS_TYPE_THREEBYTES = addressType
+     * s |   children address, 3 bytes
+     *   | END
+     *   | This address is relative to the position of this field.
      *
      *   | IF FLAG_IS_TERMINAL && FLAG_HAS_SHORTCUT_TARGETS
      *   | shortcut string list
@@ -122,42 +167,43 @@
      * characters which should never happen anyway (and still work, but take 3 bytes).
      *
      * bigram address list is:
-     * <flags> = | hasNext = 1 bit, 1 = yes, 0 = no     : FLAG_ATTRIBUTE_HAS_NEXT
-     *           | addressSign = 1 bit,                 : FLAG_ATTRIBUTE_OFFSET_NEGATIVE
+     * <flags> = | hasNext = 1 bit, 1 = yes, 0 = no     : FLAG_BIGRAM_SHORTCUT_ATTR_HAS_NEXT
+     *           | addressSign = 1 bit,                 : FLAG_BIGRAM_ATTR_OFFSET_NEGATIVE
      *           |                      1 = must take -address, 0 = must take +address
-     *           |                         xx : mask with MASK_ATTRIBUTE_ADDRESS_TYPE
-     *           | addressFormat = 2 bits, 00 = unused  : FLAG_ATTRIBUTE_ADDRESS_TYPE_ONEBYTE
-     *           |                         01 = 1 byte  : FLAG_ATTRIBUTE_ADDRESS_TYPE_ONEBYTE
-     *           |                         10 = 2 bytes : FLAG_ATTRIBUTE_ADDRESS_TYPE_TWOBYTES
-     *           |                         11 = 3 bytes : FLAG_ATTRIBUTE_ADDRESS_TYPE_THREEBYTES
-     *           | 4 bits : frequency         : mask with FLAG_ATTRIBUTE_FREQUENCY
-     * <address> | IF (01 == FLAG_ATTRIBUTE_ADDRESS_TYPE_ONEBYTE == addressFormat)
+     *           |                         xx : mask with MASK_BIGRAM_ATTR_ADDRESS_TYPE
+     *           | addressFormat = 2 bits, 00 = unused  : FLAG_BIGRAM_ATTR_ADDRESS_TYPE_ONEBYTE
+     *           |                         01 = 1 byte  : FLAG_BIGRAM_ATTR_ADDRESS_TYPE_ONEBYTE
+     *           |                         10 = 2 bytes : FLAG_BIGRAM_ATTR_ADDRESS_TYPE_TWOBYTES
+     *           |                         11 = 3 bytes : FLAG_BIGRAM_ATTR_ADDRESS_TYPE_THREEBYTES
+     *           | 4 bits : frequency         : mask with FLAG_BIGRAM_SHORTCUT_ATTR_FREQUENCY
+     * <address> | IF (01 == FLAG_BIGRAM_ATTR_ADDRESS_TYPE_ONEBYTE == addressFormat)
      *           |   read 1 byte, add top 4 bits
-     *           | ELSIF (10 == FLAG_ATTRIBUTE_ADDRESS_TYPE_TWOBYTES == addressFormat)
+     *           | ELSIF (10 == FLAG_BIGRAM_ATTR_ADDRESS_TYPE_TWOBYTES == addressFormat)
      *           |   read 2 bytes, add top 4 bits
-     *           | ELSE // 11 == FLAG_ATTRIBUTE_ADDRESS_TYPE_THREEBYTES == addressFormat
+     *           | ELSE // 11 == FLAG_BIGRAM_ATTR_ADDRESS_TYPE_THREEBYTES == addressFormat
      *           |   read 3 bytes, add top 4 bits
      *           | END
-     *           | if (FLAG_ATTRIBUTE_OFFSET_NEGATIVE) then address = -address
-     * if (FLAG_ATTRIBUTE_HAS_NEXT) goto bigram_and_shortcut_address_list_is
+     *           | if (FLAG_BIGRAM_ATTR_OFFSET_NEGATIVE) then address = -address
+     * if (FLAG_BIGRAM_SHORTCUT_ATTR_HAS_NEXT) goto bigram_and_shortcut_address_list_is
      *
      * shortcut string list is:
-     * <byte size> = GROUP_SHORTCUT_LIST_SIZE_SIZE bytes, big-endian: size of the list, in bytes.
-     * <flags>     = | hasNext = 1 bit, 1 = yes, 0 = no : FLAG_ATTRIBUTE_HAS_NEXT
+     * <byte size> = PTNODE_SHORTCUT_LIST_SIZE_SIZE bytes, big-endian: size of the list, in bytes.
+     * <flags>     = | hasNext = 1 bit, 1 = yes, 0 = no : FLAG_BIGRAM_SHORTCUT_ATTR_HAS_NEXT
      *               | reserved = 3 bits, must be 0
-     *               | 4 bits : frequency : mask with FLAG_ATTRIBUTE_FREQUENCY
+     *               | 4 bits : frequency : mask with FLAG_BIGRAM_SHORTCUT_ATTR_FREQUENCY
      * <shortcut>  = | string of characters at the char format described above, with the terminator
      *               | used to signal the end of the string.
-     * if (FLAG_ATTRIBUTE_HAS_NEXT goto flags
+     * if (FLAG_BIGRAM_SHORTCUT_ATTR_HAS_NEXT goto flags
      */
 
-    static final int VERSION_1_MAGIC_NUMBER = 0x78B1;
-    public static final int VERSION_2_MAGIC_NUMBER = 0x9BC13AFE;
-    static final int MINIMUM_SUPPORTED_VERSION = 1;
-    static final int MAXIMUM_SUPPORTED_VERSION = 3;
+    public static final int MAGIC_NUMBER = 0x9BC13AFE;
+    static final int MINIMUM_SUPPORTED_VERSION = 2;
+    static final int MAXIMUM_SUPPORTED_VERSION = 4;
     static final int NOT_A_VERSION_NUMBER = -1;
-    static final int FIRST_VERSION_WITH_HEADER_SIZE = 2;
     static final int FIRST_VERSION_WITH_DYNAMIC_UPDATE = 3;
+    static final int FIRST_VERSION_WITH_TERMINAL_ID = 4;
+    static final int VERSION3 = 3;
+    static final int VERSION4 = 4;
 
     // These options need to be the same numeric values as the one in the native reading code.
     static final int GERMAN_UMLAUT_PROCESSING_FLAG = 0x1;
@@ -174,11 +220,11 @@
     static final int FORWARD_LINK_ADDRESS_SIZE = 3;
 
     // These flags are used only in the static dictionary.
-    static final int MASK_GROUP_ADDRESS_TYPE = 0xC0;
-    static final int FLAG_GROUP_ADDRESS_TYPE_NOADDRESS = 0x00;
-    static final int FLAG_GROUP_ADDRESS_TYPE_ONEBYTE = 0x40;
-    static final int FLAG_GROUP_ADDRESS_TYPE_TWOBYTES = 0x80;
-    static final int FLAG_GROUP_ADDRESS_TYPE_THREEBYTES = 0xC0;
+    static final int MASK_CHILDREN_ADDRESS_TYPE = 0xC0;
+    static final int FLAG_CHILDREN_ADDRESS_TYPE_NOADDRESS = 0x00;
+    static final int FLAG_CHILDREN_ADDRESS_TYPE_ONEBYTE = 0x40;
+    static final int FLAG_CHILDREN_ADDRESS_TYPE_TWOBYTES = 0x80;
+    static final int FLAG_CHILDREN_ADDRESS_TYPE_THREEBYTES = 0xC0;
 
     static final int FLAG_HAS_MULTIPLE_CHARS = 0x20;
 
@@ -195,32 +241,39 @@
     static final int FLAG_IS_NOT_MOVED = 0x80 | FIXED_BIT_OF_DYNAMIC_UPDATE_MOVE;
     static final int FLAG_IS_DELETED = 0x80;
 
-    static final int FLAG_ATTRIBUTE_HAS_NEXT = 0x80;
-    static final int FLAG_ATTRIBUTE_OFFSET_NEGATIVE = 0x40;
-    static final int MASK_ATTRIBUTE_ADDRESS_TYPE = 0x30;
-    static final int FLAG_ATTRIBUTE_ADDRESS_TYPE_ONEBYTE = 0x10;
-    static final int FLAG_ATTRIBUTE_ADDRESS_TYPE_TWOBYTES = 0x20;
-    static final int FLAG_ATTRIBUTE_ADDRESS_TYPE_THREEBYTES = 0x30;
-    static final int FLAG_ATTRIBUTE_FREQUENCY = 0x0F;
+    static final int FLAG_BIGRAM_SHORTCUT_ATTR_HAS_NEXT = 0x80;
+    static final int FLAG_BIGRAM_ATTR_OFFSET_NEGATIVE = 0x40;
+    static final int MASK_BIGRAM_ATTR_ADDRESS_TYPE = 0x30;
+    static final int FLAG_BIGRAM_ATTR_ADDRESS_TYPE_ONEBYTE = 0x10;
+    static final int FLAG_BIGRAM_ATTR_ADDRESS_TYPE_TWOBYTES = 0x20;
+    static final int FLAG_BIGRAM_ATTR_ADDRESS_TYPE_THREEBYTES = 0x30;
+    static final int FLAG_BIGRAM_SHORTCUT_ATTR_FREQUENCY = 0x0F;
 
-    static final int GROUP_CHARACTERS_TERMINATOR = 0x1F;
+    static final int PTNODE_CHARACTERS_TERMINATOR = 0x1F;
 
-    static final int GROUP_TERMINATOR_SIZE = 1;
-    static final int GROUP_FLAGS_SIZE = 1;
-    static final int GROUP_FREQUENCY_SIZE = 1;
-    static final int GROUP_MAX_ADDRESS_SIZE = 3;
-    static final int GROUP_ATTRIBUTE_FLAGS_SIZE = 1;
-    static final int GROUP_ATTRIBUTE_MAX_ADDRESS_SIZE = 3;
-    static final int GROUP_SHORTCUT_LIST_SIZE_SIZE = 2;
+    static final int PTNODE_TERMINATOR_SIZE = 1;
+    static final int PTNODE_FLAGS_SIZE = 1;
+    static final int PTNODE_FREQUENCY_SIZE = 1;
+    static final int PTNODE_TERMINAL_ID_SIZE = 4;
+    static final int PTNODE_MAX_ADDRESS_SIZE = 3;
+    static final int PTNODE_ATTRIBUTE_FLAGS_SIZE = 1;
+    static final int PTNODE_ATTRIBUTE_MAX_ADDRESS_SIZE = 3;
+    static final int PTNODE_SHORTCUT_LIST_SIZE_SIZE = 2;
+
+    // These values are used only by version 4 or later.
+    static final String TRIE_FILE_EXTENSION = ".trie";
+    static final String FREQ_FILE_EXTENSION = ".freq";
+    static final int FREQUENCY_AND_FLAGS_SIZE = 2;
 
     static final int NO_CHILDREN_ADDRESS = Integer.MIN_VALUE;
     static final int NO_PARENT_ADDRESS = 0;
     static final int NO_FORWARD_LINK_ADDRESS = 0;
     static final int INVALID_CHARACTER = -1;
 
-    static final int MAX_CHARGROUPS_FOR_ONE_BYTE_CHARGROUP_COUNT = 0x7F; // 127
-    static final int MAX_CHARGROUPS_IN_A_NODE = 0x7FFF; // 32767
-    static final int MAX_BIGRAMS_IN_A_GROUP = 10000;
+    static final int MAX_PTNODES_FOR_ONE_BYTE_PTNODE_COUNT = 0x7F; // 127
+    static final int MAX_PTNODES_IN_A_PT_NODE_ARRAY = 0x7FFF; // 32767
+    static final int MAX_BIGRAMS_IN_A_PTNODE = 10000;
+    static final int MAX_SHORTCUT_LIST_SIZE_IN_A_PTNODE = 0xFFFF;
 
     static final int MAX_TERMINAL_FREQUENCY = 255;
     static final int MAX_BIGRAM_FREQUENCY = 15;
@@ -231,12 +284,20 @@
     static final int NOT_VALID_WORD = -99;
     static final int SIGNED_CHILDREN_ADDRESS_SIZE = 3;
 
+    static final int UINT8_MAX = 0xFF;
+    static final int UINT16_MAX = 0xFFFF;
+    static final int UINT24_MAX = 0xFFFFFF;
+    static final int SINT24_MAX = 0x7FFFFF;
+    static final int MSB8 = 0x80;
+    static final int MSB24 = 0x800000;
+
     /**
      * Options about file format.
      */
     public static final class FormatOptions {
         public final int mVersion;
         public final boolean mSupportsDynamicUpdate;
+        public final boolean mHasTerminalId;
         @UsedForTesting
         public FormatOptions(final int version) {
             this(version, false);
@@ -250,6 +311,7 @@
                         + FIRST_VERSION_WITH_DYNAMIC_UPDATE + " and ulterior.");
             }
             mSupportsDynamicUpdate = supportsDynamicUpdate;
+            mHasTerminalId = (version >= FIRST_VERSION_WITH_TERMINAL_ID);
         }
     }
 
@@ -294,6 +356,36 @@
         }
     }
 
+    /**
+     * Returns new dictionary decoder.
+     *
+     * @param dictFile the dictionary file.
+     * @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.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.isDirectory()) {
+            return new Ver4DictDecoder(dictFile, factory);
+        } else if (dictFile.isFile()) {
+            return new Ver3DictDecoder(dictFile, factory);
+        }
+        return null;
+    }
+
+    public static DictDecoder getDictDecoder(final File dictFile) {
+        return getDictDecoder(dictFile, DictDecoder.USE_READONLY_BYTEBUFFER);
+    }
+
     private FormatSpec() {
         // This utility class is not publicly instantiable.
     }
diff --git a/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java b/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java
index 118dc22..be653fe 100644
--- a/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java
+++ b/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java
@@ -37,15 +37,15 @@
     private static int CHARACTER_NOT_FOUND_INDEX = -1;
 
     /**
-     * A node of the dictionary, containing several CharGroups.
+     * A node array of the dictionary, containing several PtNodes.
      *
-     * A node is but an ordered array of CharGroups, which essentially contain all the
+     * A PtNodeArray is but an ordered array of PtNodes, which essentially contain all the
      * real information.
      * This class also contains fields to cache size and address, to help with binary
      * generation.
      */
-    public static final class Node {
-        ArrayList<CharGroup> mData;
+    public static final class PtNodeArray {
+        ArrayList<PtNode> mData;
         // To help with binary generation
         int mCachedSize = Integer.MIN_VALUE;
         // mCachedAddressBefore/AfterUpdate are helpers for binary dictionary generation. They
@@ -57,10 +57,10 @@
         int mCachedAddressAfterUpdate = Integer.MIN_VALUE;
         int mCachedParentAddress = 0;
 
-        public Node() {
-            mData = new ArrayList<CharGroup>();
+        public PtNodeArray() {
+            mData = new ArrayList<PtNode>();
         }
-        public Node(ArrayList<CharGroup> data) {
+        public PtNodeArray(ArrayList<PtNode> data) {
             mData = data;
         }
     }
@@ -93,24 +93,26 @@
     }
 
     /**
-     * A group of characters, with a frequency, shortcut targets, bigrams, and children.
+     * PtNode is a group of characters, with a frequency, shortcut targets, bigrams, and children
+     * (Pt means Patricia Trie).
      *
-     * This is the central class of the in-memory representation. A CharGroup is what can
+     * This is the central class of the in-memory representation. A PtNode is what can
      * be seen as a traditional "trie node", except it can hold several characters at the
-     * same time. A CharGroup essentially represents one or several characters in the middle
-     * of the trie trie; as such, it can be a terminal, and it can have children.
-     * In this in-memory representation, whether the CharGroup is a terminal or not is represented
+     * same time. A PtNode essentially represents one or several characters in the middle
+     * of the trie tree; as such, it can be a terminal, and it can have children.
+     * In this in-memory representation, whether the PtNode is a terminal or not is represented
      * in the frequency, where NOT_A_TERMINAL (= -1) means this is not a terminal and any other
      * value is the frequency of this terminal. A terminal may have non-null shortcuts and/or
      * bigrams, but a non-terminal may not. Moreover, children, if present, are null.
      */
-    public static final class CharGroup {
+    public static final class PtNode {
         public static final int NOT_A_TERMINAL = -1;
         final int mChars[];
         ArrayList<WeightedString> mShortcutTargets;
         ArrayList<WeightedString> mBigrams;
         int mFrequency; // NOT_A_TERMINAL == mFrequency indicates this is not a terminal.
-        Node mChildren;
+        int mTerminalId; // NOT_A_TERMINAL == mTerminalId indicates this is not a terminal.
+        PtNodeArray mChildren;
         boolean mIsNotAWord; // Only a shortcut
         boolean mIsBlacklistEntry;
         // mCachedSize and mCachedAddressBefore/AfterUpdate are helpers for binary dictionary
@@ -119,15 +121,16 @@
         // same time. Updating will update the AfterUpdate value, and the code will move them
         // to BeforeUpdate before the next update pass.
         // The update process does not need two versions of mCachedSize.
-        int mCachedSize; // The size, in bytes, of this char group.
-        int mCachedAddressBeforeUpdate; // The address of this char group (before update)
-        int mCachedAddressAfterUpdate; // The address of this char group (after update)
+        int mCachedSize; // The size, in bytes, of this PtNode.
+        int mCachedAddressBeforeUpdate; // The address of this PtNode (before update)
+        int mCachedAddressAfterUpdate; // The address of this PtNode (after update)
 
-        public CharGroup(final int[] chars, final ArrayList<WeightedString> shortcutTargets,
+        public PtNode(final int[] chars, final ArrayList<WeightedString> shortcutTargets,
                 final ArrayList<WeightedString> bigrams, final int frequency,
                 final boolean isNotAWord, final boolean isBlacklistEntry) {
             mChars = chars;
             mFrequency = frequency;
+            mTerminalId = frequency;
             mShortcutTargets = shortcutTargets;
             mBigrams = bigrams;
             mChildren = null;
@@ -135,9 +138,10 @@
             mIsBlacklistEntry = isBlacklistEntry;
         }
 
-        public CharGroup(final int[] chars, final ArrayList<WeightedString> shortcutTargets,
+        public PtNode(final int[] chars, final ArrayList<WeightedString> shortcutTargets,
                 final ArrayList<WeightedString> bigrams, final int frequency,
-                final boolean isNotAWord, final boolean isBlacklistEntry, final Node children) {
+                final boolean isNotAWord, final boolean isBlacklistEntry,
+                final PtNodeArray children) {
             mChars = chars;
             mFrequency = frequency;
             mShortcutTargets = shortcutTargets;
@@ -147,13 +151,17 @@
             mIsBlacklistEntry = isBlacklistEntry;
         }
 
-        public void addChild(CharGroup n) {
+        public void addChild(PtNode n) {
             if (null == mChildren) {
-                mChildren = new Node();
+                mChildren = new PtNodeArray();
             }
             mChildren.mData.add(n);
         }
 
+        public int getTerminalId() {
+            return mTerminalId;
+        }
+
         public boolean isTerminal() {
             return NOT_A_TERMINAL != mFrequency;
         }
@@ -244,7 +252,7 @@
         }
 
         /**
-         * Updates the CharGroup with the given properties. Adds the shortcut and bigram lists to
+         * Updates the PtNode with the given properties. Adds the shortcut and bigram lists to
          * the existing ones if any. Note: unigram, bigram, and shortcut frequencies are only
          * updated if they are higher than the existing ones.
          */
@@ -344,10 +352,10 @@
     }
 
     public final DictionaryOptions mOptions;
-    public final Node mRoot;
+    public final PtNodeArray mRootNodeArray;
 
-    public FusionDictionary(final Node root, final DictionaryOptions options) {
-        mRoot = root;
+    public FusionDictionary(final PtNodeArray rootNodeArray, final DictionaryOptions options) {
+        mRootNodeArray = rootNodeArray;
         mOptions = options;
     }
 
@@ -406,13 +414,13 @@
     }
 
     /**
-     * Sanity check for a node.
+     * Sanity check for a PtNode array.
      *
-     * This method checks that all CharGroups in a node are ordered as expected.
+     * This method checks that all PtNodes in a node array are ordered as expected.
      * If they are, nothing happens. If they aren't, an exception is thrown.
      */
-    private void checkStack(Node node) {
-        ArrayList<CharGroup> stack = node.mData;
+    private void checkStack(PtNodeArray ptNodeArray) {
+        ArrayList<PtNode> stack = ptNodeArray.mData;
         int lastValue = -1;
         for (int i = 0; i < stack.size(); ++i) {
             int currentValue = stack.get(i).mChars[0];
@@ -431,18 +439,18 @@
      * @param frequency the bigram frequency
      */
     public void setBigram(final String word1, final String word2, final int frequency) {
-        CharGroup charGroup = findWordInTree(mRoot, word1);
-        if (charGroup != null) {
-            final CharGroup charGroup2 = findWordInTree(mRoot, word2);
-            if (charGroup2 == null) {
+        PtNode ptNode = findWordInTree(mRootNodeArray, word1);
+        if (ptNode != null) {
+            final PtNode ptNode2 = findWordInTree(mRootNodeArray, word2);
+            if (ptNode2 == null) {
                 add(getCodePoints(word2), 0, null, false /* isNotAWord */,
                         false /* isBlacklistEntry */);
-                // The chargroup for the first word may have moved by the above insertion,
+                // The PtNode for the first word may have moved by the above insertion,
                 // if word1 and word2 share a common stem that happens not to have been
-                // a cutting point until now. In this case, we need to refresh charGroup.
-                charGroup = findWordInTree(mRoot, word1);
+                // a cutting point until now. In this case, we need to refresh ptNode.
+                ptNode = findWordInTree(mRootNodeArray, word1);
             }
-            charGroup.addBigram(word2, frequency);
+            ptNode.addBigram(word2, frequency);
         } else {
             throw new RuntimeException("First word of bigram not found");
         }
@@ -469,92 +477,91 @@
             return;
         }
 
-        Node currentNode = mRoot;
+        PtNodeArray currentNodeArray = mRootNodeArray;
         int charIndex = 0;
 
-        CharGroup currentGroup = null;
+        PtNode currentPtNode = null;
         int differentCharIndex = 0; // Set by the loop to the index of the char that differs
-        int nodeIndex = findIndexOfChar(mRoot, word[charIndex]);
+        int nodeIndex = findIndexOfChar(mRootNodeArray, word[charIndex]);
         while (CHARACTER_NOT_FOUND_INDEX != nodeIndex) {
-            currentGroup = currentNode.mData.get(nodeIndex);
-            differentCharIndex = compareArrays(currentGroup.mChars, word, charIndex);
+            currentPtNode = currentNodeArray.mData.get(nodeIndex);
+            differentCharIndex = compareCharArrays(currentPtNode.mChars, word, charIndex);
             if (ARRAYS_ARE_EQUAL != differentCharIndex
-                    && differentCharIndex < currentGroup.mChars.length) break;
-            if (null == currentGroup.mChildren) break;
-            charIndex += currentGroup.mChars.length;
+                    && differentCharIndex < currentPtNode.mChars.length) break;
+            if (null == currentPtNode.mChildren) break;
+            charIndex += currentPtNode.mChars.length;
             if (charIndex >= word.length) break;
-            currentNode = currentGroup.mChildren;
-            nodeIndex = findIndexOfChar(currentNode, word[charIndex]);
+            currentNodeArray = currentPtNode.mChildren;
+            nodeIndex = findIndexOfChar(currentNodeArray, word[charIndex]);
         }
 
         if (CHARACTER_NOT_FOUND_INDEX == nodeIndex) {
             // No node at this point to accept the word. Create one.
-            final int insertionIndex = findInsertionIndex(currentNode, word[charIndex]);
-            final CharGroup newGroup = new CharGroup(
-                    Arrays.copyOfRange(word, charIndex, word.length),
+            final int insertionIndex = findInsertionIndex(currentNodeArray, word[charIndex]);
+            final PtNode newPtNode = new PtNode(Arrays.copyOfRange(word, charIndex, word.length),
                     shortcutTargets, null /* bigrams */, frequency, isNotAWord, isBlacklistEntry);
-            currentNode.mData.add(insertionIndex, newGroup);
-            if (DBG) checkStack(currentNode);
+            currentNodeArray.mData.add(insertionIndex, newPtNode);
+            if (DBG) checkStack(currentNodeArray);
         } else {
             // There is a word with a common prefix.
-            if (differentCharIndex == currentGroup.mChars.length) {
+            if (differentCharIndex == currentPtNode.mChars.length) {
                 if (charIndex + differentCharIndex >= word.length) {
                     // The new word is a prefix of an existing word, but the node on which it
-                    // should end already exists as is. Since the old CharNode was not a terminal,
+                    // should end already exists as is. Since the old PtNode was not a terminal,
                     // make it one by filling in its frequency and other attributes
-                    currentGroup.update(frequency, shortcutTargets, null, isNotAWord,
+                    currentPtNode.update(frequency, shortcutTargets, null, isNotAWord,
                             isBlacklistEntry);
                 } else {
                     // The new word matches the full old word and extends past it.
                     // We only have to create a new node and add it to the end of this.
-                    final CharGroup newNode = new CharGroup(
+                    final PtNode newNode = new PtNode(
                             Arrays.copyOfRange(word, charIndex + differentCharIndex, word.length),
                                     shortcutTargets, null /* bigrams */, frequency, isNotAWord,
                                     isBlacklistEntry);
-                    currentGroup.mChildren = new Node();
-                    currentGroup.mChildren.mData.add(newNode);
+                    currentPtNode.mChildren = new PtNodeArray();
+                    currentPtNode.mChildren.mData.add(newNode);
                 }
             } else {
                 if (0 == differentCharIndex) {
                     // Exact same word. Update the frequency if higher. This will also add the
                     // new shortcuts to the existing shortcut list if it already exists.
-                    currentGroup.update(frequency, shortcutTargets, null,
-                            currentGroup.mIsNotAWord && isNotAWord,
-                            currentGroup.mIsBlacklistEntry || isBlacklistEntry);
+                    currentPtNode.update(frequency, shortcutTargets, null,
+                            currentPtNode.mIsNotAWord && isNotAWord,
+                            currentPtNode.mIsBlacklistEntry || isBlacklistEntry);
                 } else {
                     // Partial prefix match only. We have to replace the current node with a node
                     // containing the current prefix and create two new ones for the tails.
-                    Node newChildren = new Node();
-                    final CharGroup newOldWord = new CharGroup(
-                            Arrays.copyOfRange(currentGroup.mChars, differentCharIndex,
-                                    currentGroup.mChars.length), currentGroup.mShortcutTargets,
-                            currentGroup.mBigrams, currentGroup.mFrequency,
-                            currentGroup.mIsNotAWord, currentGroup.mIsBlacklistEntry,
-                            currentGroup.mChildren);
+                    PtNodeArray newChildren = new PtNodeArray();
+                    final PtNode newOldWord = new PtNode(
+                            Arrays.copyOfRange(currentPtNode.mChars, differentCharIndex,
+                                    currentPtNode.mChars.length), currentPtNode.mShortcutTargets,
+                            currentPtNode.mBigrams, currentPtNode.mFrequency,
+                            currentPtNode.mIsNotAWord, currentPtNode.mIsBlacklistEntry,
+                            currentPtNode.mChildren);
                     newChildren.mData.add(newOldWord);
 
-                    final CharGroup newParent;
+                    final PtNode newParent;
                     if (charIndex + differentCharIndex >= word.length) {
-                        newParent = new CharGroup(
-                                Arrays.copyOfRange(currentGroup.mChars, 0, differentCharIndex),
+                        newParent = new PtNode(
+                                Arrays.copyOfRange(currentPtNode.mChars, 0, differentCharIndex),
                                 shortcutTargets, null /* bigrams */, frequency,
                                 isNotAWord, isBlacklistEntry, newChildren);
                     } else {
-                        newParent = new CharGroup(
-                                Arrays.copyOfRange(currentGroup.mChars, 0, differentCharIndex),
+                        newParent = new PtNode(
+                                Arrays.copyOfRange(currentPtNode.mChars, 0, differentCharIndex),
                                 null /* shortcutTargets */, null /* bigrams */, -1,
                                 false /* isNotAWord */, false /* isBlacklistEntry */, newChildren);
-                        final CharGroup newWord = new CharGroup(Arrays.copyOfRange(word,
+                        final PtNode newWord = new PtNode(Arrays.copyOfRange(word,
                                 charIndex + differentCharIndex, word.length),
                                 shortcutTargets, null /* bigrams */, frequency,
                                 isNotAWord, isBlacklistEntry);
                         final int addIndex = word[charIndex + differentCharIndex]
-                                > currentGroup.mChars[differentCharIndex] ? 1 : 0;
+                                > currentPtNode.mChars[differentCharIndex] ? 1 : 0;
                         newChildren.mData.add(addIndex, newWord);
                     }
-                    currentNode.mData.set(nodeIndex, newParent);
+                    currentNodeArray.mData.set(nodeIndex, newParent);
                 }
-                if (DBG) checkStack(currentNode);
+                if (DBG) checkStack(currentNodeArray);
             }
         }
     }
@@ -576,7 +583,7 @@
      * @param dstOffset the offset in the right-hand side string.
      * @return the index at which the strings differ, or ARRAYS_ARE_EQUAL = 0 if they don't.
      */
-    private static int compareArrays(final int[] src, final int[] dst, int dstOffset) {
+    private static int compareCharArrays(final int[] src, final int[] dst, int dstOffset) {
         // We do NOT test the first char, because we come from a method that already
         // tested it.
         for (int i = 1; i < src.length; ++i) {
@@ -588,43 +595,43 @@
     }
 
     /**
-     * Helper class that compares and sorts two chargroups according to their
+     * Helper class that compares and sorts two PtNodes according to their
      * first element only. I repeat: ONLY the first element is considered, the rest
      * is ignored.
      * This comparator imposes orderings that are inconsistent with equals.
      */
-    static private final class CharGroupComparator implements java.util.Comparator<CharGroup> {
+    static private final class PtNodeComparator implements java.util.Comparator<PtNode> {
         @Override
-        public int compare(CharGroup c1, CharGroup c2) {
-            if (c1.mChars[0] == c2.mChars[0]) return 0;
-            return c1.mChars[0] < c2.mChars[0] ? -1 : 1;
+        public int compare(PtNode p1, PtNode p2) {
+            if (p1.mChars[0] == p2.mChars[0]) return 0;
+            return p1.mChars[0] < p2.mChars[0] ? -1 : 1;
         }
     }
-    final static private CharGroupComparator CHARGROUP_COMPARATOR = new CharGroupComparator();
+    final static private PtNodeComparator PTNODE_COMPARATOR = new PtNodeComparator();
 
     /**
-     * Finds the insertion index of a character within a node.
+     * Finds the insertion index of a character within a node array.
      */
-    private static int findInsertionIndex(final Node node, int character) {
-        final ArrayList<CharGroup> data = node.mData;
-        final CharGroup reference = new CharGroup(new int[] { character },
+    private static int findInsertionIndex(final PtNodeArray nodeArray, int character) {
+        final ArrayList<PtNode> data = nodeArray.mData;
+        final PtNode reference = new PtNode(new int[] { character },
                 null /* shortcutTargets */, null /* bigrams */, 0, false /* isNotAWord */,
                 false /* isBlacklistEntry */);
-        int result = Collections.binarySearch(data, reference, CHARGROUP_COMPARATOR);
+        int result = Collections.binarySearch(data, reference, PTNODE_COMPARATOR);
         return result >= 0 ? result : -result - 1;
     }
 
     /**
-     * Find the index of a char in a node, if it exists.
+     * Find the index of a char in a node array, if it exists.
      *
-     * @param node the node to search in.
+     * @param nodeArray the node array to search in.
      * @param character the character to search for.
      * @return the position of the character if it's there, or CHARACTER_NOT_FOUND_INDEX = -1 else.
      */
-    private static int findIndexOfChar(final Node node, int character) {
-        final int insertionIndex = findInsertionIndex(node, character);
-        if (node.mData.size() <= insertionIndex) return CHARACTER_NOT_FOUND_INDEX;
-        return character == node.mData.get(insertionIndex).mChars[0] ? insertionIndex
+    private static int findIndexOfChar(final PtNodeArray nodeArray, int character) {
+        final int insertionIndex = findInsertionIndex(nodeArray, character);
+        if (nodeArray.mData.size() <= insertionIndex) return CHARACTER_NOT_FOUND_INDEX;
+        return character == nodeArray.mData.get(insertionIndex).mChars[0] ? insertionIndex
                 : CHARACTER_NOT_FOUND_INDEX;
     }
 
@@ -632,35 +639,37 @@
      * Helper method to find a word in a given branch.
      */
     @SuppressWarnings("unused")
-    public static CharGroup findWordInTree(Node node, final String string) {
+    public static PtNode findWordInTree(PtNodeArray nodeArray, final String string) {
         int index = 0;
         final StringBuilder checker = DBG ? new StringBuilder() : null;
         final int[] codePoints = getCodePoints(string);
 
-        CharGroup currentGroup;
+        PtNode currentPtNode;
         do {
-            int indexOfGroup = findIndexOfChar(node, codePoints[index]);
+            int indexOfGroup = findIndexOfChar(nodeArray, codePoints[index]);
             if (CHARACTER_NOT_FOUND_INDEX == indexOfGroup) return null;
-            currentGroup = node.mData.get(indexOfGroup);
+            currentPtNode = nodeArray.mData.get(indexOfGroup);
 
-            if (codePoints.length - index < currentGroup.mChars.length) return null;
+            if (codePoints.length - index < currentPtNode.mChars.length) return null;
             int newIndex = index;
-            while (newIndex < codePoints.length && newIndex - index < currentGroup.mChars.length) {
-                if (currentGroup.mChars[newIndex - index] != codePoints[newIndex]) return null;
+            while (newIndex < codePoints.length && newIndex - index < currentPtNode.mChars.length) {
+                if (currentPtNode.mChars[newIndex - index] != codePoints[newIndex]) return null;
                 newIndex++;
             }
             index = newIndex;
 
-            if (DBG) checker.append(new String(currentGroup.mChars, 0, currentGroup.mChars.length));
-            if (index < codePoints.length) {
-                node = currentGroup.mChildren;
+            if (DBG) {
+                checker.append(new String(currentPtNode.mChars, 0, currentPtNode.mChars.length));
             }
-        } while (null != node && index < codePoints.length);
+            if (index < codePoints.length) {
+                nodeArray = currentPtNode.mChildren;
+            }
+        } while (null != nodeArray && index < codePoints.length);
 
         if (index < codePoints.length) return null;
-        if (!currentGroup.isTerminal()) return null;
+        if (!currentPtNode.isTerminal()) return null;
         if (DBG && !string.equals(checker.toString())) return null;
-        return currentGroup;
+        return currentPtNode;
     }
 
     /**
@@ -670,22 +679,22 @@
         if (null == s || "".equals(s)) {
             throw new RuntimeException("Can't search for a null or empty string");
         }
-        return null != findWordInTree(mRoot, s);
+        return null != findWordInTree(mRootNodeArray, s);
     }
 
     /**
-     * Recursively count the number of character groups in a given branch of the trie.
+     * Recursively count the number of PtNodes in a given branch of the trie.
      *
-     * @param node the parent node.
-     * @return the number of char groups in all the branch under this node.
+     * @param nodeArray the parent node.
+     * @return the number of PtNodes in all the branch under this node.
      */
-    public static int countCharGroups(final Node node) {
-        final int nodeSize = node.mData.size();
+    public static int countPtNodes(final PtNodeArray nodeArray) {
+        final int nodeSize = nodeArray.mData.size();
         int size = nodeSize;
         for (int i = nodeSize - 1; i >= 0; --i) {
-            CharGroup group = node.mData.get(i);
-            if (null != group.mChildren)
-                size += countCharGroups(group.mChildren);
+            PtNode ptNode = nodeArray.mData.get(i);
+            if (null != ptNode.mChildren)
+                size += countPtNodes(ptNode.mChildren);
         }
         return size;
     }
@@ -693,15 +702,15 @@
     /**
      * Recursively count the number of nodes in a given branch of the trie.
      *
-     * @param node the node to count.
+     * @param nodeArray the node array to count.
      * @return the number of nodes in this branch.
      */
-    public static int countNodes(final Node node) {
+    public static int countNodeArrays(final PtNodeArray nodeArray) {
         int size = 1;
-        for (int i = node.mData.size() - 1; i >= 0; --i) {
-            CharGroup group = node.mData.get(i);
-            if (null != group.mChildren)
-                size += countNodes(group.mChildren);
+        for (int i = nodeArray.mData.size() - 1; i >= 0; --i) {
+            PtNode ptNode = nodeArray.mData.get(i);
+            if (null != ptNode.mChildren)
+                size += countNodeArrays(ptNode.mChildren);
         }
         return size;
     }
@@ -709,12 +718,12 @@
     // Recursively find out whether there are any bigrams.
     // This can be pretty expensive especially if there aren't any (we return as soon
     // as we find one, so it's much cheaper if there are bigrams)
-    private static boolean hasBigramsInternal(final Node node) {
-        if (null == node) return false;
-        for (int i = node.mData.size() - 1; i >= 0; --i) {
-            CharGroup group = node.mData.get(i);
-            if (null != group.mBigrams) return true;
-            if (hasBigramsInternal(group.mChildren)) return true;
+    private static boolean hasBigramsInternal(final PtNodeArray nodeArray) {
+        if (null == nodeArray) return false;
+        for (int i = nodeArray.mData.size() - 1; i >= 0; --i) {
+            PtNode ptNode = nodeArray.mData.get(i);
+            if (null != ptNode.mBigrams) return true;
+            if (hasBigramsInternal(ptNode.mChildren)) return true;
         }
         return false;
     }
@@ -729,7 +738,7 @@
     // find a more efficient way of doing this, without compromising too much on memory
     // and ease of use.
     public boolean hasBigrams() {
-        return hasBigramsInternal(mRoot);
+        return hasBigramsInternal(mRootNodeArray);
     }
 
     // Historically, the tails of the words were going to be merged to save space.
@@ -747,16 +756,16 @@
         MakedictLog.i("Do not merge tails");
         return;
 
-//        MakedictLog.i("Merging nodes. Number of nodes : " + countNodes(root));
-//        MakedictLog.i("Number of groups : " + countCharGroups(root));
+//        MakedictLog.i("Merging PtNodes. Number of PtNodes : " + countPtNodes(root));
+//        MakedictLog.i("Number of PtNodes : " + countPtNodes(root));
 //
-//        final HashMap<String, ArrayList<Node>> repository =
-//                  new HashMap<String, ArrayList<Node>>();
+//        final HashMap<String, ArrayList<PtNodeArray>> repository =
+//                  new HashMap<String, ArrayList<PtNodeArray>>();
 //        mergeTailsInner(repository, root);
 //
 //        MakedictLog.i("Number of different pseudohashes : " + repository.size());
 //        int size = 0;
-//        for (ArrayList<Node> a : repository.values()) {
+//        for (ArrayList<PtNodeArray> a : repository.values()) {
 //            size += a.size();
 //        }
 //        MakedictLog.i("Number of nodes after merge : " + (1 + size));
@@ -764,58 +773,58 @@
     }
 
     // The following methods are used by the deactivated mergeTails()
-//   private static boolean isEqual(Node a, Node b) {
+//   private static boolean isEqual(PtNodeArray a, PtNodeArray b) {
 //       if (null == a && null == b) return true;
 //       if (null == a || null == b) return false;
 //       if (a.data.size() != b.data.size()) return false;
 //       final int size = a.data.size();
 //       for (int i = size - 1; i >= 0; --i) {
-//           CharGroup aGroup = a.data.get(i);
-//           CharGroup bGroup = b.data.get(i);
-//           if (aGroup.frequency != bGroup.frequency) return false;
-//           if (aGroup.alternates == null && bGroup.alternates != null) return false;
-//           if (aGroup.alternates != null && !aGroup.equals(bGroup.alternates)) return false;
-//           if (!Arrays.equals(aGroup.chars, bGroup.chars)) return false;
-//           if (!isEqual(aGroup.children, bGroup.children)) return false;
+//           PtNode aPtNode = a.data.get(i);
+//           PtNode bPtNode = b.data.get(i);
+//           if (aPtNode.frequency != bPtNode.frequency) return false;
+//           if (aPtNode.alternates == null && bPtNode.alternates != null) return false;
+//           if (aPtNode.alternates != null && !aPtNode.equals(bPtNode.alternates)) return false;
+//           if (!Arrays.equals(aPtNode.chars, bPtNode.chars)) return false;
+//           if (!isEqual(aPtNode.children, bPtNode.children)) return false;
 //       }
 //       return true;
 //   }
 
-//   static private HashMap<String, ArrayList<Node>> mergeTailsInner(
-//           final HashMap<String, ArrayList<Node>> map, final Node node) {
-//       final ArrayList<CharGroup> branches = node.data;
+//   static private HashMap<String, ArrayList<PtNodeArray>> mergeTailsInner(
+//           final HashMap<String, ArrayList<PtNodeArray>> map, final PtNodeArray nodeArray) {
+//       final ArrayList<PtNode> branches = nodeArray.data;
 //       final int nodeSize = branches.size();
 //       for (int i = 0; i < nodeSize; ++i) {
-//           CharGroup group = branches.get(i);
-//           if (null != group.children) {
-//               String pseudoHash = getPseudoHash(group.children);
-//               ArrayList<Node> similarList = map.get(pseudoHash);
+//           PtNode ptNode = branches.get(i);
+//           if (null != ptNode.children) {
+//               String pseudoHash = getPseudoHash(ptNode.children);
+//               ArrayList<PtNodeArray> similarList = map.get(pseudoHash);
 //               if (null == similarList) {
-//                   similarList = new ArrayList<Node>();
+//                   similarList = new ArrayList<PtNodeArray>();
 //                   map.put(pseudoHash, similarList);
 //               }
 //               boolean merged = false;
-//               for (Node similar : similarList) {
-//                   if (isEqual(group.children, similar)) {
-//                       group.children = similar;
+//               for (PtNodeArray similar : similarList) {
+//                   if (isEqual(ptNode.children, similar)) {
+//                       ptNode.children = similar;
 //                       merged = true;
 //                       break;
 //                   }
 //               }
 //               if (!merged) {
-//                   similarList.add(group.children);
+//                   similarList.add(ptNode.children);
 //               }
-//               mergeTailsInner(map, group.children);
+//               mergeTailsInner(map, ptNode.children);
 //           }
 //       }
 //       return map;
 //   }
 
-//  private static String getPseudoHash(final Node node) {
+//  private static String getPseudoHash(final PtNodeArray nodeArray) {
 //      StringBuilder s = new StringBuilder();
-//      for (CharGroup g : node.data) {
-//          s.append(g.frequency);
-//          for (int ch : g.chars) {
+//      for (PtNode ptNode : nodeArray.data) {
+//          s.append(ptNode.frequency);
+//          for (int ch : ptNode.chars) {
 //              s.append(Character.toChars(ch));
 //          }
 //      }
@@ -829,20 +838,20 @@
      */
     public static final class DictionaryIterator implements Iterator<Word> {
         private static final class Position {
-            public Iterator<CharGroup> pos;
+            public Iterator<PtNode> pos;
             public int length;
-            public Position(ArrayList<CharGroup> groups) {
-                pos = groups.iterator();
+            public Position(ArrayList<PtNode> ptNodes) {
+                pos = ptNodes.iterator();
                 length = 0;
             }
         }
         final StringBuilder mCurrentString;
         final LinkedList<Position> mPositions;
 
-        public DictionaryIterator(ArrayList<CharGroup> root) {
+        public DictionaryIterator(ArrayList<PtNode> ptRoot) {
             mCurrentString = new StringBuilder();
             mPositions = new LinkedList<Position>();
-            final Position rootPos = new Position(root);
+            final Position rootPos = new Position(ptRoot);
             mPositions.add(rootPos);
         }
 
@@ -863,20 +872,20 @@
 
             do {
                 if (currentPos.pos.hasNext()) {
-                    final CharGroup currentGroup = currentPos.pos.next();
+                    final PtNode currentPtNode = currentPos.pos.next();
                     currentPos.length = mCurrentString.length();
-                    for (int i : currentGroup.mChars) {
+                    for (int i : currentPtNode.mChars) {
                         mCurrentString.append(Character.toChars(i));
                     }
-                    if (null != currentGroup.mChildren) {
-                        currentPos = new Position(currentGroup.mChildren.mData);
+                    if (null != currentPtNode.mChildren) {
+                        currentPos = new Position(currentPtNode.mChildren.mData);
                         currentPos.length = mCurrentString.length();
                         mPositions.addLast(currentPos);
                     }
-                    if (currentGroup.mFrequency >= 0) {
-                        return new Word(mCurrentString.toString(), currentGroup.mFrequency,
-                                currentGroup.mShortcutTargets, currentGroup.mBigrams,
-                                currentGroup.mIsNotAWord, currentGroup.mIsBlacklistEntry);
+                    if (currentPtNode.mFrequency >= 0) {
+                        return new Word(mCurrentString.toString(), currentPtNode.mFrequency,
+                                currentPtNode.mShortcutTargets, currentPtNode.mBigrams,
+                                currentPtNode.mIsNotAWord, currentPtNode.mIsBlacklistEntry);
                     }
                 } else {
                     mPositions.removeLast();
@@ -901,6 +910,6 @@
      */
     @Override
     public Iterator<Word> iterator() {
-        return new DictionaryIterator(mRoot.mData);
+        return new DictionaryIterator(mRootNodeArray.mData);
     }
 }
diff --git a/java/src/com/android/inputmethod/latin/makedict/CharGroupInfo.java b/java/src/com/android/inputmethod/latin/makedict/PtNodeInfo.java
similarity index 88%
rename from java/src/com/android/inputmethod/latin/makedict/CharGroupInfo.java
rename to java/src/com/android/inputmethod/latin/makedict/PtNodeInfo.java
index b361744..188de7a 100644
--- a/java/src/com/android/inputmethod/latin/makedict/CharGroupInfo.java
+++ b/java/src/com/android/inputmethod/latin/makedict/PtNodeInfo.java
@@ -21,9 +21,9 @@
 import java.util.ArrayList;
 
 /**
- * Raw char group info straight out of a file. This will contain numbers for addresses.
+ * Raw PtNode info straight out of a file. This will contain numbers for addresses.
  */
-public final class CharGroupInfo {
+public final class PtNodeInfo {
 
     public final int mOriginalAddress;
     public final int mEndAddress;
@@ -35,7 +35,7 @@
     public final ArrayList<WeightedString> mShortcutTargets;
     public final ArrayList<PendingAttribute> mBigrams;
 
-    public CharGroupInfo(final int originalAddress, final int endAddress, final int flags,
+    public PtNodeInfo(final int originalAddress, final int endAddress, final int flags,
             final int[] characters, final int frequency, final int parentAddress,
             final int childrenAddress, final ArrayList<WeightedString> shortcutTargets,
             final ArrayList<PendingAttribute> bigrams) {
diff --git a/java/src/com/android/inputmethod/latin/makedict/Ver3DictDecoder.java b/java/src/com/android/inputmethod/latin/makedict/Ver3DictDecoder.java
new file mode 100644
index 0000000..848277c
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/makedict/Ver3DictDecoder.java
@@ -0,0 +1,234 @@
+/*
+ * 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 com.android.inputmethod.latin.utils.JniUtils;
+
+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 DictDecoder for version 3 binary dictionary.
+ */
+@UsedForTesting
+public class Ver3DictDecoder extends DictDecoder {
+    private static final String TAG = Ver3DictDecoder.class.getSimpleName();
+
+    static {
+        JniUtils.loadNativeLibrary();
+    }
+
+    // TODO: implement something sensical instead of just a phony method
+    private static native int doNothing();
+
+    protected static class PtNodeReader extends DictDecoder.PtNodeReader {
+        private static int readFrequency(final DictBuffer dictBuffer) {
+            return dictBuffer.readUnsignedByte();
+        }
+    }
+
+    private final File mDictionaryBinaryFile;
+    private final DictionaryBufferFactory mBufferFactory;
+    private DictBuffer mDictBuffer;
+
+    /* package */ Ver3DictDecoder(final File file, final int factoryFlag) {
+        mDictionaryBinaryFile = file;
+        mDictBuffer = 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();
+        }
+    }
+
+    /* package */ Ver3DictDecoder(final File file, final DictionaryBufferFactory factory) {
+        mDictionaryBinaryFile = file;
+        mBufferFactory = factory;
+    }
+
+    @Override
+    public void openDictBuffer() throws FileNotFoundException, IOException {
+        mDictBuffer = mBufferFactory.getDictionaryBuffer(mDictionaryBinaryFile);
+    }
+
+    @Override
+    public boolean isDictBufferOpen() {
+        return mDictBuffer != null;
+    }
+
+    /* package */ DictBuffer getDictBuffer() {
+        return mDictBuffer;
+    }
+
+    @UsedForTesting
+    /* package */ DictBuffer openAndGetDictBuffer() throws FileNotFoundException, IOException {
+        openDictBuffer();
+        return getDictBuffer();
+    }
+
+    @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 >= 2 && version <= 3)) {
+          throw new UnsupportedFormatException("File header has a wrong version : " + version);
+        }
+        return header;
+    }
+
+    // TODO: Make this buffer multi thread safe.
+    private final int[] mCharacterBuffer = new int[FormatSpec.MAX_WORD_LENGTH];
+    @Override
+    public PtNodeInfo readPtNode(final int ptNodePos, final 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) {
+                // 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.
+                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 frequency;
+        if (0 != (FormatSpec.FLAG_IS_TERMINAL & flags)) {
+            frequency = PtNodeReader.readFrequency(mDictBuffer);
+            addressPointer += FormatSpec.PTNODE_FREQUENCY_SIZE;
+        } 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 PtNode.");
+            }
+        } else {
+            bigrams = null;
+        }
+        return new PtNodeInfo(ptNodePos, addressPointer, flags, characters, frequency,
+                parentAddress, childrenAddress, shortcutTargets, bigrams);
+    }
+
+    @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 " + mDictionaryBinaryFile.getName() + " is broken.", e);
+            if (deleteDictIfBroken && !mDictionaryBinaryFile.delete()) {
+                Log.e(TAG, "Failed to delete the broken dictionary.");
+            }
+            throw e;
+        } catch (UnsupportedFormatException e) {
+            Log.e(TAG, "The dictionary " + mDictionaryBinaryFile.getName() + " is broken.", e);
+            if (deleteDictIfBroken && !mDictionaryBinaryFile.delete()) {
+                Log.e(TAG, "Failed to delete the broken dictionary.");
+            }
+            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/java/src/com/android/inputmethod/latin/makedict/Ver3DictEncoder.java b/java/src/com/android/inputmethod/latin/makedict/Ver3DictEncoder.java
new file mode 100644
index 0000000..76f0f40
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/makedict/Ver3DictEncoder.java
@@ -0,0 +1,255 @@
+/*
+ * 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.latin.makedict.BinaryDictDecoderUtils.CharEncoding;
+import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions;
+import com.android.inputmethod.latin.makedict.FusionDictionary.PtNode;
+import com.android.inputmethod.latin.makedict.FusionDictionary.PtNodeArray;
+import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Iterator;
+
+/**
+ * An implementation of DictEncoder for version 3 binary dictionary.
+ */
+public class Ver3DictEncoder implements DictEncoder {
+
+    private final File mDictFile;
+    private OutputStream mOutStream;
+    private byte[] mBuffer;
+    private int mPosition;
+
+    public Ver3DictEncoder(final File dictFile) {
+        mDictFile = dictFile;
+        mOutStream = null;
+        mBuffer = null;
+    }
+
+    // This constructor is used only by BinaryDictOffdeviceUtilsTests.
+    // If you want to use this in the production code, you should consider keeping consistency of
+    // the interface of Ver3DictDecoder by using factory.
+    public Ver3DictEncoder(final OutputStream outStream) {
+        mDictFile = null;
+        mOutStream = outStream;
+    }
+
+    private void openStream() throws FileNotFoundException {
+        mOutStream = new FileOutputStream(mDictFile);
+    }
+
+    private void close() throws IOException {
+        if (mOutStream != null) {
+            mOutStream.close();
+            mOutStream = null;
+        }
+    }
+
+    @Override
+    public void writeDictionary(final FusionDictionary dict, final FormatOptions formatOptions)
+            throws IOException, UnsupportedFormatException {
+        if (formatOptions.mVersion > FormatSpec.VERSION3) {
+            throw new UnsupportedFormatException(
+                    "The given format options has wrong version number : "
+                    + formatOptions.mVersion);
+        }
+
+        if (mOutStream == null) {
+            openStream();
+        }
+        BinaryDictEncoderUtils.writeDictionaryHeader(mOutStream, dict, formatOptions);
+
+        // Addresses are limited to 3 bytes, but since addresses can be relative to each node
+        // array, the structure itself is not limited to 16MB. However, if it is over 16MB deciding
+        // the order of the PtNode arrays becomes a quite complicated problem, because though the
+        // dictionary itself does not have a size limit, each node array must still be within 16MB
+        // of all its children and parents. As long as this is ensured, the dictionary file may
+        // grow to any size.
+
+        // Leave the choice of the optimal node order to the flattenTree function.
+        MakedictLog.i("Flattening the tree...");
+        ArrayList<PtNodeArray> flatNodes = BinaryDictEncoderUtils.flattenTree(dict.mRootNodeArray);
+
+        MakedictLog.i("Computing addresses...");
+        BinaryDictEncoderUtils.computeAddresses(dict, flatNodes, formatOptions);
+        MakedictLog.i("Checking PtNode array...");
+        if (MakedictLog.DBG) BinaryDictEncoderUtils.checkFlatPtNodeArrayList(flatNodes);
+
+        // Create a buffer that matches the final dictionary size.
+        final PtNodeArray lastNodeArray = flatNodes.get(flatNodes.size() - 1);
+        final int bufferSize = lastNodeArray.mCachedAddressAfterUpdate + lastNodeArray.mCachedSize;
+        mBuffer = new byte[bufferSize];
+
+        MakedictLog.i("Writing file...");
+
+        for (PtNodeArray nodeArray : flatNodes) {
+            BinaryDictEncoderUtils.writePlacedPtNodeArray(dict, this, nodeArray, formatOptions);
+        }
+        if (MakedictLog.DBG) BinaryDictEncoderUtils.showStatistics(flatNodes);
+        mOutStream.write(mBuffer, 0, mPosition);
+
+        MakedictLog.i("Done");
+        close();
+    }
+
+    @Override
+    public void setPosition(final int position) {
+        if (mBuffer == null || position < 0 || position >= mBuffer.length) return;
+        mPosition = position;
+    }
+
+    @Override
+    public int getPosition() {
+        return mPosition;
+    }
+
+    @Override
+    public void writePtNodeCount(final int ptNodeCount) {
+        final int countSize = BinaryDictIOUtils.getPtNodeCountSize(ptNodeCount);
+        if (countSize != 1 && countSize != 2) {
+            throw new RuntimeException("Strange size from getGroupCountSize : " + countSize);
+        }
+        mPosition = BinaryDictEncoderUtils.writeUIntToBuffer(mBuffer, mPosition, ptNodeCount,
+                countSize);
+    }
+
+    private void writePtNodeFlags(final PtNode ptNode, final int parentAddress,
+            final FormatOptions formatOptions) {
+        final int childrenPos = BinaryDictEncoderUtils.getChildrenPosition(ptNode, formatOptions);
+        mPosition = BinaryDictEncoderUtils.writeUIntToBuffer(mBuffer, mPosition,
+                BinaryDictEncoderUtils.makePtNodeFlags(ptNode, mPosition, childrenPos,
+                        formatOptions),
+                FormatSpec.PTNODE_FLAGS_SIZE);
+    }
+
+    private void writeParentPosition(final int parentPosition, final PtNode ptNode,
+            final FormatOptions formatOptions) {
+        if (parentPosition == FormatSpec.NO_PARENT_ADDRESS) {
+            mPosition = BinaryDictEncoderUtils.writeParentAddress(mBuffer, mPosition,
+                    parentPosition, formatOptions);
+        } else {
+            mPosition = BinaryDictEncoderUtils.writeParentAddress(mBuffer, mPosition,
+                    parentPosition - ptNode.mCachedAddressAfterUpdate, formatOptions);
+        }
+    }
+
+    private void writeCharacters(final int[] codePoints, final boolean hasSeveralChars) {
+        mPosition = CharEncoding.writeCharArray(codePoints, mBuffer, mPosition);
+        if (hasSeveralChars) {
+            mBuffer[mPosition++] = FormatSpec.PTNODE_CHARACTERS_TERMINATOR;
+        }
+    }
+
+    private void writeFrequency(final int frequency) {
+        if (frequency >= 0) {
+            mPosition = BinaryDictEncoderUtils.writeUIntToBuffer(mBuffer, mPosition, frequency,
+                    FormatSpec.PTNODE_FREQUENCY_SIZE);
+        }
+    }
+
+    private void writeChildrenPosition(final PtNode ptNode, final FormatOptions formatOptions) {
+        final int childrenPos = BinaryDictEncoderUtils.getChildrenPosition(ptNode, formatOptions);
+        if (formatOptions.mSupportsDynamicUpdate) {
+            mPosition += BinaryDictEncoderUtils.writeSignedChildrenPosition(mBuffer, mPosition,
+                    childrenPos);
+        } else {
+            mPosition += BinaryDictEncoderUtils.writeChildrenPosition(mBuffer, mPosition,
+                    childrenPos);
+        }
+    }
+
+    /**
+     * Write a shortcut attributes list to mBuffer.
+     *
+     * @param shortcuts the shortcut attributes list.
+     */
+    private void writeShortcuts(final ArrayList<WeightedString> shortcuts) {
+        if (null == shortcuts || shortcuts.isEmpty()) return;
+
+        final int indexOfShortcutByteSize = mPosition;
+        mPosition += FormatSpec.PTNODE_SHORTCUT_LIST_SIZE_SIZE;
+        final Iterator<WeightedString> shortcutIterator = shortcuts.iterator();
+        while (shortcutIterator.hasNext()) {
+            final WeightedString target = shortcutIterator.next();
+            final int shortcutFlags = BinaryDictEncoderUtils.makeShortcutFlags(
+                    shortcutIterator.hasNext(),
+                    target.mFrequency);
+            mPosition = BinaryDictEncoderUtils.writeUIntToBuffer(mBuffer, mPosition, shortcutFlags,
+                    FormatSpec.PTNODE_ATTRIBUTE_FLAGS_SIZE);
+            final int shortcutShift = CharEncoding.writeString(mBuffer, mPosition, target.mWord);
+            mPosition += shortcutShift;
+        }
+        final int shortcutByteSize = mPosition - indexOfShortcutByteSize;
+        if (shortcutByteSize > FormatSpec.MAX_SHORTCUT_LIST_SIZE_IN_A_PTNODE) {
+            throw new RuntimeException("Shortcut list too large");
+        }
+        BinaryDictEncoderUtils.writeUIntToBuffer(mBuffer, indexOfShortcutByteSize, shortcutByteSize,
+                FormatSpec.PTNODE_SHORTCUT_LIST_SIZE_SIZE);
+    }
+
+    /**
+     * Write a bigram attributes list to mBuffer.
+     *
+     * @param bigrams the bigram attributes list.
+     * @param dict the dictionary the node array is a part of (for relative offsets).
+     */
+    private void writeBigrams(final ArrayList<WeightedString> bigrams,
+            final FusionDictionary dict) {
+        if (bigrams == null) return;
+
+        final Iterator<WeightedString> bigramIterator = bigrams.iterator();
+        while (bigramIterator.hasNext()) {
+            final WeightedString bigram = bigramIterator.next();
+            final PtNode target =
+                    FusionDictionary.findWordInTree(dict.mRootNodeArray, bigram.mWord);
+            final int addressOfBigram = target.mCachedAddressAfterUpdate;
+            final int unigramFrequencyForThisWord = target.mFrequency;
+            final int offset = addressOfBigram
+                    - (mPosition + FormatSpec.PTNODE_ATTRIBUTE_FLAGS_SIZE);
+            final int bigramFlags = BinaryDictEncoderUtils.makeBigramFlags(bigramIterator.hasNext(),
+                    offset, bigram.mFrequency, unigramFrequencyForThisWord, bigram.mWord);
+            mPosition = BinaryDictEncoderUtils.writeUIntToBuffer(mBuffer, mPosition, bigramFlags,
+                    FormatSpec.PTNODE_ATTRIBUTE_FLAGS_SIZE);
+            mPosition += BinaryDictEncoderUtils.writeChildrenPosition(mBuffer, mPosition,
+                    Math.abs(offset));
+        }
+    }
+
+    @Override
+    public void writeForwardLinkAddress(final int forwardLinkAddress) {
+        mPosition = BinaryDictEncoderUtils.writeUIntToBuffer(mBuffer, mPosition, forwardLinkAddress,
+                FormatSpec.FORWARD_LINK_ADDRESS_SIZE);
+    }
+
+    @Override
+    public void writePtNode(final PtNode ptNode, final int parentPosition,
+            final FormatOptions formatOptions, final FusionDictionary dict) {
+        writePtNodeFlags(ptNode, parentPosition, formatOptions);
+        writeParentPosition(parentPosition, ptNode, formatOptions);
+        writeCharacters(ptNode.mChars, ptNode.hasSeveralChars());
+        writeFrequency(ptNode.mFrequency);
+        writeChildrenPosition(ptNode, formatOptions);
+        writeShortcuts(ptNode.mShortcutTargets);
+        writeBigrams(ptNode.mBigrams, dict);
+    }
+}
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/java/src/com/android/inputmethod/latin/makedict/Ver4DictEncoder.java b/java/src/com/android/inputmethod/latin/makedict/Ver4DictEncoder.java
new file mode 100644
index 0000000..75b75ae
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/makedict/Ver4DictEncoder.java
@@ -0,0 +1,269 @@
+/*
+/*
+ * 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.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;
+import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Iterator;
+
+/**
+ * An implementation of DictEncoder for version 4 binary dictionary.
+ */
+@UsedForTesting
+public class Ver4DictEncoder implements DictEncoder {
+    private final File mDictPlacedDir;
+    private byte[] mTrieBuf;
+    private byte[] mFreqBuf;
+    private int mTriePos;
+    private OutputStream mTrieOutStream;
+    private OutputStream mFreqOutStream;
+
+    @UsedForTesting
+    public Ver4DictEncoder(final File dictPlacedDir) {
+        mDictPlacedDir = dictPlacedDir;
+    }
+
+    private void openStreams(final FormatOptions formatOptions, final DictionaryOptions dictOptions)
+            throws FileNotFoundException, IOException {
+        final FileHeader header = new FileHeader(0, dictOptions, formatOptions);
+        final String filename = header.getId() + "." + header.getVersion();
+        final File mDictDir = new File(mDictPlacedDir, filename);
+        final File trieFile = new File(mDictDir, filename + FormatSpec.TRIE_FILE_EXTENSION);
+        final File freqFile = new File(mDictDir, filename + FormatSpec.FREQ_FILE_EXTENSION);
+        if (!mDictDir.isDirectory()) {
+            if (mDictDir.exists()) mDictDir.delete();
+            mDictDir.mkdirs();
+        }
+        if (!trieFile.exists()) trieFile.createNewFile();
+        if (!freqFile.exists()) freqFile.createNewFile();
+        mTrieOutStream = new FileOutputStream(trieFile);
+        mFreqOutStream = new FileOutputStream(freqFile);
+    }
+
+    private void close() throws IOException {
+        try {
+            if (mTrieOutStream != null) {
+                mTrieOutStream.close();
+            }
+            if (mFreqOutStream != null) {
+                mFreqOutStream.close();
+            }
+        } finally {
+            mTrieOutStream = null;
+            mFreqOutStream = null;
+        }
+    }
+
+    @Override
+    public void writeDictionary(final FusionDictionary dict, final FormatOptions formatOptions)
+            throws IOException, UnsupportedFormatException {
+        if (formatOptions.mVersion != FormatSpec.VERSION4) {
+            throw new UnsupportedFormatException("File header has a wrong version number : "
+                    + formatOptions.mVersion);
+        }
+        if (!mDictPlacedDir.isDirectory()) {
+            throw new UnsupportedFormatException("Given path is not a directory.");
+        }
+
+        if (mTrieOutStream == null) {
+            openStreams(formatOptions, dict.mOptions);
+        }
+
+        BinaryDictEncoderUtils.writeDictionaryHeader(mTrieOutStream, dict, formatOptions);
+
+        MakedictLog.i("Flattening the tree...");
+        ArrayList<PtNodeArray> flatNodes = BinaryDictEncoderUtils.flattenTree(dict.mRootNodeArray);
+        int terminalCount = 0;
+        for (final PtNodeArray array : flatNodes) {
+            for (final PtNode node : array.mData) {
+                if (node.isTerminal()) node.mTerminalId = terminalCount++;
+            }
+        }
+
+        MakedictLog.i("Computing addresses...");
+        BinaryDictEncoderUtils.computeAddresses(dict, flatNodes, formatOptions);
+        if (MakedictLog.DBG) BinaryDictEncoderUtils.checkFlatPtNodeArrayList(flatNodes);
+
+        final PtNodeArray lastNodeArray = flatNodes.get(flatNodes.size() - 1);
+        final int bufferSize = lastNodeArray.mCachedAddressAfterUpdate + lastNodeArray.mCachedSize;
+        mTrieBuf = new byte[bufferSize];
+        mFreqBuf = new byte[terminalCount * FormatSpec.FREQUENCY_AND_FLAGS_SIZE];
+
+        MakedictLog.i("Writing file...");
+        for (PtNodeArray nodeArray : flatNodes) {
+            BinaryDictEncoderUtils.writePlacedPtNodeArray(dict, this, nodeArray, formatOptions);
+        }
+        if (MakedictLog.DBG) {
+            BinaryDictEncoderUtils.showStatistics(flatNodes);
+            MakedictLog.i("has " + terminalCount + " terminals.");
+        }
+        mTrieOutStream.write(mTrieBuf);
+        mFreqOutStream.write(mFreqBuf);
+
+        MakedictLog.i("Done");
+        close();
+    }
+
+    @Override
+    public void setPosition(int position) {
+        if (mTrieBuf == null || position < 0 || position >- mTrieBuf.length) return;
+        mTriePos = position;
+    }
+
+    @Override
+    public int getPosition() {
+        return mTriePos;
+    }
+
+    @Override
+    public void writePtNodeCount(int ptNodeCount) {
+        final int countSize = BinaryDictIOUtils.getPtNodeCountSize(ptNodeCount);
+        // ptNodeCount must fit on one byte or two bytes.
+        // Please see comments in FormatSpec
+        if (countSize != 1 && countSize != 2) {
+            throw new RuntimeException("Strange size from getPtNodeCountSize : " + countSize);
+        }
+        mTriePos = BinaryDictEncoderUtils.writeUIntToBuffer(mTrieBuf, mTriePos, ptNodeCount,
+                countSize);
+    }
+
+    private void writePtNodeFlags(final PtNode ptNode, final int parentAddress,
+            final FormatOptions formatOptions) {
+        final int childrenPos = BinaryDictEncoderUtils.getChildrenPosition(ptNode, formatOptions);
+        mTriePos = BinaryDictEncoderUtils.writeUIntToBuffer(mTrieBuf, mTriePos,
+                BinaryDictEncoderUtils.makePtNodeFlags(ptNode, mTriePos, childrenPos,
+                        formatOptions),
+                FormatSpec.PTNODE_FLAGS_SIZE);
+    }
+
+    private void writeParentPosition(int parentPos, final PtNode ptNode,
+            final FormatOptions formatOptions) {
+        if (parentPos != FormatSpec.NO_PARENT_ADDRESS) {
+            parentPos -= ptNode.mCachedAddressAfterUpdate;
+        }
+        mTriePos = BinaryDictEncoderUtils.writeParentAddress(mTrieBuf, mTriePos, parentPos,
+                formatOptions);
+    }
+
+    private void writeCharacters(final int[] characters, final boolean hasSeveralChars) {
+        mTriePos = CharEncoding.writeCharArray(characters, mTrieBuf, mTriePos);
+        if (hasSeveralChars) {
+            mTrieBuf[mTriePos++] = FormatSpec.PTNODE_CHARACTERS_TERMINATOR;
+        }
+    }
+
+    private void writeTerminalId(final int terminalId) {
+        mTriePos = BinaryDictEncoderUtils.writeUIntToBuffer(mTrieBuf, mTriePos, terminalId,
+                FormatSpec.PTNODE_TERMINAL_ID_SIZE);
+    }
+
+    private void writeFrequency(final int frequency, final int terminalId) {
+        final int freqPos = terminalId * FormatSpec.FREQUENCY_AND_FLAGS_SIZE;
+        BinaryDictEncoderUtils.writeUIntToBuffer(mFreqBuf, freqPos, frequency,
+                FormatSpec.FREQUENCY_AND_FLAGS_SIZE);
+    }
+
+    private void writeChildrenPosition(PtNode ptNode, FormatOptions formatOptions) {
+        final int childrenPos = BinaryDictEncoderUtils.getChildrenPosition(ptNode, formatOptions);
+        if (formatOptions.mSupportsDynamicUpdate) {
+            mTriePos += BinaryDictEncoderUtils.writeSignedChildrenPosition(mTrieBuf,
+                    mTriePos, childrenPos);
+        } else {
+            mTriePos += BinaryDictEncoderUtils.writeChildrenPosition(mTrieBuf,
+                    mTriePos, childrenPos);
+        }
+    }
+
+    private void writeShortcuts(ArrayList<WeightedString> shortcuts) {
+        if (null == shortcuts || shortcuts.isEmpty()) return;
+
+        final int indexOfShortcutByteSize = mTriePos;
+        mTriePos += FormatSpec.PTNODE_SHORTCUT_LIST_SIZE_SIZE;
+        final Iterator<WeightedString> shortcutIterator = shortcuts.iterator();
+        while (shortcutIterator.hasNext()) {
+            final WeightedString target = shortcutIterator.next();
+            final int shortcutFlags = BinaryDictEncoderUtils.makeShortcutFlags(
+                    shortcutIterator.hasNext(),
+                    target.mFrequency);
+            mTrieBuf[mTriePos++] = (byte)shortcutFlags;
+            final int shortcutShift = CharEncoding.writeString(mTrieBuf, mTriePos,
+                    target.mWord);
+            mTriePos += shortcutShift;
+        }
+        final int shortcutByteSize = mTriePos - indexOfShortcutByteSize;
+        if (shortcutByteSize > FormatSpec.MAX_SHORTCUT_LIST_SIZE_IN_A_PTNODE) {
+            throw new RuntimeException("Shortcut list too large : " + shortcutByteSize);
+        }
+        BinaryDictEncoderUtils.writeUIntToBuffer(mTrieBuf, indexOfShortcutByteSize,
+                shortcutByteSize, FormatSpec.PTNODE_SHORTCUT_LIST_SIZE_SIZE);
+    }
+
+    private void writeBigrams(ArrayList<WeightedString> bigrams, FusionDictionary dict) {
+        if (bigrams == null) return;
+
+        final Iterator<WeightedString> bigramIterator = bigrams.iterator();
+        while (bigramIterator.hasNext()) {
+            final WeightedString bigram = bigramIterator.next();
+            final PtNode target =
+                    FusionDictionary.findWordInTree(dict.mRootNodeArray, bigram.mWord);
+            final int addressOfBigram = target.mCachedAddressAfterUpdate;
+            final int unigramFrequencyForThisWord = target.mFrequency;
+            final int offset = addressOfBigram
+                    - (mTriePos + FormatSpec.PTNODE_ATTRIBUTE_FLAGS_SIZE);
+            int bigramFlags = BinaryDictEncoderUtils.makeBigramFlags(bigramIterator.hasNext(),
+                    offset, bigram.mFrequency, unigramFrequencyForThisWord, bigram.mWord);
+            mTrieBuf[mTriePos++] = (byte) bigramFlags;
+            mTriePos += BinaryDictEncoderUtils.writeChildrenPosition(mTrieBuf,
+                    mTriePos, Math.abs(offset));
+        }
+    }
+
+    @Override
+    public void writeForwardLinkAddress(int forwardLinkAddress) {
+        mTriePos = BinaryDictEncoderUtils.writeUIntToBuffer(mTrieBuf, mTriePos,
+                forwardLinkAddress, FormatSpec.FORWARD_LINK_ADDRESS_SIZE);
+    }
+
+    @Override
+    public void writePtNode(final PtNode ptNode, final int parentPosition,
+            final FormatOptions formatOptions, final FusionDictionary dict) {
+        writePtNodeFlags(ptNode, parentPosition, formatOptions);
+        writeParentPosition(parentPosition, ptNode, formatOptions);
+        writeCharacters(ptNode.mChars, ptNode.hasSeveralChars());
+        if (ptNode.isTerminal()) {
+            writeTerminalId(ptNode.mTerminalId);
+            writeFrequency(ptNode.mFrequency, ptNode.mTerminalId);
+        }
+        writeChildrenPosition(ptNode, formatOptions);
+        writeShortcuts(ptNode.mShortcutTargets);
+        writeBigrams(ptNode.mBigrams, dict);
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/personalization/DynamicPersonalizationDictionaryWriter.java b/java/src/com/android/inputmethod/latin/personalization/DynamicPersonalizationDictionaryWriter.java
new file mode 100644
index 0000000..0af028a
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/personalization/DynamicPersonalizationDictionaryWriter.java
@@ -0,0 +1,183 @@
+/*
+ * 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.personalization;
+
+import android.content.Context;
+
+import com.android.inputmethod.annotations.UsedForTesting;
+import com.android.inputmethod.compat.ActivityManagerCompatUtils;
+import com.android.inputmethod.keyboard.ProximityInfo;
+import com.android.inputmethod.latin.AbstractDictionaryWriter;
+import com.android.inputmethod.latin.ExpandableDictionary;
+import com.android.inputmethod.latin.WordComposer;
+import com.android.inputmethod.latin.ExpandableDictionary.NextWord;
+import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
+import com.android.inputmethod.latin.makedict.DictEncoder;
+import com.android.inputmethod.latin.makedict.FormatSpec;
+import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
+import com.android.inputmethod.latin.utils.UserHistoryDictIOUtils;
+import com.android.inputmethod.latin.utils.UserHistoryDictIOUtils.BigramDictionaryInterface;
+import com.android.inputmethod.latin.utils.UserHistoryForgettingCurveUtils;
+import com.android.inputmethod.latin.utils.UserHistoryForgettingCurveUtils.ForgettingCurveParams;
+
+import java.io.IOException;
+import java.util.ArrayList;
+
+// Currently this class is used to implement dynamic prodiction dictionary.
+// TODO: Move to native code.
+public class DynamicPersonalizationDictionaryWriter extends AbstractDictionaryWriter {
+    private static final String TAG = DynamicPersonalizationDictionaryWriter.class.getSimpleName();
+    /** Maximum number of pairs. Pruning will start when databases goes above this number. */
+    public static final int DEFAULT_MAX_HISTORY_BIGRAMS = 10000;
+    public static final int LOW_MEMORY_MAX_HISTORY_BIGRAMS = 2000;
+
+    /** Any pair being typed or picked */
+    private static final int FREQUENCY_FOR_TYPED = 2;
+
+    private static final int BINARY_DICT_VERSION = 3;
+    private static final FormatSpec.FormatOptions FORMAT_OPTIONS =
+            new FormatSpec.FormatOptions(BINARY_DICT_VERSION, true /* supportsDynamicUpdate */);
+
+    private final UserHistoryDictionaryBigramList mBigramList =
+            new UserHistoryDictionaryBigramList();
+    private final ExpandableDictionary mExpandableDictionary;
+    private final int mMaxHistoryBigrams;
+
+    public DynamicPersonalizationDictionaryWriter(final Context context, final String dictType) {
+        super(context, dictType);
+        mExpandableDictionary = new ExpandableDictionary(dictType);
+        final boolean isLowRamDevice = ActivityManagerCompatUtils.isLowRamDevice(context);
+        mMaxHistoryBigrams = isLowRamDevice ?
+                LOW_MEMORY_MAX_HISTORY_BIGRAMS : DEFAULT_MAX_HISTORY_BIGRAMS;
+    }
+
+    @Override
+    public void clear() {
+        mBigramList.evictAll();
+        mExpandableDictionary.clearDictionary();
+    }
+
+    /**
+     * Adds a word unigram to the fusion dictionary. Call updateBinaryDictionary when all changes
+     * are done to update the binary dictionary.
+     */
+    @Override
+    public void addUnigramWord(final String word, final String shortcutTarget, final int frequency,
+            final boolean isNotAWord) {
+        if (mBigramList.size() > mMaxHistoryBigrams * 2) {
+            // Too many entries: just stop adding new vocabrary and wait next refresh.
+            return;
+        }
+        mExpandableDictionary.addWord(word, shortcutTarget, frequency);
+        mBigramList.addBigram(null, word, (byte)frequency);
+    }
+
+    @Override
+    public void addBigramWords(final String word0, final String word1, final int frequency,
+            final boolean isValid, final long lastModifiedTime) {
+        if (mBigramList.size() > mMaxHistoryBigrams * 2) {
+            // Too many entries: just stop adding new vocabrary and wait next refresh.
+            return;
+        }
+        if (lastModifiedTime > 0) {
+            mExpandableDictionary.setBigramAndGetFrequency(word0, word1,
+                    new ForgettingCurveParams(frequency, System.currentTimeMillis(),
+                            lastModifiedTime));
+            mBigramList.addBigram(word0, word1, (byte)frequency);
+        } else {
+            mExpandableDictionary.setBigramAndGetFrequency(word0, word1,
+                    new ForgettingCurveParams(isValid));
+            mBigramList.addBigram(word0, word1, (byte)frequency);
+        }
+    }
+
+    @Override
+    public void removeBigramWords(final String word0, final String word1) {
+        if (mBigramList.removeBigram(word0, word1)) {
+            mExpandableDictionary.removeBigram(word0, word1);
+        }
+    }
+
+    @Override
+    protected void writeDictionary(final DictEncoder dictEncoder)
+            throws IOException, UnsupportedFormatException {
+        UserHistoryDictIOUtils.writeDictionary(dictEncoder,
+                new FrequencyProvider(mBigramList, mExpandableDictionary, mMaxHistoryBigrams),
+                mBigramList, FORMAT_OPTIONS);
+    }
+
+    private static class FrequencyProvider implements BigramDictionaryInterface {
+        private final UserHistoryDictionaryBigramList mBigramList;
+        private final ExpandableDictionary mExpandableDictionary;
+        private final int mMaxHistoryBigrams;
+
+        public FrequencyProvider(final UserHistoryDictionaryBigramList bigramList,
+                final ExpandableDictionary expandableDictionary, final int maxHistoryBigrams) {
+            mBigramList = bigramList;
+            mExpandableDictionary = expandableDictionary;
+            mMaxHistoryBigrams = maxHistoryBigrams;
+        }
+
+        @Override
+        public int getFrequency(final String word0, final String word1) {
+            final int freq;
+            if (word0 == null) { // unigram
+                freq = FREQUENCY_FOR_TYPED;
+            } else { // bigram
+                final NextWord nw = mExpandableDictionary.getBigramWord(word0, word1);
+                if (nw != null) {
+                    final ForgettingCurveParams forgettingCurveParams = nw.getFcParams();
+                    final byte prevFc = mBigramList.getBigrams(word0).get(word1);
+                    final byte fc = forgettingCurveParams.getFc();
+                    final boolean isValid = forgettingCurveParams.isValid();
+                    if (prevFc > 0 && prevFc == fc) {
+                        freq = fc & 0xFF;
+                    } else if (UserHistoryForgettingCurveUtils.
+                            needsToSave(fc, isValid, mBigramList.size() <= mMaxHistoryBigrams)) {
+                        freq = fc & 0xFF;
+                    } else {
+                        // Delete this entry
+                        freq = -1;
+                    }
+                } else {
+                    // Delete this entry
+                    freq = -1;
+                }
+            }
+            return freq;
+        }
+    }
+
+    @Override
+    public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
+            final String prevWord, final ProximityInfo proximityInfo,
+            boolean blockOffensiveWords, final int[] additionalFeaturesOptions) {
+        return mExpandableDictionary.getSuggestions(composer, prevWord, proximityInfo,
+                blockOffensiveWords, additionalFeaturesOptions);
+    }
+
+    @Override
+    public boolean isValidWord(final String word) {
+        return mExpandableDictionary.isValidWord(word);
+    }
+
+    @UsedForTesting
+    public boolean isInDictionaryForTests(final String word) {
+        // TODO: Use native method to determine whether the word is in dictionary or not
+        return mBigramList.containsKey(word);
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/personalization/DynamicPredictionDictionaryBase.java b/java/src/com/android/inputmethod/latin/personalization/DynamicPredictionDictionaryBase.java
index bb6ec6b..075d7e3 100644
--- a/java/src/com/android/inputmethod/latin/personalization/DynamicPredictionDictionaryBase.java
+++ b/java/src/com/android/inputmethod/latin/personalization/DynamicPredictionDictionaryBase.java
@@ -18,59 +18,40 @@
 
 import android.content.Context;
 import android.content.SharedPreferences;
-import android.os.AsyncTask;
 import android.util.Log;
 
 import com.android.inputmethod.annotations.UsedForTesting;
-import com.android.inputmethod.keyboard.ProximityInfo;
 import com.android.inputmethod.latin.Constants;
-import com.android.inputmethod.latin.ExpandableDictionary;
+import com.android.inputmethod.latin.ExpandableBinaryDictionary;
 import com.android.inputmethod.latin.LatinImeLogger;
-import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
-import com.android.inputmethod.latin.WordComposer;
-import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions;
+import com.android.inputmethod.latin.makedict.DictDecoder;
+import com.android.inputmethod.latin.makedict.FormatSpec;
 import com.android.inputmethod.latin.settings.Settings;
-import com.android.inputmethod.latin.utils.ByteArrayWrapper;
 import com.android.inputmethod.latin.utils.CollectionUtils;
 import com.android.inputmethod.latin.utils.UserHistoryDictIOUtils;
-import com.android.inputmethod.latin.utils.UserHistoryDictIOUtils.BigramDictionaryInterface;
 import com.android.inputmethod.latin.utils.UserHistoryDictIOUtils.OnAddWordListener;
-import com.android.inputmethod.latin.utils.UserHistoryForgettingCurveUtils;
-import com.android.inputmethod.latin.utils.UserHistoryForgettingCurveUtils.ForgettingCurveParams;
 
 import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
 import java.io.IOException;
 import java.util.ArrayList;
-import java.util.concurrent.locks.ReentrantLock;
 
 /**
  * This class is a base class of a dictionary for the personalized prediction language model.
  */
-public abstract class DynamicPredictionDictionaryBase extends ExpandableDictionary {
-
+public abstract class DynamicPredictionDictionaryBase extends ExpandableBinaryDictionary {
     private static final String TAG = DynamicPredictionDictionaryBase.class.getSimpleName();
     public static final boolean DBG_SAVE_RESTORE = false;
     private static final boolean DBG_STRESS_TEST = false;
     private static final boolean PROFILE_SAVE_RESTORE = LatinImeLogger.sDBG;
 
-    private static final FormatOptions VERSION3 = new FormatOptions(3,
-            true /* supportsDynamicUpdate */);
-
     /** Any pair being typed or picked */
-    private static final int FREQUENCY_FOR_TYPED = 2;
-
-    /** Maximum number of pairs. Pruning will start when databases goes above this number. */
-    private static final int MAX_HISTORY_BIGRAMS = 10000;
+    public static final int FREQUENCY_FOR_TYPED = 2;
 
     /** Locale for which this user history dictionary is storing words */
     private final String mLocale;
 
-    private final UserHistoryDictionaryBigramList mBigramList =
-            new UserHistoryDictionaryBigramList();
-    private final ReentrantLock mBigramListLock = new ReentrantLock();
+    private final String mFileName;
+
     private final SharedPreferences mPrefs;
 
     private final ArrayList<PersonalizationDictionaryUpdateSession> mSessions =
@@ -80,41 +61,44 @@
     @UsedForTesting boolean mIsTest = false;
 
     /* package */ DynamicPredictionDictionaryBase(final Context context, final String locale,
-            final SharedPreferences sp, final String dictionaryType) {
-        super(context, dictionaryType);
+            final SharedPreferences sp, final String dictionaryType, final String fileName) {
+        super(context, fileName, dictionaryType, true);
         mLocale = locale;
+        mFileName = fileName;
         mPrefs = sp;
         if (mLocale != null && mLocale.length() > 1) {
-            loadDictionary();
+            asyncLoadDictionaryToMemory();
+            reloadDictionaryIfRequired();
         }
     }
 
     @Override
     public void close() {
-        flushPendingWrites();
-        // Don't close the database as locale changes will require it to be reopened anyway
-        // Also, the database is written to somewhat frequently, so it needs to be kept alive
-        // throughout the life of the process.
-        // mOpenHelper.close();
-        // Ignore close because we cache PersonalizationPredictionDictionary for each language.
-        // See getInstance() above.
-        // super.close();
+        if (!ExpandableBinaryDictionary.ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE) {
+            closeBinaryDictionary();
+        }
+        // Flush pending writes.
+        // TODO: Remove after this class become to use a dynamic binary dictionary.
+        asyncFlashAllBinaryDictionary();
+        Settings.writeLastUserHistoryWriteTime(mPrefs, mLocale);
     }
 
     @Override
-    protected ArrayList<SuggestedWordInfo> getWordsInner(final WordComposer composer,
-            final String prevWord, final ProximityInfo proximityInfo) {
-        // Inhibit suggestions (not predictions) for user history for now. Removing this method
-        // is enough to use it through the standard ExpandableDictionary way.
-        return null;
+    protected boolean hasContentChanged() {
+        return false;
+    }
+
+    @Override
+    protected boolean needsToReloadBeforeWriting() {
+        return false;
     }
 
     /**
      * Return whether the passed charsequence is in the dictionary.
      */
     @Override
-    public synchronized boolean isValidWord(final String word) {
-        // TODO: figure out what is the correct thing to do here.
+    public boolean isValidWord(final String word) {
+     // Words included only in the user history should be treated as not in dictionary words.
         return false;
     }
 
@@ -126,70 +110,29 @@
      * context, as in beginning of a sentence for example.
      * The second word may not be null (a NullPointerException would be thrown).
      */
-    public int addToPersonalizationPredictionDictionary(
-            final String word1, final String word2, final boolean isValid) {
-        if (word2.length() >= Constants.DICTIONARY_MAX_WORD_LENGTH ||
-                (word1 != null && word1.length() >= Constants.DICTIONARY_MAX_WORD_LENGTH)) {
-            return -1;
+    public void addToPersonalizationPredictionDictionary(
+            final String word0, final String word1, final boolean isValid) {
+        if (word1.length() >= Constants.DICTIONARY_MAX_WORD_LENGTH ||
+                (word0 != null && word0.length() >= Constants.DICTIONARY_MAX_WORD_LENGTH)) {
+            return;
         }
-        if (mBigramListLock.tryLock()) {
-            try {
-                super.addWord(
-                        word2, null /* the "shortcut" parameter is null */, FREQUENCY_FOR_TYPED);
-                mBigramList.addBigram(null, word2, (byte)FREQUENCY_FOR_TYPED);
-                // Do not insert a word as a bigram of itself
-                if (word2.equals(word1)) {
-                    return 0;
-                }
-                final int freq;
-                if (null == word1) {
-                    freq = FREQUENCY_FOR_TYPED;
-                } else {
-                    freq = super.setBigramAndGetFrequency(
-                            word1, word2, new ForgettingCurveParams(isValid));
-                }
-                mBigramList.addBigram(word1, word2);
-                return freq;
-            } finally {
-                mBigramListLock.unlock();
-            }
+        addWordDynamically(word1, null /* the "shortcut" parameter is null */, FREQUENCY_FOR_TYPED,
+                false /* isNotAWord */);
+        // Do not insert a word as a bigram of itself
+        if (word1.equals(word0)) {
+            return;
         }
-        return -1;
+        if (null != word0) {
+            addBigramDynamically(word0, word1, FREQUENCY_FOR_TYPED, isValid);
+        }
     }
 
-    public boolean cancelAddingUserHistory(final String word1, final String word2) {
-        if (mBigramListLock.tryLock()) {
-            try {
-                if (mBigramList.removeBigram(word1, word2)) {
-                    return super.removeBigram(word1, word2);
-                }
-            } finally {
-                mBigramListLock.unlock();
-            }
-        }
-        return false;
-    }
-
-    /**
-     * Schedules a background thread to write any pending words to the database.
-     */
-    private void flushPendingWrites() {
-        // Create a background thread to write the pending entries
-        new UpdateBinaryTask(mBigramList, mLocale, this, mPrefs, getContext()).execute();
+    public void cancelAddingUserHistory(final String word0, final String word1) {
+        removeBigramDynamically(word0, word1);
     }
 
     @Override
-    public final void loadDictionaryAsync() {
-        // This must be run on non-main thread
-        mBigramListLock.lock();
-        try {
-            loadDictionaryAsyncLocked();
-        } finally {
-            mBigramListLock.unlock();
-        }
-    }
-
-    private void loadDictionaryAsyncLocked() {
+    protected void loadDictionaryAsync() {
         final int[] profTotalCount = { 0 };
         final String locale = getLocale();
         if (DBG_STRESS_TEST) {
@@ -201,10 +144,8 @@
             }
         }
         final long last = Settings.readLastUserHistoryWriteTime(mPrefs, locale);
-        final boolean initializing = last == 0;
         final long now = System.currentTimeMillis();
-        final String fileName = getDictionaryFileName();
-        final ExpandableDictionary dictionary = this;
+        final ExpandableBinaryDictionary dictionary = this;
         final OnAddWordListener listener = new OnAddWordListener() {
             @Override
             public void setUnigram(final String word, final String shortcutTarget,
@@ -212,49 +153,39 @@
                 if (DBG_SAVE_RESTORE) {
                     Log.d(TAG, "load unigram: " + word + "," + frequency);
                 }
-                dictionary.addWord(word, shortcutTarget, frequency);
+                addWord(word, shortcutTarget, frequency, false /* isNotAWord */);
                 ++profTotalCount[0];
-                addToBigramListLocked(null, word, (byte)frequency);
             }
 
             @Override
-            public void setBigram(final String word1, final String word2, final int frequency) {
-                if (word1.length() < Constants.DICTIONARY_MAX_WORD_LENGTH
-                        && word2.length() < Constants.DICTIONARY_MAX_WORD_LENGTH) {
+            public void setBigram(final String word0, final String word1, final int frequency) {
+                if (word0.length() < Constants.DICTIONARY_MAX_WORD_LENGTH
+                        && word1.length() < Constants.DICTIONARY_MAX_WORD_LENGTH) {
                     if (DBG_SAVE_RESTORE) {
-                        Log.d(TAG, "load bigram: " + word1 + "," + word2 + "," + frequency);
+                        Log.d(TAG, "load bigram: " + word0 + "," + word1 + "," + frequency);
                     }
                     ++profTotalCount[0];
-                    dictionary.setBigramAndGetFrequency(
-                            word1, word2, initializing ? new ForgettingCurveParams(true)
-                            : new ForgettingCurveParams(frequency, now, last));
+                    addBigram(word0, word1, frequency, last);
                 }
-                addToBigramListLocked(word1, word2, (byte)frequency);
             }
         };
 
         // Load the dictionary from binary file
-        FileInputStream inStream = null;
-        try {
-            final File file = new File(getContext().getFilesDir(), fileName);
-            final byte[] buffer = new byte[(int)file.length()];
-            inStream = new FileInputStream(file);
-            inStream.read(buffer);
-            UserHistoryDictIOUtils.readDictionaryBinary(
-                    new ByteArrayWrapper(buffer), listener);
-        } catch (FileNotFoundException e) {
+        final File dictFile = new File(mContext.getFilesDir(), mFileName);
+        final DictDecoder dictDecoder = FormatSpec.getDictDecoder(dictFile,
+                DictDecoder.USE_BYTEARRAY);
+        if (dictDecoder == null) {
             // This is an expected condition: we don't have a user history dictionary for this
             // language yet. It will be created sometime later.
+            return;
+        }
+
+        try {
+            dictDecoder.openDictBuffer();
+            UserHistoryDictIOUtils.readDictionaryBinary(dictDecoder, listener);
         } catch (IOException e) {
-            Log.e(TAG, "IOException on opening a bytebuffer", e);
+            Log.d(TAG, "IOException on opening a bytebuffer", e);
         } finally {
-            if (inStream != null) {
-                try {
-                    inStream.close();
-                } catch (IOException e) {
-                    // do nothing
-                }
-            }
             if (PROFILE_SAVE_RESTORE) {
                 final long diff = System.currentTimeMillis() - now;
                 Log.d(TAG, "PROF: Load UserHistoryDictionary: "
@@ -263,146 +194,24 @@
         }
     }
 
-    protected abstract String getDictionaryFileName();
-
     protected String getLocale() {
         return mLocale;
     }
 
-    private void addToBigramListLocked(String word0, String word1, byte fcValue) {
-        mBigramList.addBigram(word0, word1, fcValue);
-    }
-
-    /**
-     * Async task to write pending words to the binarydicts.
-     */
-    private static final class UpdateBinaryTask extends AsyncTask<Void, Void, Void>
-            implements BigramDictionaryInterface {
-        private final UserHistoryDictionaryBigramList mBigramList;
-        private final boolean mAddLevel0Bigrams;
-        private final String mLocale;
-        private final DynamicPredictionDictionaryBase mDynamicPredictionDictionary;
-        private final SharedPreferences mPrefs;
-        private final Context mContext;
-
-        public UpdateBinaryTask(final UserHistoryDictionaryBigramList pendingWrites,
-                final String locale, final DynamicPredictionDictionaryBase dict,
-                final SharedPreferences prefs, final Context context) {
-            mBigramList = pendingWrites;
-            mLocale = locale;
-            mDynamicPredictionDictionary = dict;
-            mPrefs = prefs;
-            mContext = context;
-            mAddLevel0Bigrams = mBigramList.size() <= MAX_HISTORY_BIGRAMS;
-        }
-
-        @Override
-        protected Void doInBackground(final Void... v) {
-            if (mDynamicPredictionDictionary.mIsTest) {
-                // If mIsTest == true, wait until the lock is released.
-                mDynamicPredictionDictionary.mBigramListLock.lock();
-                try {
-                    doWriteTaskLocked();
-                } finally {
-                    mDynamicPredictionDictionary.mBigramListLock.unlock();
-                }
-            } else if (mDynamicPredictionDictionary.mBigramListLock.tryLock()) {
-                try {
-                    doWriteTaskLocked();
-                } finally {
-                    mDynamicPredictionDictionary.mBigramListLock.unlock();
-                }
-            }
-            return null;
-        }
-
-        private void doWriteTaskLocked() {
-            if (DBG_STRESS_TEST) {
-                try {
-                    Log.w(TAG, "Start stress in closing: " + mLocale);
-                    Thread.sleep(15000);
-                    Log.w(TAG, "End stress in closing");
-                } catch (InterruptedException e) {
-                    Log.e(TAG, "In stress test", e);
-                }
-            }
-
-            final long now = PROFILE_SAVE_RESTORE ? System.currentTimeMillis() : 0;
-            final String fileName =
-                    mDynamicPredictionDictionary.getDictionaryFileName();
-            final File file = new File(mContext.getFilesDir(), fileName);
-            FileOutputStream out = null;
-
-            try {
-                out = new FileOutputStream(file);
-                UserHistoryDictIOUtils.writeDictionaryBinary(out, this, mBigramList, VERSION3);
-                out.flush();
-                out.close();
-            } catch (IOException e) {
-                Log.e(TAG, "IO Exception while writing file", e);
-            } finally {
-                if (out != null) {
-                    try {
-                        out.close();
-                    } catch (IOException e) {
-                        // ignore
-                    }
-                }
-            }
-
-            // Save the timestamp after we finish writing the binary dictionary.
-            Settings.writeLastUserHistoryWriteTime(mPrefs, mLocale);
-            if (PROFILE_SAVE_RESTORE) {
-                final long diff = System.currentTimeMillis() - now;
-                Log.w(TAG, "PROF: Write User HistoryDictionary: " + mLocale + ", " + diff + "ms.");
-            }
-        }
-
-        @Override
-        public int getFrequency(final String word1, final String word2) {
-            final int freq;
-            if (word1 == null) { // unigram
-                freq = FREQUENCY_FOR_TYPED;
-                final byte prevFc = mBigramList.getBigrams(word1).get(word2);
-            } else { // bigram
-                final NextWord nw =
-                        mDynamicPredictionDictionary.getBigramWord(word1, word2);
-                if (nw != null) {
-                    final ForgettingCurveParams fcp = nw.getFcParams();
-                    final byte prevFc = mBigramList.getBigrams(word1).get(word2);
-                    final byte fc = fcp.getFc();
-                    final boolean isValid = fcp.isValid();
-                    if (prevFc > 0 && prevFc == fc) {
-                        freq = fc & 0xFF;
-                    } else if (UserHistoryForgettingCurveUtils.
-                            needsToSave(fc, isValid, mAddLevel0Bigrams)) {
-                        freq = fc & 0xFF;
-                    } else {
-                        // Delete this entry
-                        freq = -1;
-                    }
-                } else {
-                    // Delete this entry
-                    freq = -1;
-                }
-            }
-            return freq;
-        }
-    }
-
-    @UsedForTesting
-    /* package for test */ void forceAddWordForTest(
-            final String word1, final String word2, final boolean isValid) {
-        mBigramListLock.lock();
-        try {
-            addToPersonalizationPredictionDictionary(word1, word2, isValid);
-        } finally {
-            mBigramListLock.unlock();
-        }
-    }
-
     public void registerUpdateSession(PersonalizationDictionaryUpdateSession session) {
-        session.setDictionary(this);
+        session.setPredictionDictionary(this);
         mSessions.add(session);
+        session.onDictionaryReady();
+    }
+
+    public void unRegisterUpdateSession(PersonalizationDictionaryUpdateSession session) {
+        mSessions.remove(session);
+    }
+
+    public void clearAndFlushDictionary() {
+        // Clear the node structure on memory
+        clear();
+        // Then flush the cleared state of the dictionary on disk.
+        asyncFlashAllBinaryDictionary();
     }
 }
diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionary.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionary.java
index e38a235..f257165 100644
--- a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionary.java
+++ b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionary.java
@@ -18,26 +18,32 @@
 
 import com.android.inputmethod.latin.Dictionary;
 import com.android.inputmethod.latin.ExpandableBinaryDictionary;
+import com.android.inputmethod.latin.utils.CollectionUtils;
 
 import android.content.Context;
+import android.content.SharedPreferences;
+
+import java.util.ArrayList;
 
 /**
  * This class is a dictionary for the personalized language model that uses binary dictionary.
  */
 public class PersonalizationDictionary extends ExpandableBinaryDictionary {
     private static final String NAME = "personalization";
-
-    public static void registerUpdateListener(PersonalizationDictionaryUpdateSession listener) {
-        // TODO: Implement
-    }
+    private final ArrayList<PersonalizationDictionaryUpdateSession> mSessions =
+            CollectionUtils.newArrayList();
 
     /** Locale for which this user history dictionary is storing words */
     private final String mLocale;
 
-    // Singleton
-    private PersonalizationDictionary(final Context context, final String locale) {
-        super(context, getFilenameWithLocale(NAME, locale), Dictionary.TYPE_PERSONALIZATION);
+    public PersonalizationDictionary(final Context context, final String locale,
+            final SharedPreferences prefs) {
+        // TODO: Make isUpdatable true.
+        super(context, getFilenameWithLocale(NAME, locale), Dictionary.TYPE_PERSONALIZATION,
+                false /* isUpdatable */);
         mLocale = locale;
+        // TODO: Restore last updated time
+        loadDictionary();
     }
 
     @Override
@@ -47,15 +53,21 @@
 
     @Override
     protected boolean hasContentChanged() {
-        // TODO: Implement
         return false;
     }
 
     @Override
     protected boolean needsToReloadBeforeWriting() {
-        // TODO: Implement
         return false;
     }
 
-    // TODO: Implement
+    public void registerUpdateSession(PersonalizationDictionaryUpdateSession session) {
+        session.setDictionary(this);
+        mSessions.add(session);
+        session.onDictionaryReady();
+    }
+
+    public void unRegisterUpdateSession(PersonalizationDictionaryUpdateSession session) {
+        mSessions.remove(session);
+    }
 }
diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionarySessionRegister.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionarySessionRegister.java
new file mode 100644
index 0000000..c1833ff
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionarySessionRegister.java
@@ -0,0 +1,37 @@
+/*
+ * 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.personalization;
+
+import android.content.Context;
+import android.content.res.Configuration;
+
+public class PersonalizationDictionarySessionRegister {
+    public static void init(Context context) {
+    }
+
+    public static void onConfigurationChanged(final Context context, final Configuration conf) {
+    }
+
+    public static void onUpdateData(Context context, String type) {
+    }
+
+    public static void onRemoveData(Context context, String type) {
+    }
+
+    public static void onDestroy(Context context) {
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryUpdateSession.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryUpdateSession.java
index d62aec1..ab3de80 100644
--- a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryUpdateSession.java
+++ b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryUpdateSession.java
@@ -16,6 +16,8 @@
 
 package com.android.inputmethod.latin.personalization;
 
+import android.content.Context;
+
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 
@@ -43,18 +45,78 @@
     }
 
     // TODO: Use a dynamic binary dictionary instead
-    public WeakReference<DynamicPredictionDictionaryBase> mDictionary;
+    public WeakReference<PersonalizationDictionary> mDictionary;
+    public WeakReference<DynamicPredictionDictionaryBase> mPredictionDictionary;
+    public final String mSystemLocale;
+    public PersonalizationDictionaryUpdateSession(String locale) {
+        mSystemLocale = locale;
+    }
 
     public abstract void onDictionaryReady();
 
-    public void setDictionary(DynamicPredictionDictionaryBase dictionary) {
-        mDictionary = new WeakReference<DynamicPredictionDictionaryBase>(dictionary);
+    public abstract void onDictionaryClosed(Context context);
+
+    public void setDictionary(PersonalizationDictionary dictionary) {
+        mDictionary = new WeakReference<PersonalizationDictionary>(dictionary);
     }
 
-    public void addToPersonalizationDictionary(
+    public void setPredictionDictionary(DynamicPredictionDictionaryBase dictionary) {
+        mPredictionDictionary = new WeakReference<DynamicPredictionDictionaryBase>(dictionary);
+    }
+
+    protected PersonalizationDictionary getDictionary() {
+        return mDictionary == null ? null : mDictionary.get();
+    }
+
+    protected DynamicPredictionDictionaryBase getPredictionDictionary() {
+        return mPredictionDictionary == null ? null : mPredictionDictionary.get();
+    }
+
+    private void unsetDictionary() {
+        final PersonalizationDictionary dictionary = getDictionary();
+        if (dictionary == null) {
+            return;
+        }
+        dictionary.unRegisterUpdateSession(this);
+    }
+
+    private void unsetPredictionDictionary() {
+        final DynamicPredictionDictionaryBase dictionary = getPredictionDictionary();
+        if (dictionary == null) {
+            return;
+        }
+        dictionary.unRegisterUpdateSession(this);
+    }
+
+    public void clearAndFlushPredictionDictionary(Context context) {
+        final DynamicPredictionDictionaryBase dictionary = getPredictionDictionary();
+        if (dictionary == null) {
+            return;
+        }
+        dictionary.clearAndFlushDictionary();
+    }
+
+    public void closeSession(Context context) {
+        unsetDictionary();
+        unsetPredictionDictionary();
+        onDictionaryClosed(context);
+    }
+
+    // TODO: Support multi locale to add bigram
+    public void addBigramToPersonalizationDictionary(String word0, String word1, boolean isValid,
+            int frequency) {
+        final DynamicPredictionDictionaryBase dictionary = getPredictionDictionary();
+        if (dictionary == null) {
+            return;
+        }
+        dictionary.addToPersonalizationPredictionDictionary(word0, word1, isValid);
+    }
+
+    // Bulk import
+    // TODO: Support multi locale to add bigram
+    public void addBigramsToPersonalizationDictionary(
             final ArrayList<PersonalizationLanguageModelParam> lmParams) {
-        final DynamicPredictionDictionaryBase dictionary = mDictionary == null
-                ? null : mDictionary.get();
+        final DynamicPredictionDictionaryBase dictionary = getPredictionDictionary();
         if (dictionary == null) {
             return;
         }
diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryHelper.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java
similarity index 63%
rename from java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryHelper.java
rename to java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java
index da256f8..5f702ee 100644
--- a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryHelper.java
+++ b/java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java
@@ -26,16 +26,20 @@
 import java.lang.ref.SoftReference;
 import java.util.concurrent.ConcurrentHashMap;
 
-public class PersonalizationDictionaryHelper {
-    private static final String TAG = PersonalizationDictionaryHelper.class.getSimpleName();
+public class PersonalizationHelper {
+    private static final String TAG = PersonalizationHelper.class.getSimpleName();
     private static final boolean DEBUG = false;
 
     private static final ConcurrentHashMap<String, SoftReference<UserHistoryPredictionDictionary>>
             sLangUserHistoryDictCache = CollectionUtils.newConcurrentHashMap();
 
+    private static final ConcurrentHashMap<String, SoftReference<PersonalizationDictionary>>
+            sLangPersonalizationDictCache = CollectionUtils.newConcurrentHashMap();
+
     private static final ConcurrentHashMap<String,
             SoftReference<PersonalizationPredictionDictionary>>
-                    sLangPersonalizationDictCache = CollectionUtils.newConcurrentHashMap();
+                    sLangPersonalizationPredictionDictCache =
+                            CollectionUtils.newConcurrentHashMap();
 
     public static UserHistoryPredictionDictionary getUserHistoryPredictionDictionary(
             final Context context, final String locale, final SharedPreferences sp) {
@@ -48,6 +52,7 @@
                     if (DEBUG) {
                         Log.w(TAG, "Use cached UserHistoryPredictionDictionary for " + locale);
                     }
+                    dict.reloadDictionaryIfRequired();
                     return dict;
                 }
             }
@@ -59,22 +64,46 @@
         }
     }
 
-    public static void
-            registerPersonalizationDictionaryUpdateSession(final Context context,
-                    final PersonalizationDictionaryUpdateSession session) {
-        final PersonalizationPredictionDictionary dictionary =
-                getPersonalizationPredictionDictionary(context,
-                        context.getResources().getConfiguration().locale.toString(),
+    public static void registerPersonalizationDictionaryUpdateSession(final Context context,
+            final PersonalizationDictionaryUpdateSession session, String locale) {
+        final PersonalizationPredictionDictionary predictionDictionary =
+                getPersonalizationPredictionDictionary(context, locale,
+                        PreferenceManager.getDefaultSharedPreferences(context));
+        predictionDictionary.registerUpdateSession(session);
+        final PersonalizationDictionary dictionary =
+                getPersonalizationDictionary(context, locale,
                         PreferenceManager.getDefaultSharedPreferences(context));
         dictionary.registerUpdateSession(session);
     }
 
-    public static PersonalizationPredictionDictionary getPersonalizationPredictionDictionary(
+    public static PersonalizationDictionary getPersonalizationDictionary(
             final Context context, final String locale, final SharedPreferences sp) {
         synchronized (sLangPersonalizationDictCache) {
             if (sLangPersonalizationDictCache.containsKey(locale)) {
-                final SoftReference<PersonalizationPredictionDictionary> ref =
+                final SoftReference<PersonalizationDictionary> ref =
                         sLangPersonalizationDictCache.get(locale);
+                final PersonalizationDictionary dict = ref == null ? null : ref.get();
+                if (dict != null) {
+                    if (DEBUG) {
+                        Log.w(TAG, "Use cached PersonalizationDictCache for " + locale);
+                    }
+                    return dict;
+                }
+            }
+            final PersonalizationDictionary dict =
+                    new PersonalizationDictionary(context, locale, sp);
+            sLangPersonalizationDictCache.put(
+                    locale, new SoftReference<PersonalizationDictionary>(dict));
+            return dict;
+        }
+    }
+
+    public static PersonalizationPredictionDictionary getPersonalizationPredictionDictionary(
+            final Context context, final String locale, final SharedPreferences sp) {
+        synchronized (sLangPersonalizationPredictionDictCache) {
+            if (sLangPersonalizationPredictionDictCache.containsKey(locale)) {
+                final SoftReference<PersonalizationPredictionDictionary> ref =
+                        sLangPersonalizationPredictionDictCache.get(locale);
                 final PersonalizationPredictionDictionary dict = ref == null ? null : ref.get();
                 if (dict != null) {
                     if (DEBUG) {
@@ -85,7 +114,7 @@
             }
             final PersonalizationPredictionDictionary dict =
                     new PersonalizationPredictionDictionary(context, locale, sp);
-            sLangPersonalizationDictCache.put(
+            sLangPersonalizationPredictionDictCache.put(
                     locale, new SoftReference<PersonalizationPredictionDictionary>(dict));
             return dict;
         }
diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationPredictionDictionary.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationPredictionDictionary.java
index 955bd27..e80953c 100644
--- a/java/src/com/android/inputmethod/latin/personalization/PersonalizationPredictionDictionary.java
+++ b/java/src/com/android/inputmethod/latin/personalization/PersonalizationPredictionDictionary.java
@@ -17,6 +17,7 @@
 package com.android.inputmethod.latin.personalization;
 
 import com.android.inputmethod.latin.Dictionary;
+import com.android.inputmethod.latin.ExpandableBinaryDictionary;
 
 import android.content.Context;
 import android.content.SharedPreferences;
@@ -26,11 +27,11 @@
 
     /* package */ PersonalizationPredictionDictionary(final Context context, final String locale,
             final SharedPreferences sp) {
-        super(context, locale, sp, Dictionary.TYPE_PERSONALIZATION_PREDICTION_IN_JAVA);
+        super(context, locale, sp, Dictionary.TYPE_PERSONALIZATION_PREDICTION_IN_JAVA,
+                getDictionaryFileName(locale));
     }
 
-    @Override
-    protected String getDictionaryFileName() {
-        return NAME + "." + getLocale() + ".dict";
+    private static String getDictionaryFileName(final String locale) {
+        return NAME + "." + locale + ExpandableBinaryDictionary.DICT_FILE_EXTENSION;
     }
 }
diff --git a/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryBigramList.java b/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryBigramList.java
index f21db25..4c1803b 100644
--- a/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryBigramList.java
+++ b/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryBigramList.java
@@ -45,6 +45,7 @@
     /**
      * Called when the user typed a word.
      */
+    @UsedForTesting
     public void addBigram(String word1, String word2) {
         addBigram(word1, word2, FORGETTING_CURVE_INITIAL_VALUE);
     }
@@ -53,7 +54,7 @@
      * Called when loaded from the SQL DB.
      */
     public void addBigram(String word1, String word2, byte fcValue) {
-        if (UserHistoryPredictionDictionary.DBG_SAVE_RESTORE) {
+        if (DynamicPredictionDictionaryBase.DBG_SAVE_RESTORE) {
             Log.d(TAG, "--- add bigram: " + word1 + ", " + word2 + ", " + fcValue);
         }
         final HashMap<String, Byte> map;
@@ -73,7 +74,7 @@
      * Called when inserted to the SQL DB.
      */
     public void updateBigram(String word1, String word2, byte fcValue) {
-        if (UserHistoryPredictionDictionary.DBG_SAVE_RESTORE) {
+        if (DynamicPredictionDictionaryBase.DBG_SAVE_RESTORE) {
             Log.d(TAG, "--- update bigram: " + word1 + ", " + word2 + ", " + fcValue);
         }
         final HashMap<String, Byte> map;
@@ -96,6 +97,10 @@
         return mBigramMap.isEmpty();
     }
 
+    public boolean containsKey(String word) {
+        return mBigramMap.containsKey(word);
+    }
+
     public Set<String> keySet() {
         return mBigramMap.keySet();
     }
diff --git a/java/src/com/android/inputmethod/latin/personalization/UserHistoryPredictionDictionary.java b/java/src/com/android/inputmethod/latin/personalization/UserHistoryPredictionDictionary.java
index d117844..b140c91 100644
--- a/java/src/com/android/inputmethod/latin/personalization/UserHistoryPredictionDictionary.java
+++ b/java/src/com/android/inputmethod/latin/personalization/UserHistoryPredictionDictionary.java
@@ -17,6 +17,7 @@
 package com.android.inputmethod.latin.personalization;
 
 import com.android.inputmethod.latin.Dictionary;
+import com.android.inputmethod.latin.ExpandableBinaryDictionary;
 
 import android.content.Context;
 import android.content.SharedPreferences;
@@ -26,14 +27,14 @@
  * cancellation or manual picks. This allows the keyboard to adapt to the typist over time.
  */
 public class UserHistoryPredictionDictionary extends DynamicPredictionDictionaryBase {
-    private static final String NAME = UserHistoryPredictionDictionary.class.getSimpleName();
+    /* package for tests */ static final String NAME =
+            UserHistoryPredictionDictionary.class.getSimpleName();
     /* package */ UserHistoryPredictionDictionary(final Context context, final String locale,
             final SharedPreferences sp) {
-        super(context, locale, sp, Dictionary.TYPE_USER_HISTORY);
+        super(context, locale, sp, Dictionary.TYPE_USER_HISTORY, getDictionaryFileName(locale));
     }
 
-    @Override
-    protected String getDictionaryFileName() {
-        return NAME + "." + getLocale() + ".dict";
+    private static String getDictionaryFileName(final String locale) {
+        return NAME + "." + locale + ExpandableBinaryDictionary.DICT_FILE_EXTENSION;
     }
 }
diff --git a/java/src/com/android/inputmethod/latin/settings/AdditionalFeaturesSettingUtils.java b/java/src/com/android/inputmethod/latin/settings/AdditionalFeaturesSettingUtils.java
index 139f5e2..6543003 100644
--- a/java/src/com/android/inputmethod/latin/settings/AdditionalFeaturesSettingUtils.java
+++ b/java/src/com/android/inputmethod/latin/settings/AdditionalFeaturesSettingUtils.java
@@ -40,8 +40,4 @@
             final SharedPreferences prefs, final int[] additionalFeaturesPreferences) {
         // do nothing.
     }
-
-    public static int[] getAdditionalNativeSuggestOptions() {
-        return Settings.getInstance().getCurrent().mAdditionalFeaturesSettingValues;
-    }
 }
diff --git a/java/src/com/android/inputmethod/latin/settings/DebugSettings.java b/java/src/com/android/inputmethod/latin/settings/DebugSettings.java
index b1cd887..1b592b5 100644
--- a/java/src/com/android/inputmethod/latin/settings/DebugSettings.java
+++ b/java/src/com/android/inputmethod/latin/settings/DebugSettings.java
@@ -39,6 +39,8 @@
     public static final String PREF_STATISTICS_LOGGING = "enable_logging";
     public static final String PREF_USE_ONLY_PERSONALIZATION_DICTIONARY_FOR_DEBUG =
             "use_only_personalization_dictionary_for_debug";
+    public static final String PREF_BOOST_PERSONALIZATION_DICTIONARY_FOR_DEBUG =
+            "boost_personalization_dictionary_for_debug";
     private static final String PREF_READ_EXTERNAL_DICTIONARY = "read_external_dictionary";
     private static final boolean SHOW_STATISTICS_LOGGING = false;
 
diff --git a/java/src/com/android/inputmethod/latin/settings/NativeSuggestOptions.java b/java/src/com/android/inputmethod/latin/settings/NativeSuggestOptions.java
index 878c505..cd726c9 100644
--- a/java/src/com/android/inputmethod/latin/settings/NativeSuggestOptions.java
+++ b/java/src/com/android/inputmethod/latin/settings/NativeSuggestOptions.java
@@ -34,6 +34,9 @@
     }
 
     public void setAdditionalFeaturesOptions(final int[] additionalOptions) {
+        if (additionalOptions == null) {
+            return;
+        }
         for (int i = 0; i < additionalOptions.length; i++) {
             setIntegerOption(OPTIONS_SIZE + i, additionalOptions[i]);
         }
diff --git a/java/src/com/android/inputmethod/latin/settings/Settings.java b/java/src/com/android/inputmethod/latin/settings/Settings.java
index d432087..1a0fecc 100644
--- a/java/src/com/android/inputmethod/latin/settings/Settings.java
+++ b/java/src/com/android/inputmethod/latin/settings/Settings.java
@@ -27,10 +27,10 @@
 import com.android.inputmethod.latin.InputAttributes;
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.utils.AdditionalSubtypeUtils;
-import com.android.inputmethod.latin.utils.DebugLogUtils;
 import com.android.inputmethod.latin.utils.LocaleUtils;
 import com.android.inputmethod.latin.utils.ResourceUtils;
 import com.android.inputmethod.latin.utils.RunInLocale;
+import com.android.inputmethod.latin.utils.StringUtils;
 
 import java.util.HashMap;
 import java.util.Locale;
@@ -44,7 +44,9 @@
     public static final String PREF_VIBRATE_ON = "vibrate_on";
     public static final String PREF_SOUND_ON = "sound_on";
     public static final String PREF_POPUP_ON = "popup_on";
-    public static final String PREF_VOICE_MODE = "voice_mode";
+    // PREF_VOICE_MODE_OBSOLETE is obsolete. Use PREF_VOICE_INPUT_KEY instead.
+    public static final String PREF_VOICE_MODE_OBSOLETE = "voice_mode";
+    public static final String PREF_VOICE_INPUT_KEY = "pref_voice_input_key";
     public static final String PREF_CORRECTION_SETTINGS = "correction_settings";
     public static final String PREF_EDIT_PERSONAL_DICTIONARY = "edit_personal_dictionary";
     public static final String PREF_CONFIGURE_DICTIONARIES_KEY = "configure_dictionaries_key";
@@ -79,6 +81,7 @@
     public static final String PREF_GESTURE_FLOATING_PREVIEW_TEXT =
             "pref_gesture_floating_preview_text";
     public static final String PREF_SHOW_SETUP_WIZARD_ICON = "pref_show_setup_wizard_icon";
+    public static final String PREF_PHRASE_GESTURE_ENABLED = "pref_gesture_space_aware";
 
     public static final String PREF_INPUT_LANGUAGE = "input_language";
     public static final String PREF_SELECTED_LANGUAGES = "selected_languages";
@@ -90,9 +93,15 @@
     private static final String PREF_SUPPRESS_LANGUAGE_SWITCH_KEY =
             "pref_suppress_language_switch_key";
 
+    private static final String PREF_LAST_USED_PERSONALIZATION_TOKEN =
+            "pref_last_used_personalization_token";
     public static final String PREF_SEND_FEEDBACK = "send_feedback";
     public static final String PREF_ABOUT_KEYBOARD = "about_keyboard";
 
+    // Emoji
+    public static final String PREF_EMOJI_RECENT_KEYS = "emoji_recent_keys";
+    public static final String PREF_EMOJI_CATEGORY_LAST_TYPED_ID = "emoji_category_last_typed_id";
+
     private Resources mRes;
     private SharedPreferences mPrefs;
     private SettingsValues mSettingsValues;
@@ -212,6 +221,12 @@
                 && prefs.getBoolean(Settings.PREF_GESTURE_INPUT, true);
     }
 
+    public static boolean readPhraseGestureEnabled(final SharedPreferences prefs,
+            final Resources res) {
+        return prefs.getBoolean(Settings.PREF_PHRASE_GESTURE_ENABLED,
+                res.getBoolean(R.bool.config_default_phrase_gesture_enabled));
+    }
+
     public static boolean readFromBuildConfigIfToShowKeyPreviewPopupSettingsOption(
             final Resources res) {
         return res.getBoolean(R.bool.config_enable_show_option_of_key_preview_popup);
@@ -343,4 +358,40 @@
         return prefs.getBoolean(
                 DebugSettings.PREF_USE_ONLY_PERSONALIZATION_DICTIONARY_FOR_DEBUG, false);
     }
+
+    public static boolean readBoostPersonalizationDictionaryForDebug(
+            final SharedPreferences prefs) {
+        return prefs.getBoolean(
+                DebugSettings.PREF_BOOST_PERSONALIZATION_DICTIONARY_FOR_DEBUG, false);
+    }
+
+    public void writeLastUsedPersonalizationToken(byte[] token) {
+        final String tokenStr = StringUtils.byteArrayToHexString(token);
+        mPrefs.edit().putString(PREF_LAST_USED_PERSONALIZATION_TOKEN, tokenStr).apply();
+    }
+
+    public byte[] readLastUsedPersonalizationToken() {
+        final String tokenStr = mPrefs.getString(PREF_LAST_USED_PERSONALIZATION_TOKEN, null);
+        return StringUtils.hexStringToByteArray(tokenStr);
+    }
+
+    public static void writeEmojiRecentKeys(final SharedPreferences prefs, String str) {
+        prefs.edit().putString(PREF_EMOJI_RECENT_KEYS, str).apply();
+    }
+
+    public static String readEmojiRecentKeys(final SharedPreferences prefs) {
+        return prefs.getString(PREF_EMOJI_RECENT_KEYS, "");
+    }
+
+    public static void writeEmojiCategoryLastTypedId(
+            final SharedPreferences prefs, final int category, final int id) {
+        final String key = PREF_EMOJI_CATEGORY_LAST_TYPED_ID + category;
+        prefs.edit().putInt(key, id).apply();
+    }
+
+    public static int readEmojiCategoryLastTypedId(
+            final SharedPreferences prefs, final int category) {
+        final String key = PREF_EMOJI_CATEGORY_LAST_TYPED_ID + category;
+        return prefs.getInt(key, 0);
+    }
 }
diff --git a/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java b/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java
index 4467777..cb7dda6 100644
--- a/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java
+++ b/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java
@@ -61,7 +61,7 @@
             DBG_USE_INTERNAL_PERSONAL_DICTIONARY_SETTINGS
                     || Build.VERSION.SDK_INT <= 18 /* Build.VERSION.JELLY_BEAN_MR2 */;
 
-    private ListPreference mVoicePreference;
+    private CheckBoxPreference mVoiceInputKeyPreference;
     private ListPreference mShowCorrectionSuggestionsPreference;
     private ListPreference mAutoCorrectionThresholdPreference;
     private ListPreference mKeyPreviewPopupDismissDelay;
@@ -107,7 +107,8 @@
         SubtypeLocaleUtils.init(context);
         AudioAndHapticFeedbackManager.init(context);
 
-        mVoicePreference = (ListPreference) findPreference(Settings.PREF_VOICE_MODE);
+        mVoiceInputKeyPreference =
+                (CheckBoxPreference) findPreference(Settings.PREF_VOICE_INPUT_KEY);
         mShowCorrectionSuggestionsPreference =
                 (ListPreference) findPreference(Settings.PREF_SHOW_SUGGESTIONS_SETTING);
         final SharedPreferences prefs = getPreferenceManager().getSharedPreferences();
@@ -166,7 +167,7 @@
         final boolean showVoiceKeyOption = res.getBoolean(
                 R.bool.config_enable_show_voice_key_option);
         if (!showVoiceKeyOption) {
-            generalSettings.removePreference(mVoicePreference);
+            generalSettings.removePreference(mVoiceInputKeyPreference);
         }
 
         final PreferenceGroup advancedSettings =
@@ -229,10 +230,10 @@
 
         if (!Settings.readFromBuildConfigIfGestureInputEnabled(res)) {
             removePreference(Settings.PREF_GESTURE_SETTINGS, getPreferenceScreen());
-        } else {
-            AdditionalFeaturesSettingUtils.addAdditionalFeaturesPreferences(context, this);
         }
 
+        AdditionalFeaturesSettingUtils.addAdditionalFeaturesPreferences(context, this);
+
         setupKeyLongpressTimeoutSettings(prefs, res);
         setupKeypressVibrationDurationSettings(prefs, res);
         setupKeypressSoundVolumeSettings(prefs, res);
@@ -243,10 +244,8 @@
     public void onResume() {
         super.onResume();
         final boolean isShortcutImeEnabled = SubtypeSwitcher.getInstance().isShortcutImeEnabled();
-        if (isShortcutImeEnabled) {
-            updateVoiceModeSummary();
-        } else {
-            getPreferenceScreen().removePreference(mVoicePreference);
+        if (!isShortcutImeEnabled) {
+            getPreferenceScreen().removePreference(mVoiceInputKeyPreference);
         }
         final SharedPreferences prefs = getPreferenceManager().getSharedPreferences();
         final CheckBoxPreference showSetupWizardIcon =
@@ -287,7 +286,6 @@
             LauncherIconVisibilityManager.updateSetupWizardIconVisibility(getActivity());
         }
         ensureConsistencyOfAutoCorrectionSettings();
-        updateVoiceModeSummary();
         updateShowCorrectionSuggestionsSummary();
         updateKeyPreviewPopupDelaySummary();
         refreshEnablingsOfKeypressSoundAndVibrationSettings(prefs, getResources());
@@ -330,12 +328,6 @@
         lp.setSummary(entries[lp.findIndexOfValue(lp.getValue())]);
     }
 
-    private void updateVoiceModeSummary() {
-        mVoicePreference.setSummary(
-                getResources().getStringArray(R.array.voice_input_modes_summary)
-                        [mVoicePreference.findIndexOfValue(mVoicePreference.getValue())]);
-    }
-
     private void refreshEnablingsOfKeypressSoundAndVibrationSettings(
             final SharedPreferences sp, final Resources res) {
         setPreferenceEnabled(Settings.PREF_VIBRATION_DURATION_SETTINGS,
diff --git a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
index a25cf62..ee322e9 100644
--- a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
+++ b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
@@ -22,6 +22,7 @@
 import android.util.Log;
 import android.view.inputmethod.EditorInfo;
 
+import com.android.inputmethod.annotations.UsedForTesting;
 import com.android.inputmethod.keyboard.internal.KeySpecParser;
 import com.android.inputmethod.latin.Dictionary;
 import com.android.inputmethod.latin.InputAttributes;
@@ -56,13 +57,14 @@
     public final SuggestedWords mSuggestPuncList;
     public final String mWordSeparators;
     public final CharSequence mHintToSaveText;
+    public final boolean mCurrentLanguageHasSpaces;
 
     // From preferences, in the same order as xml/prefs.xml:
     public final boolean mAutoCap;
     public final boolean mVibrateOn;
     public final boolean mSoundOn;
     public final boolean mKeyPreviewPopupOn;
-    private final String mVoiceMode;
+    private final boolean mShowsVoiceInputKey;
     public final boolean mIncludesOtherImesInLanguageSwitchList;
     public final boolean mShowsLanguageSwitchKey;
     public final boolean mUseContactsDict;
@@ -74,6 +76,7 @@
     public final boolean mGestureTrailEnabled;
     public final boolean mGestureFloatingPreviewTextEnabled;
     public final boolean mSlidingKeyInputPreviewEnabled;
+    public final boolean mPhraseGestureEnabled;
     public final int mKeyLongpressTimeout;
     public final Locale mLocale;
 
@@ -88,8 +91,8 @@
     public final float mAutoCorrectionThreshold;
     public final boolean mCorrectionEnabled;
     public final int mSuggestionVisibility;
-    private final boolean mVoiceKeyEnabled;
-    private final boolean mVoiceKeyOnMain;
+    public final boolean mBoostPersonalizationDictionaryForDebug;
+    public final boolean mUseOnlyPersonalizationDictionaryForDebug;
 
     // Setting values for additional features
     public final int[] mAdditionalFeaturesSettingValues =
@@ -117,6 +120,7 @@
         mSuggestPuncList = createSuggestPuncList(suggestPuncsSpec);
         mWordSeparators = res.getString(R.string.symbols_word_separators);
         mHintToSaveText = res.getText(R.string.hint_add_to_dictionary);
+        mCurrentLanguageHasSpaces = res.getBoolean(R.bool.current_language_has_spaces);
 
         // Store the input attributes
         if (null == inputAttributes) {
@@ -132,9 +136,7 @@
         mKeyPreviewPopupOn = Settings.readKeyPreviewPopupEnabled(prefs, res);
         mSlidingKeyInputPreviewEnabled = prefs.getBoolean(
                 Settings.PREF_SLIDING_KEY_INPUT_PREVIEW, true);
-        final String voiceModeMain = res.getString(R.string.voice_mode_main);
-        final String voiceModeOff = res.getString(R.string.voice_mode_off);
-        mVoiceMode = prefs.getString(Settings.PREF_VOICE_MODE, voiceModeMain);
+        mShowsVoiceInputKey = needsToShowVoiceInputKey(prefs, res);
         final String autoCorrectionThresholdRawValue = prefs.getString(
                 Settings.PREF_AUTO_CORRECTION_THRESHOLD,
                 res.getString(R.string.auto_correction_threshold_mode_index_modest));
@@ -154,12 +156,11 @@
         mKeyPreviewPopupDismissDelay = Settings.readKeyPreviewPopupDismissDelay(prefs, res);
         mAutoCorrectionThreshold = readAutoCorrectionThreshold(res,
                 autoCorrectionThresholdRawValue);
-        mVoiceKeyEnabled = mVoiceMode != null && !mVoiceMode.equals(voiceModeOff);
-        mVoiceKeyOnMain = mVoiceMode != null && mVoiceMode.equals(voiceModeMain);
         mGestureInputEnabled = Settings.readGestureInputEnabled(prefs, res);
         mGestureTrailEnabled = prefs.getBoolean(Settings.PREF_GESTURE_PREVIEW_TRAIL, true);
         mGestureFloatingPreviewTextEnabled = prefs.getBoolean(
                 Settings.PREF_GESTURE_FLOATING_PREVIEW_TEXT, true);
+        mPhraseGestureEnabled = Settings.readPhraseGestureEnabled(prefs, res);
         mCorrectionEnabled = mAutoCorrectEnabled && !mInputAttributes.mInputTypeNoAutoCorrect;
         final String showSuggestionsSetting = prefs.getString(
                 Settings.PREF_SHOW_SUGGESTIONS_SETTING,
@@ -168,6 +169,61 @@
         AdditionalFeaturesSettingUtils.readAdditionalFeaturesPreferencesIntoArray(
                 prefs, mAdditionalFeaturesSettingValues);
         mIsInternal = Settings.isInternal(prefs);
+        mBoostPersonalizationDictionaryForDebug =
+                Settings.readBoostPersonalizationDictionaryForDebug(prefs);
+        mUseOnlyPersonalizationDictionaryForDebug =
+                Settings.readUseOnlyPersonalizationDictionaryForDebug(prefs);
+    }
+
+    // Only for tests
+    private SettingsValues(final Locale locale) {
+        // TODO: locale is saved, but not used yet. May have to change this if tests require.
+        mLocale = locale;
+        mDelayUpdateOldSuggestions = 0;
+        mSymbolsPrecededBySpace = new int[] { '(', '[', '{', '&' };
+        Arrays.sort(mSymbolsPrecededBySpace);
+        mSymbolsFollowedBySpace = new int[] { '.', ',', ';', ':', '!', '?', ')', ']', '}', '&' };
+        Arrays.sort(mSymbolsFollowedBySpace);
+        mWordConnectors = new int[] { '\'', '-' };
+        Arrays.sort(mWordConnectors);
+        final String[] suggestPuncsSpec = new String[] { "!", "?", ",", ":", ";" };
+        mSuggestPuncList = createSuggestPuncList(suggestPuncsSpec);
+        mWordSeparators = "&\t \n()[]{}*&<>+=|.,;:!?/_\"";
+        mHintToSaveText = "Touch again to save";
+        mCurrentLanguageHasSpaces = true;
+        mInputAttributes = new InputAttributes(null, false /* isFullscreenMode */);
+        mAutoCap = true;
+        mVibrateOn = true;
+        mSoundOn = true;
+        mKeyPreviewPopupOn = true;
+        mSlidingKeyInputPreviewEnabled = true;
+        mShowsVoiceInputKey = true;
+        mIncludesOtherImesInLanguageSwitchList = false;
+        mShowsLanguageSwitchKey = true;
+        mUseContactsDict = true;
+        mUseDoubleSpacePeriod = true;
+        mBlockPotentiallyOffensive = true;
+        mAutoCorrectEnabled = true;
+        mBigramPredictionEnabled = true;
+        mKeyLongpressTimeout = 300;
+        mKeypressVibrationDuration = 5;
+        mKeypressSoundVolume = 1;
+        mKeyPreviewPopupDismissDelay = 70;
+        mAutoCorrectionThreshold = 1;
+        mGestureInputEnabled = true;
+        mGestureTrailEnabled = true;
+        mGestureFloatingPreviewTextEnabled = true;
+        mPhraseGestureEnabled = true;
+        mCorrectionEnabled = mAutoCorrectEnabled && !mInputAttributes.mInputTypeNoAutoCorrect;
+        mSuggestionVisibility = 0;
+        mIsInternal = false;
+        mBoostPersonalizationDictionaryForDebug = false;
+        mUseOnlyPersonalizationDictionaryForDebug = false;
+    }
+
+    @UsedForTesting
+    public static SettingsValues makeDummySettingsValuesForTest(final Locale locale) {
+        return new SettingsValues(locale);
     }
 
     public boolean isApplicationSpecifiedCompletionsOn() {
@@ -194,6 +250,10 @@
         return Arrays.binarySearch(mWordConnectors, code) >= 0;
     }
 
+    public boolean isWordCodePoint(final int code) {
+        return Character.isLetter(code) || isWordConnector(code);
+    }
+
     public boolean isUsuallyPrecededBySpace(final int code) {
         return Arrays.binarySearch(mSymbolsPrecededBySpace, code) >= 0;
     }
@@ -209,14 +269,10 @@
     public boolean isVoiceKeyEnabled(final EditorInfo editorInfo) {
         final boolean shortcutImeEnabled = SubtypeSwitcher.getInstance().isShortcutImeEnabled();
         final int inputType = (editorInfo != null) ? editorInfo.inputType : 0;
-        return shortcutImeEnabled && mVoiceKeyEnabled
+        return shortcutImeEnabled && mShowsVoiceInputKey
                 && !InputTypeUtils.isPasswordInputType(inputType);
     }
 
-    public boolean isVoiceKeyOnMain() {
-        return mVoiceKeyOnMain;
-    }
-
     public boolean isLanguageSwitchKeyEnabled() {
         if (!mShowsLanguageSwitchKey) {
             return false;
@@ -241,7 +297,9 @@
                 // TODO: Stop using KeySpceParser.getLabel().
                 puncList.add(new SuggestedWordInfo(KeySpecParser.getLabel(puncSpec),
                         SuggestedWordInfo.MAX_SCORE, SuggestedWordInfo.KIND_HARDCODED,
-                        Dictionary.TYPE_HARDCODED));
+                        Dictionary.DICTIONARY_HARDCODED,
+                        SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */,
+                        SuggestedWordInfo.NOT_A_CONFIDENCE /* autoCommitFirstWordConfidence */));
             }
         }
         return new SuggestedWords(puncList,
@@ -306,4 +364,18 @@
         }
         return autoCorrectionThreshold;
     }
+
+    private static boolean needsToShowVoiceInputKey(SharedPreferences prefs, Resources res) {
+        final String voiceModeMain = res.getString(R.string.voice_mode_main);
+        final String voiceMode = prefs.getString(Settings.PREF_VOICE_MODE_OBSOLETE, voiceModeMain);
+        final boolean showsVoiceInputKey = voiceMode == null || voiceMode.equals(voiceModeMain);
+        if (!showsVoiceInputKey) {
+            // Migrate settings from PREF_VOICE_MODE_OBSOLETE to PREF_VOICE_INPUT_KEY
+            // Set voiceModeMain as a value of obsolete voice mode settings.
+            prefs.edit().putString(Settings.PREF_VOICE_MODE_OBSOLETE, voiceModeMain).apply();
+            // Disable voice input key.
+            prefs.edit().putBoolean(Settings.PREF_VOICE_INPUT_KEY, false).apply();
+        }
+        return prefs.getBoolean(Settings.PREF_VOICE_INPUT_KEY, true);
+    }
 }
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
index 6719e98..69f9a46 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
@@ -301,12 +301,14 @@
                 // TODO: make a spell checker option to block offensive words or not
                 final ArrayList<SuggestedWordInfo> suggestions =
                         dictInfo.mDictionary.getSuggestions(composer, prevWord,
-                                dictInfo.getProximityInfo(),
-                                true /* blockOffensiveWords */);
-                for (final SuggestedWordInfo suggestion : suggestions) {
-                    final String suggestionStr = suggestion.mWord;
-                    suggestionsGatherer.addWord(suggestionStr.toCharArray(), null, 0,
-                            suggestionStr.length(), suggestion.mScore);
+                                dictInfo.getProximityInfo(), true /* blockOffensiveWords */,
+                                null /* additionalFeaturesOptions */);
+                if (suggestions != null) {
+                    for (final SuggestedWordInfo suggestion : suggestions) {
+                        final String suggestionStr = suggestion.mWord;
+                        suggestionsGatherer.addWord(suggestionStr.toCharArray(), null, 0,
+                                suggestionStr.length(), suggestion.mScore);
+                    }
                 }
                 isInDict = isInDictForAnyCapitalization(dictInfo.mDictionary, text, capitalizeType);
             } finally {
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java b/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java
index ac8f687..a0aed28 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java
@@ -52,7 +52,7 @@
                 @Override
                 public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
                         final String prevWord, final ProximityInfo proximityInfo,
-                        final boolean blockOffensiveWords) {
+                        final boolean blockOffensiveWords, final int[] additionalFeaturesOptions) {
                     return noSuggestions;
                 }
                 @Override
diff --git a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
index e97069d..acd4745 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
@@ -210,7 +210,8 @@
                 final int indexInMoreSuggestions = index + SUGGESTION_CODE_BASE;
                 final Key key = new Key(
                         params, word, info, KeyboardIconsSet.ICON_UNDEFINED, indexInMoreSuggestions,
-                        null, x, y, width, params.mDefaultRowHeight, 0);
+                        null /* outputText */, x, y, width, params.mDefaultRowHeight,
+                        0 /* labelFlags */, Key.BACKGROUND_TYPE_NORMAL);
                 params.markAsEdgeKey(key, index);
                 params.onAddKey(key);
                 final int columnNumber = params.getColumnNumber(index);
diff --git a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java
index d585b5c..0ebe377 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java
@@ -34,7 +34,7 @@
     private static final String TAG = MoreSuggestionsView.class.getSimpleName();
 
     public MoreSuggestionsView(final Context context, final AttributeSet attrs) {
-        this(context, attrs, R.attr.moreSuggestionsViewStyle);
+        this(context, attrs, R.attr.moreKeysKeyboardViewStyle);
     }
 
     public MoreSuggestionsView(final Context context, final AttributeSet attrs,
diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java
index 1dd04fc..8d2689a 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java
@@ -122,27 +122,15 @@
         mSuggestionsStripHeight = res.getDimensionPixelSize(R.dimen.suggestions_strip_height);
 
         final TypedArray a = context.obtainStyledAttributes(attrs,
-                R.styleable.SuggestionStripView, defStyle, R.style.SuggestionStripViewStyle);
+                R.styleable.SuggestionStripView, defStyle, R.style.SuggestionStripView);
         mSuggestionStripOption = a.getInt(
                 R.styleable.SuggestionStripView_suggestionStripOption, 0);
-        final float alphaValidTypedWord = ResourceUtils.getFraction(a,
-                R.styleable.SuggestionStripView_alphaValidTypedWord, 1.0f);
-        final float alphaTypedWord = ResourceUtils.getFraction(a,
-                R.styleable.SuggestionStripView_alphaTypedWord, 1.0f);
-        final float alphaAutoCorrect = ResourceUtils.getFraction(a,
-                R.styleable.SuggestionStripView_alphaAutoCorrect, 1.0f);
-        final float alphaSuggested = ResourceUtils.getFraction(a,
-                R.styleable.SuggestionStripView_alphaSuggested, 1.0f);
         mAlphaObsoleted = ResourceUtils.getFraction(a,
-                R.styleable.SuggestionStripView_alphaSuggested, 1.0f);
-        mColorValidTypedWord = applyAlpha(a.getColor(
-                R.styleable.SuggestionStripView_colorValidTypedWord, 0), alphaValidTypedWord);
-        mColorTypedWord = applyAlpha(a.getColor(
-                R.styleable.SuggestionStripView_colorTypedWord, 0), alphaTypedWord);
-        mColorAutoCorrect = applyAlpha(a.getColor(
-                R.styleable.SuggestionStripView_colorAutoCorrect, 0), alphaAutoCorrect);
-        mColorSuggested = applyAlpha(a.getColor(
-                R.styleable.SuggestionStripView_colorSuggested, 0), alphaSuggested);
+                R.styleable.SuggestionStripView_alphaObsoleted, 1.0f);
+        mColorValidTypedWord = a.getColor(R.styleable.SuggestionStripView_colorValidTypedWord, 0);
+        mColorTypedWord = a.getColor(R.styleable.SuggestionStripView_colorTypedWord, 0);
+        mColorAutoCorrect = a.getColor(R.styleable.SuggestionStripView_colorAutoCorrect, 0);
+        mColorSuggested = a.getColor(R.styleable.SuggestionStripView_colorSuggested, 0);
         mSuggestionsCountInStrip = a.getInt(
                 R.styleable.SuggestionStripView_suggestionsCountInStrip,
                 DEFAULT_SUGGESTIONS_COUNT_IN_STRIP);
diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
index a8a14a8..75f17c5 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
@@ -198,7 +198,7 @@
 
     @Override
     public boolean onLongClick(final View view) {
-        AudioAndHapticFeedbackManager.getInstance().hapticAndAudioFeedback(
+        AudioAndHapticFeedbackManager.getInstance().performHapticAndAudioFeedback(
                 Constants.NOT_A_CODE, this);
         return showMoreSuggestions();
     }
diff --git a/java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java b/java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java
index 215faa0..44b2016 100644
--- a/java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java
@@ -25,6 +25,7 @@
 import android.text.TextUtils;
 import android.view.inputmethod.InputMethodSubtype;
 
+import com.android.inputmethod.latin.Constants;
 import com.android.inputmethod.latin.R;
 
 import java.util.ArrayList;
@@ -60,9 +61,10 @@
                 StringUtils.appendToCommaSplittableTextIfNotExists(
                         IS_ADDITIONAL_SUBTYPE, layoutDisplayNameExtraValue);
         final int nameId = SubtypeLocaleUtils.getSubtypeNameId(localeString, keyboardLayoutSetName);
-        return new InputMethodSubtype(nameId, R.drawable.ic_subtype_keyboard,
-                localeString, KEYBOARD_MODE,
-                layoutExtraValue + "," + additionalSubtypeExtraValue, false, false);
+        return new InputMethodSubtype(nameId, R.drawable.ic_ime_switcher_dark,
+                localeString, KEYBOARD_MODE, layoutExtraValue + "," + additionalSubtypeExtraValue
+                        + "," + Constants.Subtype.ExtraValue.ASCII_CAPABLE
+                        + "," + Constants.Subtype.ExtraValue.EMOJI_CAPABLE, false, false);
     }
 
     public static String getPrefSubtype(final InputMethodSubtype subtype) {
diff --git a/java/src/com/android/inputmethod/latin/utils/AsyncResultHolder.java b/java/src/com/android/inputmethod/latin/utils/AsyncResultHolder.java
new file mode 100644
index 0000000..c2e97a3
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/utils/AsyncResultHolder.java
@@ -0,0 +1,71 @@
+/*
+ * 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.utils;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * This class is a holder of a result of asynchronous computation.
+ *
+ * @param <E> the type of the result.
+ */
+public class AsyncResultHolder<E> {
+
+    private final Object mLock = new Object();
+
+    private E mResult;
+    private final CountDownLatch mLatch;
+
+    public AsyncResultHolder() {
+        mLatch = new CountDownLatch(1);
+    }
+
+    /**
+     * Sets the result value to this holder.
+     *
+     * @param result the value which is set.
+     */
+    public void set(final E result) {
+        synchronized(mLock) {
+            if (mLatch.getCount() > 0) {
+                mResult = result;
+                mLatch.countDown();
+            }
+        }
+    }
+
+    /**
+     * Gets the result value held in this holder.
+     * Causes the current thread to wait unless the value is set or the specified time is elapsed.
+     *
+     * @param defaultValue the default value.
+     * @param timeOut the time to wait.
+     * @return if the result is set until the time limit then the result, otherwise defaultValue.
+     */
+    public E get(final E defaultValue, final long timeOut) {
+        try {
+            if(mLatch.await(timeOut, TimeUnit.MILLISECONDS)) {
+                return mResult;
+            } else {
+                return defaultValue;
+            }
+        } catch (InterruptedException e) {
+            return defaultValue;
+        }
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/utils/ByteArrayWrapper.java b/java/src/com/android/inputmethod/latin/utils/ByteArrayDictBuffer.java
similarity index 89%
rename from java/src/com/android/inputmethod/latin/utils/ByteArrayWrapper.java
rename to java/src/com/android/inputmethod/latin/utils/ByteArrayDictBuffer.java
index 1bb27aa..2028298 100644
--- a/java/src/com/android/inputmethod/latin/utils/ByteArrayWrapper.java
+++ b/java/src/com/android/inputmethod/latin/utils/ByteArrayDictBuffer.java
@@ -16,17 +16,17 @@
 
 package com.android.inputmethod.latin.utils;
 
-import com.android.inputmethod.latin.makedict.BinaryDictInputOutput.FusionDictionaryBufferInterface;
+import com.android.inputmethod.latin.makedict.BinaryDictDecoderUtils.DictBuffer;
 
 /**
  * This class provides an implementation for the FusionDictionary buffer interface that is backed
  * by a simpled byte array. It allows to create a binary dictionary in memory.
  */
-public final class ByteArrayWrapper implements FusionDictionaryBufferInterface {
+public final class ByteArrayDictBuffer implements DictBuffer {
     private byte[] mBuffer;
     private int mPosition;
 
-    public ByteArrayWrapper(final byte[] buffer) {
+    public ByteArrayDictBuffer(final byte[] buffer) {
         mBuffer = buffer;
         mPosition = 0;
     }
diff --git a/java/src/com/android/inputmethod/latin/utils/CapsModeUtils.java b/java/src/com/android/inputmethod/latin/utils/CapsModeUtils.java
index 2f91c57..60b24d5 100644
--- a/java/src/com/android/inputmethod/latin/utils/CapsModeUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/CapsModeUtils.java
@@ -60,6 +60,11 @@
                 || WordComposer.CAPS_MODE_AUTO_SHIFT_LOCKED == mode;
     }
 
+    private static boolean isPeriod(final int codePoint) {
+        // TODO: make this a resource.
+        return codePoint == Constants.CODE_PERIOD || codePoint == Constants.CODE_ARMENIAN_PERIOD;
+    }
+
     /**
      * Determine what caps mode should be in effect at the current offset in
      * the text. Only the mode bits set in <var>reqModes</var> will be
@@ -190,7 +195,7 @@
         if (c == Constants.CODE_QUESTION_MARK || c == Constants.CODE_EXCLAMATION_MARK) {
             return (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_SENTENCES) & reqModes;
         }
-        if (c != Constants.CODE_PERIOD || j <= 0) {
+        if (!isPeriod(c) || j <= 0) {
             return (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_WORDS) & reqModes;
         }
 
@@ -240,7 +245,7 @@
             case WORD:
                 if (Character.isLetter(c)) {
                     state = WORD;
-                } else if (c == Constants.CODE_PERIOD) {
+                } else if (isPeriod(c)) {
                     state = PERIOD;
                 } else {
                     return caps;
@@ -256,7 +261,7 @@
             case LETTER:
                 if (Character.isLetter(c)) {
                     state = LETTER;
-                } else if (c == Constants.CODE_PERIOD) {
+                } else if (isPeriod(c)) {
                     state = PERIOD;
                 } else {
                     return noCaps;
diff --git a/java/src/com/android/inputmethod/latin/utils/CollectionUtils.java b/java/src/com/android/inputmethod/latin/utils/CollectionUtils.java
index 98f0d8b..cc25102 100644
--- a/java/src/com/android/inputmethod/latin/utils/CollectionUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/CollectionUtils.java
@@ -18,6 +18,7 @@
 
 import android.util.SparseArray;
 
+import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -94,6 +95,10 @@
         return new CopyOnWriteArrayList<E>(array);
     }
 
+    public static <E> ArrayDeque<E> newArrayDeque() {
+        return new ArrayDeque<E>();
+    }
+
     public static <E> SparseArray<E> newSparseArray() {
         return new SparseArray<E>();
     }
diff --git a/java/src/com/android/inputmethod/latin/utils/DebugLogUtils.java b/java/src/com/android/inputmethod/latin/utils/DebugLogUtils.java
index c4ead0a..ac654fa 100644
--- a/java/src/com/android/inputmethod/latin/utils/DebugLogUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/DebugLogUtils.java
@@ -65,12 +65,12 @@
 
     /**
      * Get the stack trace contained in an exception as a human-readable string.
-     * @param e the exception
+     * @param t the throwable
      * @return the human-readable stack trace
      */
-    public static String getStackTrace(final Exception e) {
+    public static String getStackTrace(final Throwable t) {
         final StringBuilder sb = new StringBuilder();
-        final StackTraceElement[] frames = e.getStackTrace();
+        final StackTraceElement[] frames = t.getStackTrace();
         for (int j = 0; j < frames.length; ++j) {
             sb.append(frames[j].toString() + "\n");
         }
diff --git a/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java b/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java
index 34eccd6..021bf08 100644
--- a/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java
@@ -27,10 +27,8 @@
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.makedict.BinaryDictIOUtils;
 import com.android.inputmethod.latin.makedict.FormatSpec.FileHeader;
-import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
 
 import java.io.File;
-import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.Locale;
@@ -281,13 +279,7 @@
     }
 
     public static FileHeader getDictionaryFileHeaderOrNull(final File file) {
-        try {
-            return BinaryDictIOUtils.getDictionaryFileHeader(file, 0, file.length());
-        } catch (UnsupportedFormatException e) {
-            return null;
-        } catch (IOException e) {
-            return null;
-        }
+        return BinaryDictIOUtils.getDictionaryFileHeaderOrNull(file, 0, file.length());
     }
 
     private static DictionaryInfo createDictionaryInfoFromFileAddress(
diff --git a/java/src/com/android/inputmethod/latin/utils/PositionalInfoForUserDictPendingAddition.java b/java/src/com/android/inputmethod/latin/utils/PositionalInfoForUserDictPendingAddition.java
deleted file mode 100644
index 1fc7ecc..0000000
--- a/java/src/com/android/inputmethod/latin/utils/PositionalInfoForUserDictPendingAddition.java
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.latin.utils;
-
-import android.view.inputmethod.EditorInfo;
-
-import com.android.inputmethod.latin.RichInputConnection;
-
-import java.util.Locale;
-
-/**
- * Holder class for data about a word already committed but that may still be edited.
- *
- * When the user chooses to add a word to the user dictionary by pressing the appropriate
- * suggestion, a dialog is presented to give a chance to edit the word before it is actually
- * registered as a user dictionary word. If the word is actually modified, the IME needs to
- * go back and replace the word that was committed with the amended version.
- * The word we need to replace with will only be known after it's actually committed, so
- * the IME needs to take a note of what it has to replace and where it is.
- * This class encapsulates this data.
- */
-public final class PositionalInfoForUserDictPendingAddition {
-    final private String mOriginalWord;
-    final private int mCursorPos; // Position of the cursor after the word
-    final private EditorInfo mEditorInfo; // On what binding this has been added
-    final private int mCapitalizedMode;
-    private String mActualWordBeingAdded;
-
-    public PositionalInfoForUserDictPendingAddition(final String word, final int cursorPos,
-            final EditorInfo editorInfo, final int capitalizedMode) {
-        mOriginalWord = word;
-        mCursorPos = cursorPos;
-        mEditorInfo = editorInfo;
-        mCapitalizedMode = capitalizedMode;
-    }
-
-    public void setActualWordBeingAdded(final String actualWordBeingAdded) {
-        mActualWordBeingAdded = actualWordBeingAdded;
-    }
-
-    /**
-     * Try to replace the string at the remembered position with the actual word being added.
-     *
-     * After the user validated the word being added, the IME has to replace the old version
-     * (which has been committed in the text view) with the amended version if it's different.
-     * This method tries to do that, but may fail because the IME is not yet ready to do so -
-     * for example, it is still waiting for the new string, or it is waiting to return to the text
-     * view in which the amendment should be made. In these cases, we should keep the data
-     * and wait until all conditions are met.
-     * This method returns true if the replacement has been successfully made and this data
-     * can be forgotten; it returns false if the replacement can't be made yet and we need to
-     * keep this until a later time.
-     * The IME knows about the actual word being added through a callback called by the
-     * user dictionary facility of the device. When this callback comes, the keyboard may still
-     * be connected to the edition dialog, or it may have already returned to the original text
-     * field. Replacement has to work in both cases.
-     * Accordingly, this method is called at two different points in time : upon getting the
-     * event that a new word was added to the user dictionary, and upon starting up in a
-     * new text field.
-     * @param connection The RichInputConnection through which to contact the editor.
-     * @param editorInfo Information pertaining to the editor we are currently in.
-     * @param currentCursorPosition The current cursor position, for checking purposes.
-     * @param locale The locale for changing case, if necessary
-     * @return true if the edit has been successfully made, false if we need to try again later
-     */
-    public boolean tryReplaceWithActualWord(final RichInputConnection connection,
-            final EditorInfo editorInfo, final int currentCursorPosition, final Locale locale) {
-        // If we still don't know the actual word being added, we need to try again later.
-        if (null == mActualWordBeingAdded) return false;
-        // The entered text and the registered text were the same anyway : we can
-        // return success right away even if focus has not returned yet to the text field we
-        // want to amend.
-        if (mActualWordBeingAdded.equals(mOriginalWord)) return true;
-        // Not the same text field : we need to try again later. This happens when the addition
-        // is reported by the user dictionary provider before the focus has moved back to the
-        // original text view, so the IME is still in the text view of the dialog and has no way to
-        // edit the original text view at this time.
-        if (!mEditorInfo.packageName.equals(editorInfo.packageName)
-                || mEditorInfo.fieldId != editorInfo.fieldId) {
-            return false;
-        }
-        // Same text field, but not the same cursor position : we give up, so we return success
-        // so that it won't be tried again
-        if (currentCursorPosition != mCursorPos) return true;
-        // We have made all the checks : do the replacement and report success
-        // If this was auto-capitalized, we need to restore the case before committing
-        final String wordWithCaseFixed = CapsModeUtils.applyAutoCapsMode(mActualWordBeingAdded,
-                mCapitalizedMode, locale);
-        connection.setComposingRegion(currentCursorPosition - mOriginalWord.length(),
-                currentCursorPosition);
-        connection.commitText(wordWithCaseFixed, wordWithCaseFixed.length());
-        return true;
-    }
-}
diff --git a/java/src/com/android/inputmethod/latin/utils/PrioritizedSerialExecutor.java b/java/src/com/android/inputmethod/latin/utils/PrioritizedSerialExecutor.java
new file mode 100644
index 0000000..5dc0b58
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/utils/PrioritizedSerialExecutor.java
@@ -0,0 +1,147 @@
+/*
+ * 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.utils;
+
+import java.util.ArrayDeque;
+import java.util.Queue;
+
+/**
+ * An object that executes submitted tasks using a thread.
+ */
+public class PrioritizedSerialExecutor {
+    public static final String TAG = PrioritizedSerialExecutor.class.getSimpleName();
+
+    private final Object mLock = new Object();
+
+    // The default value of capacities of task queues.
+    private static final int TASK_QUEUE_CAPACITY = 1000;
+    private final Queue<Runnable> mTasks;
+    private final Queue<Runnable> mPrioritizedTasks;
+    private boolean mIsShutdown;
+
+    // The task which is running now.
+    private Runnable mActive;
+
+    public PrioritizedSerialExecutor() {
+        mTasks = new ArrayDeque<Runnable>(TASK_QUEUE_CAPACITY);
+        mPrioritizedTasks = new ArrayDeque<Runnable>(TASK_QUEUE_CAPACITY);
+        mIsShutdown = false;
+    }
+
+    /**
+     * Clears all queued tasks.
+     */
+    public void clearAllTasks() {
+        synchronized(mLock) {
+            mTasks.clear();
+            mPrioritizedTasks.clear();
+        }
+    }
+
+    /**
+     * Enqueues the given task into the task queue.
+     * @param r the enqueued task
+     */
+    public void execute(final Runnable r) {
+        synchronized(mLock) {
+            if (!mIsShutdown) {
+                mTasks.offer(r);
+                if (mActive == null) {
+                    scheduleNext();
+                }
+            }
+        }
+    }
+
+    /**
+     * Enqueues the given task into the prioritized task queue.
+     * @param r the enqueued task
+     */
+    public void executePrioritized(final Runnable r) {
+        synchronized(mLock) {
+            if (!mIsShutdown) {
+                mPrioritizedTasks.offer(r);
+                if (mActive ==  null) {
+                    scheduleNext();
+                }
+            }
+        }
+    }
+
+    private boolean fetchNextTasks() {
+        synchronized(mLock) {
+            mActive = mPrioritizedTasks.poll();
+            if (mActive == null) {
+                mActive = mTasks.poll();
+            }
+            return mActive != null;
+        }
+    }
+
+    private void scheduleNext() {
+        synchronized(mLock) {
+            if (!fetchNextTasks()) {
+                return;
+            }
+            new Thread(new Runnable() {
+                @Override
+                public void run() {
+                    try {
+                        do {
+                            synchronized(mLock) {
+                                if (mActive != null) {
+                                    mActive.run();
+                                }
+                            }
+                        } while (fetchNextTasks());
+                    } finally {
+                        scheduleNext();
+                    }
+                }
+            }).start();
+        }
+    }
+
+    public void remove(final Runnable r) {
+        synchronized(mLock) {
+            mTasks.remove(r);
+            mPrioritizedTasks.remove(r);
+        }
+    }
+
+    public void replaceAndExecute(final Runnable oldTask, final Runnable newTask) {
+        synchronized(mLock) {
+            if (oldTask != null) remove(oldTask);
+            execute(newTask);
+        }
+    }
+
+    public void shutdown() {
+        synchronized(mLock) {
+            mIsShutdown = true;
+        }
+    }
+
+    public boolean isTerminated() {
+        synchronized(mLock) {
+            if (!mIsShutdown) {
+                return false;
+            }
+            return mPrioritizedTasks.isEmpty() && mTasks.isEmpty() && mActive == null;
+        }
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/utils/ResizableIntArray.java b/java/src/com/android/inputmethod/latin/utils/ResizableIntArray.java
index 4c7739a..7c6fe93 100644
--- a/java/src/com/android/inputmethod/latin/utils/ResizableIntArray.java
+++ b/java/src/com/android/inputmethod/latin/utils/ResizableIntArray.java
@@ -132,6 +132,15 @@
         }
     }
 
+    /**
+     * Shift to the left by elementCount, discarding elementCount pointers at the start.
+     * @param elementCount how many elements to shift.
+     */
+    public void shift(final int elementCount) {
+        System.arraycopy(mArray, elementCount, mArray, 0, mLength - elementCount);
+        mLength -= elementCount;
+    }
+
     @Override
     public String toString() {
         final StringBuilder sb = new StringBuilder();
diff --git a/java/src/com/android/inputmethod/latin/utils/StringUtils.java b/java/src/com/android/inputmethod/latin/utils/StringUtils.java
index 7406d85..327780a 100644
--- a/java/src/com/android/inputmethod/latin/utils/StringUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/StringUtils.java
@@ -16,14 +16,30 @@
 
 package com.android.inputmethod.latin.utils;
 
-import android.text.TextUtils;
-
+import com.android.inputmethod.annotations.UsedForTesting;
 import com.android.inputmethod.latin.Constants;
+import com.android.inputmethod.latin.settings.SettingsValues;
 
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.Spanned;
+import android.text.SpannedString;
+import android.text.TextUtils;
+import android.text.style.SuggestionSpan;
+import android.util.JsonReader;
+import android.util.JsonWriter;
+import android.util.Log;
+
+import java.io.IOException;
+import java.io.StringReader;
+import java.io.StringWriter;
 import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
 import java.util.Locale;
 
 public final class StringUtils {
+    private static final String TAG = StringUtils.class.getSimpleName();
     public static final int CAPITALIZE_NONE = 0;  // No caps, or mixed case
     public static final int CAPITALIZE_FIRST = 1; // First only
     public static final int CAPITALIZE_ALL = 2;   // All caps
@@ -193,27 +209,56 @@
     }
 
     public static boolean isIdenticalAfterUpcase(final String text) {
-        final int len = text.length();
-        for (int i = 0; i < len; i = text.offsetByCodePoints(i, 1)) {
+        final int length = text.length();
+        int i = 0;
+        while (i < length) {
             final int codePoint = text.codePointAt(i);
             if (Character.isLetter(codePoint) && !Character.isUpperCase(codePoint)) {
                 return false;
             }
+            i += Character.charCount(codePoint);
         }
         return true;
     }
 
     public static boolean isIdenticalAfterDowncase(final String text) {
-        final int len = text.length();
-        for (int i = 0; i < len; i = text.offsetByCodePoints(i, 1)) {
+        final int length = text.length();
+        int i = 0;
+        while (i < length) {
             final int codePoint = text.codePointAt(i);
             if (Character.isLetter(codePoint) && !Character.isLowerCase(codePoint)) {
                 return false;
             }
+            i += Character.charCount(codePoint);
         }
         return true;
     }
 
+    @UsedForTesting
+    public static boolean looksValidForDictionaryInsertion(final CharSequence text,
+            final SettingsValues settings) {
+        if (TextUtils.isEmpty(text)) return false;
+        final int length = text.length();
+        int i = 0;
+        int digitCount = 0;
+        while (i < length) {
+            final int codePoint = Character.codePointAt(text, i);
+            final int charCount = Character.charCount(codePoint);
+            i += charCount;
+            if (Character.isDigit(codePoint)) {
+                // Count digits: see below
+                digitCount += charCount;
+                continue;
+            }
+            if (!settings.isWordCodePoint(codePoint)) return false;
+        }
+        // We reject strings entirely comprised of digits to avoid using PIN codes or credit
+        // card numbers. It would come in handy for word prediction though; a good example is
+        // when writing one's address where the street number is usually quite discriminative,
+        // as well as the postal code.
+        return digitCount < length;
+    }
+
     public static boolean isIdenticalAfterCapitalizeEachWord(final String text,
             final String separators) {
         boolean needCapsNext = true;
@@ -316,4 +361,194 @@
         // Otherwise, it doesn't look like an URL.
         return false;
     }
+
+    public static boolean isEmptyStringOrWhiteSpaces(String s) {
+        final int N = codePointCount(s);
+        for (int i = 0; i < N; ++i) {
+            if (!Character.isWhitespace(s.codePointAt(i))) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    @UsedForTesting
+    public static String byteArrayToHexString(byte[] bytes) {
+        if (bytes == null || bytes.length == 0) {
+            return "";
+        }
+        final StringBuilder sb = new StringBuilder();
+        for (byte b : bytes) {
+            sb.append(String.format("%02x", b & 0xff));
+        }
+        return sb.toString();
+    }
+
+    /**
+     * Convert hex string to byte array. The string length must be an even number.
+     */
+    @UsedForTesting
+    public static byte[] hexStringToByteArray(String hexString) {
+        if (TextUtils.isEmpty(hexString)) {
+            return null;
+        }
+        final int N = hexString.length();
+        if (N % 2 != 0) {
+            throw new NumberFormatException("Input hex string length must be an even number."
+                    + " Length = " + N);
+        }
+        final byte[] bytes = new byte[N / 2];
+        for (int i = 0; i < N; i += 2) {
+            bytes[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4)
+                    + Character.digit(hexString.charAt(i + 1), 16));
+        }
+        return bytes;
+    }
+
+    public static List<Object> jsonStrToList(String s) {
+        final ArrayList<Object> retval = CollectionUtils.newArrayList();
+        final JsonReader reader = new JsonReader(new StringReader(s));
+        try {
+            reader.beginArray();
+            while(reader.hasNext()) {
+                reader.beginObject();
+                while (reader.hasNext()) {
+                    final String name = reader.nextName();
+                    if (name.equals(Integer.class.getSimpleName())) {
+                        retval.add(reader.nextInt());
+                    } else if (name.equals(String.class.getSimpleName())) {
+                        retval.add(reader.nextString());
+                    } else {
+                        Log.w(TAG, "Invalid name: " + name);
+                        reader.skipValue();
+                    }
+                }
+                reader.endObject();
+            }
+            reader.endArray();
+            return retval;
+        } catch (IOException e) {
+        } finally {
+            try {
+                reader.close();
+            } catch (IOException e) {
+            }
+        }
+        return Collections.<Object>emptyList();
+    }
+
+    public static String listToJsonStr(List<Object> list) {
+        if (list == null || list.isEmpty()) {
+            return "";
+        }
+        final StringWriter sw = new StringWriter();
+        final JsonWriter writer = new JsonWriter(sw);
+        try {
+            writer.beginArray();
+            for (final Object o : list) {
+                writer.beginObject();
+                if (o instanceof Integer) {
+                    writer.name(Integer.class.getSimpleName()).value((Integer)o);
+                } else if (o instanceof String) {
+                    writer.name(String.class.getSimpleName()).value((String)o);
+                }
+                writer.endObject();
+            }
+            writer.endArray();
+            return sw.toString();
+        } catch (IOException e) {
+        } finally {
+            try {
+                if (writer != null) {
+                    writer.close();
+                }
+            } catch (IOException e) {
+            }
+        }
+        return "";
+    }
+
+    /**
+     * Copies the spans from the region <code>start...end</code> in
+     * <code>source</code> to the region
+     * <code>destoff...destoff+end-start</code> in <code>dest</code>.
+     * Spans in <code>source</code> that begin before <code>start</code>
+     * or end after <code>end</code> but overlap this range are trimmed
+     * as if they began at <code>start</code> or ended at <code>end</code>.
+     * Only SuggestionSpans that don't have the SPAN_PARAGRAPH span are copied.
+     *
+     * This code is almost entirely taken from {@link TextUtils#copySpansFrom}, except for the
+     * kind of span that is copied.
+     *
+     * @throws IndexOutOfBoundsException if any of the copied spans
+     * are out of range in <code>dest</code>.
+     */
+    public static void copyNonParagraphSuggestionSpansFrom(Spanned source, int start, int end,
+                                     Spannable dest, int destoff) {
+        Object[] spans = source.getSpans(start, end, SuggestionSpan.class);
+
+        for (int i = 0; i < spans.length; i++) {
+            int fl = source.getSpanFlags(spans[i]);
+            if (0 != (fl & Spannable.SPAN_PARAGRAPH)) continue;
+
+            int st = source.getSpanStart(spans[i]);
+            int en = source.getSpanEnd(spans[i]);
+
+            if (st < start)
+                st = start;
+            if (en > end)
+                en = end;
+
+            dest.setSpan(spans[i], st - start + destoff, en - start + destoff,
+                         fl);
+        }
+    }
+
+    /**
+     * Returns a CharSequence concatenating the specified CharSequences, retaining their
+     * SuggestionSpans that don't have the PARAGRAPH flag, but not other spans.
+     *
+     * This code is almost entirely taken from {@link TextUtils#concat(CharSequence...)}, except
+     * it calls copyNonParagraphSuggestionSpansFrom instead of {@link TextUtils#copySpansFrom}.
+     */
+    public static CharSequence concatWithNonParagraphSuggestionSpansOnly(CharSequence... text) {
+        if (text.length == 0) {
+            return "";
+        }
+
+        if (text.length == 1) {
+            return text[0];
+        }
+
+        boolean spanned = false;
+        for (int i = 0; i < text.length; i++) {
+            if (text[i] instanceof Spanned) {
+                spanned = true;
+                break;
+            }
+        }
+
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < text.length; i++) {
+            sb.append(text[i]);
+        }
+
+        if (!spanned) {
+            return sb.toString();
+        }
+
+        SpannableString ss = new SpannableString(sb);
+        int off = 0;
+        for (int i = 0; i < text.length; i++) {
+            int len = text[i].length();
+
+            if (text[i] instanceof Spanned) {
+                copyNonParagraphSuggestionSpansFrom((Spanned) text[i], 0, len, ss, off);
+            }
+
+            off += len;
+        }
+
+        return new SpannedString(ss);
+    }
 }
diff --git a/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java b/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java
index 1672809..102a41b 100644
--- a/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java
@@ -40,6 +40,7 @@
     // Special language code to represent "no language".
     public static final String NO_LANGUAGE = "zz";
     public static final String QWERTY = "qwerty";
+    public static final String EMOJI = "emoji";
     public static final int UNKNOWN_KEYBOARD_LAYOUT = R.string.subtype_generic;
 
     private static boolean sInitialized = false;
diff --git a/java/src/com/android/inputmethod/latin/utils/TextRange.java b/java/src/com/android/inputmethod/latin/utils/TextRange.java
index 5793e41..48b443d 100644
--- a/java/src/com/android/inputmethod/latin/utils/TextRange.java
+++ b/java/src/com/android/inputmethod/latin/utils/TextRange.java
@@ -40,6 +40,10 @@
         return mWordAtCursorEndIndex - mCursorIndex;
     }
 
+    public int length() {
+        return mWord.length();
+    }
+
     /**
      * Gets the suggestion spans that are put squarely on the word, with the exact start
      * and end of the span matching the boundaries of the word.
diff --git a/java/src/com/android/inputmethod/latin/utils/TypefaceUtils.java b/java/src/com/android/inputmethod/latin/utils/TypefaceUtils.java
index 544e4d2..47ea1ea 100644
--- a/java/src/com/android/inputmethod/latin/utils/TypefaceUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/TypefaceUtils.java
@@ -66,6 +66,11 @@
         }
     }
 
+    public static float getStringWidth(final String string, final Paint paint) {
+        paint.getTextBounds(string, 0, string.length(), sTextWidthBounds);
+        return sTextWidthBounds.width();
+    }
+
     private static int getCharGeometryCacheKey(final char referenceChar, final Paint paint) {
         final int labelSize = (int)paint.getTextSize();
         final Typeface face = paint.getTypeface();
diff --git a/java/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtils.java b/java/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtils.java
index d02f718..ea32a74 100644
--- a/java/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtils.java
@@ -20,20 +20,21 @@
 
 import com.android.inputmethod.annotations.UsedForTesting;
 import com.android.inputmethod.latin.makedict.BinaryDictIOUtils;
-import com.android.inputmethod.latin.makedict.BinaryDictInputOutput;
-import com.android.inputmethod.latin.makedict.BinaryDictInputOutput.FusionDictionaryBufferInterface;
+import com.android.inputmethod.latin.makedict.DictDecoder;
+import com.android.inputmethod.latin.makedict.DictEncoder;
 import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions;
 import com.android.inputmethod.latin.makedict.FusionDictionary;
-import com.android.inputmethod.latin.makedict.FusionDictionary.Node;
+import com.android.inputmethod.latin.makedict.FusionDictionary.PtNodeArray;
 import com.android.inputmethod.latin.makedict.PendingAttribute;
 import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
 import com.android.inputmethod.latin.personalization.UserHistoryDictionaryBigramList;
 
 import java.io.IOException;
-import java.io.OutputStream;
 import java.util.ArrayList;
 import java.util.HashMap;
-import java.util.Map;
+import java.util.Map.Entry;
+import java.util.TreeMap;
+import java.util.concurrent.TimeUnit;
 
 /**
  * Reads and writes Binary files for a UserHistoryDictionary.
@@ -43,6 +44,9 @@
 public final class UserHistoryDictIOUtils {
     private static final String TAG = UserHistoryDictIOUtils.class.getSimpleName();
     private static final boolean DEBUG = false;
+    private static final String USES_FORGETTING_CURVE_KEY = "USES_FORGETTING_CURVE";
+    private static final String USES_FORGETTING_CURVE_VALUE = "1";
+    private static final String LAST_UPDATED_TIME_KEY = "date";
 
     public interface OnAddWordListener {
         public void setUnigram(final String word, final String shortcutTarget, final int frequency);
@@ -57,12 +61,15 @@
     /**
      * Writes dictionary to file.
      */
-    public static void writeDictionaryBinary(final OutputStream destination,
+    public static void writeDictionary(final DictEncoder dictEncoder,
             final BigramDictionaryInterface dict, final UserHistoryDictionaryBigramList bigrams,
             final FormatOptions formatOptions) {
         final FusionDictionary fusionDict = constructFusionDictionary(dict, bigrams);
+        fusionDict.addOptionAttribute(USES_FORGETTING_CURVE_KEY, USES_FORGETTING_CURVE_VALUE);
+        fusionDict.addOptionAttribute(LAST_UPDATED_TIME_KEY,
+                String.valueOf(TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis())));
         try {
-            BinaryDictInputOutput.writeDictionaryBinary(destination, fusionDict, formatOptions);
+            dictEncoder.writeDictionary(fusionDict, formatOptions);
             Log.d(TAG, "end writing");
         } catch (IOException e) {
             Log.e(TAG, "IO exception while writing file", e);
@@ -77,7 +84,7 @@
     @UsedForTesting
     static FusionDictionary constructFusionDictionary(
             final BigramDictionaryInterface dict, final UserHistoryDictionaryBigramList bigrams) {
-        final FusionDictionary fusionDict = new FusionDictionary(new Node(),
+        final FusionDictionary fusionDict = new FusionDictionary(new PtNodeArray(),
                 new FusionDictionary.DictionaryOptions(new HashMap<String, String>(), false,
                         false));
         int profTotal = 0;
@@ -101,7 +108,7 @@
                 if (word1 == null) { // unigram
                     fusionDict.add(word2, freq, null, false /* isNotAWord */);
                 } else { // bigram
-                    if (FusionDictionary.findWordInTree(fusionDict.mRoot, word1) == null) {
+                    if (FusionDictionary.findWordInTree(fusionDict.mRootNodeArray, word1) == null) {
                         fusionDict.add(word1, 2, null, false /* isNotAWord */);
                     }
                     fusionDict.setBigram(word1, word2, freq);
@@ -118,14 +125,13 @@
     /**
      * Reads dictionary from file.
      */
-    public static void readDictionaryBinary(final FusionDictionaryBufferInterface buffer,
+    public static void readDictionaryBinary(final DictDecoder dictDecoder,
             final OnAddWordListener dict) {
-        final Map<Integer, String> unigrams = CollectionUtils.newTreeMap();
-        final Map<Integer, Integer> frequencies = CollectionUtils.newTreeMap();
-        final Map<Integer, ArrayList<PendingAttribute>> bigrams = CollectionUtils.newTreeMap();
+        final TreeMap<Integer, String> unigrams = CollectionUtils.newTreeMap();
+        final TreeMap<Integer, Integer> frequencies = CollectionUtils.newTreeMap();
+        final TreeMap<Integer, ArrayList<PendingAttribute>> bigrams = CollectionUtils.newTreeMap();
         try {
-            BinaryDictIOUtils.readUnigramsAndBigramsBinary(buffer, unigrams, frequencies,
-                    bigrams);
+            dictDecoder.readUnigramsAndBigramsBinary(unigrams, frequencies, bigrams);
         } catch (IOException e) {
             Log.e(TAG, "IO exception while reading file", e);
         } catch (UnsupportedFormatException e) {
@@ -140,10 +146,11 @@
      * Adds all unigrams and bigrams in maps to OnAddWordListener.
      */
     @UsedForTesting
-    static void addWordsFromWordMap(final Map<Integer, String> unigrams,
-            final Map<Integer, Integer> frequencies,
-            final Map<Integer, ArrayList<PendingAttribute>> bigrams, final OnAddWordListener to) {
-        for (Map.Entry<Integer, String> entry : unigrams.entrySet()) {
+    static void addWordsFromWordMap(final TreeMap<Integer, String> unigrams,
+            final TreeMap<Integer, Integer> frequencies,
+            final TreeMap<Integer, ArrayList<PendingAttribute>> bigrams,
+            final OnAddWordListener to) {
+        for (Entry<Integer, String> entry : unigrams.entrySet()) {
             final String word1 = entry.getValue();
             final int unigramFrequency = frequencies.get(entry.getKey());
             to.setUnigram(word1, null, unigramFrequency);
@@ -156,7 +163,7 @@
                         continue;
                     }
                     to.setBigram(word1, word2,
-                            BinaryDictInputOutput.reconstructBigramFrequency(unigramFrequency,
+                            BinaryDictIOUtils.reconstructBigramFrequency(unigramFrequency,
                                     attr.mFrequency));
                 }
             }
diff --git a/java/src/com/android/inputmethod/latin/utils/UserHistoryForgettingCurveUtils.java b/java/src/com/android/inputmethod/latin/utils/UserHistoryForgettingCurveUtils.java
index 713a45b..1992b2f 100644
--- a/java/src/com/android/inputmethod/latin/utils/UserHistoryForgettingCurveUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/UserHistoryForgettingCurveUtils.java
@@ -23,7 +23,9 @@
 public final class UserHistoryForgettingCurveUtils {
     private static final String TAG = UserHistoryForgettingCurveUtils.class.getSimpleName();
     private static final boolean DEBUG = false;
-    private static final int FC_FREQ_MAX = 127;
+    private static final int DEFAULT_FC_FREQ = 127;
+    private static final int BOOSTED_FC_FREQ = 200;
+    private static int FC_FREQ_MAX = DEFAULT_FC_FREQ;
     /* package */ static final int COUNT_MAX = 3;
     private static final int FC_LEVEL_MAX = 3;
     /* package */ static final int ELAPSED_TIME_MAX = 15;
@@ -33,6 +35,14 @@
     private static final int HALF_LIFE_HOURS = 48;
     private static final int MAX_PUSH_ELAPSED = (FC_LEVEL_MAX + 1) * (ELAPSED_TIME_MAX + 1);
 
+    public static void boostMaxFreqForDebug() {
+        FC_FREQ_MAX = BOOSTED_FC_FREQ;
+    }
+
+    public static void resetMaxFreqForDebug() {
+        FC_FREQ_MAX = DEFAULT_FC_FREQ;
+    }
+
     private UserHistoryForgettingCurveUtils() {
         // This utility class is not publicly instantiable.
     }
diff --git a/java/src/com/android/inputmethod/latin/utils/UserLogRingCharBuffer.java b/java/src/com/android/inputmethod/latin/utils/UserLogRingCharBuffer.java
index 161386e..a75d353 100644
--- a/java/src/com/android/inputmethod/latin/utils/UserLogRingCharBuffer.java
+++ b/java/src/com/android/inputmethod/latin/utils/UserLogRingCharBuffer.java
@@ -19,6 +19,7 @@
 import android.inputmethodservice.InputMethodService;
 
 import com.android.inputmethod.annotations.UsedForTesting;
+import com.android.inputmethod.latin.LatinImeLogger;
 import com.android.inputmethod.latin.settings.Settings;
 
 public final class UserLogRingCharBuffer {
@@ -64,6 +65,9 @@
         if (!mEnabled) {
             return;
         }
+        if (LatinImeLogger.sUsabilityStudy) {
+            UsabilityStudyLogUtils.getInstance().writeChar(c, x, y);
+        }
         mCharBuf[mEnd] = c;
         mXBuf[mEnd] = x;
         mYBuf[mEnd] = y;
diff --git a/java/src/com/android/inputmethod/research/JsonUtils.java b/java/src/com/android/inputmethod/research/JsonUtils.java
index 63d524d..2beebdf 100644
--- a/java/src/com/android/inputmethod/research/JsonUtils.java
+++ b/java/src/com/android/inputmethod/research/JsonUtils.java
@@ -75,12 +75,12 @@
 
     private static void writeJson(final Key key, final JsonWriter jsonWriter) throws IOException {
         jsonWriter.beginObject();
-        jsonWriter.name("code").value(key.mCode);
+        jsonWriter.name("code").value(key.getCode());
         jsonWriter.name("altCode").value(key.getAltCode());
-        jsonWriter.name("x").value(key.mX);
-        jsonWriter.name("y").value(key.mY);
-        jsonWriter.name("w").value(key.mWidth);
-        jsonWriter.name("h").value(key.mHeight);
+        jsonWriter.name("x").value(key.getX());
+        jsonWriter.name("y").value(key.getY());
+        jsonWriter.name("w").value(key.getWidth());
+        jsonWriter.name("h").value(key.getHeight());
         jsonWriter.endObject();
     }
 
@@ -103,7 +103,7 @@
             jsonWriter.name("word").value(wordInfo.toString());
             jsonWriter.name("score").value(wordInfo.mScore);
             jsonWriter.name("kind").value(wordInfo.mKind);
-            jsonWriter.name("sourceDict").value(wordInfo.mSourceDict);
+            jsonWriter.name("sourceDict").value(wordInfo.mSourceDict.mDictType);
             jsonWriter.endObject();
         }
         jsonWriter.endArray();
diff --git a/java/src/com/android/inputmethod/research/ResearchLogger.java b/java/src/com/android/inputmethod/research/ResearchLogger.java
index 3a34082..da9c611 100644
--- a/java/src/com/android/inputmethod/research/ResearchLogger.java
+++ b/java/src/com/android/inputmethod/research/ResearchLogger.java
@@ -1427,7 +1427,7 @@
                 kid.navigateNext(), kid.navigatePrevious(), kid.mClobberSettingsKey,
                 isPasswordView, kid.mShortcutKeyEnabled, kid.mHasShortcutKey,
                 kid.mLanguageSwitchKeyEnabled, kid.isMultiLine(), keyboard.mOccupiedWidth,
-                keyboard.mOccupiedHeight, keyboard.mKeys);
+                keyboard.mOccupiedHeight, keyboard.getKeys());
     }
 
     /**
diff --git a/native/jni/Android.mk b/native/jni/Android.mk
index e14cf5a..c207032 100644
--- a/native/jni/Android.mk
+++ b/native/jni/Android.mk
@@ -43,6 +43,7 @@
     com_android_inputmethod_keyboard_ProximityInfo.cpp \
     com_android_inputmethod_latin_BinaryDictionary.cpp \
     com_android_inputmethod_latin_DicTraverseSession.cpp \
+    com_android_inputmethod_latin_makedict_Ver3DictDecoder.cpp \
     jni_common.cpp
 
 LATIN_IME_CORE_SRC_FILES := \
@@ -53,12 +54,7 @@
         dic_nodes_cache.cpp) \
     $(addprefix suggest/core/dictionary/, \
         bigram_dictionary.cpp \
-        binary_dictionary_format_utils.cpp \
-        binary_dictionary_header.cpp \
-        binary_dictionary_header_reading_utils.cpp \
-        binary_dictionary_terminal_attributes_reading_utils.cpp \
         bloom_filter.cpp \
-        byte_array_utils.cpp \
         dictionary.cpp \
         digraph_utils.cpp \
         multi_bigram_map.cpp) \
@@ -71,11 +67,25 @@
     suggest/core/policy/weighting.cpp \
     suggest/core/session/dic_traverse_session.cpp \
     $(addprefix suggest/policyimpl/dictionary/, \
+        bigram/bigram_list_read_write_utils.cpp \
+        bigram/dynamic_bigram_list_policy.cpp \
+        header/header_policy.cpp \
+        header/header_read_write_utils.cpp \
+        shortcut/shortcut_list_reading_utils.cpp \
+        dictionary_structure_with_buffer_policy_factory.cpp \
+        dynamic_patricia_trie_gc_event_listeners.cpp \
         dynamic_patricia_trie_node_reader.cpp \
         dynamic_patricia_trie_policy.cpp \
+        dynamic_patricia_trie_reading_helper.cpp \
         dynamic_patricia_trie_reading_utils.cpp \
+        dynamic_patricia_trie_writing_helper.cpp \
+        dynamic_patricia_trie_writing_utils.cpp \
         patricia_trie_policy.cpp \
         patricia_trie_reading_utils.cpp) \
+    $(addprefix suggest/policyimpl/dictionary/utils/, \
+        buffer_with_extendable_buffer.cpp \
+        byte_array_utils.cpp \
+        format_utils.cpp) \
     suggest/policyimpl/gesture/gesture_suggest_policy_factory.cpp \
     $(addprefix suggest/policyimpl/typing/, \
         scoring_params.cpp \
diff --git a/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
index 8b46c26..7f47493 100644
--- a/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
+++ b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
@@ -18,37 +18,20 @@
 
 #include "com_android_inputmethod_latin_BinaryDictionary.h"
 
-#include <cerrno>
 #include <cstring> // for memset()
-#include <fcntl.h>
-#include <sys/mman.h>
-#include <unistd.h>
 
 #include "defines.h"
 #include "jni.h"
 #include "jni_common.h"
-#include "suggest/core/dictionary/binary_dictionary_format_utils.h"
-#include "suggest/core/dictionary/binary_dictionary_info.h"
 #include "suggest/core/dictionary/dictionary.h"
 #include "suggest/core/suggest_options.h"
+#include "suggest/policyimpl/dictionary/dictionary_structure_with_buffer_policy_factory.h"
 #include "utils/autocorrection_threshold_utils.h"
 
 namespace latinime {
 
 class ProximityInfo;
 
-// Helper method
-static void releaseDictBuf(const void *dictBuf, const size_t length, const int fd) {
-    int ret = munmap(const_cast<void *>(dictBuf), length);
-    if (ret != 0) {
-        AKLOGE("DICT: Failure in munmap. ret=%d errno=%d", ret, errno);
-    }
-    ret = close(fd);
-    if (ret != 0) {
-        AKLOGE("DICT: Failure in close. ret=%d errno=%d", ret, errno);
-    }
-}
-
 static jlong latinime_BinaryDictionary_open(JNIEnv *env, jclass clazz, jstring sourceDir,
         jlong dictOffset, jlong dictSize, jboolean isUpdatable) {
     PROF_OPEN;
@@ -61,56 +44,52 @@
     char sourceDirChars[sourceDirUtf8Length + 1];
     env->GetStringUTFRegion(sourceDir, 0, env->GetStringLength(sourceDir), sourceDirChars);
     sourceDirChars[sourceDirUtf8Length] = '\0';
-    int fd = 0;
-    void *dictBuf = 0;
-    int offset = 0;
-    const bool updatableMmap = (isUpdatable == JNI_TRUE);
-    const int openMode = updatableMmap ? O_RDWR : O_RDONLY;
-    fd = open(sourceDirChars, openMode);
-    if (fd < 0) {
-        AKLOGE("DICT: Can't open sourceDir. sourceDirChars=%s errno=%d", sourceDirChars, errno);
+    DictionaryStructureWithBufferPolicy *const dictionaryStructureWithBufferPolicy =
+            DictionaryStructureWithBufferPolicyFactory::newDictionaryStructureWithBufferPolicy(
+                    sourceDirChars, static_cast<int>(dictOffset), static_cast<int>(dictSize),
+                    isUpdatable == JNI_TRUE);
+    if (!dictionaryStructureWithBufferPolicy) {
         return 0;
     }
-    int pagesize = getpagesize();
-    offset = static_cast<int>(dictOffset) % pagesize;
-    int adjDictOffset = static_cast<int>(dictOffset) - offset;
-    int adjDictSize = static_cast<int>(dictSize) + offset;
-    const int protMode = updatableMmap ? PROT_READ | PROT_WRITE : PROT_READ;
-    dictBuf = mmap(0, adjDictSize, protMode, MAP_PRIVATE, fd, adjDictOffset);
-    if (dictBuf == MAP_FAILED) {
-        AKLOGE("DICT: Can't mmap dictionary. errno=%d", errno);
-        return 0;
-    }
-    dictBuf = static_cast<char *>(dictBuf) + offset;
-    if (!dictBuf) {
-        AKLOGE("DICT: dictBuf is null");
-        return 0;
-    }
-    Dictionary *dictionary = 0;
-    if (BinaryDictionaryFormatUtils::UNKNOWN_VERSION
-            == BinaryDictionaryFormatUtils::detectFormatVersion(static_cast<uint8_t *>(dictBuf),
-                    static_cast<int>(dictSize))) {
-        AKLOGE("DICT: dictionary format is unknown, bad magic number");
-        releaseDictBuf(static_cast<const char *>(dictBuf) - offset, adjDictSize, fd);
-    } else {
-        dictionary = new Dictionary(env, dictBuf, static_cast<int>(dictSize), fd, offset,
-                updatableMmap);
-    }
+
+    Dictionary *const dictionary = new Dictionary(env, dictionaryStructureWithBufferPolicy);
     PROF_END(66);
     PROF_CLOSE;
     return reinterpret_cast<jlong>(dictionary);
 }
 
+static void latinime_BinaryDictionary_flush(JNIEnv *env, jclass clazz, jlong dict,
+        jstring filePath) {
+    Dictionary *dictionary = reinterpret_cast<Dictionary *>(dict);
+    if (!dictionary) return;
+    const jsize filePathUtf8Length = env->GetStringUTFLength(filePath);
+    char filePathChars[filePathUtf8Length + 1];
+    env->GetStringUTFRegion(filePath, 0, env->GetStringLength(filePath), filePathChars);
+    filePathChars[filePathUtf8Length] = '\0';
+    dictionary->flush(filePathChars);
+}
+
+static bool latinime_BinaryDictionary_needsToRunGC(JNIEnv *env, jclass clazz,
+        jlong dict) {
+    Dictionary *dictionary = reinterpret_cast<Dictionary *>(dict);
+    if (!dictionary) return false;
+    return dictionary->needsToRunGC();
+}
+
+static void latinime_BinaryDictionary_flushWithGC(JNIEnv *env, jclass clazz, jlong dict,
+        jstring filePath) {
+    Dictionary *dictionary = reinterpret_cast<Dictionary *>(dict);
+    if (!dictionary) return;
+    const jsize filePathUtf8Length = env->GetStringUTFLength(filePath);
+    char filePathChars[filePathUtf8Length + 1];
+    env->GetStringUTFRegion(filePath, 0, env->GetStringLength(filePath), filePathChars);
+    filePathChars[filePathUtf8Length] = '\0';
+    dictionary->flushWithGC(filePathChars);
+}
+
 static void latinime_BinaryDictionary_close(JNIEnv *env, jclass clazz, jlong dict) {
     Dictionary *dictionary = reinterpret_cast<Dictionary *>(dict);
     if (!dictionary) return;
-    const BinaryDictionaryInfo *const binaryDictionaryInfo = dictionary->getBinaryDictionaryInfo();
-    const int dictBufOffset = binaryDictionaryInfo->getDictBufOffset();
-    const void *dictBuf = binaryDictionaryInfo->getDictBuf();
-    if (!dictBuf) return;
-    releaseDictBuf(static_cast<const char *>(dictBuf) - dictBufOffset,
-            binaryDictionaryInfo->getDictSize() + dictBufOffset,
-            binaryDictionaryInfo->getMmapFd());
     delete dictionary;
 }
 
@@ -119,7 +98,8 @@
         jintArray yCoordinatesArray, jintArray timesArray, jintArray pointerIdsArray,
         jintArray inputCodePointsArray, jint inputSize, jint commitPoint, jintArray suggestOptions,
         jintArray prevWordCodePointsForBigrams, jintArray outputCodePointsArray,
-        jintArray scoresArray, jintArray spaceIndicesArray, jintArray outputTypesArray) {
+        jintArray scoresArray, jintArray spaceIndicesArray, jintArray outputTypesArray,
+        jintArray outputAutoCommitFirstWordConfidence) {
     Dictionary *dictionary = reinterpret_cast<Dictionary *>(dict);
     if (!dictionary) return 0;
     ProximityInfo *pInfo = reinterpret_cast<ProximityInfo *>(proximityInfo);
@@ -208,8 +188,8 @@
     return dictionary->getProbability(codePoints, wordLength);
 }
 
-static jboolean latinime_BinaryDictionary_isValidBigram(JNIEnv *env, jclass clazz, jlong dict,
-        jintArray word0, jintArray word1) {
+static jint latinime_BinaryDictionary_getBigramProbability(JNIEnv *env, jclass clazz,
+        jlong dict, jintArray word0, jintArray word1) {
     Dictionary *dictionary = reinterpret_cast<Dictionary *>(dict);
     if (!dictionary) return JNI_FALSE;
     const jsize word0Length = env->GetArrayLength(word0);
@@ -218,7 +198,8 @@
     int word1CodePoints[word1Length];
     env->GetIntArrayRegion(word0, 0, word0Length, word0CodePoints);
     env->GetIntArrayRegion(word1, 0, word1Length, word1CodePoints);
-    return dictionary->isValidBigram(word0CodePoints, word0Length, word1CodePoints, word1Length);
+    return dictionary->getBigramProbability(word0CodePoints, word0Length, word1CodePoints,
+            word1Length);
 }
 
 static jfloat latinime_BinaryDictionary_calcNormalizedScore(JNIEnv *env, jclass clazz,
@@ -253,6 +234,7 @@
     }
     jsize wordLength = env->GetArrayLength(word);
     int codePoints[wordLength];
+    env->GetIntArrayRegion(word, 0, wordLength, codePoints);
     dictionary->addUnigramWord(codePoints, wordLength, probability);
 }
 
@@ -264,8 +246,10 @@
     }
     jsize word0Length = env->GetArrayLength(word0);
     int word0CodePoints[word0Length];
+    env->GetIntArrayRegion(word0, 0, word0Length, word0CodePoints);
     jsize word1Length = env->GetArrayLength(word1);
     int word1CodePoints[word1Length];
+    env->GetIntArrayRegion(word1, 0, word1Length, word1CodePoints);
     dictionary->addBigramWords(word0CodePoints, word0Length, word1CodePoints,
             word1Length, probability);
 }
@@ -278,12 +262,24 @@
     }
     jsize word0Length = env->GetArrayLength(word0);
     int word0CodePoints[word0Length];
+    env->GetIntArrayRegion(word0, 0, word0Length, word0CodePoints);
     jsize word1Length = env->GetArrayLength(word1);
     int word1CodePoints[word1Length];
+    env->GetIntArrayRegion(word1, 0, word1Length, word1CodePoints);
     dictionary->removeBigramWords(word0CodePoints, word0Length, word1CodePoints,
             word1Length);
 }
 
+static int latinime_BinaryDictionary_calculateProbabilityNative(JNIEnv *env, jclass clazz,
+        jlong dict, jint unigramProbability, jint bigramProbability) {
+    Dictionary *dictionary = reinterpret_cast<Dictionary *>(dict);
+    if (!dictionary) {
+        return NOT_A_PROBABILITY;
+    }
+    return dictionary->getDictionaryStructurePolicy()->getProbability(unigramProbability,
+            bigramProbability);
+}
+
 static const JNINativeMethod sMethods[] = {
     {
         const_cast<char *>("openNative"),
@@ -296,8 +292,23 @@
         reinterpret_cast<void *>(latinime_BinaryDictionary_close)
     },
     {
+        const_cast<char *>("flushNative"),
+        const_cast<char *>("(JLjava/lang/String;)V"),
+        reinterpret_cast<void *>(latinime_BinaryDictionary_flush)
+    },
+    {
+        const_cast<char *>("needsToRunGCNative"),
+        const_cast<char *>("(J)Z"),
+        reinterpret_cast<void *>(latinime_BinaryDictionary_needsToRunGC)
+    },
+    {
+        const_cast<char *>("flushWithGCNative"),
+        const_cast<char *>("(JLjava/lang/String;)V"),
+        reinterpret_cast<void *>(latinime_BinaryDictionary_flushWithGC)
+    },
+    {
         const_cast<char *>("getSuggestionsNative"),
-        const_cast<char *>("(JJJ[I[I[I[I[III[I[I[I[I[I[I)I"),
+        const_cast<char *>("(JJJ[I[I[I[I[III[I[I[I[I[I[I[I)I"),
         reinterpret_cast<void *>(latinime_BinaryDictionary_getSuggestions)
     },
     {
@@ -306,9 +317,9 @@
         reinterpret_cast<void *>(latinime_BinaryDictionary_getProbability)
     },
     {
-        const_cast<char *>("isValidBigramNative"),
-        const_cast<char *>("(J[I[I)Z"),
-        reinterpret_cast<void *>(latinime_BinaryDictionary_isValidBigram)
+        const_cast<char *>("getBigramProbabilityNative"),
+        const_cast<char *>("(J[I[I)I"),
+        reinterpret_cast<void *>(latinime_BinaryDictionary_getBigramProbability)
     },
     {
         const_cast<char *>("calcNormalizedScoreNative"),
@@ -334,6 +345,11 @@
         const_cast<char *>("removeBigramWordsNative"),
         const_cast<char *>("(J[I[I)V"),
         reinterpret_cast<void *>(latinime_BinaryDictionary_removeBigramWords)
+    },
+    {
+        const_cast<char *>("calculateProbabilityNative"),
+        const_cast<char *>("(JII)I"),
+        reinterpret_cast<void *>(latinime_BinaryDictionary_calculateProbabilityNative)
     }
 };
 
diff --git a/native/jni/com_android_inputmethod_latin_DicTraverseSession.cpp b/native/jni/com_android_inputmethod_latin_DicTraverseSession.cpp
index 72e6258..3866433 100644
--- a/native/jni/com_android_inputmethod_latin_DicTraverseSession.cpp
+++ b/native/jni/com_android_inputmethod_latin_DicTraverseSession.cpp
@@ -25,8 +25,9 @@
 
 namespace latinime {
 class Dictionary;
-static jlong latinime_setDicTraverseSession(JNIEnv *env, jclass clazz, jstring localeJStr) {
-    void *traverseSession = DicTraverseSession::getSessionInstance(env, localeJStr);
+static jlong latinime_setDicTraverseSession(JNIEnv *env, jclass clazz, jstring localeJStr,
+        jlong dictSize) {
+    void *traverseSession = DicTraverseSession::getSessionInstance(env, localeJStr, dictSize);
     return reinterpret_cast<jlong>(traverseSession);
 }
 
@@ -53,7 +54,7 @@
 static const JNINativeMethod sMethods[] = {
     {
         const_cast<char *>("setDicTraverseSessionNative"),
-        const_cast<char *>("(Ljava/lang/String;)J"),
+        const_cast<char *>("(Ljava/lang/String;J)J"),
         reinterpret_cast<void *>(latinime_setDicTraverseSession)
     },
     {
diff --git a/native/jni/com_android_inputmethod_latin_makedict_Ver3DictDecoder.cpp b/native/jni/com_android_inputmethod_latin_makedict_Ver3DictDecoder.cpp
new file mode 100644
index 0000000..15088b6
--- /dev/null
+++ b/native/jni/com_android_inputmethod_latin_makedict_Ver3DictDecoder.cpp
@@ -0,0 +1,47 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "LatinIME: jni: Ver3DictDecoder"
+
+#include "com_android_inputmethod_latin_makedict_Ver3DictDecoder.h"
+
+#include "defines.h"
+#include "jni.h"
+#include "jni_common.h"
+
+namespace latinime {
+static int latinime_Ver3DictDecoder_doNothing(JNIEnv *env, jclass clazz) {
+    // This is a phony method for test - it does nothing. It just returns some value
+    // unlikely to be in memory by chance for testing purposes.
+    // TODO: remove this method.
+    return 2097;
+}
+
+static const JNINativeMethod sMethods[] = {
+    {
+        // TODO: remove this entry when we have one useful method in here
+        const_cast<char *>("doNothing"),
+        const_cast<char *>("()I"),
+        reinterpret_cast<void *>(latinime_Ver3DictDecoder_doNothing)
+    },
+};
+
+int register_Ver3DictDecoder(JNIEnv *env) {
+    const char *const kClassPathName =
+            "com/android/inputmethod/latin/makedict/Ver3DictDecoder";
+    return registerNativeMethods(env, kClassPathName, sMethods, NELEMS(sMethods));
+}
+} // namespace latinime
diff --git a/native/jni/src/suggest/core/dictionary/byte_array_utils.cpp b/native/jni/com_android_inputmethod_latin_makedict_Ver3DictDecoder.h
similarity index 71%
copy from native/jni/src/suggest/core/dictionary/byte_array_utils.cpp
copy to native/jni/com_android_inputmethod_latin_makedict_Ver3DictDecoder.h
index 68b1d5d..07e80f1 100644
--- a/native/jni/src/suggest/core/dictionary/byte_array_utils.cpp
+++ b/native/jni/com_android_inputmethod_latin_makedict_Ver3DictDecoder.h
@@ -14,11 +14,12 @@
  * limitations under the License.
  */
 
-#include "suggest/core/dictionary/byte_array_utils.h"
+#ifndef _COM_ANDROID_INPUTMETHOD_LATIN_MAKEDICT_VER3DICTDECODER_H
+#define _COM_ANDROID_INPUTMETHOD_LATIN_MAKEDICT_VER3DICTDECODER_H
+
+#include "jni.h"
 
 namespace latinime {
-
-const uint8_t ByteArrayUtils::MINIMAL_ONE_BYTE_CHARACTER_VALUE = 0x20;
-const uint8_t ByteArrayUtils::CHARACTER_ARRAY_TERMINATOR = 0x1F;
-
+int register_Ver3DictDecoder(JNIEnv *env);
 } // namespace latinime
+#endif // _COM_ANDROID_INPUTMETHOD_LATIN_MAKEDICT_VER3DICTDECODER_H
diff --git a/native/jni/jni_common.cpp b/native/jni/jni_common.cpp
index f2867d7..3a8f436 100644
--- a/native/jni/jni_common.cpp
+++ b/native/jni/jni_common.cpp
@@ -18,9 +18,12 @@
 
 #include "jni_common.h"
 
+#ifndef HOST_TOOL
 #include "com_android_inputmethod_keyboard_ProximityInfo.h"
 #include "com_android_inputmethod_latin_BinaryDictionary.h"
 #include "com_android_inputmethod_latin_DicTraverseSession.h"
+#endif
+#include "com_android_inputmethod_latin_makedict_Ver3DictDecoder.h"
 #include "defines.h"
 
 /*
@@ -38,6 +41,7 @@
         AKLOGE("ERROR: JNIEnv is invalid");
         return -1;
     }
+#ifndef HOST_TOOL
     if (!latinime::register_BinaryDictionary(env)) {
         AKLOGE("ERROR: BinaryDictionary native registration failed");
         return -1;
@@ -50,6 +54,11 @@
         AKLOGE("ERROR: ProximityInfo native registration failed");
         return -1;
     }
+#endif
+    if (!latinime::register_Ver3DictDecoder(env)) {
+        AKLOGE("ERROR: Ver3DictDecoder native registration failed");
+        return -1;
+    }
     /* success -- return valid version number */
     return JNI_VERSION_1_6;
 }
diff --git a/native/jni/src/defines.h b/native/jni/src/defines.h
index 34a646f..89dfa39 100644
--- a/native/jni/src/defines.h
+++ b/native/jni/src/defines.h
@@ -292,7 +292,6 @@
 // of the binary dictionary where a {key,value} string pair scheme is used.
 #define LARGEST_INT_DIGIT_COUNT 11
 
-#define NOT_A_VALID_WORD_POS (-99)
 #define NOT_A_CODE_POINT (-1)
 #define NOT_A_DISTANCE (-1)
 #define NOT_A_COORDINATE (-1)
@@ -322,13 +321,6 @@
 #define MAX_POINTER_COUNT 1
 #define MAX_POINTER_COUNT_G 2
 
-// Queue IDs and size for DicNodesCache
-#define DIC_NODES_CACHE_INITIAL_QUEUE_ID_ACTIVE 0
-#define DIC_NODES_CACHE_INITIAL_QUEUE_ID_NEXT_ACTIVE 1
-#define DIC_NODES_CACHE_INITIAL_QUEUE_ID_TERMINAL 2
-#define DIC_NODES_CACHE_INITIAL_QUEUE_ID_CACHE_FOR_CONTINUOUS_SUGGESTION 3
-#define DIC_NODES_CACHE_PRIORITY_QUEUES_SIZE 4
-
 template<typename T> AK_FORCE_INLINE const T &min(const T &a, const T &b) { return a < b ? a : b; }
 template<typename T> AK_FORCE_INLINE const T &max(const T &a, const T &b) { return a > b ? a : b; }
 
diff --git a/native/jni/src/suggest/core/dicnode/dic_node.h b/native/jni/src/suggest/core/dicnode/dic_node.h
index cdd9f59..41ef9d2 100644
--- a/native/jni/src/suggest/core/dicnode/dic_node.h
+++ b/native/jni/src/suggest/core/dicnode/dic_node.h
@@ -112,7 +112,7 @@
         mIsUsed = true;
         mIsCachedForNextSuggestion = false;
         mDicNodeProperties.init(
-                NOT_A_VALID_WORD_POS /* pos */, rootGroupPos, NOT_A_CODE_POINT /* nodeCodePoint */,
+                NOT_A_DICT_POS /* pos */, rootGroupPos, NOT_A_CODE_POINT /* nodeCodePoint */,
                 NOT_A_PROBABILITY /* probability */, false /* isTerminal */,
                 true /* hasChildren */, false /* isBlacklistedOrNotAWord */, 0 /* depth */,
                 0 /* terminalDepth */);
@@ -125,7 +125,7 @@
         mIsUsed = true;
         mIsCachedForNextSuggestion = dicNode->mIsCachedForNextSuggestion;
         mDicNodeProperties.init(
-                NOT_A_VALID_WORD_POS /* pos */, rootGroupPos, NOT_A_CODE_POINT /* nodeCodePoint */,
+                NOT_A_DICT_POS /* pos */, rootGroupPos, NOT_A_CODE_POINT /* nodeCodePoint */,
                 NOT_A_PROBABILITY /* probability */, false /* isTerminal */,
                 true /* hasChildren */, false /* isBlacklistedOrNotAWord */,  0 /* depth */,
                 0 /* terminalDepth */);
@@ -143,7 +143,7 @@
                 dicNode->mDicNodeState.mDicNodeStatePrevWord.getPrevWordLength(),
                 dicNode->getOutputWordBuf(),
                 dicNode->mDicNodeProperties.getDepth(),
-                dicNode->mDicNodeState.mDicNodeStatePrevWord.mPrevSpacePositions,
+                dicNode->mDicNodeState.mDicNodeStatePrevWord.getSecondWordFirstInputIndex(),
                 mDicNodeState.mDicNodeStateInput.getInputIndex(0) /* lastInputIndex */);
         PROF_NODE_COPY(&dicNode->mProfiler, mProfiler);
     }
@@ -234,7 +234,7 @@
     }
 
     bool isFirstWord() const {
-        return mDicNodeState.mDicNodeStatePrevWord.getPrevWordNodePos() == NOT_A_VALID_WORD_POS;
+        return mDicNodeState.mDicNodeStatePrevWord.getPrevWordNodePos() == NOT_A_DICT_POS;
     }
 
     bool isCompletion(const int inputSize) const {
@@ -321,8 +321,13 @@
         DUMP_WORD_AND_SCORE("OUTPUT");
     }
 
-    void outputSpacePositionsResult(int *spaceIndices) const {
-        mDicNodeState.mDicNodeStatePrevWord.outputSpacePositions(spaceIndices);
+    int getSecondWordFirstInputIndex(const ProximityInfoState *const pInfoState) const {
+        const int inputIndex = mDicNodeState.mDicNodeStatePrevWord.getSecondWordFirstInputIndex();
+        if (inputIndex == NOT_AN_INDEX) {
+            return NOT_AN_INDEX;
+        } else {
+            return pInfoState->getInputIndexOfSampledPoint(inputIndex);
+        }
     }
 
     bool hasMultipleWords() const {
@@ -573,7 +578,11 @@
         }
     }
 
-    AK_FORCE_INLINE void updateInputIndexG(DicNode_InputStateG *inputStateG) {
+    AK_FORCE_INLINE void updateInputIndexG(const DicNode_InputStateG *const inputStateG) {
+        if (mDicNodeState.mDicNodeStatePrevWord.getPrevWordCount() == 1 && isFirstLetter()) {
+            mDicNodeState.mDicNodeStatePrevWord.setSecondWordFirstInputIndex(
+                    inputStateG->mInputIndex);
+        }
         mDicNodeState.mDicNodeStateInput.updateInputIndexG(inputStateG->mPointerId,
                 inputStateG->mInputIndex, inputStateG->mPrevCodePoint,
                 inputStateG->mTerminalDiffCost, inputStateG->mRawLength);
diff --git a/native/jni/src/suggest/core/dicnode/dic_node_priority_queue.h b/native/jni/src/suggest/core/dicnode/dic_node_priority_queue.h
index 2a486b8..7461f0c 100644
--- a/native/jni/src/suggest/core/dicnode/dic_node_priority_queue.h
+++ b/native/jni/src/suggest/core/dicnode/dic_node_priority_queue.h
@@ -24,20 +24,16 @@
 #include "suggest/core/dicnode/dic_node.h"
 #include "suggest/core/dicnode/dic_node_release_listener.h"
 
-// The biggest value among MAX_CACHE_DIC_NODE_SIZE, MAX_CACHE_DIC_NODE_SIZE_FOR_SINGLE_POINT, ...
-#define MAX_DIC_NODE_PRIORITY_QUEUE_CAPACITY 310
-
 namespace latinime {
 
 class DicNodePriorityQueue : public DicNodeReleaseListener {
  public:
-    AK_FORCE_INLINE DicNodePriorityQueue()
-            : MAX_CAPACITY(MAX_DIC_NODE_PRIORITY_QUEUE_CAPACITY),
-              mMaxSize(MAX_DIC_NODE_PRIORITY_QUEUE_CAPACITY), mDicNodesBuf(), mUnusedNodeIndices(),
-              mNextUnusedNodeId(0), mDicNodesQueue() {
-        mDicNodesBuf.resize(MAX_CAPACITY + 1);
-        mUnusedNodeIndices.resize(MAX_CAPACITY + 1);
-        reset();
+    AK_FORCE_INLINE explicit DicNodePriorityQueue(const int capacity)
+            : mCapacity(capacity), mMaxSize(capacity), mDicNodesBuf(),
+              mUnusedNodeIndices(), mNextUnusedNodeId(0), mDicNodesQueue() {
+        mDicNodesBuf.resize(mCapacity + 1);
+        mUnusedNodeIndices.resize(mCapacity + 1);
+        clearAndResizeToCapacity();
     }
 
     // Non virtual inline destructor -- never inherit this class
@@ -52,11 +48,12 @@
     }
 
     AK_FORCE_INLINE void setMaxSize(const int maxSize) {
-        mMaxSize = min(maxSize, MAX_CAPACITY);
+        ASSERT(maxSize <= mCapacity);
+        mMaxSize = min(maxSize, mCapacity);
     }
 
-    AK_FORCE_INLINE void reset() {
-        clearAndResize(MAX_CAPACITY);
+    AK_FORCE_INLINE void clearAndResizeToCapacity() {
+        clearAndResize(mCapacity);
     }
 
     AK_FORCE_INLINE void clear() {
@@ -64,27 +61,19 @@
     }
 
     AK_FORCE_INLINE void clearAndResize(const int maxSize) {
+        ASSERT(maxSize <= mCapacity);
         while (!mDicNodesQueue.empty()) {
             mDicNodesQueue.pop();
         }
         setMaxSize(maxSize);
-        for (int i = 0; i < MAX_CAPACITY + 1; ++i) {
+        for (int i = 0; i < mCapacity + 1; ++i) {
             mDicNodesBuf[i].remove();
             mDicNodesBuf[i].setReleaseListener(this);
-            mUnusedNodeIndices[i] = i == MAX_CAPACITY ? NOT_A_NODE_ID : static_cast<int>(i) + 1;
+            mUnusedNodeIndices[i] = i == mCapacity ? NOT_A_NODE_ID : static_cast<int>(i) + 1;
         }
         mNextUnusedNodeId = 0;
     }
 
-    AK_FORCE_INLINE DicNode *newDicNode(DicNode *dicNode) {
-        DicNode *newNode = searchEmptyDicNode();
-        if (newNode) {
-            DicNodeUtils::initByCopy(dicNode, newNode);
-            return newNode;
-        }
-        return 0;
-    }
-
     // Copy
     AK_FORCE_INLINE DicNode *copyPush(DicNode *dicNode) {
         return copyPush(dicNode, mMaxSize);
@@ -111,12 +100,12 @@
         }
         mUnusedNodeIndices[index] = mNextUnusedNodeId;
         mNextUnusedNodeId = index;
-        ASSERT(index >= 0 && index < (MAX_CAPACITY + 1));
+        ASSERT(index >= 0 && index < (mCapacity + 1));
     }
 
     AK_FORCE_INLINE void dump() const {
         AKLOGI("\n\n\n\n\n===========================");
-        for (int i = 0; i < MAX_CAPACITY + 1; ++i) {
+        for (int i = 0; i < mCapacity + 1; ++i) {
             if (mDicNodesBuf[i].isUsed()) {
                 mDicNodesBuf[i].dump("QUEUE: ");
             }
@@ -125,7 +114,7 @@
     }
 
  private:
-    DISALLOW_COPY_AND_ASSIGN(DicNodePriorityQueue);
+    DISALLOW_IMPLICIT_CONSTRUCTORS(DicNodePriorityQueue);
     static const int NOT_A_NODE_ID = -1;
 
     AK_FORCE_INLINE static bool compareDicNode(DicNode *left, DicNode *right) {
@@ -139,7 +128,7 @@
     };
 
     typedef std::priority_queue<DicNode *, std::vector<DicNode *>, DicNodeComparator> DicNodesQueue;
-    const int MAX_CAPACITY;
+    const int mCapacity;
     int mMaxSize;
     std::vector<DicNode> mDicNodesBuf; // of each element of mDicNodesBuf respectively
     std::vector<int> mUnusedNodeIndices;
@@ -163,13 +152,12 @@
     }
 
     AK_FORCE_INLINE DicNode *searchEmptyDicNode() {
-        // TODO: Currently O(n) but should be improved to O(1)
-        if (MAX_CAPACITY == 0) {
+        if (mCapacity == 0) {
             return 0;
         }
         if (mNextUnusedNodeId == NOT_A_NODE_ID) {
             AKLOGI("No unused node found.");
-            for (int i = 0; i < MAX_CAPACITY + 1; ++i) {
+            for (int i = 0; i < mCapacity + 1; ++i) {
                 AKLOGI("Dump node availability, %d, %d, %d",
                         i, mDicNodesBuf[i].isUsed(), mUnusedNodeIndices[i]);
             }
@@ -185,7 +173,7 @@
         const int index = static_cast<int>(dicNode - &mDicNodesBuf[0]);
         mNextUnusedNodeId = mUnusedNodeIndices[index];
         mUnusedNodeIndices[index] = NOT_A_NODE_ID;
-        ASSERT(index >= 0 && index < (MAX_CAPACITY + 1));
+        ASSERT(index >= 0 && index < (mCapacity + 1));
     }
 
     AK_FORCE_INLINE DicNode *pushPoolNodeWithMaxSize(DicNode *dicNode, const int maxSize) {
@@ -209,6 +197,15 @@
     AK_FORCE_INLINE DicNode *copyPush(DicNode *dicNode, const int maxSize) {
         return pushPoolNodeWithMaxSize(newDicNode(dicNode), maxSize);
     }
+
+    AK_FORCE_INLINE DicNode *newDicNode(DicNode *dicNode) {
+        DicNode *newNode = searchEmptyDicNode();
+        if (newNode) {
+            DicNodeUtils::initByCopy(dicNode, newNode);
+        }
+        return newNode;
+    }
+
 };
 } // namespace latinime
 #endif // LATINIME_DIC_NODE_PRIORITY_QUEUE_H
diff --git a/native/jni/src/suggest/core/dicnode/dic_node_proximity_filter.h b/native/jni/src/suggest/core/dicnode/dic_node_proximity_filter.h
deleted file mode 100644
index 1a39f2e..0000000
--- a/native/jni/src/suggest/core/dicnode/dic_node_proximity_filter.h
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * 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.
- */
-
-#ifndef LATINIME_DIC_NODE_PROXIMITY_FILTER_H
-#define LATINIME_DIC_NODE_PROXIMITY_FILTER_H
-
-#include "defines.h"
-#include "suggest/core/layout/proximity_info_state.h"
-#include "suggest/core/layout/proximity_info_utils.h"
-#include "suggest/core/policy/dictionary_structure_policy.h"
-
-namespace latinime {
-
-class DicNodeProximityFilter : public DictionaryStructurePolicy::NodeFilter {
- public:
-    DicNodeProximityFilter(const ProximityInfoState *const pInfoState,
-            const int pointIndex, const bool exactOnly)
-            : mProximityInfoState(pInfoState), mPointIndex(pointIndex), mExactOnly(exactOnly) {}
-
-    bool isFilteredOut(const int codePoint) const {
-        return !isProximityCodePoint(codePoint);
-    }
-
- private:
-    DISALLOW_IMPLICIT_CONSTRUCTORS(DicNodeProximityFilter);
-
-    const ProximityInfoState *const mProximityInfoState;
-    const int mPointIndex;
-    const bool mExactOnly;
-
-    // TODO: Move to proximity info state
-    bool isProximityCodePoint(const int codePoint) const {
-        if (!mProximityInfoState) {
-            return true;
-        }
-        if (mExactOnly) {
-            return mProximityInfoState->getPrimaryCodePointAt(mPointIndex) == codePoint;
-        }
-        const ProximityType matchedId = mProximityInfoState->getProximityType(
-                mPointIndex, codePoint, true /* checkProximityChars */);
-        return ProximityInfoUtils::isMatchOrProximityChar(matchedId);
-    }
-};
-} // namespace latinime
-#endif // LATINIME_DIC_NODE_PROXIMITY_FILTER_H
diff --git a/native/jni/src/suggest/core/dicnode/dic_node_utils.cpp b/native/jni/src/suggest/core/dicnode/dic_node_utils.cpp
index 6b4ef2f..ec65114 100644
--- a/native/jni/src/suggest/core/dicnode/dic_node_utils.cpp
+++ b/native/jni/src/suggest/core/dicnode/dic_node_utils.cpp
@@ -19,12 +19,9 @@
 #include <cstring>
 
 #include "suggest/core/dicnode/dic_node.h"
-#include "suggest/core/dicnode/dic_node_proximity_filter.h"
 #include "suggest/core/dicnode/dic_node_vector.h"
-#include "suggest/core/dictionary/binary_dictionary_info.h"
 #include "suggest/core/dictionary/multi_bigram_map.h"
-#include "suggest/core/dictionary/probability_utils.h"
-#include "suggest/core/policy/dictionary_structure_policy.h"
+#include "suggest/core/policy/dictionary_structure_with_buffer_policy.h"
 #include "utils/char_utils.h"
 
 namespace latinime {
@@ -33,17 +30,17 @@
 // Node initialization utils //
 ///////////////////////////////
 
-/* static */ void DicNodeUtils::initAsRoot(const BinaryDictionaryInfo *const binaryDictionaryInfo,
+/* static */ void DicNodeUtils::initAsRoot(
+        const DictionaryStructureWithBufferPolicy *const dictionaryStructurePolicy,
         const int prevWordNodePos, DicNode *const newRootNode) {
-    newRootNode->initAsRoot(binaryDictionaryInfo->getStructurePolicy()->getRootPosition(),
-            prevWordNodePos);
+    newRootNode->initAsRoot(dictionaryStructurePolicy->getRootPosition(), prevWordNodePos);
 }
 
 /*static */ void DicNodeUtils::initAsRootWithPreviousWord(
-        const BinaryDictionaryInfo *const binaryDictionaryInfo,
+        const DictionaryStructureWithBufferPolicy *const dictionaryStructurePolicy,
         DicNode *const prevWordLastNode, DicNode *const newRootNode) {
     newRootNode->initAsRootWithPreviousWord(
-            prevWordLastNode, binaryDictionaryInfo->getStructurePolicy()->getRootPosition());
+            prevWordLastNode, dictionaryStructurePolicy->getRootPosition());
 }
 
 /* static */ void DicNodeUtils::initByCopy(DicNode *srcNode, DicNode *destNode) {
@@ -53,37 +50,16 @@
 ///////////////////////////////////
 // Traverse node expansion utils //
 ///////////////////////////////////
-
-/* static */ void DicNodeUtils::createAndGetPassingChildNode(DicNode *dicNode,
-        const DicNodeProximityFilter *const childrenFilter,
-        DicNodeVector *childDicNodes) {
-    // Passing multiple chars node. No need to traverse child
-    const int codePoint = dicNode->getNodeTypedCodePoint();
-    const int baseLowerCaseCodePoint = CharUtils::toBaseLowerCase(codePoint);
-    if (!childrenFilter->isFilteredOut(codePoint)
-            || CharUtils::isIntentionalOmissionCodePoint(baseLowerCaseCodePoint)) {
-        childDicNodes->pushPassingChild(dicNode);
-    }
-}
-
 /* static */ void DicNodeUtils::getAllChildDicNodes(DicNode *dicNode,
-        const BinaryDictionaryInfo *const binaryDictionaryInfo, DicNodeVector *childDicNodes) {
-    getProximityChildDicNodes(dicNode, binaryDictionaryInfo, 0, 0, false, childDicNodes);
-}
-
-/* static */ void DicNodeUtils::getProximityChildDicNodes(DicNode *dicNode,
-        const BinaryDictionaryInfo *const binaryDictionaryInfo,
-        const ProximityInfoState *pInfoState, const int pointIndex, bool exactOnly,
+        const DictionaryStructureWithBufferPolicy *const dictionaryStructurePolicy,
         DicNodeVector *childDicNodes) {
     if (dicNode->isTotalInputSizeExceedingLimit()) {
         return;
     }
-    const DicNodeProximityFilter childrenFilter(pInfoState, pointIndex, exactOnly);
     if (!dicNode->isLeavingNode()) {
-        DicNodeUtils::createAndGetPassingChildNode(dicNode, &childrenFilter, childDicNodes);
+        childDicNodes->pushPassingChild(dicNode);
     } else {
-        binaryDictionaryInfo->getStructurePolicy()->createAndGetAllChildNodes(dicNode,
-                binaryDictionaryInfo, &childrenFilter, childDicNodes);
+        dictionaryStructurePolicy->createAndGetAllChildNodes(dicNode, childDicNodes);
     }
 }
 
@@ -94,12 +70,13 @@
  * Computes the combined bigram / unigram cost for the given dicNode.
  */
 /* static */ float DicNodeUtils::getBigramNodeImprobability(
-        const BinaryDictionaryInfo *const binaryDictionaryInfo,
+        const DictionaryStructureWithBufferPolicy *const dictionaryStructurePolicy,
         const DicNode *const node, MultiBigramMap *multiBigramMap) {
     if (node->hasMultipleWords() && !node->isValidMultipleWordSuggestion()) {
         return static_cast<float>(MAX_VALUE_FOR_WEIGHTING);
     }
-    const int probability = getBigramNodeProbability(binaryDictionaryInfo, node, multiBigramMap);
+    const int probability = getBigramNodeProbability(dictionaryStructurePolicy, node,
+            multiBigramMap);
     // TODO: This equation to calculate the improbability looks unreasonable.  Investigate this.
     const float cost = static_cast<float>(MAX_PROBABILITY - probability)
             / static_cast<float>(MAX_PROBABILITY);
@@ -107,21 +84,23 @@
 }
 
 /* static */ int DicNodeUtils::getBigramNodeProbability(
-        const BinaryDictionaryInfo *const binaryDictionaryInfo,
+        const DictionaryStructureWithBufferPolicy *const dictionaryStructurePolicy,
         const DicNode *const node, MultiBigramMap *multiBigramMap) {
     const int unigramProbability = node->getProbability();
     const int wordPos = node->getPos();
     const int prevWordPos = node->getPrevWordPos();
-    if (NOT_A_VALID_WORD_POS == wordPos || NOT_A_VALID_WORD_POS == prevWordPos) {
+    if (NOT_A_DICT_POS == wordPos || NOT_A_DICT_POS == prevWordPos) {
         // Note: Normally wordPos comes from the dictionary and should never equal
         // NOT_A_VALID_WORD_POS.
-        return ProbabilityUtils::backoff(unigramProbability);
+        return dictionaryStructurePolicy->getProbability(unigramProbability,
+                NOT_A_PROBABILITY);
     }
     if (multiBigramMap) {
-        return multiBigramMap->getBigramProbability(
-                binaryDictionaryInfo, prevWordPos, wordPos, unigramProbability);
+        return multiBigramMap->getBigramProbability(dictionaryStructurePolicy, prevWordPos,
+                wordPos, unigramProbability);
     }
-    return ProbabilityUtils::backoff(unigramProbability);
+    return dictionaryStructurePolicy->getProbability(unigramProbability,
+            NOT_A_PROBABILITY);
 }
 
 ////////////////
diff --git a/native/jni/src/suggest/core/dicnode/dic_node_utils.h b/native/jni/src/suggest/core/dicnode/dic_node_utils.h
index 4f12b29..3fb351a 100644
--- a/native/jni/src/suggest/core/dicnode/dic_node_utils.h
+++ b/native/jni/src/suggest/core/dicnode/dic_node_utils.h
@@ -23,41 +23,37 @@
 
 namespace latinime {
 
-class BinaryDictionaryInfo;
 class DicNode;
-class DicNodeProximityFilter;
 class DicNodeVector;
-class ProximityInfoState;
+class DictionaryStructureWithBufferPolicy;
 class MultiBigramMap;
 
 class DicNodeUtils {
  public:
     static int appendTwoWords(const int *src0, const int16_t length0, const int *src1,
             const int16_t length1, int *dest);
-    static void initAsRoot(const BinaryDictionaryInfo *const binaryDictionaryInfo,
+    static void initAsRoot(
+            const DictionaryStructureWithBufferPolicy *const dictionaryStructurePolicy,
             const int prevWordNodePos, DicNode *newRootNode);
-    static void initAsRootWithPreviousWord(const BinaryDictionaryInfo *const binaryDictionaryInfo,
+    static void initAsRootWithPreviousWord(
+            const DictionaryStructureWithBufferPolicy *const dictionaryStructurePolicy,
             DicNode *prevWordLastNode, DicNode *newRootNode);
     static void initByCopy(DicNode *srcNode, DicNode *destNode);
     static void getAllChildDicNodes(DicNode *dicNode,
-            const BinaryDictionaryInfo *const binaryDictionaryInfo, DicNodeVector *childDicNodes);
-    static float getBigramNodeImprobability(const BinaryDictionaryInfo *const binaryDictionaryInfo,
-            const DicNode *const node, MultiBigramMap *const multiBigramMap);
-    // TODO: Move to private
-    static void getProximityChildDicNodes(DicNode *dicNode,
-            const BinaryDictionaryInfo *const binaryDictionaryInfo,
-            const ProximityInfoState *pInfoState, const int pointIndex, bool exactOnly,
+            const DictionaryStructureWithBufferPolicy *const dictionaryStructurePolicy,
             DicNodeVector *childDicNodes);
+    static float getBigramNodeImprobability(
+            const DictionaryStructureWithBufferPolicy *const dictionaryStructurePolicy,
+            const DicNode *const node, MultiBigramMap *const multiBigramMap);
 
  private:
     DISALLOW_IMPLICIT_CONSTRUCTORS(DicNodeUtils);
     // Max number of bigrams to look up
     static const int MAX_BIGRAMS_CONSIDERED_PER_CONTEXT = 500;
 
-    static int getBigramNodeProbability(const BinaryDictionaryInfo *const binaryDictionaryInfo,
+    static int getBigramNodeProbability(
+            const DictionaryStructureWithBufferPolicy *const dictionaryStructurePolicy,
             const DicNode *const node, MultiBigramMap *multiBigramMap);
-    static void createAndGetPassingChildNode(DicNode *dicNode,
-            const DicNodeProximityFilter *const childrenFilter, DicNodeVector *childDicNodes);
 };
 } // namespace latinime
 #endif // LATINIME_DIC_NODE_UTILS_H
diff --git a/native/jni/src/suggest/core/dicnode/dic_nodes_cache.cpp b/native/jni/src/suggest/core/dicnode/dic_nodes_cache.cpp
index c3d2a2e..b6be47e 100644
--- a/native/jni/src/suggest/core/dicnode/dic_nodes_cache.cpp
+++ b/native/jni/src/suggest/core/dicnode/dic_nodes_cache.cpp
@@ -23,6 +23,11 @@
 
 namespace latinime {
 
+// The biggest value among MAX_CACHE_DIC_NODE_SIZE, MAX_CACHE_DIC_NODE_SIZE_FOR_SINGLE_POINT, ...
+const int DicNodesCache::LARGE_PRIORITY_QUEUE_CAPACITY = 310;
+// Capacity for reducing memory footprint.
+const int DicNodesCache::SMALL_PRIORITY_QUEUE_CAPACITY = 100;
+
 /**
  * Truncates all of the dicNodes so that they start at the given commit point.
  * Only called for multi-word typing input.
diff --git a/native/jni/src/suggest/core/dicnode/dic_nodes_cache.h b/native/jni/src/suggest/core/dicnode/dic_nodes_cache.h
index 7aab090..8493b6a 100644
--- a/native/jni/src/suggest/core/dicnode/dic_nodes_cache.h
+++ b/native/jni/src/suggest/core/dicnode/dic_nodes_cache.h
@@ -31,25 +31,32 @@
  */
 class DicNodesCache {
  public:
-    AK_FORCE_INLINE DicNodesCache()
-            : mActiveDicNodes(&mDicNodePriorityQueues[DIC_NODES_CACHE_INITIAL_QUEUE_ID_ACTIVE]),
-              mNextActiveDicNodes(&mDicNodePriorityQueues[
-                      DIC_NODES_CACHE_INITIAL_QUEUE_ID_NEXT_ACTIVE]),
-              mTerminalDicNodes(&mDicNodePriorityQueues[DIC_NODES_CACHE_INITIAL_QUEUE_ID_TERMINAL]),
-              mCachedDicNodesForContinuousSuggestion(&mDicNodePriorityQueues[
-                      DIC_NODES_CACHE_INITIAL_QUEUE_ID_CACHE_FOR_CONTINUOUS_SUGGESTION]),
-              mInputIndex(0), mLastCachedInputIndex(0) {
-    }
+    AK_FORCE_INLINE explicit DicNodesCache(const bool usesLargeCapacityCache)
+            : mUsesLargeCapacityCache(usesLargeCapacityCache),
+              mDicNodePriorityQueue0(getCacheCapacity()),
+              mDicNodePriorityQueue1(getCacheCapacity()),
+              mDicNodePriorityQueue2(getCacheCapacity()),
+              mDicNodePriorityQueueForTerminal(MAX_RESULTS),
+              mActiveDicNodes(&mDicNodePriorityQueue0),
+              mNextActiveDicNodes(&mDicNodePriorityQueue1),
+              mCachedDicNodesForContinuousSuggestion(&mDicNodePriorityQueue2),
+              mTerminalDicNodes(&mDicNodePriorityQueueForTerminal),
+              mInputIndex(0), mLastCachedInputIndex(0) {}
 
     AK_FORCE_INLINE virtual ~DicNodesCache() {}
 
     AK_FORCE_INLINE void reset(const int nextActiveSize, const int terminalSize) {
         mInputIndex = 0;
         mLastCachedInputIndex = 0;
-        mActiveDicNodes->reset();
-        mNextActiveDicNodes->clearAndResize(nextActiveSize);
+        // We want to use the max capacity for the current active dic node queue.
+        mActiveDicNodes->clearAndResizeToCapacity();
+        // nextActiveSize is used to limit the next iteration's active dic node size.
+        const int nextActiveSizeFittingToTheCapacity = min(nextActiveSize, getCacheCapacity());
+        mNextActiveDicNodes->clearAndResize(nextActiveSizeFittingToTheCapacity);
         mTerminalDicNodes->clearAndResize(terminalSize);
-        mCachedDicNodesForContinuousSuggestion->reset();
+        // We want to use the max capacity for the cached dic nodes that will be used for the
+        // continuous suggestion.
+        mCachedDicNodesForContinuousSuggestion->clearAndResizeToCapacity();
     }
 
     AK_FORCE_INLINE void continueSearch() {
@@ -157,21 +164,35 @@
         return tmp;
     }
 
+    AK_FORCE_INLINE int getCacheCapacity() const {
+        return mUsesLargeCapacityCache ?
+                LARGE_PRIORITY_QUEUE_CAPACITY : SMALL_PRIORITY_QUEUE_CAPACITY;
+    }
+
     AK_FORCE_INLINE void resetTemporaryCaches() {
         mActiveDicNodes->clear();
         mNextActiveDicNodes->clear();
         mTerminalDicNodes->clear();
     }
 
-    DicNodePriorityQueue mDicNodePriorityQueues[DIC_NODES_CACHE_PRIORITY_QUEUES_SIZE];
+    static const int LARGE_PRIORITY_QUEUE_CAPACITY;
+    static const int SMALL_PRIORITY_QUEUE_CAPACITY;
+
+    const bool mUsesLargeCapacityCache;
+    // Instances
+    DicNodePriorityQueue mDicNodePriorityQueue0;
+    DicNodePriorityQueue mDicNodePriorityQueue1;
+    DicNodePriorityQueue mDicNodePriorityQueue2;
+    DicNodePriorityQueue mDicNodePriorityQueueForTerminal;
+
     // Active dicNodes currently being expanded.
     DicNodePriorityQueue *mActiveDicNodes;
     // Next dicNodes to be expanded.
     DicNodePriorityQueue *mNextActiveDicNodes;
-    // Current top terminal dicNodes.
-    DicNodePriorityQueue *mTerminalDicNodes;
     // Cached dicNodes used for continuous suggestion.
     DicNodePriorityQueue *mCachedDicNodesForContinuousSuggestion;
+    // Current top terminal dicNodes.
+    DicNodePriorityQueue *mTerminalDicNodes;
     int mInputIndex;
     int mLastCachedInputIndex;
 };
diff --git a/native/jni/src/suggest/core/dicnode/internal/dic_node_state_output.h b/native/jni/src/suggest/core/dicnode/internal/dic_node_state_output.h
index 45c7f5c..74eb5df 100644
--- a/native/jni/src/suggest/core/dicnode/internal/dic_node_state_output.h
+++ b/native/jni/src/suggest/core/dicnode/internal/dic_node_state_output.h
@@ -49,8 +49,10 @@
     void addMergedNodeCodePoints(const uint16_t mergedNodeCodePointCount,
             const int *const mergedNodeCodePoints) {
         if (mergedNodeCodePoints) {
+            const int additionalCodePointCount = min(static_cast<int>(mergedNodeCodePointCount),
+                    MAX_WORD_LENGTH - mOutputtedCodePointCount);
             memcpy(&mCodePointsBuf[mOutputtedCodePointCount], mergedNodeCodePoints,
-                    mergedNodeCodePointCount * sizeof(mCodePointsBuf[0]));
+                    additionalCodePointCount * sizeof(mCodePointsBuf[0]));
             mOutputtedCodePointCount = static_cast<uint16_t>(
                     mOutputtedCodePointCount + mergedNodeCodePointCount);
             if (mOutputtedCodePointCount < MAX_WORD_LENGTH) {
diff --git a/native/jni/src/suggest/core/dicnode/internal/dic_node_state_prevword.h b/native/jni/src/suggest/core/dicnode/internal/dic_node_state_prevword.h
index 5854f4f..b898620 100644
--- a/native/jni/src/suggest/core/dicnode/internal/dic_node_state_prevword.h
+++ b/native/jni/src/suggest/core/dicnode/internal/dic_node_state_prevword.h
@@ -22,6 +22,7 @@
 
 #include "defines.h"
 #include "suggest/core/dicnode/dic_node_utils.h"
+#include "suggest/core/layout/proximity_info_state.h"
 
 namespace latinime {
 
@@ -29,9 +30,8 @@
  public:
     AK_FORCE_INLINE DicNodeStatePrevWord()
             : mPrevWordCount(0), mPrevWordLength(0), mPrevWordStart(0), mPrevWordProbability(0),
-              mPrevWordNodePos(NOT_A_VALID_WORD_POS) {
+              mPrevWordNodePos(NOT_A_DICT_POS), mSecondWordFirstInputIndex(NOT_AN_INDEX) {
         memset(mPrevWord, 0, sizeof(mPrevWord));
-        memset(mPrevSpacePositions, 0, sizeof(mPrevSpacePositions));
     }
 
     virtual ~DicNodeStatePrevWord() {}
@@ -41,8 +41,8 @@
         mPrevWordCount = 0;
         mPrevWordStart = 0;
         mPrevWordProbability = -1;
-        mPrevWordNodePos = NOT_A_VALID_WORD_POS;
-        memset(mPrevSpacePositions, 0, sizeof(mPrevSpacePositions));
+        mPrevWordNodePos = NOT_A_DICT_POS;
+        mSecondWordFirstInputIndex = NOT_AN_INDEX;
     }
 
     void init(const int prevWordNodePos) {
@@ -51,7 +51,7 @@
         mPrevWordStart = 0;
         mPrevWordProbability = -1;
         mPrevWordNodePos = prevWordNodePos;
-        memset(mPrevSpacePositions, 0, sizeof(mPrevSpacePositions));
+        mSecondWordFirstInputIndex = NOT_AN_INDEX;
     }
 
     // Init by copy
@@ -61,24 +61,26 @@
         mPrevWordStart = prevWord->mPrevWordStart;
         mPrevWordProbability = prevWord->mPrevWordProbability;
         mPrevWordNodePos = prevWord->mPrevWordNodePos;
+        mSecondWordFirstInputIndex = prevWord->mSecondWordFirstInputIndex;
         memcpy(mPrevWord, prevWord->mPrevWord, prevWord->mPrevWordLength * sizeof(mPrevWord[0]));
-        memcpy(mPrevSpacePositions, prevWord->mPrevSpacePositions, sizeof(mPrevSpacePositions));
     }
 
     void init(const int16_t prevWordCount, const int16_t prevWordProbability,
             const int prevWordNodePos, const int *const src0, const int16_t length0,
-            const int *const src1, const int16_t length1, const int *const prevSpacePositions,
-            const int lastInputIndex) {
-        mPrevWordCount = prevWordCount;
+            const int *const src1, const int16_t length1,
+            const int prevWordSecondWordFirstInputIndex, const int lastInputIndex) {
+        mPrevWordCount = min(prevWordCount, static_cast<int16_t>(MAX_RESULTS));
         mPrevWordProbability = prevWordProbability;
         mPrevWordNodePos = prevWordNodePos;
-        const int twoWordsLen =
+        int twoWordsLen =
                 DicNodeUtils::appendTwoWords(src0, length0, src1, length1, mPrevWord);
+        if (twoWordsLen >= MAX_WORD_LENGTH) {
+            twoWordsLen = MAX_WORD_LENGTH - 1;
+        }
         mPrevWord[twoWordsLen] = KEYCODE_SPACE;
         mPrevWordStart = length0;
         mPrevWordLength = static_cast<int16_t>(twoWordsLen + 1);
-        memcpy(mPrevSpacePositions, prevSpacePositions, sizeof(mPrevSpacePositions));
-        mPrevSpacePositions[mPrevWordCount - 1] = lastInputIndex;
+        mSecondWordFirstInputIndex = prevWordSecondWordFirstInputIndex;
     }
 
     void truncate(const int offset) {
@@ -93,11 +95,12 @@
         mPrevWordLength = newPrevWordLength;
     }
 
-    void outputSpacePositions(int *spaceIndices) const {
-        // Convert uint16_t to int
-        for (int i = 0; i < MAX_RESULTS; i++) {
-            spaceIndices[i] = mPrevSpacePositions[i];
-        }
+    void setSecondWordFirstInputIndex(const int inputIndex) {
+        mSecondWordFirstInputIndex = inputIndex;
+    }
+
+    int getSecondWordFirstInputIndex() const {
+        return mSecondWordFirstInputIndex;
     }
 
     // TODO: remove
@@ -113,10 +116,6 @@
         return mPrevWordStart;
     }
 
-    int16_t getPrevWordProbability() const {
-        return mPrevWordProbability;
-    }
-
     int getPrevWordNodePos() const {
         return mPrevWordNodePos;
     }
@@ -139,8 +138,6 @@
 
     // TODO: Move to private
     int mPrevWord[MAX_WORD_LENGTH];
-    // TODO: Move to private
-    int mPrevSpacePositions[MAX_RESULTS];
 
  private:
     // Caution!!!
@@ -151,6 +148,7 @@
     int16_t mPrevWordStart;
     int16_t mPrevWordProbability;
     int mPrevWordNodePos;
+    int mSecondWordFirstInputIndex;
 };
 } // namespace latinime
 #endif // LATINIME_DIC_NODE_STATE_PREVWORD_H
diff --git a/native/jni/src/suggest/core/dictionary/bigram_dictionary.cpp b/native/jni/src/suggest/core/dictionary/bigram_dictionary.cpp
index 3751ae5..71f4ef6 100644
--- a/native/jni/src/suggest/core/dictionary/bigram_dictionary.cpp
+++ b/native/jni/src/suggest/core/dictionary/bigram_dictionary.cpp
@@ -22,15 +22,15 @@
 
 #include "defines.h"
 #include "suggest/core/dictionary/binary_dictionary_bigrams_iterator.h"
-#include "suggest/core/dictionary/binary_dictionary_info.h"
 #include "suggest/core/dictionary/dictionary.h"
-#include "suggest/core/dictionary/probability_utils.h"
+#include "suggest/core/policy/dictionary_structure_with_buffer_policy.h"
 #include "utils/char_utils.h"
 
 namespace latinime {
 
-BigramDictionary::BigramDictionary(const BinaryDictionaryInfo *const binaryDictionaryInfo)
-        : mBinaryDictionaryInfo(binaryDictionaryInfo) {
+BigramDictionary::BigramDictionary(
+        const DictionaryStructureWithBufferPolicy *const dictionaryStructurePolicy)
+        : mDictionaryStructurePolicy(dictionaryStructurePolicy) {
     if (DEBUG_DICT) {
         AKLOGI("BigramDictionary - constructor");
     }
@@ -112,22 +112,28 @@
     int bigramCount = 0;
     int unigramProbability = 0;
     int bigramBuffer[MAX_WORD_LENGTH];
-    BinaryDictionaryBigramsIterator bigramsIt(mBinaryDictionaryInfo, pos);
+    BinaryDictionaryBigramsIterator bigramsIt(
+            mDictionaryStructurePolicy->getBigramsStructurePolicy(), pos);
     while (bigramsIt.hasNext()) {
         bigramsIt.next();
-        const int length = mBinaryDictionaryInfo->getStructurePolicy()->
-                getCodePointsAndProbabilityAndReturnCodePointCount(
-                        mBinaryDictionaryInfo, bigramsIt.getBigramPos(), MAX_WORD_LENGTH,
-                        bigramBuffer, &unigramProbability);
+        if (bigramsIt.getBigramPos() == NOT_A_DICT_POS) {
+            continue;
+        }
+        const int codePointCount = mDictionaryStructurePolicy->
+                getCodePointsAndProbabilityAndReturnCodePointCount(bigramsIt.getBigramPos(),
+                        MAX_WORD_LENGTH, bigramBuffer, &unigramProbability);
+        if (codePointCount <= 0) {
+            continue;
+        }
         // Due to space constraints, the probability for bigrams is approximate - the lower the
         // unigram probability, the worse the precision. The theoritical maximum error in
         // resulting probability is 8 - although in the practice it's never bigger than 3 or 4
         // in very bad cases. This means that sometimes, we'll see some bigrams interverted
         // here, but it can't get too bad.
-        const int probability = ProbabilityUtils::computeProbabilityForBigram(
+        const int probability = mDictionaryStructurePolicy->getProbability(
                 unigramProbability, bigramsIt.getProbability());
-        addWordBigram(bigramBuffer, length, probability, outBigramProbability, outBigramCodePoints,
-                outputTypes);
+        addWordBigram(bigramBuffer, codePointCount, probability, outBigramProbability,
+                outBigramCodePoints, outputTypes);
         ++bigramCount;
     }
     return min(bigramCount, MAX_RESULTS);
@@ -138,30 +144,32 @@
 int BigramDictionary::getBigramListPositionForWord(const int *prevWord, const int prevWordLength,
         const bool forceLowerCaseSearch) const {
     if (0 >= prevWordLength) return NOT_A_DICT_POS;
-    int pos = mBinaryDictionaryInfo->getStructurePolicy()->getTerminalNodePositionOfWord(
-            mBinaryDictionaryInfo, prevWord, prevWordLength, forceLowerCaseSearch);
-    if (NOT_A_VALID_WORD_POS == pos) return NOT_A_DICT_POS;
-    return mBinaryDictionaryInfo->getStructurePolicy()->getBigramsPositionOfNode(
-            mBinaryDictionaryInfo, pos);
+    int pos = mDictionaryStructurePolicy->getTerminalNodePositionOfWord(prevWord, prevWordLength,
+            forceLowerCaseSearch);
+    if (NOT_A_DICT_POS == pos) return NOT_A_DICT_POS;
+    return mDictionaryStructurePolicy->getBigramsPositionOfPtNode(pos);
 }
 
-bool BigramDictionary::isValidBigram(const int *word0, int length0, const int *word1,
+int BigramDictionary::getBigramProbability(const int *word0, int length0, const int *word1,
         int length1) const {
     int pos = getBigramListPositionForWord(word0, length0, false /* forceLowerCaseSearch */);
     // getBigramListPositionForWord returns 0 if this word isn't in the dictionary or has no bigrams
-    if (NOT_A_DICT_POS == pos) return false;
-    int nextWordPos = mBinaryDictionaryInfo->getStructurePolicy()->getTerminalNodePositionOfWord(
-            mBinaryDictionaryInfo, word1, length1, false /* forceLowerCaseSearch */);
-    if (NOT_A_VALID_WORD_POS == nextWordPos) return false;
+    if (NOT_A_DICT_POS == pos) return NOT_A_PROBABILITY;
+    int nextWordPos = mDictionaryStructurePolicy->getTerminalNodePositionOfWord(word1, length1,
+            false /* forceLowerCaseSearch */);
+    if (NOT_A_DICT_POS == nextWordPos) return NOT_A_PROBABILITY;
 
-    BinaryDictionaryBigramsIterator bigramsIt(mBinaryDictionaryInfo, pos);
+    BinaryDictionaryBigramsIterator bigramsIt(
+            mDictionaryStructurePolicy->getBigramsStructurePolicy(), pos);
     while (bigramsIt.hasNext()) {
         bigramsIt.next();
         if (bigramsIt.getBigramPos() == nextWordPos) {
-            return true;
+            return mDictionaryStructurePolicy->getProbability(
+                    mDictionaryStructurePolicy->getUnigramProbabilityOfPtNode(nextWordPos),
+                    bigramsIt.getProbability());
         }
     }
-    return false;
+    return NOT_A_PROBABILITY;
 }
 
 // TODO: Move functions related to bigram to here
diff --git a/native/jni/src/suggest/core/dictionary/bigram_dictionary.h b/native/jni/src/suggest/core/dictionary/bigram_dictionary.h
index 438c34c..8af7ee7 100644
--- a/native/jni/src/suggest/core/dictionary/bigram_dictionary.h
+++ b/native/jni/src/suggest/core/dictionary/bigram_dictionary.h
@@ -21,15 +21,15 @@
 
 namespace latinime {
 
-class BinaryDictionaryInfo;
+class DictionaryStructureWithBufferPolicy;
 
 class BigramDictionary {
  public:
-    BigramDictionary(const BinaryDictionaryInfo *const binaryDictionaryInfo);
+    BigramDictionary(const DictionaryStructureWithBufferPolicy *const dictionaryStructurePolicy);
 
     int getPredictions(const int *word, int length, int *outBigramCodePoints,
             int *outBigramProbability, int *outputTypes) const;
-    bool isValidBigram(const int *word1, int length1, const int *word2, int length2) const;
+    int getBigramProbability(const int *word1, int length1, const int *word2, int length2) const;
     ~BigramDictionary();
 
  private:
@@ -40,7 +40,7 @@
     int getBigramListPositionForWord(const int *prevWord, const int prevWordLength,
             const bool forceLowerCaseSearch) const;
 
-    const BinaryDictionaryInfo *const mBinaryDictionaryInfo;
+    const DictionaryStructureWithBufferPolicy *const mDictionaryStructurePolicy;
 };
 } // namespace latinime
 #endif // LATINIME_BIGRAM_DICTIONARY_H
diff --git a/native/jni/src/suggest/core/dictionary/binary_dictionary_bigrams_iterator.h b/native/jni/src/suggest/core/dictionary/binary_dictionary_bigrams_iterator.h
index 8cbb129..d16ac47 100644
--- a/native/jni/src/suggest/core/dictionary/binary_dictionary_bigrams_iterator.h
+++ b/native/jni/src/suggest/core/dictionary/binary_dictionary_bigrams_iterator.h
@@ -18,51 +18,41 @@
 #define LATINIME_BINARY_DICTIONARY_BIGRAMS_ITERATOR_H
 
 #include "defines.h"
-#include "suggest/core/dictionary/binary_dictionary_info.h"
-#include "suggest/core/dictionary/binary_dictionary_terminal_attributes_reading_utils.h"
+#include "suggest/core/policy/dictionary_bigrams_structure_policy.h"
 
 namespace latinime {
 
 class BinaryDictionaryBigramsIterator {
  public:
     BinaryDictionaryBigramsIterator(
-            const BinaryDictionaryInfo *const binaryDictionaryInfo, const int pos)
-            : mBinaryDictionaryInfo(binaryDictionaryInfo), mPos(pos), mBigramFlags(0),
-              mBigramPos(NOT_A_DICT_POS), mHasNext(pos != NOT_A_DICT_POS) {}
+            const DictionaryBigramsStructurePolicy *const bigramsStructurePolicy, const int pos)
+            : mBigramsStructurePolicy(bigramsStructurePolicy), mPos(pos),
+              mBigramPos(NOT_A_DICT_POS), mProbability(NOT_A_PROBABILITY),
+              mHasNext(pos != NOT_A_DICT_POS) {}
 
     AK_FORCE_INLINE bool hasNext() const {
         return mHasNext;
     }
 
     AK_FORCE_INLINE void next() {
-        mBigramFlags = BinaryDictionaryTerminalAttributesReadingUtils::getFlagsAndForwardPointer(
-                mBinaryDictionaryInfo, &mPos);
-        mBigramPos =
-                BinaryDictionaryTerminalAttributesReadingUtils::getBigramAddressAndForwardPointer(
-                        mBinaryDictionaryInfo, mBigramFlags, &mPos);
-        mHasNext = BinaryDictionaryTerminalAttributesReadingUtils::hasNext(mBigramFlags);
+        mBigramsStructurePolicy->getNextBigram(&mBigramPos, &mProbability, &mHasNext, &mPos);
     }
 
     AK_FORCE_INLINE int getProbability() const {
-        return BinaryDictionaryTerminalAttributesReadingUtils::getProbabilityFromFlags(
-                mBigramFlags);
+        return mProbability;
     }
 
     AK_FORCE_INLINE int getBigramPos() const {
         return mBigramPos;
     }
 
-    AK_FORCE_INLINE int getFlags() const {
-        return mBigramFlags;
-    }
-
  private:
     DISALLOW_COPY_AND_ASSIGN(BinaryDictionaryBigramsIterator);
 
-    const BinaryDictionaryInfo *const mBinaryDictionaryInfo;
+    const DictionaryBigramsStructurePolicy *const mBigramsStructurePolicy;
     int mPos;
-    BinaryDictionaryTerminalAttributesReadingUtils::BigramFlags mBigramFlags;
     int mBigramPos;
+    int mProbability;
     bool mHasNext;
 };
 } // namespace latinime
diff --git a/native/jni/src/suggest/core/dictionary/binary_dictionary_format_utils.cpp b/native/jni/src/suggest/core/dictionary/binary_dictionary_format_utils.cpp
deleted file mode 100644
index 0e8d72f..0000000
--- a/native/jni/src/suggest/core/dictionary/binary_dictionary_format_utils.cpp
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * 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.
- */
-
-#include "suggest/core/dictionary/binary_dictionary_format_utils.h"
-
-namespace latinime {
-
-/**
- * Dictionary size
- */
-// Any file smaller than this is not a dictionary.
-const int BinaryDictionaryFormatUtils::DICTIONARY_MINIMUM_SIZE = 4;
-
-/**
- * Format versions
- */
-
-// The versions of Latin IME that only handle format version 1 only test for the magic
-// number, so we had to change it so that version 2 files would be rejected by older
-// implementations. On this occasion, we made the magic number 32 bits long.
-const uint32_t BinaryDictionaryFormatUtils::HEADER_VERSION_2_MAGIC_NUMBER = 0x9BC13AFE;
-// Magic number (4 bytes), version (2 bytes), options (2 bytes), header size (4 bytes) = 12
-const int BinaryDictionaryFormatUtils::HEADER_VERSION_2_MINIMUM_SIZE = 12;
-
-/* static */ BinaryDictionaryFormatUtils::FORMAT_VERSION
-        BinaryDictionaryFormatUtils::detectFormatVersion(const uint8_t *const dict,
-                const int dictSize) {
-    // The magic number is stored big-endian.
-    // If the dictionary is less than 4 bytes, we can't even read the magic number, so we don't
-    // understand this format.
-    if (dictSize < DICTIONARY_MINIMUM_SIZE) {
-        return UNKNOWN_VERSION;
-    }
-    const uint32_t magicNumber = ByteArrayUtils::readUint32(dict, 0);
-    switch (magicNumber) {
-        case HEADER_VERSION_2_MAGIC_NUMBER:
-            // Version 2 header are at least 12 bytes long.
-            // If this header has the version 2 magic number but is less than 12 bytes long,
-            // then it's an unknown format and we need to avoid confidently reading the next bytes.
-            if (dictSize < HEADER_VERSION_2_MINIMUM_SIZE) {
-                return UNKNOWN_VERSION;
-            }
-            // Version 2 header is as follows:
-            // Magic number (4 bytes) 0x9B 0xC1 0x3A 0xFE
-            // Version number (2 bytes)
-            // Options (2 bytes)
-            // Header size (4 bytes) : integer, big endian
-            if (ByteArrayUtils::readUint16(dict, 4) == 2) {
-                return VERSION_2;
-            } else if (ByteArrayUtils::readUint16(dict, 4) == 3) {
-                return VERSION_3;
-            } else {
-                return UNKNOWN_VERSION;
-            }
-        default:
-            return UNKNOWN_VERSION;
-    }
-}
-
-} // namespace latinime
diff --git a/native/jni/src/suggest/core/dictionary/binary_dictionary_header.cpp b/native/jni/src/suggest/core/dictionary/binary_dictionary_header.cpp
deleted file mode 100644
index 91c643a..0000000
--- a/native/jni/src/suggest/core/dictionary/binary_dictionary_header.cpp
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * 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.
- */
-
-#include "suggest/core/dictionary/binary_dictionary_header.h"
-
-#include "defines.h"
-#include "suggest/core/dictionary/binary_dictionary_info.h"
-
-namespace latinime {
-
-const char *const BinaryDictionaryHeader::MULTIPLE_WORDS_DEMOTION_RATE_KEY =
-        "MULTIPLE_WORDS_DEMOTION_RATE";
-const float BinaryDictionaryHeader::DEFAULT_MULTI_WORD_COST_MULTIPLIER = 1.0f;
-const float BinaryDictionaryHeader::MULTI_WORD_COST_MULTIPLIER_SCALE = 100.0f;
-
-BinaryDictionaryHeader::BinaryDictionaryHeader(
-        const BinaryDictionaryInfo *const binaryDictionaryInfo)
-        : mBinaryDictionaryInfo(binaryDictionaryInfo),
-          mDictionaryFlags(BinaryDictionaryHeaderReadingUtils::getFlags(binaryDictionaryInfo)),
-          mSize(BinaryDictionaryHeaderReadingUtils::getHeaderSize(binaryDictionaryInfo)),
-          mMultiWordCostMultiplier(readMultiWordCostMultiplier()) {}
-
-float BinaryDictionaryHeader::readMultiWordCostMultiplier() const {
-    const int headerValue = BinaryDictionaryHeaderReadingUtils::readHeaderValueInt(
-            mBinaryDictionaryInfo, MULTIPLE_WORDS_DEMOTION_RATE_KEY);
-    if (headerValue == S_INT_MIN) {
-        // not found
-        return DEFAULT_MULTI_WORD_COST_MULTIPLIER;
-    }
-    if (headerValue <= 0) {
-        return static_cast<float>(MAX_VALUE_FOR_WEIGHTING);
-    }
-    return MULTI_WORD_COST_MULTIPLIER_SCALE / static_cast<float>(headerValue);
-}
-
-} // namespace latinime
diff --git a/native/jni/src/suggest/core/dictionary/binary_dictionary_header.h b/native/jni/src/suggest/core/dictionary/binary_dictionary_header.h
deleted file mode 100644
index 240512b..0000000
--- a/native/jni/src/suggest/core/dictionary/binary_dictionary_header.h
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * 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.
- */
-
-#ifndef LATINIME_BINARY_DICTIONARY_HEADER_H
-#define LATINIME_BINARY_DICTIONARY_HEADER_H
-
-#include "defines.h"
-#include "suggest/core/dictionary/binary_dictionary_header_reading_utils.h"
-
-namespace latinime {
-
-class BinaryDictionaryInfo;
-
-/**
- * This class abstracts dictionary header structures and provide interface to access dictionary
- * header information.
- */
-class BinaryDictionaryHeader {
- public:
-    explicit BinaryDictionaryHeader(const BinaryDictionaryInfo *const binaryDictionaryInfo);
-
-    AK_FORCE_INLINE int getSize() const {
-        return mSize;
-    }
-
-    AK_FORCE_INLINE bool supportsDynamicUpdate() const {
-        return BinaryDictionaryHeaderReadingUtils::supportsDynamicUpdate(mDictionaryFlags);
-    }
-
-    AK_FORCE_INLINE bool requiresGermanUmlautProcessing() const {
-        return BinaryDictionaryHeaderReadingUtils::requiresGermanUmlautProcessing(mDictionaryFlags);
-    }
-
-    AK_FORCE_INLINE bool requiresFrenchLigatureProcessing() const {
-        return BinaryDictionaryHeaderReadingUtils::requiresFrenchLigatureProcessing(
-                mDictionaryFlags);
-    }
-
-    AK_FORCE_INLINE float getMultiWordCostMultiplier() const {
-        return mMultiWordCostMultiplier;
-    }
-
-    AK_FORCE_INLINE void readHeaderValueOrQuestionMark(const char *const key,
-            int *outValue, int outValueSize) const {
-        if (outValueSize <= 0) return;
-        if (outValueSize == 1) {
-            outValue[0] = '\0';
-            return;
-        }
-        if (!BinaryDictionaryHeaderReadingUtils::readHeaderValue(mBinaryDictionaryInfo,
-                key, outValue, outValueSize)) {
-            outValue[0] = '?';
-            outValue[1] = '\0';
-        }
-    }
-
- private:
-    DISALLOW_COPY_AND_ASSIGN(BinaryDictionaryHeader);
-
-    static const char *const MULTIPLE_WORDS_DEMOTION_RATE_KEY;
-    static const float DEFAULT_MULTI_WORD_COST_MULTIPLIER;
-    static const float MULTI_WORD_COST_MULTIPLIER_SCALE;
-
-    const BinaryDictionaryInfo *const mBinaryDictionaryInfo;
-    const BinaryDictionaryHeaderReadingUtils::DictionaryFlags mDictionaryFlags;
-    const int mSize;
-    const float mMultiWordCostMultiplier;
-
-    float readMultiWordCostMultiplier() const;
-};
-} // namespace latinime
-#endif // LATINIME_BINARY_DICTIONARY_HEADER_H
diff --git a/native/jni/src/suggest/core/dictionary/binary_dictionary_header_reading_utils.cpp b/native/jni/src/suggest/core/dictionary/binary_dictionary_header_reading_utils.cpp
deleted file mode 100644
index a57b0f8..0000000
--- a/native/jni/src/suggest/core/dictionary/binary_dictionary_header_reading_utils.cpp
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- * 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.
- */
-
-#include "suggest/core/dictionary/binary_dictionary_header_reading_utils.h"
-
-#include <cctype>
-#include <cstdlib>
-
-#include "defines.h"
-#include "suggest/core/dictionary/binary_dictionary_info.h"
-
-namespace latinime {
-
-const int BinaryDictionaryHeaderReadingUtils::MAX_OPTION_KEY_LENGTH = 256;
-
-const int BinaryDictionaryHeaderReadingUtils::VERSION_2_HEADER_MAGIC_NUMBER_SIZE = 4;
-const int BinaryDictionaryHeaderReadingUtils::VERSION_2_HEADER_DICTIONARY_VERSION_SIZE = 2;
-const int BinaryDictionaryHeaderReadingUtils::VERSION_2_HEADER_FLAG_SIZE = 2;
-const int BinaryDictionaryHeaderReadingUtils::VERSION_2_HEADER_SIZE_FIELD_SIZE = 4;
-
-const BinaryDictionaryHeaderReadingUtils::DictionaryFlags
-        BinaryDictionaryHeaderReadingUtils::NO_FLAGS = 0;
-// Flags for special processing
-// Those *must* match the flags in makedict (BinaryDictInputOutput#*_PROCESSING_FLAG) or
-// something very bad (like, the apocalypse) will happen. Please update both at the same time.
-const BinaryDictionaryHeaderReadingUtils::DictionaryFlags
-        BinaryDictionaryHeaderReadingUtils::GERMAN_UMLAUT_PROCESSING_FLAG = 0x1;
-const BinaryDictionaryHeaderReadingUtils::DictionaryFlags
-        BinaryDictionaryHeaderReadingUtils::SUPPORTS_DYNAMIC_UPDATE_FLAG = 0x2;
-const BinaryDictionaryHeaderReadingUtils::DictionaryFlags
-        BinaryDictionaryHeaderReadingUtils::FRENCH_LIGATURE_PROCESSING_FLAG = 0x4;
-
-/* static */ int BinaryDictionaryHeaderReadingUtils::getHeaderSize(
-        const BinaryDictionaryInfo *const binaryDictionaryInfo) {
-    switch (getHeaderVersion(binaryDictionaryInfo->getFormat())) {
-        case HEADER_VERSION_2:
-            // See the format of the header in the comment in
-            // BinaryDictionaryFormatUtils::detectFormatVersion()
-            return ByteArrayUtils::readUint32(binaryDictionaryInfo->getDictBuf(),
-                    VERSION_2_HEADER_MAGIC_NUMBER_SIZE + VERSION_2_HEADER_DICTIONARY_VERSION_SIZE
-                            + VERSION_2_HEADER_FLAG_SIZE);
-        default:
-            return S_INT_MAX;
-    }
-}
-
-/* static */ BinaryDictionaryHeaderReadingUtils::DictionaryFlags
-        BinaryDictionaryHeaderReadingUtils::getFlags(
-                const BinaryDictionaryInfo *const binaryDictionaryInfo) {
-    switch (getHeaderVersion(binaryDictionaryInfo->getFormat())) {
-        case HEADER_VERSION_2:
-            return ByteArrayUtils::readUint16(binaryDictionaryInfo->getDictBuf(),
-                    VERSION_2_HEADER_MAGIC_NUMBER_SIZE + VERSION_2_HEADER_DICTIONARY_VERSION_SIZE);
-        default:
-            return NO_FLAGS;
-    }
-}
-
-// Returns if the key is found or not and reads the found value into outValue.
-/* static */ bool BinaryDictionaryHeaderReadingUtils::readHeaderValue(
-        const BinaryDictionaryInfo *const binaryDictionaryInfo,
-        const char *const key, int *outValue, const int outValueSize) {
-    if (outValueSize <= 0) {
-        return false;
-    }
-    const int headerSize = getHeaderSize(binaryDictionaryInfo);
-    int pos = getHeaderOptionsPosition(binaryDictionaryInfo->getFormat());
-    if (pos == NOT_A_DICT_POS) {
-        // The header doesn't have header options.
-        return false;
-    }
-    while (pos < headerSize) {
-        if(ByteArrayUtils::compareStringInBufferWithCharArray(
-                binaryDictionaryInfo->getDictBuf(), key, headerSize - pos, &pos) == 0) {
-            // The key was found.
-            const int length = ByteArrayUtils::readStringAndAdvancePosition(
-                    binaryDictionaryInfo->getDictBuf(), outValueSize, outValue, &pos);
-            // Add a 0 terminator to the string.
-            outValue[length < outValueSize ? length : outValueSize - 1] = '\0';
-            return true;
-        }
-        ByteArrayUtils::advancePositionToBehindString(
-                binaryDictionaryInfo->getDictBuf(), headerSize - pos, &pos);
-    }
-    // The key was not found.
-    return false;
-}
-
-/* static */ int BinaryDictionaryHeaderReadingUtils::readHeaderValueInt(
-        const BinaryDictionaryInfo *const binaryDictionaryInfo, const char *const key) {
-    const int bufferSize = LARGEST_INT_DIGIT_COUNT;
-    int intBuffer[bufferSize];
-    char charBuffer[bufferSize];
-    if (!readHeaderValue(binaryDictionaryInfo, key, intBuffer, bufferSize)) {
-        return S_INT_MIN;
-    }
-    for (int i = 0; i < bufferSize; ++i) {
-        charBuffer[i] = intBuffer[i];
-        if (charBuffer[i] == '0') {
-            break;
-        }
-        if (!isdigit(charBuffer[i])) {
-            // If not a number, return S_INT_MIN
-            return S_INT_MIN;
-        }
-    }
-    return atoi(charBuffer);
-}
-
-} // namespace latinime
diff --git a/native/jni/src/suggest/core/dictionary/binary_dictionary_header_reading_utils.h b/native/jni/src/suggest/core/dictionary/binary_dictionary_header_reading_utils.h
deleted file mode 100644
index 6174822..0000000
--- a/native/jni/src/suggest/core/dictionary/binary_dictionary_header_reading_utils.h
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * 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.
- */
-
-#ifndef LATINIME_DICTIONARY_HEADER_READING_UTILS_H
-#define LATINIME_DICTIONARY_HEADER_READING_UTILS_H
-
-#include <stdint.h>
-
-#include "defines.h"
-#include "suggest/core/dictionary/binary_dictionary_format_utils.h"
-
-namespace latinime {
-
-class BinaryDictionaryInfo;
-
-class BinaryDictionaryHeaderReadingUtils {
- public:
-    typedef uint16_t DictionaryFlags;
-
-    static const int MAX_OPTION_KEY_LENGTH;
-
-    static int getHeaderSize(const BinaryDictionaryInfo *const binaryDictionaryInfo);
-
-    static DictionaryFlags getFlags(const BinaryDictionaryInfo *const binaryDictionaryInfo);
-
-    static AK_FORCE_INLINE bool supportsDynamicUpdate(const DictionaryFlags flags) {
-        return (flags & SUPPORTS_DYNAMIC_UPDATE_FLAG) != 0;
-    }
-
-    static AK_FORCE_INLINE bool requiresGermanUmlautProcessing(const DictionaryFlags flags) {
-        return (flags & GERMAN_UMLAUT_PROCESSING_FLAG) != 0;
-    }
-
-    static AK_FORCE_INLINE bool requiresFrenchLigatureProcessing(const DictionaryFlags flags) {
-        return (flags & FRENCH_LIGATURE_PROCESSING_FLAG) != 0;
-    }
-
-    static AK_FORCE_INLINE int getHeaderOptionsPosition(
-            const BinaryDictionaryFormatUtils::FORMAT_VERSION dictionaryFormat) {
-        switch (getHeaderVersion(dictionaryFormat)) {
-        case HEADER_VERSION_2:
-            return VERSION_2_HEADER_MAGIC_NUMBER_SIZE + VERSION_2_HEADER_DICTIONARY_VERSION_SIZE
-                    + VERSION_2_HEADER_FLAG_SIZE + VERSION_2_HEADER_SIZE_FIELD_SIZE;
-            break;
-        default:
-            return NOT_A_DICT_POS;
-        }
-    }
-
-    static bool readHeaderValue(
-            const BinaryDictionaryInfo *const binaryDictionaryInfo,
-            const char *const key, int *outValue, const int outValueSize);
-
-    static int readHeaderValueInt(
-            const BinaryDictionaryInfo *const binaryDictionaryInfo, const char *const key);
-
- private:
-    DISALLOW_IMPLICIT_CONSTRUCTORS(BinaryDictionaryHeaderReadingUtils);
-
-    enum HEADER_VERSION {
-        HEADER_VERSION_2,
-        UNKNOWN_HEADER_VERSION
-    };
-
-    static const int VERSION_2_HEADER_MAGIC_NUMBER_SIZE;
-    static const int VERSION_2_HEADER_DICTIONARY_VERSION_SIZE;
-    static const int VERSION_2_HEADER_FLAG_SIZE;
-    static const int VERSION_2_HEADER_SIZE_FIELD_SIZE;
-
-    static const DictionaryFlags NO_FLAGS;
-    // Flags for special processing
-    // Those *must* match the flags in makedict (FormatSpec#*_PROCESSING_FLAGS) or
-    // something very bad (like, the apocalypse) will happen. Please update both at the same time.
-    static const DictionaryFlags GERMAN_UMLAUT_PROCESSING_FLAG;
-    static const DictionaryFlags SUPPORTS_DYNAMIC_UPDATE_FLAG;
-    static const DictionaryFlags FRENCH_LIGATURE_PROCESSING_FLAG;
-    static const DictionaryFlags CONTAINS_BIGRAMS_FLAG;
-
-    static HEADER_VERSION getHeaderVersion(
-            const BinaryDictionaryFormatUtils::FORMAT_VERSION formatVersion) {
-        switch(formatVersion) {
-            case BinaryDictionaryFormatUtils::VERSION_2:
-                // Fall through
-            case BinaryDictionaryFormatUtils::VERSION_3:
-                return HEADER_VERSION_2;
-            default:
-                return UNKNOWN_HEADER_VERSION;
-        }
-    }
-};
-}
-#endif /* LATINIME_DICTIONARY_HEADER_READING_UTILS_H */
diff --git a/native/jni/src/suggest/core/dictionary/binary_dictionary_info.h b/native/jni/src/suggest/core/dictionary/binary_dictionary_info.h
deleted file mode 100644
index cbea18f..0000000
--- a/native/jni/src/suggest/core/dictionary/binary_dictionary_info.h
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * 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.
- */
-
-#ifndef LATINIME_BINARY_DICTIONARY_INFO_H
-#define LATINIME_BINARY_DICTIONARY_INFO_H
-
-#include <stdint.h>
-
-#include "defines.h"
-#include "jni.h"
-#include "suggest/core/dictionary/binary_dictionary_format_utils.h"
-#include "suggest/core/dictionary/binary_dictionary_header.h"
-#include "suggest/policyimpl/dictionary/dictionary_structure_policy_factory.h"
-#include "utils/log_utils.h"
-
-namespace latinime {
-
-class BinaryDictionaryInfo {
- public:
-     AK_FORCE_INLINE BinaryDictionaryInfo(JNIEnv *env, const uint8_t *const dictBuf,
-            const int dictSize, const int mmapFd, const int dictBufOffset, const bool isUpdatable)
-            : mDictBuf(dictBuf), mDictSize(dictSize), mMmapFd(mmapFd),
-              mDictBufOffset(dictBufOffset), mIsUpdatable(isUpdatable),
-              mDictionaryFormat(BinaryDictionaryFormatUtils::detectFormatVersion(
-                      mDictBuf, mDictSize)),
-              mDictionaryHeader(this), mDictRoot(mDictBuf + mDictionaryHeader.getSize()),
-              mStructurePolicy(DictionaryStructurePolicyFactory::getDictionaryStructurePolicy(
-                      mDictionaryFormat)) {
-        logDictionaryInfo(env);
-    }
-
-    AK_FORCE_INLINE const uint8_t *getDictBuf() const {
-        return mDictBuf;
-    }
-
-    AK_FORCE_INLINE int getDictSize() const {
-        return mDictSize;
-    }
-
-    AK_FORCE_INLINE int getMmapFd() const {
-        return mMmapFd;
-    }
-
-    AK_FORCE_INLINE int getDictBufOffset() const {
-        return mDictBufOffset;
-    }
-
-    AK_FORCE_INLINE const uint8_t *getDictRoot() const {
-        return mDictRoot;
-    }
-
-    AK_FORCE_INLINE BinaryDictionaryFormatUtils::FORMAT_VERSION getFormat() const {
-        return mDictionaryFormat;
-    }
-
-    AK_FORCE_INLINE const BinaryDictionaryHeader *getHeader() const {
-        return &mDictionaryHeader;
-    }
-
-    AK_FORCE_INLINE bool isDynamicallyUpdatable() const {
-        // TODO: Support dynamic dictionary formats.
-        const bool isUpdatableDictionaryFormat = false;
-        return mIsUpdatable && isUpdatableDictionaryFormat;
-    }
-
-    AK_FORCE_INLINE const DictionaryStructurePolicy *getStructurePolicy() const {
-        return mStructurePolicy;
-    }
-
- private:
-    DISALLOW_COPY_AND_ASSIGN(BinaryDictionaryInfo);
-
-    const uint8_t *const mDictBuf;
-    const int mDictSize;
-    const int mMmapFd;
-    const int mDictBufOffset;
-    const bool mIsUpdatable;
-    const BinaryDictionaryFormatUtils::FORMAT_VERSION mDictionaryFormat;
-    const BinaryDictionaryHeader mDictionaryHeader;
-    const uint8_t *const mDictRoot;
-    const DictionaryStructurePolicy *const mStructurePolicy;
-
-    AK_FORCE_INLINE void logDictionaryInfo(JNIEnv *const env) const {
-        const int BUFFER_SIZE = 16;
-        int dictionaryIdCodePointBuffer[BUFFER_SIZE];
-        int versionStringCodePointBuffer[BUFFER_SIZE];
-        int dateStringCodePointBuffer[BUFFER_SIZE];
-        mDictionaryHeader.readHeaderValueOrQuestionMark("dictionary",
-                dictionaryIdCodePointBuffer, BUFFER_SIZE);
-        mDictionaryHeader.readHeaderValueOrQuestionMark("version",
-                versionStringCodePointBuffer, BUFFER_SIZE);
-        mDictionaryHeader.readHeaderValueOrQuestionMark("date",
-                dateStringCodePointBuffer, BUFFER_SIZE);
-
-        char dictionaryIdCharBuffer[BUFFER_SIZE];
-        char versionStringCharBuffer[BUFFER_SIZE];
-        char dateStringCharBuffer[BUFFER_SIZE];
-        intArrayToCharArray(dictionaryIdCodePointBuffer, BUFFER_SIZE,
-                dictionaryIdCharBuffer, BUFFER_SIZE);
-        intArrayToCharArray(versionStringCodePointBuffer, BUFFER_SIZE,
-                versionStringCharBuffer, BUFFER_SIZE);
-        intArrayToCharArray(dateStringCodePointBuffer, BUFFER_SIZE,
-                dateStringCharBuffer, BUFFER_SIZE);
-
-        LogUtils::logToJava(env,
-                "Dictionary info: dictionary = %s ; version = %s ; date = %s ; filesize = %i",
-                dictionaryIdCharBuffer, versionStringCharBuffer, dateStringCharBuffer, mDictSize);
-    }
-};
-}
-#endif /* LATINIME_BINARY_DICTIONARY_INFO_H */
diff --git a/native/jni/src/suggest/core/dictionary/binary_dictionary_shortcut_iterator.h b/native/jni/src/suggest/core/dictionary/binary_dictionary_shortcut_iterator.h
new file mode 100644
index 0000000..558e0a5
--- /dev/null
+++ b/native/jni/src/suggest/core/dictionary/binary_dictionary_shortcut_iterator.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LATINIME_BINARY_DICTIONARY_SHORTCUT_ITERATOR_H
+#define LATINIME_BINARY_DICTIONARY_SHORTCUT_ITERATOR_H
+
+#include "defines.h"
+#include "suggest/core/policy/dictionary_shortcuts_structure_policy.h"
+
+namespace latinime {
+
+class BinaryDictionaryShortcutIterator {
+ public:
+    BinaryDictionaryShortcutIterator(
+            const DictionaryShortcutsStructurePolicy *const shortcutStructurePolicy,
+            const int shortcutPos)
+            : mShortcutStructurePolicy(shortcutStructurePolicy),
+              mPos(shortcutStructurePolicy->getStartPos(shortcutPos)),
+              mHasNextShortcutTarget(shortcutPos != NOT_A_DICT_POS) {}
+
+    AK_FORCE_INLINE bool hasNextShortcutTarget() const {
+        return mHasNextShortcutTarget;
+    }
+
+    // Gets the shortcut target itself as an int string and put it to outTarget, put its length
+    // to outTargetLength, put whether it is whitelist to outIsWhitelist.
+    AK_FORCE_INLINE void nextShortcutTarget(
+            const int maxDepth, int *const outTarget, int *const outTargetLength,
+            bool *const outIsWhitelist) {
+        mShortcutStructurePolicy->getNextShortcut(maxDepth, outTarget, outTargetLength,
+                outIsWhitelist, &mHasNextShortcutTarget, &mPos);
+    }
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(BinaryDictionaryShortcutIterator);
+
+    const DictionaryShortcutsStructurePolicy *const mShortcutStructurePolicy;
+    int mPos;
+    bool mHasNextShortcutTarget;
+};
+} // namespace latinime
+#endif // LATINIME_BINARY_DICTIONARY_SHORTCUT_ITERATOR_H
diff --git a/native/jni/src/suggest/core/dictionary/binary_dictionary_terminal_attributes_reading_utils.cpp b/native/jni/src/suggest/core/dictionary/binary_dictionary_terminal_attributes_reading_utils.cpp
deleted file mode 100644
index 20b77b3..0000000
--- a/native/jni/src/suggest/core/dictionary/binary_dictionary_terminal_attributes_reading_utils.cpp
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * 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.
- */
-
-#include "suggest/core/dictionary/binary_dictionary_terminal_attributes_reading_utils.h"
-
-#include "suggest/core/dictionary/binary_dictionary_info.h"
-#include "suggest/core/dictionary/byte_array_utils.h"
-
-namespace latinime {
-
-typedef BinaryDictionaryTerminalAttributesReadingUtils TaUtils;
-
-const TaUtils::TerminalAttributeFlags TaUtils::MASK_ATTRIBUTE_ADDRESS_TYPE = 0x30;
-const TaUtils::TerminalAttributeFlags TaUtils::FLAG_ATTRIBUTE_ADDRESS_TYPE_ONEBYTE = 0x10;
-const TaUtils::TerminalAttributeFlags TaUtils::FLAG_ATTRIBUTE_ADDRESS_TYPE_TWOBYTES = 0x20;
-const TaUtils::TerminalAttributeFlags TaUtils::FLAG_ATTRIBUTE_ADDRESS_TYPE_THREEBYTES = 0x30;
-const TaUtils::TerminalAttributeFlags TaUtils::FLAG_ATTRIBUTE_OFFSET_NEGATIVE = 0x40;
-// Flag for presence of more attributes
-const TaUtils::TerminalAttributeFlags TaUtils::FLAG_ATTRIBUTE_HAS_NEXT = 0x80;
-// Mask for attribute probability, stored on 4 bits inside the flags byte.
-const TaUtils::TerminalAttributeFlags TaUtils::MASK_ATTRIBUTE_PROBABILITY = 0x0F;
-const int TaUtils::ATTRIBUTE_ADDRESS_SHIFT = 4;
-const int TaUtils::SHORTCUT_LIST_SIZE_FIELD_SIZE = 2;
-// The numeric value of the shortcut probability that means 'whitelist'.
-const int TaUtils::WHITELIST_SHORTCUT_PROBABILITY = 15;
-
-/* static */ int TaUtils::getBigramAddressAndForwardPointer(
-        const BinaryDictionaryInfo *const binaryDictionaryInfo, const TerminalAttributeFlags flags,
-        int *const pos) {
-    int offset = 0;
-    const int origin = *pos;
-    switch (MASK_ATTRIBUTE_ADDRESS_TYPE & flags) {
-        case FLAG_ATTRIBUTE_ADDRESS_TYPE_ONEBYTE:
-            offset = ByteArrayUtils::readUint8AndAdvancePosition(
-                    binaryDictionaryInfo->getDictRoot(), pos);
-            break;
-        case FLAG_ATTRIBUTE_ADDRESS_TYPE_TWOBYTES:
-            offset = ByteArrayUtils::readUint16AndAdvancePosition(
-                    binaryDictionaryInfo->getDictRoot(), pos);
-            break;
-        case FLAG_ATTRIBUTE_ADDRESS_TYPE_THREEBYTES:
-            offset = ByteArrayUtils::readUint24AndAdvancePosition(
-                    binaryDictionaryInfo->getDictRoot(), pos);
-            break;
-    }
-    if (isOffsetNegative(flags)) {
-        return origin - offset;
-    } else {
-        return origin + offset;
-    }
-}
-
-} // namespace latinime
diff --git a/native/jni/src/suggest/core/dictionary/binary_dictionary_terminal_attributes_reading_utils.h b/native/jni/src/suggest/core/dictionary/binary_dictionary_terminal_attributes_reading_utils.h
deleted file mode 100644
index 375fc7d..0000000
--- a/native/jni/src/suggest/core/dictionary/binary_dictionary_terminal_attributes_reading_utils.h
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- * 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.
- */
-
-#ifndef LATINIME_BINARY_DICTIONARY_TERMINAL_ATTRIBUTES_READING_UTILS_H
-#define LATINIME_BINARY_DICTIONARY_TERMINAL_ATTRIBUTES_READING_UTILS_H
-
-#include <stdint.h>
-
-#include "defines.h"
-#include "suggest/core/dictionary/binary_dictionary_info.h"
-#include "suggest/core/dictionary/byte_array_utils.h"
-
-namespace latinime {
-
-class BinaryDictionaryTerminalAttributesReadingUtils {
- public:
-    typedef uint8_t TerminalAttributeFlags;
-    typedef TerminalAttributeFlags BigramFlags;
-    typedef TerminalAttributeFlags ShortcutFlags;
-
-    static AK_FORCE_INLINE TerminalAttributeFlags getFlagsAndForwardPointer(
-            const BinaryDictionaryInfo *const binaryDictionaryInfo, int *const pos) {
-        return ByteArrayUtils::readUint8AndAdvancePosition(
-                binaryDictionaryInfo->getDictRoot(), pos);
-    }
-
-    static AK_FORCE_INLINE int getProbabilityFromFlags(const TerminalAttributeFlags flags) {
-        return flags & MASK_ATTRIBUTE_PROBABILITY;
-    }
-
-    static AK_FORCE_INLINE bool hasNext(const TerminalAttributeFlags flags) {
-        return (flags & FLAG_ATTRIBUTE_HAS_NEXT) != 0;
-    }
-
-    // Bigrams reading methods
-    static AK_FORCE_INLINE void skipExistingBigrams(
-            const BinaryDictionaryInfo *const binaryDictionaryInfo, int *const pos) {
-        BigramFlags flags = getFlagsAndForwardPointer(binaryDictionaryInfo, pos);
-        while (hasNext(flags)) {
-            *pos += attributeAddressSize(flags);
-            flags = getFlagsAndForwardPointer(binaryDictionaryInfo, pos);
-        }
-        *pos += attributeAddressSize(flags);
-    }
-
-    static int getBigramAddressAndForwardPointer(
-            const BinaryDictionaryInfo *const binaryDictionaryInfo, const BigramFlags flags,
-                    int *const pos);
-
-    // Shortcuts reading methods
-    // This method returns the size of the shortcut list region excluding the shortcut list size
-    // field at the beginning.
-    static AK_FORCE_INLINE int getShortcutListSizeAndForwardPointer(
-            const BinaryDictionaryInfo *const binaryDictionaryInfo, int *const pos) {
-        // readUint16andAdvancePosition() returns an offset *including* the uint16 field itself.
-        return ByteArrayUtils::readUint16AndAdvancePosition(
-                binaryDictionaryInfo->getDictRoot(), pos) - SHORTCUT_LIST_SIZE_FIELD_SIZE;
-    }
-
-    static AK_FORCE_INLINE void skipShortcuts(
-            const BinaryDictionaryInfo *const binaryDictionaryInfo, int *const pos) {
-        const int shortcutListSize = getShortcutListSizeAndForwardPointer(
-                binaryDictionaryInfo, pos);
-        *pos += shortcutListSize;
-    }
-
-    static AK_FORCE_INLINE bool isWhitelist(const ShortcutFlags flags) {
-        return getProbabilityFromFlags(flags) == WHITELIST_SHORTCUT_PROBABILITY;
-    }
-
-    static AK_FORCE_INLINE int readShortcutTarget(
-            const BinaryDictionaryInfo *const binaryDictionaryInfo, const int maxLength,
-            int *const outWord, int *const pos) {
-        return ByteArrayUtils::readStringAndAdvancePosition(
-                binaryDictionaryInfo->getDictRoot(), maxLength, outWord, pos);
-    }
-
- private:
-    DISALLOW_IMPLICIT_CONSTRUCTORS(BinaryDictionaryTerminalAttributesReadingUtils);
-
-    static const TerminalAttributeFlags MASK_ATTRIBUTE_ADDRESS_TYPE;
-    static const TerminalAttributeFlags FLAG_ATTRIBUTE_ADDRESS_TYPE_ONEBYTE;
-    static const TerminalAttributeFlags FLAG_ATTRIBUTE_ADDRESS_TYPE_TWOBYTES;
-    static const TerminalAttributeFlags FLAG_ATTRIBUTE_ADDRESS_TYPE_THREEBYTES;
-    static const TerminalAttributeFlags FLAG_ATTRIBUTE_OFFSET_NEGATIVE;
-    static const TerminalAttributeFlags FLAG_ATTRIBUTE_HAS_NEXT;
-    static const TerminalAttributeFlags MASK_ATTRIBUTE_PROBABILITY;
-    static const int ATTRIBUTE_ADDRESS_SHIFT;
-    static const int SHORTCUT_LIST_SIZE_FIELD_SIZE;
-    static const int WHITELIST_SHORTCUT_PROBABILITY;
-
-    static AK_FORCE_INLINE bool isOffsetNegative(const TerminalAttributeFlags flags) {
-        return (flags & FLAG_ATTRIBUTE_OFFSET_NEGATIVE) != 0;
-    }
-
-    static AK_FORCE_INLINE int attributeAddressSize(const TerminalAttributeFlags flags) {
-        return (flags & MASK_ATTRIBUTE_ADDRESS_TYPE) >> ATTRIBUTE_ADDRESS_SHIFT;
-        /* Note: this is a value-dependant optimization of what may probably be
-           more readably written this way:
-           switch (flags * BinaryFormat::MASK_ATTRIBUTE_ADDRESS_TYPE) {
-           case FLAG_ATTRIBUTE_ADDRESS_TYPE_ONEBYTE: return 1;
-           case FLAG_ATTRIBUTE_ADDRESS_TYPE_TWOBYTES: return 2;
-           case FLAG_ATTRIBUTE_ADDRESS_TYPE_THREEBYTE: return 3;
-           default: return 0;
-           }
-        */
-    }
-};
-}
-#endif /* LATINIME_BINARY_DICTIONARY_TERMINAL_ATTRIBUTES_READING_UTILS_H */
diff --git a/native/jni/src/suggest/core/dictionary/byte_array_utils.h b/native/jni/src/suggest/core/dictionary/byte_array_utils.h
deleted file mode 100644
index 75ccfc7..0000000
--- a/native/jni/src/suggest/core/dictionary/byte_array_utils.h
+++ /dev/null
@@ -1,192 +0,0 @@
-/*
- * 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.
- */
-
-#ifndef LATINIME_BYTE_ARRAY_UTILS_H
-#define LATINIME_BYTE_ARRAY_UTILS_H
-
-#include <stdint.h>
-
-#include "defines.h"
-
-namespace latinime {
-
-/**
- * Utility methods for reading byte arrays.
- */
-class ByteArrayUtils {
- public:
-    /**
-     * Integer
-     *
-     * Each method read a corresponding size integer in a big endian manner.
-     */
-    static AK_FORCE_INLINE uint32_t readUint32(const uint8_t *const buffer, const int pos) {
-        return (buffer[pos] << 24) ^ (buffer[pos + 1] << 16)
-                ^ (buffer[pos + 2] << 8) ^ buffer[pos + 3];
-    }
-
-    static AK_FORCE_INLINE uint32_t readUint24(const uint8_t *const buffer, const int pos) {
-        return (buffer[pos] << 16) ^ (buffer[pos + 1] << 8) ^ buffer[pos + 2];
-    }
-
-    static AK_FORCE_INLINE uint16_t readUint16(const uint8_t *const buffer, const int pos) {
-        return (buffer[pos] << 8) ^ buffer[pos + 1];
-    }
-
-    static AK_FORCE_INLINE uint8_t readUint8(const uint8_t *const buffer, const int pos) {
-        return buffer[pos];
-    }
-
-    static AK_FORCE_INLINE uint32_t readUint32AndAdvancePosition(
-            const uint8_t *const buffer, int *const pos) {
-        const uint32_t value = readUint32(buffer, *pos);
-        *pos += 4;
-        return value;
-    }
-
-    static AK_FORCE_INLINE int readSint24AndAdvancePosition(
-            const uint8_t *const buffer, int *const pos) {
-        const uint8_t value = readUint8(buffer, *pos);
-        if (value < 0x80) {
-            return readUint24AndAdvancePosition(buffer, pos);
-        } else {
-            (*pos)++;
-            return -(((value & 0x7F) << 16) ^ readUint16AndAdvancePosition(buffer, pos));
-        }
-    }
-
-    static AK_FORCE_INLINE uint32_t readUint24AndAdvancePosition(
-            const uint8_t *const buffer, int *const pos) {
-        const uint32_t value = readUint24(buffer, *pos);
-        *pos += 3;
-        return value;
-    }
-
-    static AK_FORCE_INLINE uint16_t readUint16AndAdvancePosition(
-            const uint8_t *const buffer, int *const pos) {
-        const uint16_t value = readUint16(buffer, *pos);
-        *pos += 2;
-        return value;
-    }
-
-    static AK_FORCE_INLINE uint8_t readUint8AndAdvancePosition(
-            const uint8_t *const buffer, int *const pos) {
-        return buffer[(*pos)++];
-    }
-
-    /**
-     * Code Point
-     *
-     * 1 byte = bbbbbbbb match
-     * case 000xxxxx: xxxxx << 16 + next byte << 8 + next byte
-     * else: if 00011111 (= 0x1F) : this is the terminator. This is a relevant choice because
-     *       unicode code points range from 0 to 0x10FFFF, so any 3-byte value starting with
-     *       00011111 would be outside unicode.
-     * else: iso-latin-1 code
-     * This allows for the whole unicode range to be encoded, including chars outside of
-     * the BMP. Also everything in the iso-latin-1 charset is only 1 byte, except control
-     * characters which should never happen anyway (and still work, but take 3 bytes).
-     */
-    static AK_FORCE_INLINE int readCodePoint(const uint8_t *const buffer, const int pos) {
-        int p = pos;
-        return readCodePointAndAdvancePosition(buffer, &p);
-    }
-
-    static AK_FORCE_INLINE int readCodePointAndAdvancePosition(
-            const uint8_t *const buffer, int *const pos) {
-        const uint8_t firstByte = readUint8(buffer, *pos);
-        if (firstByte < MINIMAL_ONE_BYTE_CHARACTER_VALUE) {
-            if (firstByte == CHARACTER_ARRAY_TERMINATOR) {
-                *pos += 1;
-                return NOT_A_CODE_POINT;
-            } else {
-                return readUint24AndAdvancePosition(buffer, pos);
-            }
-        } else {
-            *pos += 1;
-            return firstByte;
-        }
-    }
-
-    /**
-     * String (array of code points)
-     *
-     * Reads code points until the terminator is found.
-     */
-    // Returns the length of the string.
-    static int readStringAndAdvancePosition(const uint8_t *const buffer,
-            const int maxLength, int *const outBuffer, int *const pos) {
-        int length = 0;
-        int codePoint = readCodePointAndAdvancePosition(buffer, pos);
-        while (NOT_A_CODE_POINT != codePoint && length < maxLength) {
-            outBuffer[length++] = codePoint;
-            codePoint = readCodePointAndAdvancePosition(buffer, pos);
-        }
-        return length;
-    }
-
-    // Advances the position and returns the length of the string.
-    static int advancePositionToBehindString(
-            const uint8_t *const buffer, const int maxLength, int *const pos) {
-        int length = 0;
-        int codePoint = readCodePointAndAdvancePosition(buffer, pos);
-        while (NOT_A_CODE_POINT != codePoint && length < maxLength) {
-            codePoint = readCodePointAndAdvancePosition(buffer, pos);
-        }
-        return length;
-    }
-
-    // Returns an integer less than, equal to, or greater than zero when string starting from pos
-    // in buffer is less than, match, or is greater than charArray.
-    static AK_FORCE_INLINE int compareStringInBufferWithCharArray(const uint8_t *const buffer,
-            const char *const charArray, const int maxLength, int *const pos) {
-        int index = 0;
-        int codePoint = readCodePointAndAdvancePosition(buffer, pos);
-        const uint8_t *const uint8CharArrayForComparison =
-                reinterpret_cast<const uint8_t *>(charArray);
-        while (NOT_A_CODE_POINT != codePoint
-                && '\0' != uint8CharArrayForComparison[index] && index < maxLength) {
-            if (codePoint != uint8CharArrayForComparison[index]) {
-                // Different character is found.
-                // Skip the rest of the string in the buffer.
-                advancePositionToBehindString(buffer, maxLength - index, pos);
-                return codePoint - uint8CharArrayForComparison[index];
-            }
-            // Advance
-            codePoint = readCodePointAndAdvancePosition(buffer, pos);
-            ++index;
-        }
-        if (NOT_A_CODE_POINT != codePoint && index < maxLength) {
-            // Skip the rest of the string in the buffer.
-            advancePositionToBehindString(buffer, maxLength - index, pos);
-        }
-        if (NOT_A_CODE_POINT == codePoint && '\0' == uint8CharArrayForComparison[index]) {
-            // When both of the last characters are terminals, we consider the string in the buffer
-            // matches the given char array
-            return 0;
-        } else {
-            return codePoint - uint8CharArrayForComparison[index];
-        }
-    }
-
- private:
-    DISALLOW_IMPLICIT_CONSTRUCTORS(ByteArrayUtils);
-
-    static const uint8_t MINIMAL_ONE_BYTE_CHARACTER_VALUE;
-    static const uint8_t CHARACTER_ARRAY_TERMINATOR;
-};
-} // namespace latinime
-#endif /* LATINIME_BYTE_ARRAY_UTILS_H */
diff --git a/native/jni/src/suggest/core/dictionary/dictionary.cpp b/native/jni/src/suggest/core/dictionary/dictionary.cpp
index 4a9e38f..ec1b63a 100644
--- a/native/jni/src/suggest/core/dictionary/dictionary.cpp
+++ b/native/jni/src/suggest/core/dictionary/dictionary.cpp
@@ -18,33 +18,35 @@
 
 #include "suggest/core/dictionary/dictionary.h"
 
-#include <map> // TODO: remove
 #include <stdint.h>
 
 #include "defines.h"
-#include "jni.h"
 #include "suggest/core/dictionary/bigram_dictionary.h"
+#include "suggest/core/policy/dictionary_header_structure_policy.h"
+#include "suggest/core/policy/dictionary_structure_with_buffer_policy.h"
 #include "suggest/core/session/dic_traverse_session.h"
 #include "suggest/core/suggest.h"
 #include "suggest/core/suggest_options.h"
 #include "suggest/policyimpl/gesture/gesture_suggest_policy_factory.h"
 #include "suggest/policyimpl/typing/typing_suggest_policy_factory.h"
+#include "utils/log_utils.h"
 
 namespace latinime {
 
-Dictionary::Dictionary(JNIEnv *env, void *dict, int dictSize, int mmapFd,
-        int dictBufOffset, bool isUpdatable)
-        : mBinaryDictionaryInfo(env, static_cast<const uint8_t *>(dict), dictSize, mmapFd,
-                dictBufOffset, isUpdatable),
-          mBigramDictionary(new BigramDictionary(&mBinaryDictionaryInfo)),
+Dictionary::Dictionary(JNIEnv *env,
+        DictionaryStructureWithBufferPolicy *const dictionaryStructureWithBufferPolicy)
+        : mDictionaryStructureWithBufferPolicy(dictionaryStructureWithBufferPolicy),
+          mBigramDictionary(new BigramDictionary(mDictionaryStructureWithBufferPolicy)),
           mGestureSuggest(new Suggest(GestureSuggestPolicyFactory::getGestureSuggestPolicy())),
           mTypingSuggest(new Suggest(TypingSuggestPolicyFactory::getTypingSuggestPolicy())) {
+    logDictionaryInfo(env);
 }
 
 Dictionary::~Dictionary() {
     delete mBigramDictionary;
     delete mGestureSuggest;
     delete mTypingSuggest;
+    delete mDictionaryStructureWithBufferPolicy;
 }
 
 int Dictionary::getSuggestions(ProximityInfo *proximityInfo, DicTraverseSession *traverseSession,
@@ -83,47 +85,72 @@
 }
 
 int Dictionary::getProbability(const int *word, int length) const {
-    const DictionaryStructurePolicy *const structurePolicy =
-            mBinaryDictionaryInfo.getStructurePolicy();
-    int pos = structurePolicy->getTerminalNodePositionOfWord(&mBinaryDictionaryInfo, word, length,
+    int pos = getDictionaryStructurePolicy()->getTerminalNodePositionOfWord(word, length,
             false /* forceLowerCaseSearch */);
-    if (NOT_A_VALID_WORD_POS == pos) {
+    if (NOT_A_DICT_POS == pos) {
         return NOT_A_PROBABILITY;
     }
-    return structurePolicy->getUnigramProbability(&mBinaryDictionaryInfo, pos);
+    return getDictionaryStructurePolicy()->getUnigramProbabilityOfPtNode(pos);
 }
 
-bool Dictionary::isValidBigram(const int *word0, int length0, const int *word1, int length1) const {
-    return mBigramDictionary->isValidBigram(word0, length0, word1, length1);
+int Dictionary::getBigramProbability(const int *word0, int length0, const int *word1,
+        int length1) const {
+    return mBigramDictionary->getBigramProbability(word0, length0, word1, length1);
 }
 
 void Dictionary::addUnigramWord(const int *const word, const int length, const int probability) {
-    if (!mBinaryDictionaryInfo.isDynamicallyUpdatable()) {
-        // This method should not be called for non-updatable dictionary.
-        AKLOGI("Warning: Dictionary::addUnigramWord() is called for non-updatable dictionary.");
-        return;
-    }
-    // TODO: Support dynamic update
+    mDictionaryStructureWithBufferPolicy->addUnigramWord(word, length, probability);
 }
 
 void Dictionary::addBigramWords(const int *const word0, const int length0, const int *const word1,
         const int length1, const int probability) {
-    if (!mBinaryDictionaryInfo.isDynamicallyUpdatable()) {
-        // This method should not be called for non-updatable dictionary.
-        AKLOGI("Warning: Dictionary::addBigramWords() is called for non-updatable dictionary.");
-        return;
-    }
-    // TODO: Support dynamic update
+    mDictionaryStructureWithBufferPolicy->addBigramWords(word0, length0, word1, length1,
+            probability);
 }
 
 void Dictionary::removeBigramWords(const int *const word0, const int length0,
         const int *const word1, const int length1) {
-    if (!mBinaryDictionaryInfo.isDynamicallyUpdatable()) {
-        // This method should not be called for non-updatable dictionary.
-        AKLOGI("Warning: Dictionary::removeBigramWords() is called for non-updatable dictionary.");
-        return;
-    }
-    // TODO: Support dynamic update
+    mDictionaryStructureWithBufferPolicy->removeBigramWords(word0, length0, word1, length1);
+}
+
+void Dictionary::flush(const char *const filePath) {
+    mDictionaryStructureWithBufferPolicy->flush(filePath);
+}
+
+void Dictionary::flushWithGC(const char *const filePath) {
+    mDictionaryStructureWithBufferPolicy->flushWithGC(filePath);
+}
+
+bool Dictionary::needsToRunGC() {
+    return mDictionaryStructureWithBufferPolicy->needsToRunGC();
+}
+
+void Dictionary::logDictionaryInfo(JNIEnv *const env) const {
+    const int BUFFER_SIZE = 16;
+    int dictionaryIdCodePointBuffer[BUFFER_SIZE];
+    int versionStringCodePointBuffer[BUFFER_SIZE];
+    int dateStringCodePointBuffer[BUFFER_SIZE];
+    const DictionaryHeaderStructurePolicy *const headerPolicy =
+            getDictionaryStructurePolicy()->getHeaderStructurePolicy();
+    headerPolicy->readHeaderValueOrQuestionMark("dictionary", dictionaryIdCodePointBuffer,
+            BUFFER_SIZE);
+    headerPolicy->readHeaderValueOrQuestionMark("version", versionStringCodePointBuffer,
+            BUFFER_SIZE);
+    headerPolicy->readHeaderValueOrQuestionMark("date", dateStringCodePointBuffer, BUFFER_SIZE);
+
+    char dictionaryIdCharBuffer[BUFFER_SIZE];
+    char versionStringCharBuffer[BUFFER_SIZE];
+    char dateStringCharBuffer[BUFFER_SIZE];
+    intArrayToCharArray(dictionaryIdCodePointBuffer, BUFFER_SIZE,
+            dictionaryIdCharBuffer, BUFFER_SIZE);
+    intArrayToCharArray(versionStringCodePointBuffer, BUFFER_SIZE,
+            versionStringCharBuffer, BUFFER_SIZE);
+    intArrayToCharArray(dateStringCodePointBuffer, BUFFER_SIZE,
+            dateStringCharBuffer, BUFFER_SIZE);
+
+    LogUtils::logToJava(env,
+            "Dictionary info: dictionary = %s ; version = %s ; date = %s",
+            dictionaryIdCharBuffer, versionStringCharBuffer, dateStringCharBuffer);
 }
 
 } // namespace latinime
diff --git a/native/jni/src/suggest/core/dictionary/dictionary.h b/native/jni/src/suggest/core/dictionary/dictionary.h
index 9f1e072..9744474 100644
--- a/native/jni/src/suggest/core/dictionary/dictionary.h
+++ b/native/jni/src/suggest/core/dictionary/dictionary.h
@@ -21,11 +21,11 @@
 
 #include "defines.h"
 #include "jni.h"
-#include "suggest/core/dictionary/binary_dictionary_info.h"
 
 namespace latinime {
 
 class BigramDictionary;
+class DictionaryStructureWithBufferPolicy;
 class DicTraverseSession;
 class ProximityInfo;
 class SuggestInterface;
@@ -53,8 +53,8 @@
     static const int KIND_FLAG_POSSIBLY_OFFENSIVE = 0x80000000;
     static const int KIND_FLAG_EXACT_MATCH = 0x40000000;
 
-    Dictionary(JNIEnv *env, void *dict, int dictSize, int mmapFd, int dictBufOffset,
-            bool isUpdatable);
+    Dictionary(JNIEnv *env,
+            DictionaryStructureWithBufferPolicy *const dictionaryStructureWithBufferPoilcy);
 
     int getSuggestions(ProximityInfo *proximityInfo, DicTraverseSession *traverseSession,
             int *xcoordinates, int *ycoordinates, int *times, int *pointerIds, int *inputCodePoints,
@@ -67,7 +67,7 @@
 
     int getProbability(const int *word, int length) const;
 
-    bool isValidBigram(const int *word0, int length0, const int *word1, int length1) const;
+    int getBigramProbability(const int *word0, int length0, const int *word1, int length1) const;
 
     void addUnigramWord(const int *const word, const int length, const int probability);
 
@@ -77,8 +77,14 @@
     void removeBigramWords(const int *const word0, const int length0, const int *const word1,
             const int length1);
 
-    const BinaryDictionaryInfo *getBinaryDictionaryInfo() const {
-        return &mBinaryDictionaryInfo;
+    void flush(const char *const filePath);
+
+    void flushWithGC(const char *const filePath);
+
+    bool needsToRunGC();
+
+    const DictionaryStructureWithBufferPolicy *getDictionaryStructurePolicy() const {
+        return mDictionaryStructureWithBufferPolicy;
     }
 
     virtual ~Dictionary();
@@ -86,10 +92,12 @@
  private:
     DISALLOW_IMPLICIT_CONSTRUCTORS(Dictionary);
 
-    const BinaryDictionaryInfo mBinaryDictionaryInfo;
-    const BigramDictionary *mBigramDictionary;
-    SuggestInterface *mGestureSuggest;
-    SuggestInterface *mTypingSuggest;
+    DictionaryStructureWithBufferPolicy *const mDictionaryStructureWithBufferPolicy;
+    const BigramDictionary *const mBigramDictionary;
+    const SuggestInterface *const mGestureSuggest;
+    const SuggestInterface *const mTypingSuggest;
+
+    void logDictionaryInfo(JNIEnv *const env) const;
 };
 } // namespace latinime
 #endif // LATINIME_DICTIONARY_H
diff --git a/native/jni/src/suggest/core/dictionary/digraph_utils.cpp b/native/jni/src/suggest/core/dictionary/digraph_utils.cpp
index af378b1..3271c1b 100644
--- a/native/jni/src/suggest/core/dictionary/digraph_utils.cpp
+++ b/native/jni/src/suggest/core/dictionary/digraph_utils.cpp
@@ -19,7 +19,7 @@
 #include <cstdlib>
 
 #include "defines.h"
-#include "suggest/core/dictionary/binary_dictionary_header.h"
+#include "suggest/core/policy/dictionary_header_structure_policy.h"
 #include "utils/char_utils.h"
 
 namespace latinime {
@@ -35,8 +35,9 @@
         { DIGRAPH_TYPE_GERMAN_UMLAUT, DIGRAPH_TYPE_FRENCH_LIGATURES };
 
 /* static */ bool DigraphUtils::hasDigraphForCodePoint(
-        const BinaryDictionaryHeader *const header, const int compositeGlyphCodePoint) {
-    const DigraphUtils::DigraphType digraphType = getDigraphTypeForDictionary(header);
+        const DictionaryHeaderStructurePolicy *const headerPolicy,
+        const int compositeGlyphCodePoint) {
+    const DigraphUtils::DigraphType digraphType = getDigraphTypeForDictionary(headerPolicy);
     if (DigraphUtils::getDigraphForDigraphTypeAndCodePoint(digraphType, compositeGlyphCodePoint)) {
         return true;
     }
@@ -45,11 +46,11 @@
 
 // Returns the digraph type associated with the given dictionary.
 /* static */ DigraphUtils::DigraphType DigraphUtils::getDigraphTypeForDictionary(
-        const BinaryDictionaryHeader *const header) {
-    if (header->requiresGermanUmlautProcessing()) {
+        const DictionaryHeaderStructurePolicy *const headerPolicy) {
+    if (headerPolicy->requiresGermanUmlautProcessing()) {
         return DIGRAPH_TYPE_GERMAN_UMLAUT;
     }
-    if (header->requiresFrenchLigatureProcessing()) {
+    if (headerPolicy->requiresFrenchLigatureProcessing()) {
         return DIGRAPH_TYPE_FRENCH_LIGATURES;
     }
     return DIGRAPH_TYPE_NONE;
diff --git a/native/jni/src/suggest/core/dictionary/digraph_utils.h b/native/jni/src/suggest/core/dictionary/digraph_utils.h
index 9d74fe3..6ae16e3 100644
--- a/native/jni/src/suggest/core/dictionary/digraph_utils.h
+++ b/native/jni/src/suggest/core/dictionary/digraph_utils.h
@@ -21,7 +21,7 @@
 
 namespace latinime {
 
-class BinaryDictionaryHeader;
+class DictionaryHeaderStructurePolicy;
 
 class DigraphUtils {
  public:
@@ -39,14 +39,15 @@
 
     typedef struct { int first; int second; int compositeGlyph; } digraph_t;
 
-    static bool hasDigraphForCodePoint(
-            const BinaryDictionaryHeader *const header, const int compositeGlyphCodePoint);
+    static bool hasDigraphForCodePoint(const DictionaryHeaderStructurePolicy *const headerPolicy,
+            const int compositeGlyphCodePoint);
     static int getDigraphCodePointForIndex(const int compositeGlyphCodePoint,
             const DigraphCodePointIndex digraphCodePointIndex);
 
  private:
     DISALLOW_IMPLICIT_CONSTRUCTORS(DigraphUtils);
-    static DigraphType getDigraphTypeForDictionary(const BinaryDictionaryHeader *const header);
+    static DigraphType getDigraphTypeForDictionary(
+            const DictionaryHeaderStructurePolicy *const headerPolicy);
     static int getAllDigraphsForDigraphTypeAndReturnSize(
             const DigraphType digraphType, const digraph_t **const digraphs);
     static const digraph_t *getDigraphForCodePoint(const int compositeGlyphCodePoint);
diff --git a/native/jni/src/suggest/core/dictionary/multi_bigram_map.h b/native/jni/src/suggest/core/dictionary/multi_bigram_map.h
index d5eafe1..4633c07 100644
--- a/native/jni/src/suggest/core/dictionary/multi_bigram_map.h
+++ b/native/jni/src/suggest/core/dictionary/multi_bigram_map.h
@@ -21,9 +21,8 @@
 
 #include "defines.h"
 #include "suggest/core/dictionary/binary_dictionary_bigrams_iterator.h"
-#include "suggest/core/dictionary/binary_dictionary_info.h"
 #include "suggest/core/dictionary/bloom_filter.h"
-#include "suggest/core/dictionary/probability_utils.h"
+#include "suggest/core/policy/dictionary_structure_with_buffer_policy.h"
 #include "utils/hash_map_compat.h"
 
 namespace latinime {
@@ -38,20 +37,21 @@
 
     // Look up the bigram probability for the given word pair from the cached bigram maps.
     // Also caches the bigrams if there is space remaining and they have not been cached already.
-    int getBigramProbability(const BinaryDictionaryInfo *const binaryDictionaryInfo,
+    int getBigramProbability(const DictionaryStructureWithBufferPolicy *const structurePolicy,
             const int wordPosition, const int nextWordPosition, const int unigramProbability) {
         hash_map_compat<int, BigramMap>::const_iterator mapPosition =
                 mBigramMaps.find(wordPosition);
         if (mapPosition != mBigramMaps.end()) {
-            return mapPosition->second.getBigramProbability(nextWordPosition, unigramProbability);
+            return mapPosition->second.getBigramProbability(structurePolicy, nextWordPosition,
+                    unigramProbability);
         }
         if (mBigramMaps.size() < MAX_CACHED_PREV_WORDS_IN_BIGRAM_MAP) {
-            addBigramsForWordPosition(binaryDictionaryInfo, wordPosition);
-            return mBigramMaps[wordPosition].getBigramProbability(
+            addBigramsForWordPosition(structurePolicy, wordPosition);
+            return mBigramMaps[wordPosition].getBigramProbability(structurePolicy,
                     nextWordPosition, unigramProbability);
         }
-        return readBigramProbabilityFromBinaryDictionary(binaryDictionaryInfo,
-                wordPosition, nextWordPosition, unigramProbability);
+        return readBigramProbabilityFromBinaryDictionary(structurePolicy, wordPosition,
+                nextWordPosition, unigramProbability);
     }
 
     void clear() {
@@ -66,29 +66,33 @@
         BigramMap() : mBigramMap(DEFAULT_HASH_MAP_SIZE_FOR_EACH_BIGRAM_MAP), mBloomFilter() {}
         ~BigramMap() {}
 
-        void init(const BinaryDictionaryInfo *const binaryDictionaryInfo, const int nodePos) {
-            const int bigramsListPos = binaryDictionaryInfo->getStructurePolicy()->
-                    getBigramsPositionOfNode(binaryDictionaryInfo, nodePos);
-            BinaryDictionaryBigramsIterator bigramsIt(binaryDictionaryInfo, bigramsListPos);
+        void init(const DictionaryStructureWithBufferPolicy *const structurePolicy,
+                const int nodePos) {
+            const int bigramsListPos = structurePolicy->getBigramsPositionOfPtNode(nodePos);
+            BinaryDictionaryBigramsIterator bigramsIt(structurePolicy->getBigramsStructurePolicy(),
+                    bigramsListPos);
             while (bigramsIt.hasNext()) {
                 bigramsIt.next();
+                if (bigramsIt.getBigramPos() == NOT_A_DICT_POS) {
+                    continue;
+                }
                 mBigramMap[bigramsIt.getBigramPos()] = bigramsIt.getProbability();
                 mBloomFilter.setInFilter(bigramsIt.getBigramPos());
             }
         }
 
         AK_FORCE_INLINE int getBigramProbability(
+                const DictionaryStructureWithBufferPolicy *const structurePolicy,
                 const int nextWordPosition, const int unigramProbability) const {
+            int bigramProbability = NOT_A_PROBABILITY;
             if (mBloomFilter.isInFilter(nextWordPosition)) {
                 const hash_map_compat<int, int>::const_iterator bigramProbabilityIt =
                         mBigramMap.find(nextWordPosition);
                 if (bigramProbabilityIt != mBigramMap.end()) {
-                    const int bigramProbability = bigramProbabilityIt->second;
-                    return ProbabilityUtils::computeProbabilityForBigram(
-                            unigramProbability, bigramProbability);
+                    bigramProbability = bigramProbabilityIt->second;
                 }
             }
-            return ProbabilityUtils::backoff(unigramProbability);
+            return structurePolicy->getProbability(unigramProbability, bigramProbability);
         }
 
      private:
@@ -100,24 +104,25 @@
     };
 
     AK_FORCE_INLINE void addBigramsForWordPosition(
-            const BinaryDictionaryInfo *const binaryDictionaryInfo, const int position) {
-        mBigramMaps[position].init(binaryDictionaryInfo, position);
+            const DictionaryStructureWithBufferPolicy *const structurePolicy, const int position) {
+        mBigramMaps[position].init(structurePolicy, position);
     }
 
     AK_FORCE_INLINE int readBigramProbabilityFromBinaryDictionary(
-            const BinaryDictionaryInfo *const binaryDictionaryInfo, const int nodePos,
+            const DictionaryStructureWithBufferPolicy *const structurePolicy, const int nodePos,
             const int nextWordPosition, const int unigramProbability) {
-        const int bigramsListPos = binaryDictionaryInfo->getStructurePolicy()->
-                getBigramsPositionOfNode(binaryDictionaryInfo, nodePos);
-        BinaryDictionaryBigramsIterator bigramsIt(binaryDictionaryInfo, bigramsListPos);
+        int bigramProbability = NOT_A_PROBABILITY;
+        const int bigramsListPos = structurePolicy->getBigramsPositionOfPtNode(nodePos);
+        BinaryDictionaryBigramsIterator bigramsIt(structurePolicy->getBigramsStructurePolicy(),
+                bigramsListPos);
         while (bigramsIt.hasNext()) {
             bigramsIt.next();
             if (bigramsIt.getBigramPos() == nextWordPosition) {
-                return ProbabilityUtils::computeProbabilityForBigram(
-                        unigramProbability, bigramsIt.getProbability());
+                bigramProbability = bigramsIt.getProbability();
+                break;
             }
         }
-        return ProbabilityUtils::backoff(unigramProbability);
+        return structurePolicy->getProbability(unigramProbability, bigramProbability);
     }
 
     static const size_t MAX_CACHED_PREV_WORDS_IN_BIGRAM_MAP;
diff --git a/native/jni/src/suggest/core/dictionary/shortcut_utils.h b/native/jni/src/suggest/core/dictionary/shortcut_utils.h
index 3c21809..461d7b4 100644
--- a/native/jni/src/suggest/core/dictionary/shortcut_utils.h
+++ b/native/jni/src/suggest/core/dictionary/shortcut_utils.h
@@ -19,21 +19,20 @@
 
 #include "defines.h"
 #include "suggest/core/dicnode/dic_node_utils.h"
-#include "suggest/core/dictionary/terminal_attributes.h"
+#include "suggest/core/dictionary/binary_dictionary_shortcut_iterator.h"
 
 namespace latinime {
 
 class ShortcutUtils {
  public:
-    static int outputShortcuts(const TerminalAttributes *const terminalAttributes,
+    static int outputShortcuts(BinaryDictionaryShortcutIterator *const shortcutIt,
             int outputWordIndex, const int finalScore, int *const outputCodePoints,
             int *const frequencies, int *const outputTypes, const bool sameAsTyped) {
-        TerminalAttributes::ShortcutIterator iterator = terminalAttributes->getShortcutIterator();
         int shortcutTarget[MAX_WORD_LENGTH];
-        while (iterator.hasNextShortcutTarget() && outputWordIndex < MAX_RESULTS) {
+        while (shortcutIt->hasNextShortcutTarget() && outputWordIndex < MAX_RESULTS) {
             bool isWhilelist;
             int shortcutTargetStringLength;
-            iterator.nextShortcutTarget(MAX_WORD_LENGTH, shortcutTarget,
+            shortcutIt->nextShortcutTarget(MAX_WORD_LENGTH, shortcutTarget,
                     &shortcutTargetStringLength, &isWhilelist);
             int shortcutScore;
             int kind;
diff --git a/native/jni/src/suggest/core/dictionary/terminal_attributes.h b/native/jni/src/suggest/core/dictionary/terminal_attributes.h
deleted file mode 100644
index 0da6504..0000000
--- a/native/jni/src/suggest/core/dictionary/terminal_attributes.h
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef LATINIME_TERMINAL_ATTRIBUTES_H
-#define LATINIME_TERMINAL_ATTRIBUTES_H
-
-#include <stdint.h>
-
-#include "suggest/core/dictionary/binary_dictionary_info.h"
-#include "suggest/core/dictionary/binary_dictionary_terminal_attributes_reading_utils.h"
-
-namespace latinime {
-
-/**
- * This class encapsulates information about a terminal that allows to
- * retrieve local node attributes like the list of shortcuts without
- * exposing the format structure to the client.
- */
-class TerminalAttributes {
- public:
-    class ShortcutIterator {
-     public:
-        ShortcutIterator(const BinaryDictionaryInfo *const binaryDictionaryInfo,
-                const int shortcutPos, const bool hasShortcutList)
-                : mBinaryDictionaryInfo(binaryDictionaryInfo), mPos(shortcutPos),
-                  mHasNextShortcutTarget(hasShortcutList) {}
-
-        inline bool hasNextShortcutTarget() const {
-            return mHasNextShortcutTarget;
-        }
-
-        // Gets the shortcut target itself as an int string and put it to outTarget, put its length
-        // to outTargetLength, put whether it is whitelist to outIsWhitelist.
-        AK_FORCE_INLINE void nextShortcutTarget(
-                const int maxDepth, int *const outTarget, int *const outTargetLength,
-                bool *const outIsWhitelist) {
-            const BinaryDictionaryTerminalAttributesReadingUtils::ShortcutFlags flags =
-                    BinaryDictionaryTerminalAttributesReadingUtils::getFlagsAndForwardPointer(
-                            mBinaryDictionaryInfo, &mPos);
-            mHasNextShortcutTarget =
-                    BinaryDictionaryTerminalAttributesReadingUtils::hasNext(flags);
-            if (outIsWhitelist) {
-                *outIsWhitelist =
-                        BinaryDictionaryTerminalAttributesReadingUtils::isWhitelist(flags);
-            }
-            if (outTargetLength) {
-                *outTargetLength =
-                        BinaryDictionaryTerminalAttributesReadingUtils::readShortcutTarget(
-                                mBinaryDictionaryInfo, maxDepth, outTarget, &mPos);
-            }
-        }
-
-     private:
-        const BinaryDictionaryInfo *const mBinaryDictionaryInfo;
-        int mPos;
-        bool mHasNextShortcutTarget;
-    };
-
-    TerminalAttributes(const BinaryDictionaryInfo *const binaryDictionaryInfo,
-            const int shortcutPos)
-            : mBinaryDictionaryInfo(binaryDictionaryInfo), mShortcutListSizePos(shortcutPos) {}
-
-    inline ShortcutIterator getShortcutIterator() const {
-        int shortcutPos = mShortcutListSizePos;
-        const bool hasShortcutList = shortcutPos != NOT_A_DICT_POS;
-        if (hasShortcutList) {
-            BinaryDictionaryTerminalAttributesReadingUtils::getShortcutListSizeAndForwardPointer(
-                    mBinaryDictionaryInfo, &shortcutPos);
-        }
-        // shortcutPos is never used if hasShortcutList is false.
-        return ShortcutIterator(mBinaryDictionaryInfo, shortcutPos, hasShortcutList);
-    }
-
- private:
-    DISALLOW_IMPLICIT_CONSTRUCTORS(TerminalAttributes);
-    const BinaryDictionaryInfo *const mBinaryDictionaryInfo;
-    const int mShortcutListSizePos;
-};
-} // namespace latinime
-#endif // LATINIME_TERMINAL_ATTRIBUTES_H
diff --git a/native/jni/src/suggest/core/layout/proximity_info_state.cpp b/native/jni/src/suggest/core/layout/proximity_info_state.cpp
index 7780efd..fbabd92 100644
--- a/native/jni/src/suggest/core/layout/proximity_info_state.cpp
+++ b/native/jni/src/suggest/core/layout/proximity_info_state.cpp
@@ -36,8 +36,8 @@
         const int *const xCoordinates, const int *const yCoordinates, const int *const times,
         const int *const pointerIds, const bool isGeometric) {
     ASSERT(isGeometric || (inputSize < MAX_WORD_LENGTH));
-    mIsContinuousSuggestionPossible =
-            ProximityInfoStateUtils::checkAndReturnIsContinuousSuggestionPossible(
+    mIsContinuousSuggestionPossible = (mHasBeenUpdatedByGeometricInput != isGeometric) ?
+            false : ProximityInfoStateUtils::checkAndReturnIsContinuousSuggestionPossible(
                     inputSize, xCoordinates, yCoordinates, times, mSampledInputSize,
                     &mSampledInputXs, &mSampledInputYs, &mSampledTimes, &mSampledInputIndice);
     if (DEBUG_DICT) {
@@ -155,6 +155,7 @@
     if (DEBUG_GEO_FULL) {
         AKLOGI("ProximityState init finished: %d points out of %d", mSampledInputSize, inputSize);
     }
+    mHasBeenUpdatedByGeometricInput = isGeometric;
 }
 
 // This function basically converts from a length to an edit distance. Accordingly, it's obviously
diff --git a/native/jni/src/suggest/core/layout/proximity_info_state.h b/native/jni/src/suggest/core/layout/proximity_info_state.h
index dbcd544..c94060f 100644
--- a/native/jni/src/suggest/core/layout/proximity_info_state.h
+++ b/native/jni/src/suggest/core/layout/proximity_info_state.h
@@ -46,10 +46,11 @@
             : mProximityInfo(0), mMaxPointToKeyLength(0.0f), mAverageSpeed(0.0f),
               mHasTouchPositionCorrectionData(false), mMostCommonKeyWidthSquare(0),
               mKeyCount(0), mCellHeight(0), mCellWidth(0), mGridHeight(0), mGridWidth(0),
-              mIsContinuousSuggestionPossible(false), mSampledInputXs(), mSampledInputYs(),
-              mSampledTimes(), mSampledInputIndice(), mSampledLengthCache(),
-              mBeelineSpeedPercentiles(), mSampledNormalizedSquaredLengthCache(), mSpeedRates(),
-              mDirections(), mCharProbabilities(), mSampledNearKeySets(), mSampledSearchKeySets(),
+              mIsContinuousSuggestionPossible(false), mHasBeenUpdatedByGeometricInput(false),
+              mSampledInputXs(), mSampledInputYs(), mSampledTimes(), mSampledInputIndice(),
+              mSampledLengthCache(), mBeelineSpeedPercentiles(),
+              mSampledNormalizedSquaredLengthCache(), mSpeedRates(), mDirections(),
+              mCharProbabilities(), mSampledNearKeySets(), mSampledSearchKeySets(),
               mSampledSearchKeyVectors(), mTouchPositionCorrectionEnabled(false),
               mSampledInputSize(0), mMostProbableStringProbability(0.0f) {
         memset(mInputProximities, 0, sizeof(mInputProximities));
@@ -129,6 +130,10 @@
         return mSampledInputYs[index];
     }
 
+    int getInputIndexOfSampledPoint(const int sampledIndex) const {
+        return mSampledInputIndice[sampledIndex];
+    }
+
     bool hasSpaceProximity(const int index) const;
 
     int getLengthCache(const int index) const {
@@ -204,6 +209,7 @@
     int mGridHeight;
     int mGridWidth;
     bool mIsContinuousSuggestionPossible;
+    bool mHasBeenUpdatedByGeometricInput;
 
     std::vector<int> mSampledInputXs;
     std::vector<int> mSampledInputYs;
diff --git a/native/jni/src/suggest/core/policy/dictionary_bigrams_structure_policy.h b/native/jni/src/suggest/core/policy/dictionary_bigrams_structure_policy.h
new file mode 100644
index 0000000..661ef1b
--- /dev/null
+++ b/native/jni/src/suggest/core/policy/dictionary_bigrams_structure_policy.h
@@ -0,0 +1,42 @@
+/*
+ * 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.
+ */
+
+#ifndef LATINIME_DICTIONARY_BIGRAMS_STRUCTURE_POLICY_H
+#define LATINIME_DICTIONARY_BIGRAMS_STRUCTURE_POLICY_H
+
+#include "defines.h"
+
+namespace latinime {
+
+/*
+ * This class abstracts structure of bigrams.
+ */
+class DictionaryBigramsStructurePolicy {
+ public:
+    virtual ~DictionaryBigramsStructurePolicy() {}
+
+    virtual void getNextBigram(int *const outBigramPos, int *const outProbability,
+            bool *const outHasNext, int *const pos) const = 0;
+    virtual void skipAllBigrams(int *const pos) const = 0;
+
+ protected:
+    DictionaryBigramsStructurePolicy() {}
+
+ private:
+    DISALLOW_COPY_AND_ASSIGN(DictionaryBigramsStructurePolicy);
+};
+} // namespace latinime
+#endif /* LATINIME_DICTIONARY_BIGRAMS_STRUCTURE_POLICY_H */
diff --git a/native/jni/src/suggest/core/policy/dictionary_header_structure_policy.h b/native/jni/src/suggest/core/policy/dictionary_header_structure_policy.h
new file mode 100644
index 0000000..a6829b4
--- /dev/null
+++ b/native/jni/src/suggest/core/policy/dictionary_header_structure_policy.h
@@ -0,0 +1,50 @@
+/*
+ * 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.
+ */
+
+#ifndef LATINIME_DICTIONARY_HEADER_STRUCTURE_POLICY_H
+#define LATINIME_DICTIONARY_HEADER_STRUCTURE_POLICY_H
+
+#include "defines.h"
+
+namespace latinime {
+
+/*
+ * This class abstracts structure of dictionaries.
+ * Implement this policy to support additional dictionaries.
+ */
+class DictionaryHeaderStructurePolicy {
+ public:
+    virtual ~DictionaryHeaderStructurePolicy() {}
+
+    virtual bool supportsDynamicUpdate() const = 0;
+
+    virtual bool requiresGermanUmlautProcessing() const = 0;
+
+    virtual bool requiresFrenchLigatureProcessing() const = 0;
+
+    virtual float getMultiWordCostMultiplier() const = 0;
+
+    virtual void readHeaderValueOrQuestionMark(const char *const key, int *outValue,
+            int outValueSize) const = 0;
+
+ protected:
+    DictionaryHeaderStructurePolicy() {}
+
+ private:
+    DISALLOW_COPY_AND_ASSIGN(DictionaryHeaderStructurePolicy);
+};
+} // namespace latinime
+#endif /* LATINIME_DICTIONARY_HEADER_STRUCTURE_POLICY_H */
diff --git a/native/jni/src/suggest/core/policy/dictionary_shortcuts_structure_policy.h b/native/jni/src/suggest/core/policy/dictionary_shortcuts_structure_policy.h
new file mode 100644
index 0000000..40b6c2d
--- /dev/null
+++ b/native/jni/src/suggest/core/policy/dictionary_shortcuts_structure_policy.h
@@ -0,0 +1,46 @@
+/*
+ * 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.
+ */
+
+#ifndef LATINIME_DICTIONARY_SHORTCUTS_STRUCTURE_POLICY_H
+#define LATINIME_DICTIONARY_SHORTCUTS_STRUCTURE_POLICY_H
+
+#include "defines.h"
+
+namespace latinime {
+
+/*
+ * This class abstracts structure of shortcuts.
+ */
+class DictionaryShortcutsStructurePolicy {
+ public:
+    virtual ~DictionaryShortcutsStructurePolicy() {}
+
+    virtual int getStartPos(const int pos) const = 0;
+
+    virtual void getNextShortcut(const int maxCodePointCount, int *const outCodePoint,
+            int *const outCodePointCount, bool *const outIsWhitelist, bool *const outHasNext,
+            int *const pos) const = 0;
+
+    virtual void skipAllShortcuts(int *const pos) const = 0;
+
+ protected:
+    DictionaryShortcutsStructurePolicy() {}
+
+ private:
+    DISALLOW_COPY_AND_ASSIGN(DictionaryShortcutsStructurePolicy);
+};
+} // namespace latinime
+#endif /* LATINIME_DICTIONARY_SHORTCUTS_STRUCTURE_POLICY_H */
diff --git a/native/jni/src/suggest/core/policy/dictionary_structure_policy.h b/native/jni/src/suggest/core/policy/dictionary_structure_policy.h
deleted file mode 100644
index cc14c98..0000000
--- a/native/jni/src/suggest/core/policy/dictionary_structure_policy.h
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * 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.
- */
-
-#ifndef LATINIME_DICTIONARY_STRUCTURE_POLICY_H
-#define LATINIME_DICTIONARY_STRUCTURE_POLICY_H
-
-#include "defines.h"
-
-namespace latinime {
-
-class BinaryDictionaryInfo;
-class DicNode;
-class DicNodeVector;
-
-/*
- * This class abstracts structure of dictionaries.
- * Implement this policy to support additional dictionaries.
- */
-class DictionaryStructurePolicy {
- public:
-    // This provides a filtering method for filtering new node.
-    class NodeFilter {
-     public:
-        virtual bool isFilteredOut(const int codePoint) const = 0;
-
-     protected:
-        NodeFilter() {}
-        virtual ~NodeFilter() {}
-
-     private:
-        DISALLOW_COPY_AND_ASSIGN(NodeFilter);
-    };
-
-    virtual int getRootPosition() const = 0;
-
-    virtual void createAndGetAllChildNodes(const DicNode *const dicNode,
-            const BinaryDictionaryInfo *const binaryDictionaryInfo,
-            const NodeFilter *const nodeFilter, DicNodeVector *const childDicNodes) const = 0;
-
-    virtual int getCodePointsAndProbabilityAndReturnCodePointCount(
-            const BinaryDictionaryInfo *const binaryDictionaryInfo,
-            const int nodePos, const int maxCodePointCount, int *const outCodePoints,
-            int *const outUnigramProbability) const = 0;
-
-    virtual int getTerminalNodePositionOfWord(
-            const BinaryDictionaryInfo *const binaryDictionaryInfo, const int *const inWord,
-            const int length, const bool forceLowerCaseSearch) const = 0;
-
-    virtual int getUnigramProbability(const BinaryDictionaryInfo *const binaryDictionaryInfo,
-            const int nodePos) const = 0;
-
-    virtual int getShortcutPositionOfNode(const BinaryDictionaryInfo *const binaryDictionaryInfo,
-            const int nodePos) const = 0;
-
-    virtual int getBigramsPositionOfNode(const BinaryDictionaryInfo *const binaryDictionaryInfo,
-            const int nodePos) const = 0;
-
- protected:
-    DictionaryStructurePolicy() {}
-    virtual ~DictionaryStructurePolicy() {}
-
- private:
-    DISALLOW_COPY_AND_ASSIGN(DictionaryStructurePolicy);
-};
-} // namespace latinime
-#endif /* LATINIME_DICTIONARY_STRUCTURE_POLICY_H */
diff --git a/native/jni/src/suggest/core/policy/dictionary_structure_with_buffer_policy.h b/native/jni/src/suggest/core/policy/dictionary_structure_with_buffer_policy.h
new file mode 100644
index 0000000..b95488e
--- /dev/null
+++ b/native/jni/src/suggest/core/policy/dictionary_structure_with_buffer_policy.h
@@ -0,0 +1,90 @@
+/*
+ * 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.
+ */
+
+#ifndef LATINIME_DICTIONARY_STRUCTURE_POLICY_H
+#define LATINIME_DICTIONARY_STRUCTURE_POLICY_H
+
+#include "defines.h"
+
+namespace latinime {
+
+class DicNode;
+class DicNodeVector;
+class DictionaryBigramsStructurePolicy;
+class DictionaryHeaderStructurePolicy;
+class DictionaryShortcutsStructurePolicy;
+
+/*
+ * This class abstracts structure of dictionaries.
+ * Implement this policy to support additional dictionaries.
+ */
+class DictionaryStructureWithBufferPolicy {
+ public:
+    virtual ~DictionaryStructureWithBufferPolicy() {}
+
+    virtual int getRootPosition() const = 0;
+
+    virtual void createAndGetAllChildNodes(const DicNode *const dicNode,
+            DicNodeVector *const childDicNodes) const = 0;
+
+    virtual int getCodePointsAndProbabilityAndReturnCodePointCount(
+            const int nodePos, const int maxCodePointCount, int *const outCodePoints,
+            int *const outUnigramProbability) const = 0;
+
+    virtual int getTerminalNodePositionOfWord(const int *const inWord,
+            const int length, const bool forceLowerCaseSearch) const = 0;
+
+    virtual int getProbability(const int unigramProbability,
+            const int bigramProbability) const = 0;
+
+    virtual int getUnigramProbabilityOfPtNode(const int nodePos) const = 0;
+
+    virtual int getShortcutPositionOfPtNode(const int nodePos) const = 0;
+
+    virtual int getBigramsPositionOfPtNode(const int nodePos) const = 0;
+
+    virtual const DictionaryHeaderStructurePolicy *getHeaderStructurePolicy() const = 0;
+
+    virtual const DictionaryBigramsStructurePolicy *getBigramsStructurePolicy() const = 0;
+
+    virtual const DictionaryShortcutsStructurePolicy *getShortcutsStructurePolicy() const = 0;
+
+    // Returns whether the update was success or not.
+    virtual bool addUnigramWord(const int *const word, const int length,
+            const int probability) = 0;
+
+    // Returns whether the update was success or not.
+    virtual bool addBigramWords(const int *const word0, const int length0, const int *const word1,
+            const int length1, const int probability) = 0;
+
+    // Returns whether the update was success or not.
+    virtual bool removeBigramWords(const int *const word0, const int length0,
+            const int *const word1, const int length1) = 0;
+
+    virtual void flush(const char *const filePath) = 0;
+
+    virtual void flushWithGC(const char *const filePath) = 0;
+
+    virtual bool needsToRunGC() const = 0;
+
+ protected:
+    DictionaryStructureWithBufferPolicy() {}
+
+ private:
+    DISALLOW_COPY_AND_ASSIGN(DictionaryStructureWithBufferPolicy);
+};
+} // namespace latinime
+#endif /* LATINIME_DICTIONARY_STRUCTURE_POLICY_H */
diff --git a/native/jni/src/suggest/core/policy/weighting.cpp b/native/jni/src/suggest/core/policy/weighting.cpp
index 5872922..f9b777d 100644
--- a/native/jni/src/suggest/core/policy/weighting.cpp
+++ b/native/jni/src/suggest/core/policy/weighting.cpp
@@ -148,7 +148,7 @@
     case CT_TERMINAL: {
         const float languageImprobability =
                 DicNodeUtils::getBigramNodeImprobability(
-                        traverseSession->getBinaryDictionaryInfo(), dicNode, multiBigramMap);
+                        traverseSession->getDictionaryStructurePolicy(), dicNode, multiBigramMap);
         return weighting->getTerminalLanguageCost(traverseSession, dicNode, languageImprobability);
     }
     case CT_TERMINAL_INSERTION:
diff --git a/native/jni/src/suggest/core/session/dic_traverse_session.cpp b/native/jni/src/suggest/core/session/dic_traverse_session.cpp
index 7651b19..50f2bbd 100644
--- a/native/jni/src/suggest/core/session/dic_traverse_session.cpp
+++ b/native/jni/src/suggest/core/session/dic_traverse_session.cpp
@@ -17,32 +17,35 @@
 #include "suggest/core/session/dic_traverse_session.h"
 
 #include "defines.h"
-#include "jni.h"
-#include "suggest/core/dictionary/binary_dictionary_header.h"
-#include "suggest/core/dictionary/binary_dictionary_info.h"
 #include "suggest/core/dictionary/dictionary.h"
+#include "suggest/core/policy/dictionary_header_structure_policy.h"
+#include "suggest/core/policy/dictionary_structure_with_buffer_policy.h"
 
 namespace latinime {
 
+// 256K bytes threshold is heuristically used to distinguish dictionaries containing many unigrams
+// (e.g. main dictionary) from small dictionaries (e.g. contacts...)
+const int DicTraverseSession::DICTIONARY_SIZE_THRESHOLD_TO_USE_LARGE_CACHE_FOR_SUGGESTION =
+        256 * 1024;
+
 void DicTraverseSession::init(const Dictionary *const dictionary, const int *prevWord,
         int prevWordLength, const SuggestOptions *const suggestOptions) {
     mDictionary = dictionary;
-    const BinaryDictionaryInfo *const binaryDictionaryInfo =
-            mDictionary->getBinaryDictionaryInfo();
-    mMultiWordCostMultiplier = binaryDictionaryInfo->getHeader()->getMultiWordCostMultiplier();
+    mMultiWordCostMultiplier = getDictionaryStructurePolicy()->getHeaderStructurePolicy()
+            ->getMultiWordCostMultiplier();
     mSuggestOptions = suggestOptions;
     if (!prevWord) {
-        mPrevWordPos = NOT_A_VALID_WORD_POS;
+        mPrevWordPos = NOT_A_DICT_POS;
         return;
     }
     // TODO: merge following similar calls to getTerminalPosition into one case-insensitive call.
-    mPrevWordPos = binaryDictionaryInfo->getStructurePolicy()->getTerminalNodePositionOfWord(
-            binaryDictionaryInfo, prevWord, prevWordLength, false /* forceLowerCaseSearch */);
-    if (mPrevWordPos == NOT_A_VALID_WORD_POS) {
+    mPrevWordPos = getDictionaryStructurePolicy()->getTerminalNodePositionOfWord(
+            prevWord, prevWordLength, false /* forceLowerCaseSearch */);
+    if (mPrevWordPos == NOT_A_DICT_POS) {
         // Check bigrams for lower-cased previous word if original was not found. Useful for
         // auto-capitalized words like "The [current_word]".
-        mPrevWordPos = binaryDictionaryInfo->getStructurePolicy()->getTerminalNodePositionOfWord(
-                binaryDictionaryInfo, prevWord, prevWordLength, true /* forceLowerCaseSearch */);
+        mPrevWordPos = getDictionaryStructurePolicy()->getTerminalNodePositionOfWord(
+                prevWord, prevWordLength, true /* forceLowerCaseSearch */);
     }
 }
 
@@ -56,12 +59,14 @@
             maxSpatialDistance, maxPointerCount);
 }
 
-const BinaryDictionaryInfo *DicTraverseSession::getBinaryDictionaryInfo() const {
-    return mDictionary->getBinaryDictionaryInfo();
+const DictionaryStructureWithBufferPolicy *DicTraverseSession::getDictionaryStructurePolicy()
+        const {
+    return mDictionary->getDictionaryStructurePolicy();
 }
 
-void DicTraverseSession::resetCache(const int nextActiveCacheSize, const int maxWords) {
-    mDicNodesCache.reset(nextActiveCacheSize, maxWords);
+void DicTraverseSession::resetCache(const int thresholdForNextActiveDicNodes, const int maxWords) {
+    mDicNodesCache.reset(thresholdForNextActiveDicNodes /* nextActiveSize */,
+            maxWords /* terminalSize */);
     mMultiBigramMap.clear();
     mPartiallyCommited = false;
 }
diff --git a/native/jni/src/suggest/core/session/dic_traverse_session.h b/native/jni/src/suggest/core/session/dic_traverse_session.h
index de57e04..e0b1c67 100644
--- a/native/jni/src/suggest/core/session/dic_traverse_session.h
+++ b/native/jni/src/suggest/core/session/dic_traverse_session.h
@@ -28,8 +28,8 @@
 
 namespace latinime {
 
-class BinaryDictionaryInfo;
 class Dictionary;
+class DictionaryStructureWithBufferPolicy;
 class ProximityInfo;
 class SuggestOptions;
 
@@ -37,8 +37,12 @@
  public:
 
     // A factory method for DicTraverseSession
-    static AK_FORCE_INLINE void *getSessionInstance(JNIEnv *env, jstring localeStr) {
-        return new DicTraverseSession(env, localeStr);
+    static AK_FORCE_INLINE void *getSessionInstance(JNIEnv *env, jstring localeStr,
+            jlong dictSize) {
+        // To deal with the trade-off between accuracy and memory space, large cache is used for
+        // dictionaries larger that the threshold
+        return new DicTraverseSession(env, localeStr,
+                dictSize >= DICTIONARY_SIZE_THRESHOLD_TO_USE_LARGE_CACHE_FOR_SUGGESTION);
     }
 
     static AK_FORCE_INLINE void initSessionInstance(DicTraverseSession *traverseSession,
@@ -54,10 +58,10 @@
         delete traverseSession;
     }
 
-    AK_FORCE_INLINE DicTraverseSession(JNIEnv *env, jstring localeStr)
-            : mPrevWordPos(NOT_A_VALID_WORD_POS), mProximityInfo(0),
-              mDictionary(0), mSuggestOptions(0), mDicNodesCache(), mMultiBigramMap(),
-              mInputSize(0), mPartiallyCommited(false), mMaxPointerCount(1),
+    AK_FORCE_INLINE DicTraverseSession(JNIEnv *env, jstring localeStr, bool usesLargeCache)
+            : mPrevWordPos(NOT_A_DICT_POS), mProximityInfo(0),
+              mDictionary(0), mSuggestOptions(0), mDicNodesCache(usesLargeCache),
+              mMultiBigramMap(), mInputSize(0), mPartiallyCommited(false), mMaxPointerCount(1),
               mMultiWordCostMultiplier(1.0f) {
         // NOTE: mProximityInfoStates is an array of instances.
         // No need to initialize it explicitly here.
@@ -73,10 +77,9 @@
             const int inputSize, const int *const inputXs, const int *const inputYs,
             const int *const times, const int *const pointerIds, const float maxSpatialDistance,
             const int maxPointerCount);
-    void resetCache(const int nextActiveCacheSize, const int maxWords);
+    void resetCache(const int thresholdForNextActiveDicNodes, const int maxWords);
 
-    // TODO: Remove
-    const BinaryDictionaryInfo *getBinaryDictionaryInfo() const;
+    const DictionaryStructureWithBufferPolicy *getDictionaryStructurePolicy() const;
 
     //--------------------
     // getters and setters
@@ -110,7 +113,9 @@
         if (usedPointerCount != 1) {
             return false;
         }
-        *pointerId = usedPointerId;
+        if (pointerId) {
+            *pointerId = usedPointerId;
+        }
         return true;
     }
 
@@ -182,6 +187,7 @@
     DISALLOW_IMPLICIT_CONSTRUCTORS(DicTraverseSession);
     // threshold to start caching
     static const int CACHE_START_INPUT_LENGTH_THRESHOLD;
+    static const int DICTIONARY_SIZE_THRESHOLD_TO_USE_LARGE_CACHE_FOR_SUGGESTION;
     void initializeProximityInfoStates(const int *const inputCodePoints, const int *const inputXs,
             const int *const inputYs, const int *const times, const int *const pointerIds,
             const int inputSize, const float maxSpatialDistance, const int maxPointerCount);
diff --git a/native/jni/src/suggest/core/suggest.cpp b/native/jni/src/suggest/core/suggest.cpp
index 9376d7b..b1340e1 100644
--- a/native/jni/src/suggest/core/suggest.cpp
+++ b/native/jni/src/suggest/core/suggest.cpp
@@ -19,12 +19,12 @@
 #include "suggest/core/dicnode/dic_node.h"
 #include "suggest/core/dicnode/dic_node_priority_queue.h"
 #include "suggest/core/dicnode/dic_node_vector.h"
-#include "suggest/core/dictionary/binary_dictionary_info.h"
+#include "suggest/core/dictionary/binary_dictionary_shortcut_iterator.h"
 #include "suggest/core/dictionary/dictionary.h"
 #include "suggest/core/dictionary/digraph_utils.h"
 #include "suggest/core/dictionary/shortcut_utils.h"
-#include "suggest/core/dictionary/terminal_attributes.h"
 #include "suggest/core/layout/proximity_info.h"
+#include "suggest/core/policy/dictionary_structure_with_buffer_policy.h"
 #include "suggest/core/policy/scoring.h"
 #include "suggest/core/policy/traversal.h"
 #include "suggest/core/policy/weighting.h"
@@ -107,7 +107,7 @@
                 MAX_RESULTS);
         // Create a new dic node here
         DicNode rootNode;
-        DicNodeUtils::initAsRoot(traverseSession->getBinaryDictionaryInfo(),
+        DicNodeUtils::initAsRoot(traverseSession->getDictionaryStructurePolicy(),
                 traverseSession->getPrevWordPos(), &rootNode);
         traverseSession->getDicTraverseCache()->copyPushActive(&rootNode);
     }
@@ -117,7 +117,7 @@
  * Outputs the final list of suggestions (i.e., terminal nodes).
  */
 int Suggest::outputSuggestions(DicTraverseSession *traverseSession, int *frequencies,
-        int *outputCodePoints, int *spaceIndices, int *outputTypes) const {
+        int *outputCodePoints, int *outputIndicesToPartialCommit, int *outputTypes) const {
 #if DEBUG_EVALUATE_MOST_PROBABLE_STRING
     const int terminalSize = 0;
 #else
@@ -139,6 +139,7 @@
             SCORING->getMostProbableString(traverseSession, terminalSize, languageWeight,
                     &outputCodePoints[0], &outputTypes[0], &frequencies[0]);
     if (hasMostProbableString) {
+        outputIndicesToPartialCommit[outputWordIndex] = NOT_AN_INDEX;
         ++outputWordIndex;
     }
 
@@ -160,6 +161,9 @@
                             || (traverseSession->getInputSize()
                                     >= MIN_LEN_FOR_MULTI_WORD_AUTOCORRECT
                                             && terminals[0].hasMultipleWords())) : false;
+    // TODO: have partial commit work even with multiple pointers.
+    const bool outputSecondWordFirstLetterInputIndex =
+            traverseSession->isOnlyOnePointerUsed(0 /* pointerId */);
     // Output suggestion results here
     for (int terminalIndex = 0; terminalIndex < terminalSize && outputWordIndex < MAX_RESULTS;
             ++terminalIndex) {
@@ -171,7 +175,9 @@
                 terminalIndex, doubleLetterTerminalIndex, doubleLetterLevel);
         const float compoundDistance = terminalDicNode->getCompoundDistance(languageWeight)
                 + doubleLetterCost;
-        const bool isPossiblyOffensiveWord = terminalDicNode->getProbability() <= 0;
+        const bool isPossiblyOffensiveWord =
+                traverseSession->getDictionaryStructurePolicy()->getProbability(
+                        terminalDicNode->getProbability(), NOT_A_PROBABILITY) <= 0;
         const bool isExactMatch = terminalDicNode->isExactMatch();
         const bool isFirstCharUppercase = terminalDicNode->isFirstCharUppercase();
         // Heuristic: We exclude freq=0 first-char-uppercase words from exact match.
@@ -192,18 +198,21 @@
                 terminalDicNode->isExactMatch()
                         || (forceCommitMultiWords && terminalDicNode->hasMultipleWords())
                                 || (isValidWord && SCORING->doesAutoCorrectValidWord()));
-        maxScore = max(maxScore, finalScore);
-
-        // TODO: Implement a smarter auto-commit method for handling multi-word suggestions.
-        // Index for top typing suggestion should be 0.
-        if (isValidWord && outputWordIndex == 0) {
-            terminalDicNode->outputSpacePositionsResult(spaceIndices);
+        if (maxScore < finalScore && isValidWord) {
+            maxScore = finalScore;
         }
 
         // Don't output invalid words. However, we still need to submit their shortcuts if any.
         if (isValidWord) {
             outputTypes[outputWordIndex] = Dictionary::KIND_CORRECTION | outputTypeFlags;
             frequencies[outputWordIndex] = finalScore;
+            if (outputSecondWordFirstLetterInputIndex) {
+                outputIndicesToPartialCommit[outputWordIndex] =
+                        terminalDicNode->getSecondWordFirstInputIndex(
+                                traverseSession->getProximityInfoState(0));
+            } else {
+                outputIndicesToPartialCommit[outputWordIndex] = NOT_AN_INDEX;
+            }
             // Populate the outputChars array with the suggested word.
             const int startIndex = outputWordIndex * MAX_WORD_LENGTH;
             terminalDicNode->outputResult(&outputCodePoints[startIndex]);
@@ -211,16 +220,26 @@
         }
 
         if (!terminalDicNode->hasMultipleWords()) {
-            const BinaryDictionaryInfo *const binaryDictionaryInfo =
-                    traverseSession->getBinaryDictionaryInfo();
-            const TerminalAttributes terminalAttributes(traverseSession->getBinaryDictionaryInfo(),
-                    binaryDictionaryInfo->getStructurePolicy()->getShortcutPositionOfNode(
-                            binaryDictionaryInfo, terminalDicNode->getPos()));
+            BinaryDictionaryShortcutIterator shortcutIt(
+                    traverseSession->getDictionaryStructurePolicy()->getShortcutsStructurePolicy(),
+                    traverseSession->getDictionaryStructurePolicy()
+                            ->getShortcutPositionOfPtNode(terminalDicNode->getPos()));
             // Shortcut is not supported for multiple words suggestions.
             // TODO: Check shortcuts during traversal for multiple words suggestions.
             const bool sameAsTyped = TRAVERSAL->sameAsTyped(traverseSession, terminalDicNode);
-            outputWordIndex = ShortcutUtils::outputShortcuts(&terminalAttributes, outputWordIndex,
-                    finalScore, outputCodePoints, frequencies, outputTypes, sameAsTyped);
+            const int updatedOutputWordIndex = ShortcutUtils::outputShortcuts(&shortcutIt,
+                    outputWordIndex,  finalScore, outputCodePoints, frequencies, outputTypes,
+                    sameAsTyped);
+            const int secondWordFirstInputIndex = terminalDicNode->getSecondWordFirstInputIndex(
+                    traverseSession->getProximityInfoState(0));
+            for (int i = outputWordIndex; i < updatedOutputWordIndex; ++i) {
+                if (outputSecondWordFirstLetterInputIndex) {
+                    outputIndicesToPartialCommit[i] = secondWordFirstInputIndex;
+                } else {
+                    outputIndicesToPartialCommit[i] = NOT_AN_INDEX;
+                }
+            }
+            outputWordIndex = updatedOutputWordIndex;
         }
         DicNode::managedDelete(terminalDicNode);
     }
@@ -298,7 +317,7 @@
             }
 
             DicNodeUtils::getAllChildDicNodes(
-                    &dicNode, traverseSession->getBinaryDictionaryInfo(), &childDicNodes);
+                    &dicNode, traverseSession->getDictionaryStructurePolicy(), &childDicNodes);
 
             const int childDicNodesSize = childDicNodes.getSizeAndLock();
             for (int i = 0; i < childDicNodesSize; ++i) {
@@ -309,7 +328,8 @@
                     continue;
                 }
                 if (DigraphUtils::hasDigraphForCodePoint(
-                        traverseSession->getBinaryDictionaryInfo()->getHeader(),
+                        traverseSession->getDictionaryStructurePolicy()
+                                ->getHeaderStructurePolicy(),
                         childDicNode->getNodeCodePoint())) {
                     correctionDicNode.initByCopy(childDicNode);
                     correctionDicNode.advanceDigraphIndex();
@@ -447,7 +467,7 @@
         DicTraverseSession *traverseSession, DicNode *dicNode) const {
     DicNodeVector childDicNodes;
     DicNodeUtils::getAllChildDicNodes(
-            dicNode, traverseSession->getBinaryDictionaryInfo(), &childDicNodes);
+            dicNode, traverseSession->getDictionaryStructurePolicy(), &childDicNodes);
 
     const int size = childDicNodes.getSizeAndLock();
     for (int i = 0; i < size; i++) {
@@ -456,7 +476,6 @@
         Weighting::addCostAndForwardInputIndex(WEIGHTING, CT_OMISSION, traverseSession,
                 dicNode, childDicNode, 0 /* multiBigramMap */);
         weightChildNode(traverseSession, childDicNode);
-
         if (!TRAVERSAL->isPossibleOmissionChildNode(traverseSession, dicNode, childDicNode)) {
             continue;
         }
@@ -472,10 +491,14 @@
         DicNode *dicNode) const {
     const int16_t pointIndex = dicNode->getInputIndex(0);
     DicNodeVector childDicNodes;
-    DicNodeUtils::getProximityChildDicNodes(dicNode, traverseSession->getBinaryDictionaryInfo(),
-            traverseSession->getProximityInfoState(0), pointIndex + 1, true, &childDicNodes);
+    DicNodeUtils::getAllChildDicNodes(dicNode, traverseSession->getDictionaryStructurePolicy(),
+            &childDicNodes);
     const int size = childDicNodes.getSizeAndLock();
     for (int i = 0; i < size; i++) {
+        if (traverseSession->getProximityInfoState(0)->getPrimaryCodePointAt(pointIndex + 1)
+                != childDicNodes[i]->getNodeCodePoint()) {
+            continue;
+        }
         DicNode *const childDicNode = childDicNodes[i];
         Weighting::addCostAndForwardInputIndex(WEIGHTING, CT_INSERTION, traverseSession,
                 dicNode, childDicNode, 0 /* multiBigramMap */);
@@ -490,18 +513,29 @@
         DicNode *dicNode) const {
     const int16_t pointIndex = dicNode->getInputIndex(0);
     DicNodeVector childDicNodes1;
-    DicNodeUtils::getProximityChildDicNodes(dicNode, traverseSession->getBinaryDictionaryInfo(),
-            traverseSession->getProximityInfoState(0), pointIndex + 1, false, &childDicNodes1);
+    DicNodeUtils::getAllChildDicNodes(dicNode, traverseSession->getDictionaryStructurePolicy(),
+            &childDicNodes1);
     const int childSize1 = childDicNodes1.getSizeAndLock();
     for (int i = 0; i < childSize1; i++) {
+        const ProximityType matchedId1 = traverseSession->getProximityInfoState(0)
+                ->getProximityType(pointIndex + 1, childDicNodes1[i]->getNodeCodePoint(),
+                        true /* checkProximityChars */);
+        if (!ProximityInfoUtils::isMatchOrProximityChar(matchedId1)) {
+            continue;
+        }
         if (childDicNodes1[i]->hasChildren()) {
             DicNodeVector childDicNodes2;
-            DicNodeUtils::getProximityChildDicNodes(
-                    childDicNodes1[i], traverseSession->getBinaryDictionaryInfo(),
-                    traverseSession->getProximityInfoState(0), pointIndex, false, &childDicNodes2);
+            DicNodeUtils::getAllChildDicNodes(childDicNodes1[i],
+                    traverseSession->getDictionaryStructurePolicy(), &childDicNodes2);
             const int childSize2 = childDicNodes2.getSizeAndLock();
             for (int j = 0; j < childSize2; j++) {
                 DicNode *const childDicNode2 = childDicNodes2[j];
+                const ProximityType matchedId2 = traverseSession->getProximityInfoState(0)
+                        ->getProximityType(pointIndex, childDicNode2->getNodeCodePoint(),
+                                true /* checkProximityChars */);
+                if (!ProximityInfoUtils::isMatchOrProximityChar(matchedId2)) {
+                    continue;
+                }
                 Weighting::addCostAndForwardInputIndex(WEIGHTING, CT_TRANSPOSITION,
                         traverseSession, childDicNodes1[i], childDicNode2, 0 /* multiBigramMap */);
                 processExpandedDicNode(traverseSession, childDicNode2);
@@ -538,7 +572,7 @@
     // Create a non-cached node here.
     DicNode newDicNode;
     DicNodeUtils::initAsRootWithPreviousWord(
-            traverseSession->getBinaryDictionaryInfo(), dicNode, &newDicNode);
+            traverseSession->getDictionaryStructurePolicy(), dicNode, &newDicNode);
     const CorrectionType correctionType = spaceSubstitution ?
             CT_NEW_WORD_SPACE_SUBSTITUTION : CT_NEW_WORD_SPACE_OMITTION;
     Weighting::addCostAndForwardInputIndex(WEIGHTING, correctionType, traverseSession, dicNode,
diff --git a/native/jni/src/suggest/core/suggest.h b/native/jni/src/suggest/core/suggest.h
index 875cbe4..b240196 100644
--- a/native/jni/src/suggest/core/suggest.h
+++ b/native/jni/src/suggest/core/suggest.h
@@ -55,7 +55,7 @@
     void createNextWordDicNode(DicTraverseSession *traverseSession, DicNode *dicNode,
             const bool spaceSubstitution) const;
     int outputSuggestions(DicTraverseSession *traverseSession, int *frequencies,
-            int *outputCodePoints, int *outputIndices, int *outputTypes) const;
+            int *outputCodePoints, int *outputIndicesToPartialCommit, int *outputTypes) const;
     void initializeSearch(DicTraverseSession *traverseSession, int commitPoint) const;
     void expandCurrentDicNodes(DicTraverseSession *traverseSession) const;
     void processTerminalDicNode(DicTraverseSession *traverseSession, DicNode *dicNode) const;
diff --git a/native/jni/src/suggest/policyimpl/dictionary/bigram/bigram_list_policy.h b/native/jni/src/suggest/policyimpl/dictionary/bigram/bigram_list_policy.h
new file mode 100644
index 0000000..6ff95ca
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/dictionary/bigram/bigram_list_policy.h
@@ -0,0 +1,53 @@
+/*
+ * 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.
+ */
+
+#ifndef LATINIME_BIGRAM_LIST_POLICY_H
+#define LATINIME_BIGRAM_LIST_POLICY_H
+
+#include <stdint.h>
+
+#include "defines.h"
+#include "suggest/core/policy/dictionary_bigrams_structure_policy.h"
+#include "suggest/policyimpl/dictionary/bigram/bigram_list_read_write_utils.h"
+
+namespace latinime {
+
+class BigramListPolicy : public DictionaryBigramsStructurePolicy {
+ public:
+    explicit BigramListPolicy(const uint8_t *const bigramsBuf) : mBigramsBuf(bigramsBuf) {}
+
+    ~BigramListPolicy() {}
+
+    void getNextBigram(int *const outBigramPos, int *const outProbability, bool *const outHasNext,
+            int *const pos) const {
+        BigramListReadWriteUtils::BigramFlags flags;
+        BigramListReadWriteUtils::getBigramEntryPropertiesAndAdvancePosition(mBigramsBuf, &flags,
+                outBigramPos, pos);
+        *outProbability = BigramListReadWriteUtils::getProbabilityFromFlags(flags);
+        *outHasNext = BigramListReadWriteUtils::hasNext(flags);
+    }
+
+    void skipAllBigrams(int *const pos) const {
+        BigramListReadWriteUtils::skipExistingBigrams(mBigramsBuf, pos);
+    }
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(BigramListPolicy);
+
+    const uint8_t *const mBigramsBuf;
+};
+} // namespace latinime
+#endif // LATINIME_BIGRAM_LIST_POLICY_H
diff --git a/native/jni/src/suggest/policyimpl/dictionary/bigram/bigram_list_read_write_utils.cpp b/native/jni/src/suggest/policyimpl/dictionary/bigram/bigram_list_read_write_utils.cpp
new file mode 100644
index 0000000..1926b98
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/dictionary/bigram/bigram_list_read_write_utils.cpp
@@ -0,0 +1,182 @@
+/*
+ * 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.
+ */
+
+#include "suggest/policyimpl/dictionary/bigram/bigram_list_read_write_utils.h"
+
+#include "suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_utils.h"
+#include "suggest/policyimpl/dictionary/utils/byte_array_utils.h"
+#include "suggest/policyimpl/dictionary/utils/buffer_with_extendable_buffer.h"
+
+namespace latinime {
+
+const BigramListReadWriteUtils::BigramFlags BigramListReadWriteUtils::MASK_ATTRIBUTE_ADDRESS_TYPE =
+        0x30;
+const BigramListReadWriteUtils::BigramFlags
+        BigramListReadWriteUtils::FLAG_ATTRIBUTE_ADDRESS_TYPE_ONEBYTE = 0x10;
+const BigramListReadWriteUtils::BigramFlags
+        BigramListReadWriteUtils::FLAG_ATTRIBUTE_ADDRESS_TYPE_TWOBYTES = 0x20;
+const BigramListReadWriteUtils::BigramFlags
+        BigramListReadWriteUtils::FLAG_ATTRIBUTE_ADDRESS_TYPE_THREEBYTES = 0x30;
+const BigramListReadWriteUtils::BigramFlags
+        BigramListReadWriteUtils::FLAG_ATTRIBUTE_OFFSET_NEGATIVE = 0x40;
+// Flag for presence of more attributes
+const BigramListReadWriteUtils::BigramFlags BigramListReadWriteUtils::FLAG_ATTRIBUTE_HAS_NEXT =
+        0x80;
+// Mask for attribute probability, stored on 4 bits inside the flags byte.
+const BigramListReadWriteUtils::BigramFlags
+        BigramListReadWriteUtils::MASK_ATTRIBUTE_PROBABILITY = 0x0F;
+const int BigramListReadWriteUtils::ATTRIBUTE_ADDRESS_SHIFT = 4;
+
+/* static */ void BigramListReadWriteUtils::getBigramEntryPropertiesAndAdvancePosition(
+        const uint8_t *const bigramsBuf, BigramFlags *const outBigramFlags,
+        int *const outTargetPtNodePos, int *const bigramEntryPos) {
+    const BigramFlags bigramFlags = ByteArrayUtils::readUint8AndAdvancePosition(bigramsBuf,
+            bigramEntryPos);
+    if (outBigramFlags) {
+        *outBigramFlags = bigramFlags;
+    }
+    const int targetPos = getBigramAddressAndAdvancePosition(bigramsBuf, bigramFlags,
+            bigramEntryPos);
+    if (outTargetPtNodePos) {
+        *outTargetPtNodePos = targetPos;
+    }
+}
+
+/* static */ void BigramListReadWriteUtils::skipExistingBigrams(const uint8_t *const bigramsBuf,
+        int *const bigramListPos) {
+    BigramFlags flags;
+    do {
+        getBigramEntryPropertiesAndAdvancePosition(bigramsBuf, &flags, 0 /* outTargetPtNodePos */,
+                bigramListPos);
+    } while(hasNext(flags));
+}
+
+/* static */ int BigramListReadWriteUtils::getBigramAddressAndAdvancePosition(
+        const uint8_t *const bigramsBuf, const BigramFlags flags, int *const pos) {
+    int offset = 0;
+    const int origin = *pos;
+    switch (MASK_ATTRIBUTE_ADDRESS_TYPE & flags) {
+        case FLAG_ATTRIBUTE_ADDRESS_TYPE_ONEBYTE:
+            offset = ByteArrayUtils::readUint8AndAdvancePosition(bigramsBuf, pos);
+            break;
+        case FLAG_ATTRIBUTE_ADDRESS_TYPE_TWOBYTES:
+            offset = ByteArrayUtils::readUint16AndAdvancePosition(bigramsBuf, pos);
+            break;
+        case FLAG_ATTRIBUTE_ADDRESS_TYPE_THREEBYTES:
+            offset = ByteArrayUtils::readUint24AndAdvancePosition(bigramsBuf, pos);
+            break;
+    }
+    if (offset == DynamicPatriciaTrieReadingUtils::DICT_OFFSET_INVALID) {
+        return NOT_A_DICT_POS;
+    } else if (offset == DynamicPatriciaTrieReadingUtils::DICT_OFFSET_ZERO_OFFSET) {
+        return origin;
+    }
+    if (isOffsetNegative(flags)) {
+        return origin - offset;
+    } else {
+        return origin + offset;
+    }
+}
+
+/* static */ bool BigramListReadWriteUtils::setHasNextFlag(
+        BufferWithExtendableBuffer *const buffer, const bool hasNext, const int entryPos) {
+    const bool usesAdditionalBuffer = buffer->isInAdditionalBuffer(entryPos);
+    int readingPos = entryPos;
+    if (usesAdditionalBuffer) {
+        readingPos -= buffer->getOriginalBufferSize();
+    }
+    BigramFlags bigramFlags = ByteArrayUtils::readUint8AndAdvancePosition(
+            buffer->getBuffer(usesAdditionalBuffer), &readingPos);
+    if (hasNext) {
+        bigramFlags = bigramFlags | FLAG_ATTRIBUTE_HAS_NEXT;
+    } else {
+        bigramFlags = bigramFlags & (~FLAG_ATTRIBUTE_HAS_NEXT);
+    }
+    int writingPos = entryPos;
+    return buffer->writeUintAndAdvancePosition(bigramFlags, 1 /* size */, &writingPos);
+}
+
+/* static */ bool BigramListReadWriteUtils::createAndWriteBigramEntry(
+        BufferWithExtendableBuffer *const buffer, const int targetPos, const int probability,
+        const bool hasNext, int *const writingPos) {
+    BigramFlags flags;
+    if (!createAndGetBigramFlags(*writingPos, targetPos, probability, hasNext, &flags)) {
+        return false;
+    }
+    return writeBigramEntry(buffer, flags, targetPos, writingPos);
+}
+
+/* static */ bool BigramListReadWriteUtils::writeBigramEntry(
+        BufferWithExtendableBuffer *const bufferToWrite, const BigramFlags flags,
+        const int targetPtNodePos, int *const writingPos) {
+    const int offset = getBigramTargetOffset(targetPtNodePos, *writingPos);
+    const BigramFlags flagsToWrite = (offset < 0) ?
+            (flags | FLAG_ATTRIBUTE_OFFSET_NEGATIVE) : (flags & ~FLAG_ATTRIBUTE_OFFSET_NEGATIVE);
+    if (!bufferToWrite->writeUintAndAdvancePosition(flagsToWrite, 1 /* size */, writingPos)) {
+        return false;
+    }
+    const uint32_t absOffest = abs(offset);
+    const int bigramTargetFieldSize = attributeAddressSize(flags);
+    return bufferToWrite->writeUintAndAdvancePosition(absOffest, bigramTargetFieldSize,
+            writingPos);
+}
+
+// Returns true if the bigram entry is valid and put entry flags into out*.
+/* static */ bool BigramListReadWriteUtils::createAndGetBigramFlags(const int entryPos,
+        const int targetPtNodePos, const int probability, const bool hasNext,
+        BigramFlags *const outBigramFlags) {
+    BigramFlags flags = probability & MASK_ATTRIBUTE_PROBABILITY;
+    if (hasNext) {
+        flags |= FLAG_ATTRIBUTE_HAS_NEXT;
+    }
+    const int offset = getBigramTargetOffset(targetPtNodePos, entryPos);
+    if (offset < 0) {
+        flags |= FLAG_ATTRIBUTE_OFFSET_NEGATIVE;
+    }
+    const uint32_t absOffest = abs(offset);
+    if ((absOffest >> 24) != 0) {
+        // Offset is too large.
+        return false;
+    } else if ((absOffest >> 16) != 0) {
+        flags |= FLAG_ATTRIBUTE_ADDRESS_TYPE_THREEBYTES;
+    } else if ((absOffest >> 8) != 0) {
+        flags |= FLAG_ATTRIBUTE_ADDRESS_TYPE_TWOBYTES;
+    } else {
+        flags |= FLAG_ATTRIBUTE_ADDRESS_TYPE_ONEBYTE;
+    }
+    // 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;
+    *outBigramFlags = flags;
+    return true;
+}
+
+/* static */ int BigramListReadWriteUtils::getBigramTargetOffset(const int targetPtNodePos,
+        const int entryPos) {
+    if (targetPtNodePos == NOT_A_DICT_POS) {
+        return DynamicPatriciaTrieReadingUtils::DICT_OFFSET_INVALID;
+    } else {
+        const int offset = targetPtNodePos - (entryPos + 1 /* bigramFlagsField */);
+        if (offset == 0) {
+            return DynamicPatriciaTrieReadingUtils::DICT_OFFSET_ZERO_OFFSET;
+        } else {
+            return offset;
+        }
+    }
+}
+
+} // namespace latinime
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
new file mode 100644
index 0000000..eabe4e0
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/dictionary/bigram/bigram_list_read_write_utils.h
@@ -0,0 +1,102 @@
+/*
+ * 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.
+ */
+
+#ifndef LATINIME_BIGRAM_LIST_READ_WRITE_UTILS_H
+#define LATINIME_BIGRAM_LIST_READ_WRITE_UTILS_H
+
+#include <cstdlib>
+#include <stdint.h>
+
+#include "defines.h"
+
+namespace latinime {
+
+class BufferWithExtendableBuffer;
+
+class BigramListReadWriteUtils {
+public:
+   typedef uint8_t BigramFlags;
+
+   static void getBigramEntryPropertiesAndAdvancePosition(const uint8_t *const bigramsBuf,
+           BigramFlags *const outBigramFlags, int *const outTargetPtNodePos,
+           int *const bigramEntryPos);
+
+   static AK_FORCE_INLINE int getProbabilityFromFlags(const BigramFlags flags) {
+       return flags & MASK_ATTRIBUTE_PROBABILITY;
+   }
+
+   static AK_FORCE_INLINE bool hasNext(const BigramFlags flags) {
+       return (flags & FLAG_ATTRIBUTE_HAS_NEXT) != 0;
+   }
+
+   // Bigrams reading methods
+   static void skipExistingBigrams(const uint8_t *const bigramsBuf, int *const bigramListPos);
+
+   // Returns the size of the bigram position field that is stored in bigram flags.
+   static AK_FORCE_INLINE int attributeAddressSize(const BigramFlags flags) {
+       return (flags & MASK_ATTRIBUTE_ADDRESS_TYPE) >> ATTRIBUTE_ADDRESS_SHIFT;
+       /* Note: this is a value-dependant optimization of what may probably be
+          more readably written this way:
+          switch (flags * BinaryFormat::MASK_ATTRIBUTE_ADDRESS_TYPE) {
+          case FLAG_ATTRIBUTE_ADDRESS_TYPE_ONEBYTE: return 1;
+          case FLAG_ATTRIBUTE_ADDRESS_TYPE_TWOBYTES: return 2;
+          case FLAG_ATTRIBUTE_ADDRESS_TYPE_THREEBYTE: return 3;
+          default: return 0;
+          }
+       */
+   }
+
+   static bool setHasNextFlag(BufferWithExtendableBuffer *const buffer,
+           const bool hasNext, const int entryPos);
+
+   static AK_FORCE_INLINE BigramFlags setProbabilityInFlags(const BigramFlags flags,
+           const int probability) {
+       return (flags & (~MASK_ATTRIBUTE_PROBABILITY)) | (probability & MASK_ATTRIBUTE_PROBABILITY);
+   }
+
+   static bool createAndWriteBigramEntry(BufferWithExtendableBuffer *const buffer,
+           const int targetPos, const int probability, const bool hasNext, int *const writingPos);
+
+   static bool writeBigramEntry(BufferWithExtendableBuffer *const buffer, const BigramFlags flags,
+           const int targetOffset, int *const writingPos);
+
+private:
+   DISALLOW_IMPLICIT_CONSTRUCTORS(BigramListReadWriteUtils);
+
+   static const BigramFlags MASK_ATTRIBUTE_ADDRESS_TYPE;
+   static const BigramFlags FLAG_ATTRIBUTE_ADDRESS_TYPE_ONEBYTE;
+   static const BigramFlags FLAG_ATTRIBUTE_ADDRESS_TYPE_TWOBYTES;
+   static const BigramFlags FLAG_ATTRIBUTE_ADDRESS_TYPE_THREEBYTES;
+   static const BigramFlags FLAG_ATTRIBUTE_OFFSET_NEGATIVE;
+   static const BigramFlags FLAG_ATTRIBUTE_HAS_NEXT;
+   static const BigramFlags MASK_ATTRIBUTE_PROBABILITY;
+   static const int ATTRIBUTE_ADDRESS_SHIFT;
+
+   // Returns true if the bigram entry is valid and put entry flags into out*.
+   static bool createAndGetBigramFlags(const int entryPos, const int targetPos,
+           const int probability, const bool hasNext, BigramFlags *const outBigramFlags);
+
+   static AK_FORCE_INLINE bool isOffsetNegative(const BigramFlags flags) {
+       return (flags & FLAG_ATTRIBUTE_OFFSET_NEGATIVE) != 0;
+   }
+
+   static int getBigramAddressAndAdvancePosition(const uint8_t *const bigramsBuf,
+           const BigramFlags flags, int *const pos);
+
+   static int getBigramTargetOffset(const int targetPtNodePos, const int entryPos);
+};
+} // namespace latinime
+#endif // LATINIME_BIGRAM_LIST_READ_WRITE_UTILS_H
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
new file mode 100644
index 0000000..bc2f5ee
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/dictionary/bigram/dynamic_bigram_list_policy.cpp
@@ -0,0 +1,336 @@
+/*
+ * 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.
+ */
+
+#include "suggest/policyimpl/dictionary/bigram/dynamic_bigram_list_policy.h"
+
+#include "suggest/core/policy/dictionary_shortcuts_structure_policy.h"
+#include "suggest/policyimpl/dictionary/bigram/bigram_list_read_write_utils.h"
+#include "suggest/policyimpl/dictionary/dynamic_patricia_trie_node_reader.h"
+#include "suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_helper.h"
+#include "suggest/policyimpl/dictionary/utils/buffer_with_extendable_buffer.h"
+
+namespace latinime {
+
+const int DynamicBigramListPolicy::CONTINUING_BIGRAM_LINK_COUNT_LIMIT = 10000;
+const int DynamicBigramListPolicy::BIGRAM_ENTRY_COUNT_IN_A_BIGRAM_LIST_LIMIT = 100000;
+
+void DynamicBigramListPolicy::getNextBigram(int *const outBigramPos, int *const outProbability,
+        bool *const outHasNext, int *const bigramEntryPos) const {
+    const bool usesAdditionalBuffer = mBuffer->isInAdditionalBuffer(*bigramEntryPos);
+    const uint8_t *const buffer = mBuffer->getBuffer(usesAdditionalBuffer);
+    if (usesAdditionalBuffer) {
+        *bigramEntryPos -= mBuffer->getOriginalBufferSize();
+    }
+    BigramListReadWriteUtils::BigramFlags bigramFlags;
+    int originalBigramPos;
+    BigramListReadWriteUtils::getBigramEntryPropertiesAndAdvancePosition(buffer, &bigramFlags,
+            &originalBigramPos, bigramEntryPos);
+    if (usesAdditionalBuffer && originalBigramPos != NOT_A_DICT_POS) {
+        originalBigramPos += mBuffer->getOriginalBufferSize();
+    }
+    *outBigramPos = followBigramLinkAndGetCurrentBigramPtNodePos(originalBigramPos);
+    *outProbability = BigramListReadWriteUtils::getProbabilityFromFlags(bigramFlags);
+    *outHasNext = BigramListReadWriteUtils::hasNext(bigramFlags);
+    if (usesAdditionalBuffer) {
+        *bigramEntryPos += mBuffer->getOriginalBufferSize();
+    }
+}
+
+void DynamicBigramListPolicy::skipAllBigrams(int *const bigramListPos) const {
+    const bool usesAdditionalBuffer = mBuffer->isInAdditionalBuffer(*bigramListPos);
+    const uint8_t *const buffer = mBuffer->getBuffer(usesAdditionalBuffer);
+    if (usesAdditionalBuffer) {
+        *bigramListPos -= mBuffer->getOriginalBufferSize();
+    }
+    BigramListReadWriteUtils::skipExistingBigrams(buffer, bigramListPos);
+    if (usesAdditionalBuffer) {
+        *bigramListPos += mBuffer->getOriginalBufferSize();
+    }
+}
+
+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();
+    }
+    *outBigramsCount = 0;
+    BigramListReadWriteUtils::BigramFlags bigramFlags;
+    int bigramEntryCount = 0;
+    int lastWrittenEntryPos = NOT_A_DICT_POS;
+    do {
+        if (++bigramEntryCount > BIGRAM_ENTRY_COUNT_IN_A_BIGRAM_LIST_LIMIT) {
+            AKLOGE("Too many bigram entries. Entry count: %d, Limit: %d",
+                    bigramEntryCount, BIGRAM_ENTRY_COUNT_IN_A_BIGRAM_LIST_LIMIT);
+            ASSERT(false);
+            return false;
+        }
+        // The buffer address can be changed after calling buffer writing methods.
+        int originalBigramPos;
+        BigramListReadWriteUtils::getBigramEntryPropertiesAndAdvancePosition(
+                mBuffer->getBuffer(usesAdditionalBuffer), &bigramFlags, &originalBigramPos,
+                fromPos);
+        if (originalBigramPos == NOT_A_DICT_POS) {
+            // skip invalid bigram entry.
+            continue;
+        }
+        if (usesAdditionalBuffer) {
+            originalBigramPos += mBuffer->getOriginalBufferSize();
+        }
+        const int bigramPos = followBigramLinkAndGetCurrentBigramPtNodePos(originalBigramPos);
+        if (bigramPos == NOT_A_DICT_POS) {
+            // Target PtNode has been invalidated.
+            continue;
+        }
+        lastWrittenEntryPos = *toPos;
+        if (!BigramListReadWriteUtils::createAndWriteBigramEntry(bufferToWrite, bigramPos,
+                BigramListReadWriteUtils::getProbabilityFromFlags(bigramFlags),
+                BigramListReadWriteUtils::hasNext(bigramFlags), toPos)) {
+            return false;
+        }
+        (*outBigramsCount)++;
+    } while(BigramListReadWriteUtils::hasNext(bigramFlags));
+    // Makes the last entry the terminal of the list. Updates the flags.
+    if (lastWrittenEntryPos != NOT_A_DICT_POS) {
+        if (!BigramListReadWriteUtils::setHasNextFlag(bufferToWrite, false /* hasNext */,
+                lastWrittenEntryPos)) {
+            return false;
+        }
+    }
+    if (usesAdditionalBuffer) {
+        *fromPos += mBuffer->getOriginalBufferSize();
+    }
+    return true;
+}
+
+// Finding useless bigram entries and remove them. Bigram entry is useless when the target PtNode
+// has been deleted or is not a valid terminal.
+bool DynamicBigramListPolicy::updateAllBigramEntriesAndDeleteUselessEntries(
+        int *const bigramListPos) {
+    const bool usesAdditionalBuffer = mBuffer->isInAdditionalBuffer(*bigramListPos);
+    if (usesAdditionalBuffer) {
+        *bigramListPos -= mBuffer->getOriginalBufferSize();
+    }
+    DynamicPatriciaTrieNodeReader nodeReader(mBuffer, this /* bigramsPolicy */, mShortcutPolicy);
+    BigramListReadWriteUtils::BigramFlags bigramFlags;
+    int bigramEntryCount = 0;
+    do {
+        if (++bigramEntryCount > BIGRAM_ENTRY_COUNT_IN_A_BIGRAM_LIST_LIMIT) {
+            AKLOGE("Too many bigram entries. Entry count: %d, Limit: %d",
+                    bigramEntryCount, BIGRAM_ENTRY_COUNT_IN_A_BIGRAM_LIST_LIMIT);
+            ASSERT(false);
+            return false;
+        }
+        int bigramEntryPos = *bigramListPos;
+        int originalBigramPos;
+        // The buffer address can be changed after calling buffer writing methods.
+        BigramListReadWriteUtils::getBigramEntryPropertiesAndAdvancePosition(
+                mBuffer->getBuffer(usesAdditionalBuffer), &bigramFlags, &originalBigramPos,
+                bigramListPos);
+        if (usesAdditionalBuffer) {
+            bigramEntryPos += mBuffer->getOriginalBufferSize();
+        }
+        if (originalBigramPos == NOT_A_DICT_POS) {
+            // This entry has already been removed.
+            continue;
+        }
+        if (usesAdditionalBuffer) {
+            originalBigramPos += mBuffer->getOriginalBufferSize();
+        }
+        const int bigramTargetNodePos =
+                followBigramLinkAndGetCurrentBigramPtNodePos(originalBigramPos);
+        nodeReader.fetchNodeInfoInBufferFromPtNodePos(bigramTargetNodePos);
+        // TODO: Update probability for supporting probability decaying.
+        if (nodeReader.isDeleted() || !nodeReader.isTerminal()
+                || bigramTargetNodePos == NOT_A_DICT_POS) {
+            // The target is no longer valid terminal. Invalidate the current bigram entry.
+            if (!BigramListReadWriteUtils::writeBigramEntry(mBuffer, bigramFlags,
+                    NOT_A_DICT_POS /* targetOffset */, &bigramEntryPos)) {
+                return false;
+            }
+        }
+    } while(BigramListReadWriteUtils::hasNext(bigramFlags));
+    return true;
+}
+
+// Updates bigram target PtNode positions in the list after the placing step in GC.
+bool DynamicBigramListPolicy::updateAllBigramTargetPtNodePositions(int *const bigramListPos,
+        const DynamicPatriciaTrieWritingHelper::PtNodePositionRelocationMap *const
+                ptNodePositionRelocationMap) {
+    const bool usesAdditionalBuffer = mBuffer->isInAdditionalBuffer(*bigramListPos);
+    if (usesAdditionalBuffer) {
+        *bigramListPos -= mBuffer->getOriginalBufferSize();
+    }
+    BigramListReadWriteUtils::BigramFlags bigramFlags;
+    int bigramEntryCount = 0;
+    do {
+        if (++bigramEntryCount > BIGRAM_ENTRY_COUNT_IN_A_BIGRAM_LIST_LIMIT) {
+            AKLOGE("Too many bigram entries. Entry count: %d, Limit: %d",
+                    bigramEntryCount, BIGRAM_ENTRY_COUNT_IN_A_BIGRAM_LIST_LIMIT);
+            ASSERT(false);
+            return false;
+        }
+        int bigramEntryPos = *bigramListPos;
+        if (usesAdditionalBuffer) {
+            bigramEntryPos += mBuffer->getOriginalBufferSize();
+        }
+        int bigramTargetPtNodePos;
+        // The buffer address can be changed after calling buffer writing methods.
+        BigramListReadWriteUtils::getBigramEntryPropertiesAndAdvancePosition(
+                mBuffer->getBuffer(usesAdditionalBuffer), &bigramFlags, &bigramTargetPtNodePos,
+                bigramListPos);
+        if (bigramTargetPtNodePos == NOT_A_DICT_POS) {
+            continue;
+        }
+        if (usesAdditionalBuffer) {
+            bigramTargetPtNodePos += mBuffer->getOriginalBufferSize();
+        }
+
+        DynamicPatriciaTrieWritingHelper::PtNodePositionRelocationMap::const_iterator it =
+                ptNodePositionRelocationMap->find(bigramTargetPtNodePos);
+        if (it != ptNodePositionRelocationMap->end()) {
+            bigramTargetPtNodePos = it->second;
+        } else {
+            bigramTargetPtNodePos = NOT_A_DICT_POS;
+        }
+        if (!BigramListReadWriteUtils::writeBigramEntry(mBuffer, bigramFlags,
+                bigramTargetPtNodePos, &bigramEntryPos)) {
+            return false;
+        }
+    } while(BigramListReadWriteUtils::hasNext(bigramFlags));
+    return true;
+}
+
+bool DynamicBigramListPolicy::addNewBigramEntryToBigramList(const int bigramTargetPos,
+        const int probability, int *const bigramListPos) {
+    const bool usesAdditionalBuffer = mBuffer->isInAdditionalBuffer(*bigramListPos);
+    if (usesAdditionalBuffer) {
+        *bigramListPos -= mBuffer->getOriginalBufferSize();
+    }
+    BigramListReadWriteUtils::BigramFlags bigramFlags;
+    int bigramEntryCount = 0;
+    do {
+        if (++bigramEntryCount > BIGRAM_ENTRY_COUNT_IN_A_BIGRAM_LIST_LIMIT) {
+            AKLOGE("Too many bigram entries. Entry count: %d, Limit: %d",
+                    bigramEntryCount, BIGRAM_ENTRY_COUNT_IN_A_BIGRAM_LIST_LIMIT);
+            ASSERT(false);
+            return false;
+        }
+        int entryPos = *bigramListPos;
+        if (usesAdditionalBuffer) {
+            entryPos += mBuffer->getOriginalBufferSize();
+        }
+        int originalBigramPos;
+        // The buffer address can be changed after calling buffer writing methods.
+        BigramListReadWriteUtils::getBigramEntryPropertiesAndAdvancePosition(
+                mBuffer->getBuffer(usesAdditionalBuffer), &bigramFlags, &originalBigramPos,
+                bigramListPos);
+        if (usesAdditionalBuffer && originalBigramPos != NOT_A_DICT_POS) {
+            originalBigramPos += mBuffer->getOriginalBufferSize();
+        }
+        if (followBigramLinkAndGetCurrentBigramPtNodePos(originalBigramPos) == bigramTargetPos) {
+            // Update this bigram entry.
+            const BigramListReadWriteUtils::BigramFlags updatedFlags =
+                    BigramListReadWriteUtils::setProbabilityInFlags(bigramFlags, probability);
+            return BigramListReadWriteUtils::writeBigramEntry(mBuffer, updatedFlags,
+                    originalBigramPos, &entryPos);
+        }
+        if (BigramListReadWriteUtils::hasNext(bigramFlags)) {
+            continue;
+        }
+        // The current last entry is found.
+        // First, update the flags of the last entry.
+        if (!BigramListReadWriteUtils::setHasNextFlag(mBuffer, true /* hasNext */, entryPos)) {
+            return false;
+        }
+        if (usesAdditionalBuffer) {
+            *bigramListPos += mBuffer->getOriginalBufferSize();
+        }
+        // Then, add a new entry after the last entry.
+        return writeNewBigramEntry(bigramTargetPos, probability, bigramListPos);
+    } while(BigramListReadWriteUtils::hasNext(bigramFlags));
+    // We return directly from the while loop.
+    ASSERT(false);
+    return false;
+}
+
+bool DynamicBigramListPolicy::writeNewBigramEntry(const int bigramTargetPos, const int probability,
+        int *const writingPos) {
+    // hasNext is false because we are adding a new bigram entry at the end of the bigram list.
+    return BigramListReadWriteUtils::createAndWriteBigramEntry(mBuffer, bigramTargetPos,
+            probability, false /* hasNext */, writingPos);
+}
+
+bool DynamicBigramListPolicy::removeBigram(const int bigramListPos, const int bigramTargetPos) {
+    const bool usesAdditionalBuffer = mBuffer->isInAdditionalBuffer(bigramListPos);
+    int pos = bigramListPos;
+    if (usesAdditionalBuffer) {
+        pos -= mBuffer->getOriginalBufferSize();
+    }
+    BigramListReadWriteUtils::BigramFlags bigramFlags;
+    int bigramEntryCount = 0;
+    do {
+        if (++bigramEntryCount > BIGRAM_ENTRY_COUNT_IN_A_BIGRAM_LIST_LIMIT) {
+            AKLOGE("Too many bigram entries. Entry count: %d, Limit: %d",
+                    bigramEntryCount, BIGRAM_ENTRY_COUNT_IN_A_BIGRAM_LIST_LIMIT);
+            ASSERT(false);
+            return false;
+        }
+        int bigramEntryPos = pos;
+        int originalBigramPos;
+        // The buffer address can be changed after calling buffer writing methods.
+        BigramListReadWriteUtils::getBigramEntryPropertiesAndAdvancePosition(
+                mBuffer->getBuffer(usesAdditionalBuffer), &bigramFlags, &originalBigramPos, &pos);
+        if (usesAdditionalBuffer) {
+            bigramEntryPos += mBuffer->getOriginalBufferSize();
+        }
+        if (usesAdditionalBuffer && originalBigramPos != NOT_A_DICT_POS) {
+            originalBigramPos += mBuffer->getOriginalBufferSize();
+        }
+        const int bigramPos = followBigramLinkAndGetCurrentBigramPtNodePos(originalBigramPos);
+        if (bigramPos != bigramTargetPos) {
+            continue;
+        }
+        // Target entry is found. Write an invalid target position to mark the bigram invalid.
+        return BigramListReadWriteUtils::writeBigramEntry(mBuffer, bigramFlags,
+                NOT_A_DICT_POS /* targetOffset */, &bigramEntryPos);
+    } while(BigramListReadWriteUtils::hasNext(bigramFlags));
+    return false;
+}
+
+int DynamicBigramListPolicy::followBigramLinkAndGetCurrentBigramPtNodePos(
+        const int originalBigramPos) const {
+    if (originalBigramPos == NOT_A_DICT_POS) {
+        return NOT_A_DICT_POS;
+    }
+    int currentPos = originalBigramPos;
+    DynamicPatriciaTrieNodeReader nodeReader(mBuffer, this /* bigramsPolicy */, mShortcutPolicy);
+    nodeReader.fetchNodeInfoInBufferFromPtNodePos(currentPos);
+    int bigramLinkCount = 0;
+    while (nodeReader.getBigramLinkedNodePos() != NOT_A_DICT_POS) {
+        currentPos = nodeReader.getBigramLinkedNodePos();
+        nodeReader.fetchNodeInfoInBufferFromPtNodePos(currentPos);
+        bigramLinkCount++;
+        if (bigramLinkCount > CONTINUING_BIGRAM_LINK_COUNT_LIMIT) {
+            AKLOGE("Bigram link is invalid. start position: %d", bigramPos);
+            ASSERT(false);
+            return NOT_A_DICT_POS;
+        }
+    }
+    return currentPos;
+}
+
+} // namespace latinime
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
new file mode 100644
index 0000000..8ea318a
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/dictionary/bigram/dynamic_bigram_list_policy.h
@@ -0,0 +1,81 @@
+/*
+ * 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.
+ */
+
+#ifndef LATINIME_DYNAMIC_BIGRAM_LIST_POLICY_H
+#define LATINIME_DYNAMIC_BIGRAM_LIST_POLICY_H
+
+#include <stdint.h>
+
+#include "defines.h"
+#include "suggest/core/policy/dictionary_bigrams_structure_policy.h"
+#include "suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_helper.h"
+
+namespace latinime {
+
+class BufferWithExtendableBuffer;
+class DictionaryShortcutsStructurePolicy;
+
+/*
+ * This is a dynamic version of BigramListPolicy and supports an additional buffer.
+ */
+class DynamicBigramListPolicy : public DictionaryBigramsStructurePolicy {
+ public:
+    DynamicBigramListPolicy(BufferWithExtendableBuffer *const buffer,
+            const DictionaryShortcutsStructurePolicy *const shortcutPolicy)
+            : mBuffer(buffer), mShortcutPolicy(shortcutPolicy) {}
+
+    ~DynamicBigramListPolicy() {}
+
+    void getNextBigram(int *const outBigramPos, int *const outProbability, bool *const outHasNext,
+            int *const bigramEntryPos) const;
+
+    void skipAllBigrams(int *const bigramListPos) const;
+
+    // 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 updateAllBigramEntriesAndDeleteUselessEntries(int *const bigramListPos);
+
+    bool updateAllBigramTargetPtNodePositions(int *const bigramListPos,
+            const DynamicPatriciaTrieWritingHelper::PtNodePositionRelocationMap *const
+                    ptNodePositionRelocationMap);
+
+    bool addNewBigramEntryToBigramList(const int bigramTargetPos, const int probability,
+            int *const bigramListPos);
+
+    bool writeNewBigramEntry(const int bigramTargetPos, const int probability,
+            int *const writingPos);
+
+    // Return if targetBigramPos is found or not.
+    bool removeBigram(const int bigramListPos, const int bigramTargetPos);
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(DynamicBigramListPolicy);
+
+    static const int CONTINUING_BIGRAM_LINK_COUNT_LIMIT;
+    static const int BIGRAM_ENTRY_COUNT_IN_A_BIGRAM_LIST_LIMIT;
+
+    BufferWithExtendableBuffer *const mBuffer;
+    const DictionaryShortcutsStructurePolicy *const mShortcutPolicy;
+
+    // Follow bigram link and return the position of bigram target PtNode that is currently valid.
+    int followBigramLinkAndGetCurrentBigramPtNodePos(const int originalBigramPos) const;
+};
+} // namespace latinime
+#endif // LATINIME_DYNAMIC_BIGRAM_LIST_POLICY_H
diff --git a/native/jni/src/suggest/policyimpl/dictionary/binary_format.h b/native/jni/src/suggest/policyimpl/dictionary/binary_format.h
deleted file mode 100644
index 23f4c7f..0000000
--- a/native/jni/src/suggest/policyimpl/dictionary/binary_format.h
+++ /dev/null
@@ -1,470 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef LATINIME_BINARY_FORMAT_H
-#define LATINIME_BINARY_FORMAT_H
-
-#include <stdint.h>
-
-#include "suggest/core/dictionary/probability_utils.h"
-#include "utils/char_utils.h"
-
-namespace latinime {
-
-class BinaryFormat {
- public:
-    // Mask and flags for children address type selection.
-    static const int MASK_GROUP_ADDRESS_TYPE = 0xC0;
-
-    // Flag for single/multiple char group
-    static const int FLAG_HAS_MULTIPLE_CHARS = 0x20;
-
-    // Flag for terminal groups
-    static const int FLAG_IS_TERMINAL = 0x10;
-
-    // Flag for shortcut targets presence
-    static const int FLAG_HAS_SHORTCUT_TARGETS = 0x08;
-    // Flag for bigram presence
-    static const int FLAG_HAS_BIGRAMS = 0x04;
-    // Flag for non-words (typically, shortcut only entries)
-    static const int FLAG_IS_NOT_A_WORD = 0x02;
-    // Flag for blacklist
-    static const int FLAG_IS_BLACKLISTED = 0x01;
-
-    // Attribute (bigram/shortcut) related flags:
-    // Flag for presence of more attributes
-    static const int FLAG_ATTRIBUTE_HAS_NEXT = 0x80;
-    // Flag for sign of offset. If this flag is set, the offset value must be negated.
-    static const int FLAG_ATTRIBUTE_OFFSET_NEGATIVE = 0x40;
-
-    // Mask for attribute probability, stored on 4 bits inside the flags byte.
-    static const int MASK_ATTRIBUTE_PROBABILITY = 0x0F;
-
-    // Mask and flags for attribute address type selection.
-    static const int MASK_ATTRIBUTE_ADDRESS_TYPE = 0x30;
-
-    static int getGroupCountAndForwardPointer(const uint8_t *const dict, int *pos);
-    static uint8_t getFlagsAndForwardPointer(const uint8_t *const dict, int *pos);
-    static int getCodePointAndForwardPointer(const uint8_t *const dict, int *pos);
-    static int readProbabilityWithoutMovingPointer(const uint8_t *const dict, const int pos);
-    static int skipOtherCharacters(const uint8_t *const dict, const int pos);
-    static int skipChildrenPosition(const uint8_t flags, const int pos);
-    static int skipProbability(const uint8_t flags, const int pos);
-    static int skipShortcuts(const uint8_t *const dict, const uint8_t flags, const int pos);
-    static int skipChildrenPosAndAttributes(const uint8_t *const dict, const uint8_t flags,
-            const int pos);
-    static int readChildrenPosition(const uint8_t *const dict, const uint8_t flags, const int pos);
-    static bool hasChildrenInFlags(const uint8_t flags);
-    static int getTerminalPosition(const uint8_t *const root, const int *const inWord,
-            const int length, const bool forceLowerCaseSearch);
-    static int getCodePointsAndProbabilityAndReturnCodePointCount(
-            const uint8_t *const root, const int nodePos, const int maxCodePointCount,
-            int *const outCodePoints, int *const outUnigramProbability);
-
- private:
-    DISALLOW_IMPLICIT_CONSTRUCTORS(BinaryFormat);
-
-    static const int FLAG_GROUP_ADDRESS_TYPE_NOADDRESS = 0x00;
-    static const int FLAG_GROUP_ADDRESS_TYPE_ONEBYTE = 0x40;
-    static const int FLAG_GROUP_ADDRESS_TYPE_TWOBYTES = 0x80;
-    static const int FLAG_GROUP_ADDRESS_TYPE_THREEBYTES = 0xC0;
-    static const int FLAG_ATTRIBUTE_ADDRESS_TYPE_ONEBYTE = 0x10;
-    static const int FLAG_ATTRIBUTE_ADDRESS_TYPE_TWOBYTES = 0x20;
-    static const int FLAG_ATTRIBUTE_ADDRESS_TYPE_THREEBYTES = 0x30;
-
-    static const int CHARACTER_ARRAY_TERMINATOR_SIZE = 1;
-    static const int MINIMAL_ONE_BYTE_CHARACTER_VALUE = 0x20;
-    static const int CHARACTER_ARRAY_TERMINATOR = 0x1F;
-    static const int MULTIPLE_BYTE_CHARACTER_ADDITIONAL_SIZE = 2;
-    static const int NO_FLAGS = 0;
-    static int skipAllAttributes(const uint8_t *const dict, const uint8_t flags, const int pos);
-    static int skipBigrams(const uint8_t *const dict, const uint8_t flags, const int pos);
-};
-
-AK_FORCE_INLINE int BinaryFormat::getGroupCountAndForwardPointer(const uint8_t *const dict,
-        int *pos) {
-    const int msb = dict[(*pos)++];
-    if (msb < 0x80) return msb;
-    return ((msb & 0x7F) << 8) | dict[(*pos)++];
-}
-
-inline uint8_t BinaryFormat::getFlagsAndForwardPointer(const uint8_t *const dict, int *pos) {
-    return dict[(*pos)++];
-}
-
-AK_FORCE_INLINE int BinaryFormat::getCodePointAndForwardPointer(const uint8_t *const dict,
-        int *pos) {
-    const int origin = *pos;
-    const int codePoint = dict[origin];
-    if (codePoint < MINIMAL_ONE_BYTE_CHARACTER_VALUE) {
-        if (codePoint == CHARACTER_ARRAY_TERMINATOR) {
-            *pos = origin + 1;
-            return NOT_A_CODE_POINT;
-        } else {
-            *pos = origin + 3;
-            const int char_1 = codePoint << 16;
-            const int char_2 = char_1 + (dict[origin + 1] << 8);
-            return char_2 + dict[origin + 2];
-        }
-    } else {
-        *pos = origin + 1;
-        return codePoint;
-    }
-}
-
-inline int BinaryFormat::readProbabilityWithoutMovingPointer(const uint8_t *const dict,
-        const int pos) {
-    return dict[pos];
-}
-
-AK_FORCE_INLINE int BinaryFormat::skipOtherCharacters(const uint8_t *const dict, const int pos) {
-    int currentPos = pos;
-    int character = dict[currentPos++];
-    while (CHARACTER_ARRAY_TERMINATOR != character) {
-        if (character < MINIMAL_ONE_BYTE_CHARACTER_VALUE) {
-            currentPos += MULTIPLE_BYTE_CHARACTER_ADDITIONAL_SIZE;
-        }
-        character = dict[currentPos++];
-    }
-    return currentPos;
-}
-
-static inline int attributeAddressSize(const uint8_t flags) {
-    static const int ATTRIBUTE_ADDRESS_SHIFT = 4;
-    return (flags & BinaryFormat::MASK_ATTRIBUTE_ADDRESS_TYPE) >> ATTRIBUTE_ADDRESS_SHIFT;
-    /* Note: this is a value-dependant optimization of what may probably be
-       more readably written this way:
-       switch (flags * BinaryFormat::MASK_ATTRIBUTE_ADDRESS_TYPE) {
-       case FLAG_ATTRIBUTE_ADDRESS_TYPE_ONEBYTE: return 1;
-       case FLAG_ATTRIBUTE_ADDRESS_TYPE_TWOBYTES: return 2;
-       case FLAG_ATTRIBUTE_ADDRESS_TYPE_THREEBYTE: return 3;
-       default: return 0;
-       }
-    */
-}
-
-static AK_FORCE_INLINE int skipExistingBigrams(const uint8_t *const dict, const int pos) {
-    int currentPos = pos;
-    uint8_t flags = BinaryFormat::getFlagsAndForwardPointer(dict, &currentPos);
-    while (flags & BinaryFormat::FLAG_ATTRIBUTE_HAS_NEXT) {
-        currentPos += attributeAddressSize(flags);
-        flags = BinaryFormat::getFlagsAndForwardPointer(dict, &currentPos);
-    }
-    currentPos += attributeAddressSize(flags);
-    return currentPos;
-}
-
-static inline int childrenAddressSize(const uint8_t flags) {
-    static const int CHILDREN_ADDRESS_SHIFT = 6;
-    return (BinaryFormat::MASK_GROUP_ADDRESS_TYPE & flags) >> CHILDREN_ADDRESS_SHIFT;
-    /* See the note in attributeAddressSize. The same applies here */
-}
-
-static AK_FORCE_INLINE int shortcutByteSize(const uint8_t *const dict, const int pos) {
-    return (static_cast<int>(dict[pos] << 8)) + (dict[pos + 1]);
-}
-
-inline int BinaryFormat::skipChildrenPosition(const uint8_t flags, const int pos) {
-    return pos + childrenAddressSize(flags);
-}
-
-inline int BinaryFormat::skipProbability(const uint8_t flags, const int pos) {
-    return FLAG_IS_TERMINAL & flags ? pos + 1 : pos;
-}
-
-AK_FORCE_INLINE int BinaryFormat::skipShortcuts(const uint8_t *const dict, const uint8_t flags,
-        const int pos) {
-    if (FLAG_HAS_SHORTCUT_TARGETS & flags) {
-        return pos + shortcutByteSize(dict, pos);
-    } else {
-        return pos;
-    }
-}
-
-AK_FORCE_INLINE int BinaryFormat::skipBigrams(const uint8_t *const dict, const uint8_t flags,
-        const int pos) {
-    if (FLAG_HAS_BIGRAMS & flags) {
-        return skipExistingBigrams(dict, pos);
-    } else {
-        return pos;
-    }
-}
-
-AK_FORCE_INLINE int BinaryFormat::skipAllAttributes(const uint8_t *const dict, const uint8_t flags,
-        const int pos) {
-    // This function skips all attributes: shortcuts and bigrams.
-    int newPos = pos;
-    newPos = skipShortcuts(dict, flags, newPos);
-    newPos = skipBigrams(dict, flags, newPos);
-    return newPos;
-}
-
-AK_FORCE_INLINE int BinaryFormat::skipChildrenPosAndAttributes(const uint8_t *const dict,
-        const uint8_t flags, const int pos) {
-    int currentPos = pos;
-    currentPos = skipChildrenPosition(flags, currentPos);
-    currentPos = skipAllAttributes(dict, flags, currentPos);
-    return currentPos;
-}
-
-AK_FORCE_INLINE int BinaryFormat::readChildrenPosition(const uint8_t *const dict,
-        const uint8_t flags, const int pos) {
-    int offset = 0;
-    switch (MASK_GROUP_ADDRESS_TYPE & flags) {
-        case FLAG_GROUP_ADDRESS_TYPE_ONEBYTE:
-            offset = dict[pos];
-            break;
-        case FLAG_GROUP_ADDRESS_TYPE_TWOBYTES:
-            offset = dict[pos] << 8;
-            offset += dict[pos + 1];
-            break;
-        case FLAG_GROUP_ADDRESS_TYPE_THREEBYTES:
-            offset = dict[pos] << 16;
-            offset += dict[pos + 1] << 8;
-            offset += dict[pos + 2];
-            break;
-        default:
-            // If we come here, it means we asked for the children of a word with
-            // no children.
-            return -1;
-    }
-    return pos + offset;
-}
-
-inline bool BinaryFormat::hasChildrenInFlags(const uint8_t flags) {
-    return (FLAG_GROUP_ADDRESS_TYPE_NOADDRESS != (MASK_GROUP_ADDRESS_TYPE & flags));
-}
-
-// This function gets the byte position of the last chargroup of the exact matching word in the
-// dictionary. If no match is found, it returns NOT_A_VALID_WORD_POS.
-AK_FORCE_INLINE int BinaryFormat::getTerminalPosition(const uint8_t *const root,
-        const int *const inWord, const int length, const bool forceLowerCaseSearch) {
-    int pos = 0;
-    int wordPos = 0;
-
-    while (true) {
-        // If we already traversed the tree further than the word is long, there means
-        // there was no match (or we would have found it).
-        if (wordPos >= length) return NOT_A_VALID_WORD_POS;
-        int charGroupCount = BinaryFormat::getGroupCountAndForwardPointer(root, &pos);
-        const int wChar = forceLowerCaseSearch
-                ? CharUtils::toLowerCase(inWord[wordPos]) : inWord[wordPos];
-        while (true) {
-            // If there are no more character groups in this node, it means we could not
-            // find a matching character for this depth, therefore there is no match.
-            if (0 >= charGroupCount) return NOT_A_VALID_WORD_POS;
-            const int charGroupPos = pos;
-            const uint8_t flags = BinaryFormat::getFlagsAndForwardPointer(root, &pos);
-            int character = BinaryFormat::getCodePointAndForwardPointer(root, &pos);
-            if (character == wChar) {
-                // This is the correct node. Only one character group may start with the same
-                // char within a node, so either we found our match in this node, or there is
-                // no match and we can return NOT_A_VALID_WORD_POS. So we will check all the
-                // characters in this character group indeed does match.
-                if (FLAG_HAS_MULTIPLE_CHARS & flags) {
-                    character = BinaryFormat::getCodePointAndForwardPointer(root, &pos);
-                    while (NOT_A_CODE_POINT != character) {
-                        ++wordPos;
-                        // If we shoot the length of the word we search for, or if we find a single
-                        // character that does not match, as explained above, it means the word is
-                        // not in the dictionary (by virtue of this chargroup being the only one to
-                        // match the word on the first character, but not matching the whole word).
-                        if (wordPos >= length) return NOT_A_VALID_WORD_POS;
-                        if (inWord[wordPos] != character) return NOT_A_VALID_WORD_POS;
-                        character = BinaryFormat::getCodePointAndForwardPointer(root, &pos);
-                    }
-                }
-                // If we come here we know that so far, we do match. Either we are on a terminal
-                // and we match the length, in which case we found it, or we traverse children.
-                // If we don't match the length AND don't have children, then a word in the
-                // dictionary fully matches a prefix of the searched word but not the full word.
-                ++wordPos;
-                if (FLAG_IS_TERMINAL & flags) {
-                    if (wordPos == length) {
-                        return charGroupPos;
-                    }
-                    pos = BinaryFormat::skipProbability(FLAG_IS_TERMINAL, pos);
-                }
-                if (FLAG_GROUP_ADDRESS_TYPE_NOADDRESS == (MASK_GROUP_ADDRESS_TYPE & flags)) {
-                    return NOT_A_VALID_WORD_POS;
-                }
-                // We have children and we are still shorter than the word we are searching for, so
-                // we need to traverse children. Put the pointer on the children position, and
-                // break
-                pos = BinaryFormat::readChildrenPosition(root, flags, pos);
-                break;
-            } else {
-                // This chargroup does not match, so skip the remaining part and go to the next.
-                if (FLAG_HAS_MULTIPLE_CHARS & flags) {
-                    pos = BinaryFormat::skipOtherCharacters(root, pos);
-                }
-                pos = BinaryFormat::skipProbability(flags, pos);
-                pos = BinaryFormat::skipChildrenPosAndAttributes(root, flags, pos);
-            }
-            --charGroupCount;
-        }
-    }
-}
-
-// This function searches for a terminal in the dictionary by its address.
-// Due to the fact that words are ordered in the dictionary in a strict breadth-first order,
-// it is possible to check for this with advantageous complexity. For each node, we search
-// for groups with children and compare the children address with the address we look for.
-// When we shoot the address we look for, it means the word we look for is in the children
-// of the previous group. The only tricky part is the fact that if we arrive at the end of a
-// node with the last group's children address still less than what we are searching for, we
-// must descend the last group's children (for example, if the word we are searching for starts
-// with a z, it's the last group of the root node, so all children addresses will be smaller
-// than the address we look for, and we have to descend the z node).
-/* Parameters :
- * root: the dictionary buffer
- * address: the byte position of the last chargroup of the word we are searching for (this is
- *   what is stored as the "bigram address" in each bigram)
- * outword: an array to write the found word, with MAX_WORD_LENGTH size.
- * outUnigramProbability: a pointer to an int to write the probability into.
- * Return value : the length of the word, of 0 if the word was not found.
- */
-AK_FORCE_INLINE int BinaryFormat::getCodePointsAndProbabilityAndReturnCodePointCount(
-        const uint8_t *const root, const int nodePos, const int maxCodePointCount,
-        int *const outCodePoints, int *const outUnigramProbability) {
-    int pos = 0;
-    int wordPos = 0;
-
-    // One iteration of the outer loop iterates through nodes. As stated above, we will only
-    // traverse nodes that are actually a part of the terminal we are searching, so each time
-    // we enter this loop we are one depth level further than last time.
-    // The only reason we count nodes is because we want to reduce the probability of infinite
-    // looping in case there is a bug. Since we know there is an upper bound to the depth we are
-    // supposed to traverse, it does not hurt to count iterations.
-    for (int loopCount = maxCodePointCount; loopCount > 0; --loopCount) {
-        int lastCandidateGroupPos = 0;
-        // Let's loop through char groups in this node searching for either the terminal
-        // or one of its ascendants.
-        for (int charGroupCount = getGroupCountAndForwardPointer(root, &pos); charGroupCount > 0;
-                 --charGroupCount) {
-            const int startPos = pos;
-            const uint8_t flags = getFlagsAndForwardPointer(root, &pos);
-            const int character = getCodePointAndForwardPointer(root, &pos);
-            if (nodePos == startPos) {
-                // We found the address. Copy the rest of the word in the buffer and return
-                // the length.
-                outCodePoints[wordPos] = character;
-                if (FLAG_HAS_MULTIPLE_CHARS & flags) {
-                    int nextChar = getCodePointAndForwardPointer(root, &pos);
-                    // We count chars in order to avoid infinite loops if the file is broken or
-                    // if there is some other bug
-                    int charCount = maxCodePointCount;
-                    while (NOT_A_CODE_POINT != nextChar && --charCount > 0) {
-                        outCodePoints[++wordPos] = nextChar;
-                        nextChar = getCodePointAndForwardPointer(root, &pos);
-                    }
-                }
-                *outUnigramProbability = readProbabilityWithoutMovingPointer(root, pos);
-                return ++wordPos;
-            }
-            // We need to skip past this char group, so skip any remaining chars after the
-            // first and possibly the probability.
-            if (FLAG_HAS_MULTIPLE_CHARS & flags) {
-                pos = skipOtherCharacters(root, pos);
-            }
-            pos = skipProbability(flags, pos);
-
-            // The fact that this group has children is very important. Since we already know
-            // that this group does not match, if it has no children we know it is irrelevant
-            // to what we are searching for.
-            const bool hasChildren = (FLAG_GROUP_ADDRESS_TYPE_NOADDRESS !=
-                    (MASK_GROUP_ADDRESS_TYPE & flags));
-            // We will write in `found' whether we have passed the children address we are
-            // searching for. For example if we search for "beer", the children of b are less
-            // than the address we are searching for and the children of c are greater. When we
-            // come here for c, we realize this is too big, and that we should descend b.
-            bool found;
-            if (hasChildren) {
-                // Here comes the tricky part. First, read the children position.
-                const int childrenPos = readChildrenPosition(root, flags, pos);
-                if (childrenPos > nodePos) {
-                    // If the children pos is greater than address, it means the previous chargroup,
-                    // which address is stored in lastCandidateGroupPos, was the right one.
-                    found = true;
-                } else if (1 >= charGroupCount) {
-                    // However if we are on the LAST group of this node, and we have NOT shot the
-                    // address we should descend THIS node. So we trick the lastCandidateGroupPos
-                    // so that we will descend this node, not the previous one.
-                    lastCandidateGroupPos = startPos;
-                    found = true;
-                } else {
-                    // Else, we should continue looking.
-                    found = false;
-                }
-            } else {
-                // Even if we don't have children here, we could still be on the last group of this
-                // node. If this is the case, we should descend the last group that had children,
-                // and their address is already in lastCandidateGroup.
-                found = (1 >= charGroupCount);
-            }
-
-            if (found) {
-                // Okay, we found the group we should descend. Its address is in
-                // the lastCandidateGroupPos variable, so we just re-read it.
-                if (0 != lastCandidateGroupPos) {
-                    const uint8_t lastFlags =
-                            getFlagsAndForwardPointer(root, &lastCandidateGroupPos);
-                    const int lastChar =
-                            getCodePointAndForwardPointer(root, &lastCandidateGroupPos);
-                    // We copy all the characters in this group to the buffer
-                    outCodePoints[wordPos] = lastChar;
-                    if (FLAG_HAS_MULTIPLE_CHARS & lastFlags) {
-                        int nextChar = getCodePointAndForwardPointer(root, &lastCandidateGroupPos);
-                        int charCount = maxCodePointCount;
-                        while (-1 != nextChar && --charCount > 0) {
-                            outCodePoints[++wordPos] = nextChar;
-                            nextChar = getCodePointAndForwardPointer(root, &lastCandidateGroupPos);
-                        }
-                    }
-                    ++wordPos;
-                    // Now we only need to branch to the children address. Skip the probability if
-                    // it's there, read pos, and break to resume the search at pos.
-                    lastCandidateGroupPos = skipProbability(lastFlags, lastCandidateGroupPos);
-                    pos = readChildrenPosition(root, lastFlags, lastCandidateGroupPos);
-                    break;
-                } else {
-                    // Here is a little tricky part: we come here if we found out that all children
-                    // addresses in this group are bigger than the address we are searching for.
-                    // Should we conclude the word is not in the dictionary? No! It could still be
-                    // one of the remaining chargroups in this node, so we have to keep looking in
-                    // this node until we find it (or we realize it's not there either, in which
-                    // case it's actually not in the dictionary). Pass the end of this group, ready
-                    // to start the next one.
-                    pos = skipChildrenPosAndAttributes(root, flags, pos);
-                }
-            } else {
-                // If we did not find it, we should record the last children address for the next
-                // iteration.
-                if (hasChildren) lastCandidateGroupPos = startPos;
-                // Now skip the end of this group (children pos and the attributes if any) so that
-                // our pos is after the end of this char group, at the start of the next one.
-                pos = skipChildrenPosAndAttributes(root, flags, pos);
-            }
-
-        }
-    }
-    // If we have looked through all the chargroups and found no match, the address is
-    // not the address of a terminal in this dictionary.
-    return 0;
-}
-
-} // namespace latinime
-#endif // LATINIME_BINARY_FORMAT_H
diff --git a/native/jni/src/suggest/policyimpl/dictionary/dictionary_structure_policy_factory.h b/native/jni/src/suggest/policyimpl/dictionary/dictionary_structure_policy_factory.h
deleted file mode 100644
index c0df89f..0000000
--- a/native/jni/src/suggest/policyimpl/dictionary/dictionary_structure_policy_factory.h
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * 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.
- */
-
-#ifndef LATINIME_DICTIONARY_STRUCTURE_POLICY_FACTORY_H
-#define LATINIME_DICTIONARY_STRUCTURE_POLICY_FACTORY_H
-
-#include "defines.h"
-#include "suggest/core/dictionary/binary_dictionary_format_utils.h"
-#include "suggest/policyimpl/dictionary/dynamic_patricia_trie_policy.h"
-#include "suggest/policyimpl/dictionary/patricia_trie_policy.h"
-
-namespace latinime {
-
-class DictionaryStructurePolicy;
-
-class DictionaryStructurePolicyFactory {
- public:
-    static const DictionaryStructurePolicy *getDictionaryStructurePolicy(
-            const BinaryDictionaryFormatUtils::FORMAT_VERSION dictionaryFormat) {
-        switch (dictionaryFormat) {
-            case BinaryDictionaryFormatUtils::VERSION_2:
-                return PatriciaTriePolicy::getInstance();
-            case BinaryDictionaryFormatUtils::VERSION_3:
-                return DynamicPatriciaTriePolicy::getInstance();
-            default:
-                ASSERT(false);
-                return 0;
-        }
-    }
-
- private:
-    DISALLOW_IMPLICIT_CONSTRUCTORS(DictionaryStructurePolicyFactory);
-};
-} // namespace latinime
-#endif // LATINIME_DICTIONARY_STRUCTURE_POLICY_FACTORY_H
diff --git a/native/jni/src/suggest/policyimpl/dictionary/dictionary_structure_with_buffer_policy_factory.cpp b/native/jni/src/suggest/policyimpl/dictionary/dictionary_structure_with_buffer_policy_factory.cpp
new file mode 100644
index 0000000..ff80dd2
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/dictionary/dictionary_structure_with_buffer_policy_factory.cpp
@@ -0,0 +1,53 @@
+/*
+ * 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.
+ */
+
+#include "suggest/policyimpl/dictionary/dictionary_structure_with_buffer_policy_factory.h"
+
+#include <stdint.h>
+
+#include "defines.h"
+#include "suggest/policyimpl/dictionary/dynamic_patricia_trie_policy.h"
+#include "suggest/policyimpl/dictionary/patricia_trie_policy.h"
+#include "suggest/policyimpl/dictionary/utils/format_utils.h"
+#include "suggest/policyimpl/dictionary/utils/mmapped_buffer.h"
+
+namespace latinime {
+
+/* static */ DictionaryStructureWithBufferPolicy *DictionaryStructureWithBufferPolicyFactory
+        ::newDictionaryStructureWithBufferPolicy(const char *const path, const int bufOffset,
+                const int size, const bool isUpdatable) {
+    // Allocated buffer in MmapedBuffer::openBuffer() will be freed in the destructor of
+    // impl classes of DictionaryStructureWithBufferPolicy.
+    const MmappedBuffer *const mmapedBuffer = MmappedBuffer::openBuffer(path, bufOffset, size,
+            isUpdatable);
+    if (!mmapedBuffer) {
+        return 0;
+    }
+    switch (FormatUtils::detectFormatVersion(mmapedBuffer->getBuffer(),
+            mmapedBuffer->getBufferSize())) {
+        case FormatUtils::VERSION_2:
+            return new PatriciaTriePolicy(mmapedBuffer);
+        case FormatUtils::VERSION_3:
+            return new DynamicPatriciaTriePolicy(mmapedBuffer);
+        default:
+            AKLOGE("DICT: dictionary format is unknown, bad magic number");
+            delete mmapedBuffer;
+            ASSERT(false);
+            return 0;
+    }
+}
+
+} // namespace latinime
diff --git a/native/jni/src/suggest/policyimpl/dictionary/dictionary_structure_with_buffer_policy_factory.h b/native/jni/src/suggest/policyimpl/dictionary/dictionary_structure_with_buffer_policy_factory.h
new file mode 100644
index 0000000..8cebc3b
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/dictionary/dictionary_structure_with_buffer_policy_factory.h
@@ -0,0 +1,36 @@
+/*
+ * 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.
+ */
+
+#ifndef LATINIME_DICTIONARY_STRUCTURE_WITH_BUFFER_POLICY_FACTORY_H
+#define LATINIME_DICTIONARY_STRUCTURE_WITH_BUFFER_POLICY_FACTORY_H
+
+#include <stdint.h>
+
+#include "defines.h"
+#include "suggest/core/policy/dictionary_structure_with_buffer_policy.h"
+
+namespace latinime {
+
+class DictionaryStructureWithBufferPolicyFactory {
+ public:
+    static DictionaryStructureWithBufferPolicy *newDictionaryStructureWithBufferPolicy(
+            const char *const path, const int bufOffset, const int size, const bool isUpdatable);
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(DictionaryStructureWithBufferPolicyFactory);
+};
+} // namespace latinime
+#endif // LATINIME_DICTIONARY_STRUCTURE_WITH_BUFFER_POLICY_FACTORY_H
diff --git a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_gc_event_listeners.cpp b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_gc_event_listeners.cpp
new file mode 100644
index 0000000..c60e458
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_gc_event_listeners.cpp
@@ -0,0 +1,149 @@
+/*
+ * 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.
+ */
+
+#include "suggest/policyimpl/dictionary/dynamic_patricia_trie_gc_event_listeners.h"
+
+namespace latinime {
+
+bool DynamicPatriciaTrieGcEventListeners
+        ::TraversePolicyToUpdateUnigramProbabilityAndMarkUselessPtNodesAsDeleted
+                ::onVisitingPtNode(const DynamicPatriciaTrieNodeReader *const node,
+                        const int *const nodeCodePoints) {
+    // PtNode is useless when the PtNode is not a terminal and doesn't have any not useless
+    // children.
+    bool isUselessPtNode = !node->isTerminal();
+    if (mChildrenValue > 0) {
+        isUselessPtNode = false;
+    } else if (node->isTerminal()) {
+        // Remove children as all children are useless.
+        int writingPos = node->getChildrenPosFieldPos();
+        if (!DynamicPatriciaTrieWritingUtils::writeChildrenPositionAndAdvancePosition(
+                mBuffer, NOT_A_DICT_POS /* childrenPosition */, &writingPos)) {
+            return false;
+        }
+    }
+    if (isUselessPtNode) {
+        // Current PtNode is no longer needed. Mark it as deleted.
+        if (!mWritingHelper->markNodeAsDeleted(node)) {
+            return false;
+        }
+    } else {
+        valueStack.back() += 1;
+    }
+    return true;
+}
+
+// Writes dummy PtNode array size when the head of PtNode array is read.
+bool DynamicPatriciaTrieGcEventListeners::TraversePolicyToPlaceAndWriteValidPtNodesToBuffer
+        ::onDescend(const int ptNodeArrayPos) {
+    mValidPtNodeCount = 0;
+    int writingPos = mBufferToWrite->getTailPosition();
+    mDictPositionRelocationMap->mPtNodeArrayPositionRelocationMap.insert(
+            DynamicPatriciaTrieWritingHelper::PtNodeArrayPositionRelocationMap::value_type(
+                    ptNodeArrayPos, writingPos));
+    // Writes dummy PtNode array size because arrays can have a forward link or needles PtNodes.
+    // This field will be updated later in onReadingPtNodeArrayTail() with actual PtNode count.
+    mPtNodeArraySizeFieldPos = writingPos;
+    return DynamicPatriciaTrieWritingUtils::writePtNodeArraySizeAndAdvancePosition(
+            mBufferToWrite, 0 /* arraySize */, &writingPos);
+}
+
+// Write PtNode array terminal and actual PtNode array size.
+bool DynamicPatriciaTrieGcEventListeners::TraversePolicyToPlaceAndWriteValidPtNodesToBuffer
+        ::onReadingPtNodeArrayTail() {
+    int writingPos = mBufferToWrite->getTailPosition();
+    // Write PtNode array terminal.
+    if (!DynamicPatriciaTrieWritingUtils::writeForwardLinkPositionAndAdvancePosition(
+            mBufferToWrite, NOT_A_DICT_POS /* forwardLinkPos */, &writingPos)) {
+        return false;
+    }
+    // Write actual PtNode array size.
+    if (!DynamicPatriciaTrieWritingUtils::writePtNodeArraySizeAndAdvancePosition(
+            mBufferToWrite, mValidPtNodeCount, &mPtNodeArraySizeFieldPos)) {
+        return false;
+    }
+    return true;
+}
+
+// Write valid PtNode to buffer and memorize mapping from the old position to the new position.
+bool DynamicPatriciaTrieGcEventListeners::TraversePolicyToPlaceAndWriteValidPtNodesToBuffer
+        ::onVisitingPtNode(const DynamicPatriciaTrieNodeReader *const node,
+                const int *const nodeCodePoints) {
+    if (node->isDeleted()) {
+        // Current PtNode is not written in new buffer because it has been deleted.
+        mDictPositionRelocationMap->mPtNodePositionRelocationMap.insert(
+                DynamicPatriciaTrieWritingHelper::PtNodePositionRelocationMap::value_type(
+                        node->getHeadPos(), NOT_A_DICT_POS));
+        return true;
+    }
+    int writingPos = mBufferToWrite->getTailPosition();
+    mDictPositionRelocationMap->mPtNodePositionRelocationMap.insert(
+            DynamicPatriciaTrieWritingHelper::PtNodePositionRelocationMap::value_type(
+                    node->getHeadPos(), writingPos));
+    mValidPtNodeCount++;
+    // Writes current PtNode.
+    return mWritingHelper->writePtNodeToBufferByCopyingPtNodeInfo(mBufferToWrite, node,
+            node->getParentPos(), nodeCodePoints, node->getCodePointCount(),
+            node->getProbability(), &writingPos);
+}
+
+bool DynamicPatriciaTrieGcEventListeners::TraversePolicyToUpdateAllPositionFields
+        ::onVisitingPtNode(const DynamicPatriciaTrieNodeReader *const node,
+                const int *const nodeCodePoints) {
+    // Updates parent position.
+    int parentPos = node->getParentPos();
+    if (parentPos != NOT_A_DICT_POS) {
+        DynamicPatriciaTrieWritingHelper::PtNodePositionRelocationMap::const_iterator it =
+                mDictPositionRelocationMap->mPtNodePositionRelocationMap.find(parentPos);
+        if (it != mDictPositionRelocationMap->mPtNodePositionRelocationMap.end()) {
+            parentPos = it->second;
+        }
+    }
+    int writingPos = node->getHeadPos() + DynamicPatriciaTrieWritingUtils::NODE_FLAG_FIELD_SIZE;
+    // Write updated parent offset.
+    if (!DynamicPatriciaTrieWritingUtils::writeParentPosOffsetAndAdvancePosition(mBufferToWrite,
+            parentPos, node->getHeadPos(), &writingPos)) {
+        return false;
+    }
+
+    // Updates children position.
+    int childrenPos = node->getChildrenPos();
+    if (childrenPos != NOT_A_DICT_POS) {
+        DynamicPatriciaTrieWritingHelper::PtNodeArrayPositionRelocationMap::const_iterator it =
+                mDictPositionRelocationMap->mPtNodeArrayPositionRelocationMap.find(childrenPos);
+        if (it != mDictPositionRelocationMap->mPtNodeArrayPositionRelocationMap.end()) {
+            childrenPos = it->second;
+        }
+    }
+    writingPos = node->getChildrenPosFieldPos();
+    if (!DynamicPatriciaTrieWritingUtils::writeChildrenPositionAndAdvancePosition(mBufferToWrite,
+            childrenPos, &writingPos)) {
+        return false;
+    }
+
+    // Updates bigram target PtNode positions in the bigram list.
+    int bigramsPos = node->getBigramsPos();
+    if (bigramsPos != NOT_A_DICT_POS) {
+        if (!mBigramPolicy->updateAllBigramTargetPtNodePositions(&bigramsPos,
+                &mDictPositionRelocationMap->mPtNodePositionRelocationMap)) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+} // namespace latinime
diff --git a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_gc_event_listeners.h b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_gc_event_listeners.h
new file mode 100644
index 0000000..4256f22
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_gc_event_listeners.h
@@ -0,0 +1,178 @@
+/*
+ * 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.
+ */
+
+#ifndef LATINIME_DYNAMIC_PATRICIA_TRIE_GC_EVENT_LISTENERS_H
+#define LATINIME_DYNAMIC_PATRICIA_TRIE_GC_EVENT_LISTENERS_H
+
+#include <vector>
+
+#include "defines.h"
+#include "suggest/policyimpl/dictionary/bigram/dynamic_bigram_list_policy.h"
+#include "suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_helper.h"
+#include "suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_helper.h"
+#include "suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_utils.h"
+#include "suggest/policyimpl/dictionary/utils/buffer_with_extendable_buffer.h"
+#include "utils/hash_map_compat.h"
+
+namespace latinime {
+
+class DynamicPatriciaTrieGcEventListeners {
+ public:
+    // Updates all PtNodes that can be reached from the root. Checks if each PtNode is useless or
+    // not and marks useless PtNodes as deleted. Such deleted PtNodes will be discarded in the GC.
+    // TODO: Concatenate non-terminal PtNodes.
+    class TraversePolicyToUpdateUnigramProbabilityAndMarkUselessPtNodesAsDeleted
+        : public DynamicPatriciaTrieReadingHelper::TraversingEventListener {
+     public:
+        TraversePolicyToUpdateUnigramProbabilityAndMarkUselessPtNodesAsDeleted(
+                DynamicPatriciaTrieWritingHelper *const writingHelper,
+                BufferWithExtendableBuffer *const buffer)
+                : mWritingHelper(writingHelper), mBuffer(buffer), valueStack(),
+                  mChildrenValue(0) {}
+
+        ~TraversePolicyToUpdateUnigramProbabilityAndMarkUselessPtNodesAsDeleted() {};
+
+        bool onAscend() {
+            if (valueStack.empty()) {
+                return false;
+            }
+            mChildrenValue = valueStack.back();
+            valueStack.pop_back();
+            return true;
+        }
+
+        bool onDescend(const int ptNodeArrayPos) {
+            valueStack.push_back(0);
+            return true;
+        }
+
+        bool onReadingPtNodeArrayTail() { return true; }
+
+        bool onVisitingPtNode(const DynamicPatriciaTrieNodeReader *const node,
+                const int *const nodeCodePoints);
+
+     private:
+        DISALLOW_IMPLICIT_CONSTRUCTORS(
+                TraversePolicyToUpdateUnigramProbabilityAndMarkUselessPtNodesAsDeleted);
+
+        DynamicPatriciaTrieWritingHelper *const mWritingHelper;
+        BufferWithExtendableBuffer *const mBuffer;
+        std::vector<int> valueStack;
+        int mChildrenValue;
+    };
+
+    // Updates all bigram entries that are held by valid PtNodes. This removes useless bigram
+    // entries.
+    class TraversePolicyToUpdateBigramProbability
+            : public DynamicPatriciaTrieReadingHelper::TraversingEventListener {
+     public:
+        TraversePolicyToUpdateBigramProbability(DynamicBigramListPolicy *const bigramPolicy)
+                : mBigramPolicy(bigramPolicy) {}
+
+        bool onAscend() { return true; }
+
+        bool onDescend(const int ptNodeArrayPos) { return true; }
+
+        bool onReadingPtNodeArrayTail() { return true; }
+
+        bool onVisitingPtNode(const DynamicPatriciaTrieNodeReader *const node,
+                const int *const nodeCodePoints) {
+            if (!node->isDeleted()) {
+                int pos = node->getBigramsPos();
+                if (pos != NOT_A_DICT_POS) {
+                    if (!mBigramPolicy->updateAllBigramEntriesAndDeleteUselessEntries(&pos)) {
+                        return false;
+                    }
+                }
+            }
+            return true;
+        }
+
+     private:
+        DISALLOW_IMPLICIT_CONSTRUCTORS(TraversePolicyToUpdateBigramProbability);
+
+        DynamicBigramListPolicy *const mBigramPolicy;
+    };
+
+    class TraversePolicyToPlaceAndWriteValidPtNodesToBuffer
+            : public DynamicPatriciaTrieReadingHelper::TraversingEventListener {
+     public:
+        TraversePolicyToPlaceAndWriteValidPtNodesToBuffer(
+                DynamicPatriciaTrieWritingHelper *const writingHelper,
+                BufferWithExtendableBuffer *const bufferToWrite,
+                DynamicPatriciaTrieWritingHelper::DictPositionRelocationMap *const
+                        dictPositionRelocationMap)
+                : mWritingHelper(writingHelper), mBufferToWrite(bufferToWrite),
+                  mDictPositionRelocationMap(dictPositionRelocationMap), mValidPtNodeCount(0),
+                  mPtNodeArraySizeFieldPos(NOT_A_DICT_POS) {};
+
+        bool onAscend() { return true; }
+
+        bool onDescend(const int ptNodeArrayPos);
+
+        bool onReadingPtNodeArrayTail();
+
+        bool onVisitingPtNode(const DynamicPatriciaTrieNodeReader *const node,
+                const int *const nodeCodePoints);
+
+     private:
+        DISALLOW_IMPLICIT_CONSTRUCTORS(TraversePolicyToPlaceAndWriteValidPtNodesToBuffer);
+
+        DynamicPatriciaTrieWritingHelper *const mWritingHelper;
+        BufferWithExtendableBuffer *const mBufferToWrite;
+        DynamicPatriciaTrieWritingHelper::DictPositionRelocationMap *const
+                mDictPositionRelocationMap;
+        int mValidPtNodeCount;
+        int mPtNodeArraySizeFieldPos;
+    };
+
+    class TraversePolicyToUpdateAllPositionFields
+            : public DynamicPatriciaTrieReadingHelper::TraversingEventListener {
+     public:
+        TraversePolicyToUpdateAllPositionFields(
+                DynamicPatriciaTrieWritingHelper *const writingHelper,
+                DynamicBigramListPolicy *const bigramPolicy,
+                BufferWithExtendableBuffer *const bufferToWrite,
+                const DynamicPatriciaTrieWritingHelper::DictPositionRelocationMap *const
+                        dictPositionRelocationMap)
+                : mWritingHelper(writingHelper), mBigramPolicy(bigramPolicy),
+                  mBufferToWrite(bufferToWrite),
+                  mDictPositionRelocationMap(dictPositionRelocationMap) {};
+
+        bool onAscend() { return true; }
+
+        bool onDescend(const int ptNodeArrayPos) { return true; }
+
+        bool onReadingPtNodeArrayTail() { return true; }
+
+        bool onVisitingPtNode(const DynamicPatriciaTrieNodeReader *const node,
+                const int *const nodeCodePoints);
+
+     private:
+        DISALLOW_IMPLICIT_CONSTRUCTORS(TraversePolicyToUpdateAllPositionFields);
+
+        DynamicPatriciaTrieWritingHelper *const mWritingHelper;
+        DynamicBigramListPolicy *const mBigramPolicy;
+        BufferWithExtendableBuffer *const mBufferToWrite;
+        const DynamicPatriciaTrieWritingHelper::DictPositionRelocationMap *const
+                mDictPositionRelocationMap;
+    };
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(DynamicPatriciaTrieGcEventListeners);
+};
+} // namespace latinime
+#endif /* LATINIME_DYNAMIC_PATRICIA_TRIE_GC_EVENT_LISTENERS_H */
diff --git a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_node_reader.cpp b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_node_reader.cpp
index 7ac635a..456352c 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_node_reader.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_node_reader.cpp
@@ -16,61 +16,108 @@
 
 #include "suggest/policyimpl/dictionary/dynamic_patricia_trie_node_reader.h"
 
-#include "suggest/core/dictionary/binary_dictionary_info.h"
-#include "suggest/core/dictionary/binary_dictionary_terminal_attributes_reading_utils.h"
+#include "suggest/core/policy/dictionary_bigrams_structure_policy.h"
+#include "suggest/core/policy/dictionary_shortcuts_structure_policy.h"
 #include "suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_utils.h"
+#include "suggest/policyimpl/dictionary/utils/buffer_with_extendable_buffer.h"
 
 namespace latinime {
 
-void DynamicPatriciaTrieNodeReader::fetchNodeInfoFromBufferAndProcessMovedNode(const int nodePos,
-        const int maxCodePointCount, int *const outCodePoints) {
-    const uint8_t *const dictRoot = mBinaryDictionaryInfo->getDictRoot();
-    int pos = nodePos;
-    mFlags = PatriciaTrieReadingUtils::getFlagsAndAdvancePosition(dictRoot, &pos);
-    const int parentPos =
-            DynamicPatriciaTrieReadingUtils::getParentPosAndAdvancePosition(dictRoot, &pos);
-    mParentPos = (parentPos != 0) ? mNodePos + parentPos : NOT_A_DICT_POS;
+void DynamicPatriciaTrieNodeReader::fetchPtNodeInfoFromBufferAndProcessMovedPtNode(
+        const int ptNodePos, const int maxCodePointCount, int *const outCodePoints) {
+    if (ptNodePos < 0 || ptNodePos >= mBuffer->getTailPosition()) {
+        AKLOGE("Fetching PtNode info form invalid dictionary position: %d, dictionary size: %d",
+                ptNodePos, mBuffer->getTailPosition());
+        ASSERT(false);
+        invalidatePtNodeInfo();
+        return;
+    }
+    const bool usesAdditionalBuffer = mBuffer->isInAdditionalBuffer(ptNodePos);
+    const uint8_t *const dictBuf = mBuffer->getBuffer(usesAdditionalBuffer);
+    int pos = ptNodePos;
+    mHeadPos = ptNodePos;
+    if (usesAdditionalBuffer) {
+        pos -= mBuffer->getOriginalBufferSize();
+    }
+    mFlags = PatriciaTrieReadingUtils::getFlagsAndAdvancePosition(dictBuf, &pos);
+    const int parentPosOffset =
+            DynamicPatriciaTrieReadingUtils::getParentPtNodePosOffsetAndAdvancePosition(dictBuf,
+                    &pos);
+    mParentPos = DynamicPatriciaTrieReadingUtils::getParentPtNodePos(parentPosOffset, mHeadPos);
     if (outCodePoints != 0) {
         mCodePointCount = PatriciaTrieReadingUtils::getCharsAndAdvancePosition(
-                dictRoot, mFlags, maxCodePointCount, outCodePoints, &pos);
+                dictBuf, mFlags, maxCodePointCount, outCodePoints, &pos);
     } else {
         mCodePointCount = PatriciaTrieReadingUtils::skipCharacters(
-                dictRoot, mFlags, MAX_WORD_LENGTH, &pos);
+                dictBuf, mFlags, MAX_WORD_LENGTH, &pos);
     }
     if (isTerminal()) {
-        mProbability = PatriciaTrieReadingUtils::readProbabilityAndAdvancePosition(dictRoot, &pos);
+        mProbabilityFieldPos = pos;
+        if (usesAdditionalBuffer) {
+            mProbabilityFieldPos += mBuffer->getOriginalBufferSize();
+        }
+        mProbability = PatriciaTrieReadingUtils::readProbabilityAndAdvancePosition(dictBuf, &pos);
     } else {
+        mProbabilityFieldPos = NOT_A_DICT_POS;
         mProbability = NOT_A_PROBABILITY;
     }
-    if (hasChildren()) {
-        mChildrenPos = DynamicPatriciaTrieReadingUtils::readChildrenPositionAndAdvancePosition(
-                dictRoot, mFlags, &pos);
-    } else {
-        mChildrenPos = NOT_A_DICT_POS;
+    mChildrenPosFieldPos = pos;
+    if (usesAdditionalBuffer) {
+        mChildrenPosFieldPos += mBuffer->getOriginalBufferSize();
+    }
+    mChildrenPos = DynamicPatriciaTrieReadingUtils::readChildrenPositionAndAdvancePosition(
+            dictBuf, &pos);
+    if (usesAdditionalBuffer && mChildrenPos != NOT_A_DICT_POS) {
+        mChildrenPos += mBuffer->getOriginalBufferSize();
+    }
+    if (mSiblingPos == NOT_A_DICT_POS) {
+        if (DynamicPatriciaTrieReadingUtils::isMoved(mFlags)) {
+            mBigramLinkedNodePos = mChildrenPos;
+        } else {
+            mBigramLinkedNodePos = NOT_A_DICT_POS;
+        }
+    }
+    if (usesAdditionalBuffer) {
+        pos += mBuffer->getOriginalBufferSize();
     }
     if (PatriciaTrieReadingUtils::hasShortcutTargets(mFlags)) {
         mShortcutPos = pos;
-        BinaryDictionaryTerminalAttributesReadingUtils::skipShortcuts(mBinaryDictionaryInfo, &pos);
+        mShortcutsPolicy->skipAllShortcuts(&pos);
     } else {
         mShortcutPos = NOT_A_DICT_POS;
     }
     if (PatriciaTrieReadingUtils::hasBigrams(mFlags)) {
         mBigramPos = pos;
-        BinaryDictionaryTerminalAttributesReadingUtils::skipExistingBigrams(
-                mBinaryDictionaryInfo, &pos);
+        mBigramsPolicy->skipAllBigrams(&pos);
     } else {
         mBigramPos = NOT_A_DICT_POS;
     }
     // Update siblingPos if needed.
-    if (mSiblingPos == NOT_A_VALID_WORD_POS) {
+    if (mSiblingPos == NOT_A_DICT_POS) {
         // Sibling position is the tail position of current node.
         mSiblingPos = pos;
     }
     // Read destination node if the read node is a moved node.
     if (DynamicPatriciaTrieReadingUtils::isMoved(mFlags)) {
         // The destination position is stored at the same place as the parent position.
-        fetchNodeInfoFromBufferAndProcessMovedNode(mParentPos, maxCodePointCount, outCodePoints);
+        fetchPtNodeInfoFromBufferAndProcessMovedPtNode(mParentPos, maxCodePointCount,
+                outCodePoints);
     }
 }
 
+void DynamicPatriciaTrieNodeReader::invalidatePtNodeInfo() {
+    mHeadPos = NOT_A_DICT_POS;
+    mFlags = 0;
+    mParentPos = NOT_A_DICT_POS;
+    mCodePointCount = 0;
+    mProbabilityFieldPos = NOT_A_DICT_POS;
+    mProbability = NOT_A_PROBABILITY;
+    mChildrenPosFieldPos = NOT_A_DICT_POS;
+    mChildrenPos = NOT_A_DICT_POS;
+    mBigramLinkedNodePos = NOT_A_DICT_POS;
+    mShortcutPos = NOT_A_DICT_POS;
+    mBigramPos = NOT_A_DICT_POS;
+    mSiblingPos = NOT_A_DICT_POS;
+}
+
 }
diff --git a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_node_reader.h b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_node_reader.h
index 71558ed..3b36d42 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_node_reader.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_node_reader.h
@@ -17,13 +17,17 @@
 #ifndef LATINIME_DYNAMIC_PATRICIA_TRIE_NODE_READER_H
 #define LATINIME_DYNAMIC_PATRICIA_TRIE_NODE_READER_H
 
+#include <stdint.h>
+
 #include "defines.h"
 #include "suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_utils.h"
 #include "suggest/policyimpl/dictionary/patricia_trie_reading_utils.h"
 
 namespace latinime {
 
-class BinaryDictionaryInfo;
+class BufferWithExtendableBuffer;
+class DictionaryBigramsStructurePolicy;
+class DictionaryShortcutsStructurePolicy;
 
 /*
  * This class is used for helping to read nodes of dynamic patricia trie. This class handles moved
@@ -31,30 +35,35 @@
  */
 class DynamicPatriciaTrieNodeReader {
  public:
-    explicit DynamicPatriciaTrieNodeReader(const BinaryDictionaryInfo *const binaryDictionaryInfo)
-            : mBinaryDictionaryInfo(binaryDictionaryInfo), mNodePos(NOT_A_VALID_WORD_POS),
-              mFlags(0), mParentPos(NOT_A_DICT_POS), mCodePointCount(0),
-              mProbability(NOT_A_PROBABILITY), mChildrenPos(NOT_A_DICT_POS),
-              mShortcutPos(NOT_A_DICT_POS), mBigramPos(NOT_A_DICT_POS),
-              mSiblingPos(NOT_A_VALID_WORD_POS) {}
+    DynamicPatriciaTrieNodeReader(const BufferWithExtendableBuffer *const buffer,
+            const DictionaryBigramsStructurePolicy *const bigramsPolicy,
+            const DictionaryShortcutsStructurePolicy *const shortcutsPolicy)
+            : mBuffer(buffer), mBigramsPolicy(bigramsPolicy),
+              mShortcutsPolicy(shortcutsPolicy), mHeadPos(NOT_A_DICT_POS), mFlags(0),
+              mParentPos(NOT_A_DICT_POS), mCodePointCount(0), mProbabilityFieldPos(NOT_A_DICT_POS),
+              mProbability(NOT_A_PROBABILITY), mChildrenPosFieldPos(NOT_A_DICT_POS),
+              mChildrenPos(NOT_A_DICT_POS), mBigramLinkedNodePos(NOT_A_DICT_POS),
+              mShortcutPos(NOT_A_DICT_POS),  mBigramPos(NOT_A_DICT_POS),
+              mSiblingPos(NOT_A_DICT_POS) {}
 
     ~DynamicPatriciaTrieNodeReader() {}
 
-    // Reads node information from dictionary buffer and updates members with the information.
-    AK_FORCE_INLINE void fetchNodeInfoFromBuffer(const int nodePos) {
-        fetchNodeInfoFromBufferAndGetNodeCodePoints(nodePos , 0 /* maxCodePointCount */,
-                0 /* outCodePoints */);
+    // Reads PtNode information from dictionary buffer and updates members with the information.
+    AK_FORCE_INLINE void fetchNodeInfoInBufferFromPtNodePos(const int ptNodePos) {
+        fetchNodeInfoInBufferFromPtNodePosAndGetNodeCodePoints(ptNodePos ,
+                0 /* maxCodePointCount */, 0 /* outCodePoints */);
     }
 
-    AK_FORCE_INLINE void fetchNodeInfoFromBufferAndGetNodeCodePoints(const int nodePos,
-            const int maxCodePointCount, int *const outCodePoints) {
-        mNodePos = nodePos;
-        mSiblingPos = NOT_A_VALID_WORD_POS;
-        fetchNodeInfoFromBufferAndProcessMovedNode(mNodePos, maxCodePointCount, outCodePoints);
+    AK_FORCE_INLINE void fetchNodeInfoInBufferFromPtNodePosAndGetNodeCodePoints(
+            const int ptNodePos, const int maxCodePointCount, int *const outCodePoints) {
+        mSiblingPos = NOT_A_DICT_POS;
+        mBigramLinkedNodePos = NOT_A_DICT_POS;
+        fetchPtNodeInfoFromBufferAndProcessMovedPtNode(ptNodePos, maxCodePointCount, outCodePoints);
     }
 
-    AK_FORCE_INLINE int getNodePos() const {
-        return mNodePos;
+    // HeadPos is different from NodePos when the current PtNode is a moved PtNode.
+    AK_FORCE_INLINE int getHeadPos() const {
+        return mHeadPos;
     }
 
     // Flags
@@ -63,7 +72,7 @@
     }
 
     AK_FORCE_INLINE bool hasChildren() const {
-        return PatriciaTrieReadingUtils::hasChildrenInFlags(mFlags);
+        return mChildrenPos != NOT_A_DICT_POS;
     }
 
     AK_FORCE_INLINE bool isTerminal() const {
@@ -89,15 +98,28 @@
     }
 
     // Probability
+    AK_FORCE_INLINE int getProbabilityFieldPos() const {
+        return mProbabilityFieldPos;
+    }
+
     AK_FORCE_INLINE int getProbability() const {
         return mProbability;
     }
 
-    // Children node group position
+    // Children PtNode array position
+    AK_FORCE_INLINE int getChildrenPosFieldPos() const {
+        return mChildrenPosFieldPos;
+    }
+
     AK_FORCE_INLINE int getChildrenPos() const {
         return mChildrenPos;
     }
 
+    // Bigram linked node position.
+    AK_FORCE_INLINE int getBigramLinkedNodePos() const {
+        return mBigramLinkedNodePos;
+    }
+
     // Shortcutlist position
     AK_FORCE_INLINE int getShortcutPos() const {
         return mShortcutPos;
@@ -116,19 +138,26 @@
  private:
     DISALLOW_COPY_AND_ASSIGN(DynamicPatriciaTrieNodeReader);
 
-    const BinaryDictionaryInfo *const mBinaryDictionaryInfo;
-    int mNodePos;
+    const BufferWithExtendableBuffer *const mBuffer;
+    const DictionaryBigramsStructurePolicy *const mBigramsPolicy;
+    const DictionaryShortcutsStructurePolicy *const mShortcutsPolicy;
+    int mHeadPos;
     DynamicPatriciaTrieReadingUtils::NodeFlags mFlags;
     int mParentPos;
     uint8_t mCodePointCount;
+    int mProbabilityFieldPos;
     int mProbability;
+    int mChildrenPosFieldPos;
     int mChildrenPos;
+    int mBigramLinkedNodePos;
     int mShortcutPos;
     int mBigramPos;
     int mSiblingPos;
 
-    void fetchNodeInfoFromBufferAndProcessMovedNode(const int nodePos, const int maxCodePointCount,
-            int *const outCodePoints);
+    void fetchPtNodeInfoFromBufferAndProcessMovedPtNode(const int ptNodePos,
+            const int maxCodePointCount, int *const outCodePoints);
+
+    void invalidatePtNodeInfo();
 };
 } // namespace latinime
 #endif /* LATINIME_DYNAMIC_PATRICIA_TRIE_NODE_READER_H */
diff --git a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_policy.cpp b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_policy.cpp
index 3df5056..42397c1 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_policy.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_policy.cpp
@@ -19,221 +19,257 @@
 #include "defines.h"
 #include "suggest/core/dicnode/dic_node.h"
 #include "suggest/core/dicnode/dic_node_vector.h"
-#include "suggest/core/dictionary/binary_dictionary_info.h"
 #include "suggest/policyimpl/dictionary/dynamic_patricia_trie_node_reader.h"
+#include "suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_helper.h"
 #include "suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_utils.h"
+#include "suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_helper.h"
 #include "suggest/policyimpl/dictionary/patricia_trie_reading_utils.h"
+#include "suggest/policyimpl/dictionary/utils/probability_utils.h"
 
 namespace latinime {
 
-const DynamicPatriciaTriePolicy DynamicPatriciaTriePolicy::sInstance;
-// To avoid infinite loop caused by invalid or malicious forward links.
-const int DynamicPatriciaTriePolicy::MAX_CHILD_COUNT_TO_AVOID_INFINITE_LOOP = 100000;
-
 void DynamicPatriciaTriePolicy::createAndGetAllChildNodes(const DicNode *const dicNode,
-        const BinaryDictionaryInfo *const binaryDictionaryInfo,
-        const NodeFilter *const nodeFilter, DicNodeVector *const childDicNodes) const {
+        DicNodeVector *const childDicNodes) const {
     if (!dicNode->hasChildren()) {
         return;
     }
-    DynamicPatriciaTrieNodeReader nodeReader(binaryDictionaryInfo);
-    int mergedNodeCodePoints[MAX_WORD_LENGTH];
-    int nextPos = dicNode->getChildrenPos();
-    int totalChildCount = 0;
-    do {
-        const int childCount = PatriciaTrieReadingUtils::getGroupCountAndAdvancePosition(
-                binaryDictionaryInfo->getDictRoot(), &nextPos);
-        totalChildCount += childCount;
-        if (childCount <= 0 || totalChildCount > MAX_CHILD_COUNT_TO_AVOID_INFINITE_LOOP) {
-            // Invalid dictionary.
-            AKLOGI("Invalid dictionary. childCount: %d, totalChildCount: %d, MAX: %d",
-                    childCount, totalChildCount, MAX_CHILD_COUNT_TO_AVOID_INFINITE_LOOP);
-            ASSERT(false);
-            return;
-        }
-        for (int i = 0; i < childCount; i++) {
-            nodeReader.fetchNodeInfoFromBufferAndGetNodeCodePoints(nextPos, MAX_WORD_LENGTH,
-                    mergedNodeCodePoints);
-            if (!nodeReader.isDeleted() && !nodeFilter->isFilteredOut(mergedNodeCodePoints[0])) {
-                // Push child node when the node is not deleted and not filtered out.
-                childDicNodes->pushLeavingChild(dicNode, nodeReader.getNodePos(),
-                        nodeReader.getChildrenPos(), nodeReader.getProbability(),
-                        nodeReader.isTerminal(), nodeReader.hasChildren(),
-                        nodeReader.isBlacklisted() || nodeReader.isNotAWord(),
-                        nodeReader.getCodePointCount(), mergedNodeCodePoints);
-            }
-            nextPos = nodeReader.getSiblingNodePos();
-        }
-        nextPos = DynamicPatriciaTrieReadingUtils::getForwardLinkPosition(
-                binaryDictionaryInfo->getDictRoot(), nextPos);
-    } while (DynamicPatriciaTrieReadingUtils::isValidForwardLinkPosition(nextPos));
+    DynamicPatriciaTrieReadingHelper readingHelper(&mBufferWithExtendableBuffer,
+            getBigramsStructurePolicy(), getShortcutsStructurePolicy());
+    readingHelper.initWithPtNodeArrayPos(dicNode->getChildrenPos());
+    const DynamicPatriciaTrieNodeReader *const nodeReader = readingHelper.getNodeReader();
+    while (!readingHelper.isEnd()) {
+        childDicNodes->pushLeavingChild(dicNode, nodeReader->getHeadPos(),
+                nodeReader->getChildrenPos(), nodeReader->getProbability(),
+                nodeReader->isTerminal() && !nodeReader->isDeleted(),
+                nodeReader->hasChildren(), nodeReader->isBlacklisted() || nodeReader->isNotAWord(),
+                nodeReader->getCodePointCount(), readingHelper.getMergedNodeCodePoints());
+        readingHelper.readNextSiblingNode();
+    }
 }
 
 int DynamicPatriciaTriePolicy::getCodePointsAndProbabilityAndReturnCodePointCount(
-        const BinaryDictionaryInfo *const binaryDictionaryInfo,
-        const int nodePos, const int maxCodePointCount, int *const outCodePoints,
+        const int ptNodePos, const int maxCodePointCount, int *const outCodePoints,
         int *const outUnigramProbability) const {
-    if (nodePos == NOT_A_VALID_WORD_POS) {
-        *outUnigramProbability = NOT_A_PROBABILITY;
-        return 0;
-    }
     // This method traverses parent nodes from the terminal by following parent pointers; thus,
     // node code points are stored in the buffer in the reverse order.
     int reverseCodePoints[maxCodePointCount];
-    int mergedNodeCodePoints[maxCodePointCount];
-    int codePointCount = 0;
-
-    DynamicPatriciaTrieNodeReader nodeReader(binaryDictionaryInfo);
-    // First, read terminal node and get its probability.
-    nodeReader.fetchNodeInfoFromBufferAndGetNodeCodePoints(nodePos, maxCodePointCount,
-            mergedNodeCodePoints);
-    // Store terminal node probability.
-    *outUnigramProbability = nodeReader.getProbability();
-    // Store terminal node code points to buffer in the reverse order.
-    for (int i = nodeReader.getCodePointCount() - 1; i >= 0; --i) {
-        reverseCodePoints[codePointCount++] = mergedNodeCodePoints[i];
+    DynamicPatriciaTrieReadingHelper readingHelper(&mBufferWithExtendableBuffer,
+            getBigramsStructurePolicy(), getShortcutsStructurePolicy());
+    // First, read the terminal node and get its probability.
+    readingHelper.initWithPtNodePos(ptNodePos);
+    if (!readingHelper.isValidTerminalNode()) {
+        // Node at the ptNodePos is not a valid terminal node.
+        *outUnigramProbability = NOT_A_PROBABILITY;
+        return 0;
     }
-    // Then, follow parent pos toward the root node.
-    while (nodeReader.getParentPos() != NOT_A_DICT_POS) {
-        // codePointCount must be incremented at least once in each iteration to ensure preventing
-        // infinite loop.
-        if (nodeReader.isDeleted() || codePointCount > maxCodePointCount
-                || nodeReader.getCodePointCount() <= 0) {
-            // The nodePos is not a valid terminal node position in the dictionary.
+    // Store terminal node probability.
+    *outUnigramProbability = readingHelper.getNodeReader()->getProbability();
+    // Then, following parent node link to the dictionary root and fetch node code points.
+    while (!readingHelper.isEnd()) {
+        if (readingHelper.getTotalCodePointCount() > maxCodePointCount) {
+            // The ptNodePos is not a valid terminal node position in the dictionary.
             *outUnigramProbability = NOT_A_PROBABILITY;
             return 0;
         }
-        // Read parent node.
-        nodeReader.fetchNodeInfoFromBufferAndGetNodeCodePoints(nodeReader.getParentPos(),
-                maxCodePointCount, mergedNodeCodePoints);
         // Store node code points to buffer in the reverse order.
-        for (int i = nodeReader.getCodePointCount() - 1; i >= 0; --i) {
-            reverseCodePoints[codePointCount++] = mergedNodeCodePoints[i];
-        }
+        readingHelper.fetchMergedNodeCodePointsInReverseOrder(
+                readingHelper.getPrevTotalCodePointCount(), reverseCodePoints);
+        // Follow parent node toward the root node.
+        readingHelper.readParentNode();
+    }
+    if (readingHelper.isError()) {
+        // The node position or the dictionary is invalid.
+        *outUnigramProbability = NOT_A_PROBABILITY;
+        return 0;
     }
     // Reverse the stored code points to output them.
+    const int codePointCount = readingHelper.getTotalCodePointCount();
     for (int i = 0; i < codePointCount; ++i) {
         outCodePoints[i] = reverseCodePoints[codePointCount - i - 1];
     }
     return codePointCount;
 }
 
-int DynamicPatriciaTriePolicy::getTerminalNodePositionOfWord(
-        const BinaryDictionaryInfo *const binaryDictionaryInfo, const int *const inWord,
+int DynamicPatriciaTriePolicy::getTerminalNodePositionOfWord(const int *const inWord,
         const int length, const bool forceLowerCaseSearch) const {
     int searchCodePoints[length];
     for (int i = 0; i < length; ++i) {
         searchCodePoints[i] = forceLowerCaseSearch ? CharUtils::toLowerCase(inWord[i]) : inWord[i];
     }
-    int mergedNodeCodePoints[MAX_WORD_LENGTH];
-    int currentLength = 0;
-    int pos = getRootPosition();
-    DynamicPatriciaTrieNodeReader nodeReader(binaryDictionaryInfo);
-    while (currentLength <= length) {
-        // When foundMatchedNode becomes true, currentLength is increased at least once.
-        bool foundMatchedNode = false;
-        int totalChildCount = 0;
-        do {
-            const int childCount = PatriciaTrieReadingUtils::getGroupCountAndAdvancePosition(
-                    binaryDictionaryInfo->getDictRoot(), &pos);
-            totalChildCount += childCount;
-            if (childCount <= 0 || totalChildCount > MAX_CHILD_COUNT_TO_AVOID_INFINITE_LOOP) {
-                // Invalid dictionary.
-                AKLOGI("Invalid dictionary. childCount: %d, totalChildCount: %d, MAX: %d",
-                        childCount, totalChildCount, MAX_CHILD_COUNT_TO_AVOID_INFINITE_LOOP);
-                ASSERT(false);
-                return NOT_A_VALID_WORD_POS;
-            }
-            for (int i = 0; i < childCount; i++) {
-                nodeReader.fetchNodeInfoFromBufferAndGetNodeCodePoints(pos, MAX_WORD_LENGTH,
-                        mergedNodeCodePoints);
-                if (nodeReader.isDeleted() || nodeReader.getCodePointCount() <= 0) {
-                    // Skip deleted or empty node.
-                    pos = nodeReader.getSiblingNodePos();
-                    continue;
-                }
-                bool matched = true;
-                for (int j = 0; j < nodeReader.getCodePointCount(); ++j) {
-                    if (mergedNodeCodePoints[j] != searchCodePoints[currentLength + j]) {
-                        // Different code point is found.
-                        matched = false;
-                        break;
-                    }
-                }
-                if (matched) {
-                    currentLength += nodeReader.getCodePointCount();
-                    if (length == currentLength) {
-                        // Terminal position is found.
-                        return nodeReader.getNodePos();
-                    }
-                    if (!nodeReader.hasChildren()) {
-                        return NOT_A_VALID_WORD_POS;
-                    }
-                    foundMatchedNode = true;
-                    // Advance to the children nodes.
-                    pos = nodeReader.getChildrenPos();
-                    break;
-                }
-                // Try next sibling node.
-                pos = nodeReader.getSiblingNodePos();
-            }
-            if (foundMatchedNode) {
-                break;
-            }
-            // If the matched node is not found in the current node group, try to follow the
-            // forward link.
-            pos = DynamicPatriciaTrieReadingUtils::getForwardLinkPosition(
-                    binaryDictionaryInfo->getDictRoot(), pos);
-        } while (DynamicPatriciaTrieReadingUtils::isValidForwardLinkPosition(pos));
-        if (!foundMatchedNode) {
-            // Matched node is not found.
-            return NOT_A_VALID_WORD_POS;
+    DynamicPatriciaTrieReadingHelper readingHelper(&mBufferWithExtendableBuffer,
+            getBigramsStructurePolicy(), getShortcutsStructurePolicy());
+    readingHelper.initWithPtNodeArrayPos(getRootPosition());
+    const DynamicPatriciaTrieNodeReader *const nodeReader = readingHelper.getNodeReader();
+    while (!readingHelper.isEnd()) {
+        const int matchedCodePointCount = readingHelper.getPrevTotalCodePointCount();
+        if (readingHelper.getTotalCodePointCount() > length
+                || !readingHelper.isMatchedCodePoint(0 /* index */,
+                        searchCodePoints[matchedCodePointCount])) {
+            // Current node has too many code points or its first code point is different from
+            // target code point. Skip this node and read the next sibling node.
+            readingHelper.readNextSiblingNode();
+            continue;
         }
+        // Check following merged node code points.
+        const int nodeCodePointCount = nodeReader->getCodePointCount();
+        for (int j = 1; j < nodeCodePointCount; ++j) {
+            if (!readingHelper.isMatchedCodePoint(
+                    j, searchCodePoints[matchedCodePointCount + j])) {
+                // Different code point is found. The given word is not included in the dictionary.
+                return NOT_A_DICT_POS;
+            }
+        }
+        // All characters are matched.
+        if (length == readingHelper.getTotalCodePointCount()) {
+            // Terminal position is found.
+            return nodeReader->getHeadPos();
+        }
+        if (!nodeReader->hasChildren()) {
+            return NOT_A_DICT_POS;
+        }
+        // Advance to the children nodes.
+        readingHelper.readChildNode();
     }
     // If we already traversed the tree further than the word is long, there means
     // there was no match (or we would have found it).
-    return NOT_A_VALID_WORD_POS;
+    return NOT_A_DICT_POS;
 }
 
-int DynamicPatriciaTriePolicy::getUnigramProbability(
-        const BinaryDictionaryInfo *const binaryDictionaryInfo, const int nodePos) const {
-    if (nodePos == NOT_A_VALID_WORD_POS) {
+int DynamicPatriciaTriePolicy::getProbability(const int unigramProbability,
+        const int bigramProbability) const {
+    // TODO: check mHeaderPolicy.usesForgettingCurve();
+    if (unigramProbability == NOT_A_PROBABILITY) {
+        return NOT_A_PROBABILITY;
+    } else if (bigramProbability == NOT_A_PROBABILITY) {
+        return ProbabilityUtils::backoff(unigramProbability);
+    } else {
+        return ProbabilityUtils::computeProbabilityForBigram(unigramProbability,
+                bigramProbability);
+    }
+}
+
+int DynamicPatriciaTriePolicy::getUnigramProbabilityOfPtNode(const int ptNodePos) const {
+    if (ptNodePos == NOT_A_DICT_POS) {
         return NOT_A_PROBABILITY;
     }
-    DynamicPatriciaTrieNodeReader nodeReader(binaryDictionaryInfo);
-    nodeReader.fetchNodeInfoFromBuffer(nodePos);
+    DynamicPatriciaTrieNodeReader nodeReader(&mBufferWithExtendableBuffer,
+            getBigramsStructurePolicy(), getShortcutsStructurePolicy());
+    nodeReader.fetchNodeInfoInBufferFromPtNodePos(ptNodePos);
     if (nodeReader.isDeleted() || nodeReader.isBlacklisted() || nodeReader.isNotAWord()) {
         return NOT_A_PROBABILITY;
     }
-    return nodeReader.getProbability();
+    return getProbability(nodeReader.getProbability(), NOT_A_PROBABILITY);
 }
 
-int DynamicPatriciaTriePolicy::getShortcutPositionOfNode(
-        const BinaryDictionaryInfo *const binaryDictionaryInfo,
-        const int nodePos) const {
-    if (nodePos == NOT_A_VALID_WORD_POS) {
+int DynamicPatriciaTriePolicy::getShortcutPositionOfPtNode(const int ptNodePos) const {
+    if (ptNodePos == NOT_A_DICT_POS) {
         return NOT_A_DICT_POS;
     }
-    DynamicPatriciaTrieNodeReader nodeReader(binaryDictionaryInfo);
-    nodeReader.fetchNodeInfoFromBuffer(nodePos);
+    DynamicPatriciaTrieNodeReader nodeReader(&mBufferWithExtendableBuffer,
+            getBigramsStructurePolicy(), getShortcutsStructurePolicy());
+    nodeReader.fetchNodeInfoInBufferFromPtNodePos(ptNodePos);
     if (nodeReader.isDeleted()) {
         return NOT_A_DICT_POS;
     }
     return nodeReader.getShortcutPos();
 }
 
-int DynamicPatriciaTriePolicy::getBigramsPositionOfNode(
-        const BinaryDictionaryInfo *const binaryDictionaryInfo,
-        const int nodePos) const {
-    if (nodePos == NOT_A_VALID_WORD_POS) {
+int DynamicPatriciaTriePolicy::getBigramsPositionOfPtNode(const int ptNodePos) const {
+    if (ptNodePos == NOT_A_DICT_POS) {
         return NOT_A_DICT_POS;
     }
-    DynamicPatriciaTrieNodeReader nodeReader(binaryDictionaryInfo);
-    nodeReader.fetchNodeInfoFromBuffer(nodePos);
+    DynamicPatriciaTrieNodeReader nodeReader(&mBufferWithExtendableBuffer,
+            getBigramsStructurePolicy(), getShortcutsStructurePolicy());
+    nodeReader.fetchNodeInfoInBufferFromPtNodePos(ptNodePos);
     if (nodeReader.isDeleted()) {
         return NOT_A_DICT_POS;
     }
     return nodeReader.getBigramsPos();
 }
 
+bool DynamicPatriciaTriePolicy::addUnigramWord(const int *const word, const int length,
+        const int probability) {
+    if (!mBuffer->isUpdatable()) {
+        AKLOGI("Warning: addUnigramWord() is called for non-updatable dictionary.");
+        return false;
+    }
+    DynamicPatriciaTrieReadingHelper readingHelper(&mBufferWithExtendableBuffer,
+            getBigramsStructurePolicy(), getShortcutsStructurePolicy());
+    readingHelper.initWithPtNodeArrayPos(getRootPosition());
+    DynamicPatriciaTrieWritingHelper writingHelper(&mBufferWithExtendableBuffer,
+            &mBigramListPolicy, &mShortcutListPolicy);
+    return writingHelper.addUnigramWord(&readingHelper, word, length, probability);
+}
+
+bool DynamicPatriciaTriePolicy::addBigramWords(const int *const word0, const int length0,
+        const int *const word1, const int length1, const int probability) {
+    if (!mBuffer->isUpdatable()) {
+        AKLOGI("Warning: addBigramWords() is called for non-updatable dictionary.");
+        return false;
+    }
+    const int word0Pos = getTerminalNodePositionOfWord(word0, length0,
+            false /* forceLowerCaseSearch */);
+    if (word0Pos == NOT_A_DICT_POS) {
+        return false;
+    }
+    const int word1Pos = getTerminalNodePositionOfWord(word1, length1,
+            false /* forceLowerCaseSearch */);
+    if (word1Pos == NOT_A_DICT_POS) {
+        return false;
+    }
+    DynamicPatriciaTrieWritingHelper writingHelper(&mBufferWithExtendableBuffer,
+            &mBigramListPolicy, &mShortcutListPolicy);
+    return writingHelper.addBigramWords(word0Pos, word1Pos, probability);
+}
+
+bool DynamicPatriciaTriePolicy::removeBigramWords(const int *const word0, const int length0,
+        const int *const word1, const int length1) {
+    if (!mBuffer->isUpdatable()) {
+        AKLOGI("Warning: removeBigramWords() is called for non-updatable dictionary.");
+        return false;
+    }
+    const int word0Pos = getTerminalNodePositionOfWord(word0, length0,
+            false /* forceLowerCaseSearch */);
+    if (word0Pos == NOT_A_DICT_POS) {
+        return false;
+    }
+    const int word1Pos = getTerminalNodePositionOfWord(word1, length1,
+            false /* forceLowerCaseSearch */);
+    if (word1Pos == NOT_A_DICT_POS) {
+        return false;
+    }
+    DynamicPatriciaTrieWritingHelper writingHelper(&mBufferWithExtendableBuffer,
+            &mBigramListPolicy, &mShortcutListPolicy);
+    return writingHelper.removeBigramWords(word0Pos, word1Pos);
+}
+
+void DynamicPatriciaTriePolicy::flush(const char *const filePath) {
+    if (!mBuffer->isUpdatable()) {
+        AKLOGI("Warning: flush() is called for non-updatable dictionary.");
+        return;
+    }
+    DynamicPatriciaTrieWritingHelper writingHelper(&mBufferWithExtendableBuffer,
+            &mBigramListPolicy, &mShortcutListPolicy);
+    writingHelper.writeToDictFile(filePath, &mHeaderPolicy);
+}
+
+void DynamicPatriciaTriePolicy::flushWithGC(const char *const filePath) {
+    if (!mBuffer->isUpdatable()) {
+        AKLOGI("Warning: flushWithGC() is called for non-updatable dictionary.");
+        return;
+    }
+    DynamicPatriciaTrieWritingHelper writingHelper(&mBufferWithExtendableBuffer,
+            &mBigramListPolicy, &mShortcutListPolicy);
+    writingHelper.writeToDictFileWithGC(getRootPosition(), filePath, &mHeaderPolicy);
+}
+
+bool DynamicPatriciaTriePolicy::needsToRunGC() const {
+    if (!mBuffer->isUpdatable()) {
+        AKLOGI("Warning: needsToRunGC() is called for non-updatable dictionary.");
+        return false;
+    }
+    // TODO: Implement more properly.
+    return mBufferWithExtendableBuffer.isNearSizeLimit();
+}
+
 } // namespace latinime
diff --git a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_policy.h b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_policy.h
index 6a79771..06d8095 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_policy.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_policy.h
@@ -18,18 +18,29 @@
 #define LATINIME_DYNAMIC_PATRICIA_TRIE_POLICY_H
 
 #include "defines.h"
-#include "suggest/core/policy/dictionary_structure_policy.h"
+#include "suggest/core/policy/dictionary_structure_with_buffer_policy.h"
+#include "suggest/policyimpl/dictionary/bigram/dynamic_bigram_list_policy.h"
+#include "suggest/policyimpl/dictionary/header/header_policy.h"
+#include "suggest/policyimpl/dictionary/shortcut/dynamic_shortcut_list_policy.h"
+#include "suggest/policyimpl/dictionary/utils/buffer_with_extendable_buffer.h"
+#include "suggest/policyimpl/dictionary/utils/mmapped_buffer.h"
 
 namespace latinime {
 
-class BinaryDictionaryInfo;
 class DicNode;
 class DicNodeVector;
 
-class DynamicPatriciaTriePolicy : public DictionaryStructurePolicy {
+class DynamicPatriciaTriePolicy : public DictionaryStructureWithBufferPolicy {
  public:
-    static AK_FORCE_INLINE const DynamicPatriciaTriePolicy *getInstance() {
-        return &sInstance;
+    DynamicPatriciaTriePolicy(const MmappedBuffer *const buffer)
+            : mBuffer(buffer), mHeaderPolicy(mBuffer->getBuffer(), buffer->getBufferSize()),
+              mBufferWithExtendableBuffer(mBuffer->getBuffer() + mHeaderPolicy.getSize(),
+                      mBuffer->getBufferSize() - mHeaderPolicy.getSize()),
+              mShortcutListPolicy(&mBufferWithExtendableBuffer),
+              mBigramListPolicy(&mBufferWithExtendableBuffer, &mShortcutListPolicy) {}
+
+    ~DynamicPatriciaTriePolicy() {
+        delete mBuffer;
     }
 
     AK_FORCE_INLINE int getRootPosition() const {
@@ -37,34 +48,57 @@
     }
 
     void createAndGetAllChildNodes(const DicNode *const dicNode,
-            const BinaryDictionaryInfo *const binaryDictionaryInfo,
-            const NodeFilter *const nodeFilter, DicNodeVector *const childDicNodes) const;
+            DicNodeVector *const childDicNodes) const;
 
     int getCodePointsAndProbabilityAndReturnCodePointCount(
-            const BinaryDictionaryInfo *const binaryDictionaryInfo,
-            const int terminalNodePos, const int maxCodePointCount, int *const outCodePoints,
+            const int terminalPtNodePos, const int maxCodePointCount, int *const outCodePoints,
             int *const outUnigramProbability) const;
 
-    int getTerminalNodePositionOfWord(
-            const BinaryDictionaryInfo *const binaryDictionaryInfo, const int *const inWord,
+    int getTerminalNodePositionOfWord(const int *const inWord,
             const int length, const bool forceLowerCaseSearch) const;
 
-    int getUnigramProbability(const BinaryDictionaryInfo *const binaryDictionaryInfo,
-            const int nodePos) const;
+    int getProbability(const int unigramProbability, const int bigramProbability) const;
 
-    int getShortcutPositionOfNode(const BinaryDictionaryInfo *const binaryDictionaryInfo,
-            const int nodePos) const;
+    int getUnigramProbabilityOfPtNode(const int ptNodePos) const;
 
-    int getBigramsPositionOfNode(const BinaryDictionaryInfo *const binaryDictionaryInfo,
-            const int nodePos) const;
+    int getShortcutPositionOfPtNode(const int ptNodePos) const;
+
+    int getBigramsPositionOfPtNode(const int ptNodePos) const;
+
+    const DictionaryHeaderStructurePolicy *getHeaderStructurePolicy() const {
+        return &mHeaderPolicy;
+    }
+
+    const DictionaryBigramsStructurePolicy *getBigramsStructurePolicy() const {
+        return &mBigramListPolicy;
+    }
+
+    const DictionaryShortcutsStructurePolicy *getShortcutsStructurePolicy() const {
+        return &mShortcutListPolicy;
+    }
+
+    bool addUnigramWord(const int *const word, const int length, const int probability);
+
+    bool addBigramWords(const int *const word0, const int length0, const int *const word1,
+            const int length1, const int probability);
+
+    bool removeBigramWords(const int *const word0, const int length0, const int *const word1,
+            const int length1);
+
+    void flush(const char *const filePath);
+
+    void flushWithGC(const char *const filePath);
+
+    bool needsToRunGC() const;
 
  private:
-    DISALLOW_COPY_AND_ASSIGN(DynamicPatriciaTriePolicy);
-    static const DynamicPatriciaTriePolicy sInstance;
-    static const int MAX_CHILD_COUNT_TO_AVOID_INFINITE_LOOP;
+    DISALLOW_IMPLICIT_CONSTRUCTORS(DynamicPatriciaTriePolicy);
 
-    DynamicPatriciaTriePolicy() {}
-    ~DynamicPatriciaTriePolicy() {}
+    const MmappedBuffer *const mBuffer;
+    const HeaderPolicy mHeaderPolicy;
+    BufferWithExtendableBuffer mBufferWithExtendableBuffer;
+    DynamicShortcutListPolicy mShortcutListPolicy;
+    DynamicBigramListPolicy mBigramListPolicy;
 };
 } // namespace latinime
 #endif // LATINIME_DYNAMIC_PATRICIA_TRIE_POLICY_H
diff --git a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_helper.cpp b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_helper.cpp
new file mode 100644
index 0000000..f4a2ef3
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_helper.cpp
@@ -0,0 +1,215 @@
+/*
+ * 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.
+ */
+
+#include "suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_helper.h"
+
+#include "suggest/policyimpl/dictionary/utils/buffer_with_extendable_buffer.h"
+
+namespace latinime {
+
+// To avoid infinite loop caused by invalid or malicious forward links.
+const int DynamicPatriciaTrieReadingHelper::MAX_CHILD_COUNT_TO_AVOID_INFINITE_LOOP = 100000;
+const int DynamicPatriciaTrieReadingHelper::MAX_NODE_ARRAY_COUNT_TO_AVOID_INFINITE_LOOP = 100000;
+const size_t DynamicPatriciaTrieReadingHelper::MAX_READING_STATE_STACK_SIZE = MAX_WORD_LENGTH;
+
+// Visits all PtNodes in post-order depth first manner.
+// For example, visits c -> b -> y -> x -> a for the following dictionary:
+// a _ b _ c
+//   \ x _ y
+bool DynamicPatriciaTrieReadingHelper::traverseAllPtNodesInPostorderDepthFirstManner(
+        TraversingEventListener *const listener) {
+    bool alreadyVisitedChildren = false;
+    // Descend from the root to the root PtNode array.
+    if (!listener->onDescend(getPosOfLastPtNodeArrayHead())) {
+        return false;
+    }
+    while (!isEnd()) {
+        if (!alreadyVisitedChildren) {
+            if (mNodeReader.hasChildren()) {
+                // Move to the first child.
+                if (!listener->onDescend(mNodeReader.getChildrenPos())) {
+                    return false;
+                }
+                pushReadingStateToStack();
+                readChildNode();
+            } else {
+                alreadyVisitedChildren = true;
+            }
+        } else {
+            if (!listener->onVisitingPtNode(&mNodeReader, mMergedNodeCodePoints)) {
+                return false;
+            }
+            readNextSiblingNode();
+            if (isEnd()) {
+                // All PtNodes in current linked PtNode arrays have been visited.
+                // Return to the parent.
+                if (!listener->onReadingPtNodeArrayTail()) {
+                    return false;
+                }
+                if (mReadingStateStack.size() <= 0) {
+                    break;
+                }
+                if (!listener->onAscend()) {
+                    return false;
+                }
+                popReadingStateFromStack();
+                alreadyVisitedChildren = true;
+            } else {
+                // Process sibling PtNode.
+                alreadyVisitedChildren = false;
+            }
+        }
+    }
+    // Ascend from the root PtNode array to the root.
+    if (!listener->onAscend()) {
+        return false;
+    }
+    return !isError();
+}
+
+// Visits all PtNodes in PtNode array level pre-order depth first manner, which is the same order
+// that PtNodes are written in the dictionary buffer.
+// For example, visits a -> b -> x -> c -> y for the following dictionary:
+// a _ b _ c
+//   \ x _ y
+bool DynamicPatriciaTrieReadingHelper::traverseAllPtNodesInPtNodeArrayLevelPreorderDepthFirstManner(
+        TraversingEventListener *const listener) {
+    bool alreadyVisitedAllPtNodesInArray = false;
+    bool alreadyVisitedChildren = false;
+    // Descend from the root to the root PtNode array.
+    if (!listener->onDescend(getPosOfLastPtNodeArrayHead())) {
+        return false;
+    }
+    pushReadingStateToStack();
+    while (!isEnd()) {
+        if (alreadyVisitedAllPtNodesInArray) {
+            if (alreadyVisitedChildren) {
+                // Move to next sibling PtNode's children.
+                readNextSiblingNode();
+                if (isEnd()) {
+                    // Return to the parent PTNode.
+                    if (!listener->onAscend()) {
+                        return false;
+                    }
+                    if (mReadingStateStack.size() <= 0) {
+                        break;
+                    }
+                    popReadingStateFromStack();
+                    alreadyVisitedChildren = true;
+                    alreadyVisitedAllPtNodesInArray = true;
+                } else {
+                    alreadyVisitedChildren = false;
+                }
+            } else {
+                if (mNodeReader.hasChildren()) {
+                    // Move to the first child.
+                    if (!listener->onDescend(mNodeReader.getChildrenPos())) {
+                        return false;
+                    }
+                    pushReadingStateToStack();
+                    readChildNode();
+                    // Push state to return the head of PtNode array.
+                    pushReadingStateToStack();
+                    alreadyVisitedAllPtNodesInArray = false;
+                    alreadyVisitedChildren = false;
+                } else {
+                    alreadyVisitedChildren = true;
+                }
+            }
+        } else {
+            if (!listener->onVisitingPtNode(&mNodeReader, mMergedNodeCodePoints)) {
+                return false;
+            }
+            readNextSiblingNode();
+            if (isEnd()) {
+                if (!listener->onReadingPtNodeArrayTail()) {
+                    return false;
+                }
+                // Return to the head of current PtNode array.
+                popReadingStateFromStack();
+                alreadyVisitedAllPtNodesInArray = true;
+            }
+        }
+    }
+    popReadingStateFromStack();
+    // Ascend from the root PtNode array to the root.
+    if (!listener->onAscend()) {
+        return false;
+    }
+    return !isError();
+}
+
+// Read node array size and process empty node arrays. Nodes and arrays are counted up in this
+// method to avoid an infinite loop.
+void DynamicPatriciaTrieReadingHelper::nextPtNodeArray() {
+    mReadingState.mPosOfLastPtNodeArrayHead = mReadingState.mPos;
+    const bool usesAdditionalBuffer = mBuffer->isInAdditionalBuffer(mReadingState.mPos);
+    const uint8_t *const dictBuf = mBuffer->getBuffer(usesAdditionalBuffer);
+    if (usesAdditionalBuffer) {
+        mReadingState.mPos -= mBuffer->getOriginalBufferSize();
+    }
+    mReadingState.mNodeCount = PatriciaTrieReadingUtils::getPtNodeArraySizeAndAdvancePosition(
+            dictBuf, &mReadingState.mPos);
+    if (usesAdditionalBuffer) {
+        mReadingState.mPos += mBuffer->getOriginalBufferSize();
+    }
+    // Count up nodes and node arrays to avoid infinite loop.
+    mReadingState.mTotalNodeCount += mReadingState.mNodeCount;
+    mReadingState.mNodeArrayCount++;
+    if (mReadingState.mNodeCount < 0
+            || mReadingState.mTotalNodeCount > MAX_CHILD_COUNT_TO_AVOID_INFINITE_LOOP
+            || mReadingState.mNodeArrayCount > MAX_NODE_ARRAY_COUNT_TO_AVOID_INFINITE_LOOP) {
+        // Invalid dictionary.
+        AKLOGI("Invalid dictionary. nodeCount: %d, totalNodeCount: %d, MAX_CHILD_COUNT: %d"
+                "nodeArrayCount: %d, MAX_NODE_ARRAY_COUNT: %d",
+                mReadingState.mNodeCount, mReadingState.mTotalNodeCount,
+                MAX_CHILD_COUNT_TO_AVOID_INFINITE_LOOP, mReadingState.mNodeArrayCount,
+                MAX_NODE_ARRAY_COUNT_TO_AVOID_INFINITE_LOOP);
+        ASSERT(false);
+        mIsError = true;
+        mReadingState.mPos = NOT_A_DICT_POS;
+        return;
+    }
+    if (mReadingState.mNodeCount == 0) {
+        // Empty node array. Try following forward link.
+        followForwardLink();
+    }
+}
+
+// Follow the forward link and read the next node array if exists.
+void DynamicPatriciaTrieReadingHelper::followForwardLink() {
+    const bool usesAdditionalBuffer = mBuffer->isInAdditionalBuffer(mReadingState.mPos);
+    const uint8_t *const dictBuf = mBuffer->getBuffer(usesAdditionalBuffer);
+    if (usesAdditionalBuffer) {
+        mReadingState.mPos -= mBuffer->getOriginalBufferSize();
+    }
+    const int forwardLinkPosition =
+            DynamicPatriciaTrieReadingUtils::getForwardLinkPosition(dictBuf, mReadingState.mPos);
+    if (usesAdditionalBuffer) {
+        mReadingState.mPos += mBuffer->getOriginalBufferSize();
+    }
+    mReadingState.mPosOfLastForwardLinkField = mReadingState.mPos;
+    if (DynamicPatriciaTrieReadingUtils::isValidForwardLinkPosition(forwardLinkPosition)) {
+        // Follow the forward link.
+        mReadingState.mPos += forwardLinkPosition;
+        nextPtNodeArray();
+    } else {
+        // All node arrays have been read.
+        mReadingState.mPos = NOT_A_DICT_POS;
+    }
+}
+
+} // namespace latinime
diff --git a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_helper.h b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_helper.h
new file mode 100644
index 0000000..b033eee
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_helper.h
@@ -0,0 +1,286 @@
+/*
+ * 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.
+ */
+
+#ifndef LATINIME_DYNAMIC_PATRICIA_TRIE_READING_HELPER_H
+#define LATINIME_DYNAMIC_PATRICIA_TRIE_READING_HELPER_H
+
+#include <cstddef>
+#include <vector>
+
+#include "defines.h"
+#include "suggest/policyimpl/dictionary/dynamic_patricia_trie_node_reader.h"
+#include "suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_utils.h"
+#include "suggest/policyimpl/dictionary/patricia_trie_reading_utils.h"
+
+namespace latinime {
+
+class BufferWithExtendableBuffer;
+class DictionaryBigramsStructurePolicy;
+class DictionaryShortcutsStructurePolicy;
+
+/*
+ * This class is used for traversing dynamic patricia trie. This class supports iterating nodes and
+ * dealing with additional buffer. This class counts nodes and node arrays to avoid infinite loop.
+ */
+class DynamicPatriciaTrieReadingHelper {
+ public:
+    class TraversingEventListener {
+     public:
+        virtual ~TraversingEventListener() {};
+
+        // Returns whether the event handling was succeeded or not.
+        virtual bool onAscend() = 0;
+
+        // Returns whether the event handling was succeeded or not.
+        virtual bool onDescend(const int ptNodeArrayPos) = 0;
+
+        // Returns whether the event handling was succeeded or not.
+        virtual bool onReadingPtNodeArrayTail() = 0;
+
+        // Returns whether the event handling was succeeded or not.
+        virtual bool onVisitingPtNode(const DynamicPatriciaTrieNodeReader *const node,
+                const int *const nodeCodePoints) = 0;
+
+     protected:
+        TraversingEventListener() {};
+
+     private:
+        DISALLOW_COPY_AND_ASSIGN(TraversingEventListener);
+    };
+
+    DynamicPatriciaTrieReadingHelper(const BufferWithExtendableBuffer *const buffer,
+            const DictionaryBigramsStructurePolicy *const bigramsPolicy,
+            const DictionaryShortcutsStructurePolicy *const shortcutsPolicy)
+            : mIsError(false), mReadingState(), mBuffer(buffer),
+              mNodeReader(mBuffer, bigramsPolicy, shortcutsPolicy), mReadingStateStack() {}
+
+    ~DynamicPatriciaTrieReadingHelper() {}
+
+    AK_FORCE_INLINE bool isError() const {
+        return mIsError;
+    }
+
+    AK_FORCE_INLINE bool isEnd() const {
+        return mReadingState.mPos == NOT_A_DICT_POS;
+    }
+
+    // Initialize reading state with the head position of a PtNode array.
+    AK_FORCE_INLINE void initWithPtNodeArrayPos(const int ptNodeArrayPos) {
+        if (ptNodeArrayPos == NOT_A_DICT_POS) {
+            mReadingState.mPos = NOT_A_DICT_POS;
+        } else {
+            mIsError = false;
+            mReadingState.mPos = ptNodeArrayPos;
+            mReadingState.mPrevTotalCodePointCount = 0;
+            mReadingState.mTotalNodeCount = 0;
+            mReadingState.mNodeArrayCount = 0;
+            mReadingState.mPosOfLastForwardLinkField = NOT_A_DICT_POS;
+            mReadingStateStack.clear();
+            nextPtNodeArray();
+            if (!isEnd()) {
+                fetchPtNodeInfo();
+            }
+        }
+    }
+
+    // Initialize reading state with the head position of a node.
+    AK_FORCE_INLINE void initWithPtNodePos(const int ptNodePos) {
+        if (ptNodePos == NOT_A_DICT_POS) {
+            mReadingState.mPos = NOT_A_DICT_POS;
+        } else {
+            mIsError = false;
+            mReadingState.mPos = ptNodePos;
+            mReadingState.mNodeCount = 1;
+            mReadingState.mPrevTotalCodePointCount = 0;
+            mReadingState.mTotalNodeCount = 1;
+            mReadingState.mNodeArrayCount = 1;
+            mReadingState.mPosOfLastForwardLinkField = NOT_A_DICT_POS;
+            mReadingState.mPosOfLastPtNodeArrayHead = NOT_A_DICT_POS;
+            mReadingStateStack.clear();
+            fetchPtNodeInfo();
+        }
+    }
+
+    AK_FORCE_INLINE const DynamicPatriciaTrieNodeReader* getNodeReader() const {
+        return &mNodeReader;
+    }
+
+    AK_FORCE_INLINE bool isValidTerminalNode() const {
+        return !isEnd() && !mNodeReader.isDeleted() && mNodeReader.isTerminal();
+    }
+
+    AK_FORCE_INLINE bool isMatchedCodePoint(const int index, const int codePoint) const {
+        return mMergedNodeCodePoints[index] == codePoint;
+    }
+
+    // Return code point count exclude the last read node's code points.
+    AK_FORCE_INLINE int getPrevTotalCodePointCount() const {
+        return mReadingState.mPrevTotalCodePointCount;
+    }
+
+    // Return code point count include the last read node's code points.
+    AK_FORCE_INLINE int getTotalCodePointCount() const {
+        return mReadingState.mPrevTotalCodePointCount + mNodeReader.getCodePointCount();
+    }
+
+    AK_FORCE_INLINE void fetchMergedNodeCodePointsInReverseOrder(
+            const int index, int *const outCodePoints) const {
+        const int nodeCodePointCount = mNodeReader.getCodePointCount();
+        for (int i =  0; i < nodeCodePointCount; ++i) {
+            outCodePoints[index + i] = mMergedNodeCodePoints[nodeCodePointCount - 1 - i];
+        }
+    }
+
+    AK_FORCE_INLINE const int *getMergedNodeCodePoints() const {
+        return mMergedNodeCodePoints;
+    }
+
+    AK_FORCE_INLINE void readNextSiblingNode() {
+        mReadingState.mNodeCount -= 1;
+        mReadingState.mPos = mNodeReader.getSiblingNodePos();
+        if (mReadingState.mNodeCount <= 0) {
+            // All nodes in the current node array have been read.
+            followForwardLink();
+            if (!isEnd()) {
+                fetchPtNodeInfo();
+            }
+        } else {
+            fetchPtNodeInfo();
+        }
+    }
+
+    // Read the first child node of the current node.
+    AK_FORCE_INLINE void readChildNode() {
+        if (mNodeReader.hasChildren()) {
+            mReadingState.mPrevTotalCodePointCount += mNodeReader.getCodePointCount();
+            mReadingState.mTotalNodeCount = 0;
+            mReadingState.mNodeArrayCount = 0;
+            mReadingState.mPos = mNodeReader.getChildrenPos();
+            mReadingState.mPosOfLastForwardLinkField = NOT_A_DICT_POS;
+            // Read children node array.
+            nextPtNodeArray();
+            if (!isEnd()) {
+                fetchPtNodeInfo();
+            }
+        } else {
+            mReadingState.mPos = NOT_A_DICT_POS;
+        }
+    }
+
+    // Read the parent node of the current node.
+    AK_FORCE_INLINE void readParentNode() {
+        if (mNodeReader.getParentPos() != NOT_A_DICT_POS) {
+            mReadingState.mPrevTotalCodePointCount += mNodeReader.getCodePointCount();
+            mReadingState.mTotalNodeCount = 1;
+            mReadingState.mNodeArrayCount = 1;
+            mReadingState.mNodeCount = 1;
+            mReadingState.mPos = mNodeReader.getParentPos();
+            mReadingState.mPosOfLastForwardLinkField = NOT_A_DICT_POS;
+            mReadingState.mPosOfLastPtNodeArrayHead = NOT_A_DICT_POS;
+            fetchPtNodeInfo();
+        } else {
+            mReadingState.mPos = NOT_A_DICT_POS;
+        }
+    }
+
+    AK_FORCE_INLINE int getPosOfLastForwardLinkField() const {
+        return mReadingState.mPosOfLastForwardLinkField;
+    }
+
+    AK_FORCE_INLINE int getPosOfLastPtNodeArrayHead() const {
+        return mReadingState.mPosOfLastPtNodeArrayHead;
+    }
+
+    AK_FORCE_INLINE void reloadCurrentPtNodeInfo() {
+        if (!isEnd()) {
+            fetchPtNodeInfo();
+        }
+    }
+
+    bool traverseAllPtNodesInPostorderDepthFirstManner(TraversingEventListener *const listener);
+
+    bool traverseAllPtNodesInPtNodeArrayLevelPreorderDepthFirstManner(
+            TraversingEventListener *const listener);
+
+ private:
+    DISALLOW_COPY_AND_ASSIGN(DynamicPatriciaTrieReadingHelper);
+
+    class ReadingState {
+     public:
+        // Note that copy constructor and assignment operator are used for this class to use
+        // std::vector.
+        ReadingState() : mPos(NOT_A_DICT_POS), mNodeCount(0), mPrevTotalCodePointCount(0),
+                mTotalNodeCount(0), mNodeArrayCount(0), mPosOfLastForwardLinkField(NOT_A_DICT_POS),
+                mPosOfLastPtNodeArrayHead(NOT_A_DICT_POS) {}
+
+        int mPos;
+        // Node count of a node array.
+        int mNodeCount;
+        int mPrevTotalCodePointCount;
+        int mTotalNodeCount;
+        int mNodeArrayCount;
+        int mPosOfLastForwardLinkField;
+        int mPosOfLastPtNodeArrayHead;
+    };
+
+    static const int MAX_CHILD_COUNT_TO_AVOID_INFINITE_LOOP;
+    static const int MAX_NODE_ARRAY_COUNT_TO_AVOID_INFINITE_LOOP;
+    static const size_t MAX_READING_STATE_STACK_SIZE;
+
+    bool mIsError;
+    ReadingState mReadingState;
+    const BufferWithExtendableBuffer *const mBuffer;
+    DynamicPatriciaTrieNodeReader mNodeReader;
+    int mMergedNodeCodePoints[MAX_WORD_LENGTH];
+    std::vector<ReadingState> mReadingStateStack;
+
+    void nextPtNodeArray();
+
+    void followForwardLink();
+
+    AK_FORCE_INLINE void fetchPtNodeInfo() {
+        mNodeReader.fetchNodeInfoInBufferFromPtNodePosAndGetNodeCodePoints(mReadingState.mPos,
+                MAX_WORD_LENGTH, mMergedNodeCodePoints);
+        if (mNodeReader.getCodePointCount() <= 0) {
+            // Empty node is not allowed.
+            mIsError = true;
+            mReadingState.mPos = NOT_A_DICT_POS;
+        }
+    }
+
+    AK_FORCE_INLINE void pushReadingStateToStack() {
+        if (mReadingStateStack.size() > MAX_READING_STATE_STACK_SIZE) {
+            AKLOGI("Reading state stack overflow. Max size: %d", MAX_READING_STATE_STACK_SIZE);
+            ASSERT(false);
+            mIsError = true;
+            mReadingState.mPos = NOT_A_DICT_POS;
+        } else {
+            mReadingStateStack.push_back(mReadingState);
+        }
+    }
+
+    AK_FORCE_INLINE void popReadingStateFromStack() {
+        if (mReadingStateStack.empty()) {
+            mReadingState.mPos = NOT_A_DICT_POS;
+        } else {
+            mReadingState = mReadingStateStack.back();
+            mReadingStateStack.pop_back();
+            fetchPtNodeInfo();
+        }
+    }
+};
+} // namespace latinime
+#endif /* LATINIME_DYNAMIC_PATRICIA_TRIE_READING_HELPER_H */
diff --git a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_utils.cpp b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_utils.cpp
index 0de6341..d68446d 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_utils.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_utils.cpp
@@ -17,7 +17,7 @@
 #include "suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_utils.h"
 
 #include "defines.h"
-#include "suggest/core/dictionary/byte_array_utils.h"
+#include "suggest/policyimpl/dictionary/utils/byte_array_utils.h"
 
 namespace latinime {
 
@@ -28,13 +28,44 @@
 const DptReadingUtils::NodeFlags DptReadingUtils::FLAG_IS_MOVED = 0x40;
 const DptReadingUtils::NodeFlags DptReadingUtils::FLAG_IS_DELETED = 0x80;
 
-/* static */ int DptReadingUtils::readChildrenPositionAndAdvancePosition(
-        const uint8_t *const buffer, const NodeFlags flags, int *const pos) {
-    if ((flags & MASK_MOVED) == FLAG_IS_NOT_MOVED) {
-        const int base = *pos;
-        return base + ByteArrayUtils::readSint24AndAdvancePosition(buffer, pos);
-    } else {
+// TODO: Make DICT_OFFSET_ZERO_OFFSET = 0.
+// Currently, DICT_OFFSET_INVALID is 0 in Java side but offset can be 0 during GC. So, the maximum
+// value of offsets, which is 0x7FFFFF is used to represent 0 offset.
+const int DptReadingUtils::DICT_OFFSET_INVALID = 0;
+const int DptReadingUtils::DICT_OFFSET_ZERO_OFFSET = 0x7FFFFF;
+
+/* static */ int DptReadingUtils::getForwardLinkPosition(const uint8_t *const buffer,
+        const int pos) {
+    int linkAddressPos = pos;
+    return ByteArrayUtils::readSint24AndAdvancePosition(buffer, &linkAddressPos);
+}
+
+/* static */ int DptReadingUtils::getParentPtNodePosOffsetAndAdvancePosition(
+        const uint8_t *const buffer, int *const pos) {
+    return ByteArrayUtils::readSint24AndAdvancePosition(buffer, pos);
+}
+
+/* static */ int DptReadingUtils::getParentPtNodePos(const int parentOffset, const int ptNodePos) {
+    if (parentOffset == DICT_OFFSET_INVALID) {
         return NOT_A_DICT_POS;
+    } else if (parentOffset == DICT_OFFSET_ZERO_OFFSET) {
+        return ptNodePos;
+    } else {
+        return parentOffset + ptNodePos;
+    }
+}
+
+/* static */ int DptReadingUtils::readChildrenPositionAndAdvancePosition(
+        const uint8_t *const buffer, int *const pos) {
+    const int base = *pos;
+    const int offset = ByteArrayUtils::readSint24AndAdvancePosition(buffer, pos);
+    if (offset == DICT_OFFSET_INVALID) {
+        // The PtNode does not have children.
+        return NOT_A_DICT_POS;
+    } else if (offset == DICT_OFFSET_ZERO_OFFSET) {
+        return base;
+    } else {
+        return base + offset;
     }
 }
 
diff --git a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_utils.h b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_utils.h
index 5398d7e..67c3cc5 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_utils.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_utils.h
@@ -20,7 +20,6 @@
 #include <stdint.h>
 
 #include "defines.h"
-#include "suggest/core/dictionary/byte_array_utils.h"
 
 namespace latinime {
 
@@ -28,22 +27,21 @@
  public:
     typedef uint8_t NodeFlags;
 
-    static AK_FORCE_INLINE int getForwardLinkPosition(const uint8_t *const buffer, const int pos) {
-        int linkAddressPos = pos;
-        return ByteArrayUtils::readSint24AndAdvancePosition(buffer, &linkAddressPos);
-    }
+    static const int DICT_OFFSET_INVALID;
+    static const int DICT_OFFSET_ZERO_OFFSET;
+
+    static int getForwardLinkPosition(const uint8_t *const buffer, const int pos);
 
     static AK_FORCE_INLINE bool isValidForwardLinkPosition(const int forwardLinkAddress) {
         return forwardLinkAddress != 0;
     }
 
-    static AK_FORCE_INLINE int getParentPosAndAdvancePosition(const uint8_t *const buffer,
-            int *const pos) {
-        return ByteArrayUtils::readSint24AndAdvancePosition(buffer, pos);
-    }
+    static int getParentPtNodePosOffsetAndAdvancePosition(const uint8_t *const buffer,
+            int *const pos);
 
-    static int readChildrenPositionAndAdvancePosition(const uint8_t *const buffer,
-            const NodeFlags flags, int *const pos);
+    static int getParentPtNodePos(const int parentOffset, const int ptNodePos);
+
+    static int readChildrenPositionAndAdvancePosition(const uint8_t *const buffer, int *const pos);
 
     /**
      * Node Flags
@@ -56,6 +54,15 @@
         return FLAG_IS_DELETED == (MASK_MOVED & flags);
     }
 
+    static AK_FORCE_INLINE NodeFlags updateAndGetFlags(const NodeFlags originalFlags,
+            const bool isMoved, const bool isDeleted) {
+        NodeFlags flags = originalFlags;
+        flags = isMoved ? ((flags & (~MASK_MOVED)) | FLAG_IS_MOVED) : flags;
+        flags = isDeleted ? ((flags & (~MASK_MOVED)) | FLAG_IS_DELETED) : flags;
+        flags = (!isMoved && !isDeleted) ? ((flags & (~MASK_MOVED)) | FLAG_IS_NOT_MOVED) : flags;
+        return flags;
+    }
+
  private:
     DISALLOW_IMPLICIT_CONSTRUCTORS(DynamicPatriciaTrieReadingUtils);
 
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
new file mode 100644
index 0000000..a51ae5e
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_helper.cpp
@@ -0,0 +1,569 @@
+/*
+ * 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.
+ */
+
+#include "suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_helper.h"
+
+#include <cstdio>
+#include <cstring>
+
+#include "suggest/policyimpl/dictionary/bigram/dynamic_bigram_list_policy.h"
+#include "suggest/policyimpl/dictionary/dynamic_patricia_trie_gc_event_listeners.h"
+#include "suggest/policyimpl/dictionary/dynamic_patricia_trie_node_reader.h"
+#include "suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_helper.h"
+#include "suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_utils.h"
+#include "suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_utils.h"
+#include "suggest/policyimpl/dictionary/header/header_policy.h"
+#include "suggest/policyimpl/dictionary/patricia_trie_reading_utils.h"
+#include "suggest/policyimpl/dictionary/shortcut/dynamic_shortcut_list_policy.h"
+#include "utils/hash_map_compat.h"
+
+namespace latinime {
+
+const int DynamicPatriciaTrieWritingHelper::CHILDREN_POSITION_FIELD_SIZE = 3;
+const char *const DynamicPatriciaTrieWritingHelper::TEMP_FILE_SUFFIX_FOR_WRITING_DICT_FILE =
+        ".tmp";
+// TODO: Make MAX_DICTIONARY_SIZE 8MB.
+const size_t DynamicPatriciaTrieWritingHelper::MAX_DICTIONARY_SIZE = 2 * 1024 * 1024;
+
+bool DynamicPatriciaTrieWritingHelper::addUnigramWord(
+        DynamicPatriciaTrieReadingHelper *const readingHelper,
+        const int *const wordCodePoints, const int codePointCount, const int probability) {
+    int parentPos = NOT_A_DICT_POS;
+    while (!readingHelper->isEnd()) {
+        const int matchedCodePointCount = readingHelper->getPrevTotalCodePointCount();
+        if (!readingHelper->isMatchedCodePoint(0 /* index */,
+                wordCodePoints[matchedCodePointCount])) {
+            // The first code point is different from target code point. Skip this node and read
+            // the next sibling node.
+            readingHelper->readNextSiblingNode();
+            continue;
+        }
+        // Check following merged node code points.
+        const DynamicPatriciaTrieNodeReader *const nodeReader = readingHelper->getNodeReader();
+        const int nodeCodePointCount = nodeReader->getCodePointCount();
+        for (int j = 1; j < nodeCodePointCount; ++j) {
+            const int nextIndex = matchedCodePointCount + j;
+            if (nextIndex >= codePointCount || !readingHelper->isMatchedCodePoint(j,
+                    wordCodePoints[matchedCodePointCount + j])) {
+                return reallocatePtNodeAndAddNewPtNodes(nodeReader,
+                        readingHelper->getMergedNodeCodePoints(), j, probability,
+                        wordCodePoints + matchedCodePointCount,
+                        codePointCount - matchedCodePointCount);
+            }
+        }
+        // All characters are matched.
+        if (codePointCount == readingHelper->getTotalCodePointCount()) {
+            return setPtNodeProbability(nodeReader, probability,
+                    readingHelper->getMergedNodeCodePoints());
+        }
+        if (!nodeReader->hasChildren()) {
+            return createChildrenPtNodeArrayAndAChildPtNode(nodeReader, probability,
+                    wordCodePoints + readingHelper->getTotalCodePointCount(),
+                    codePointCount - readingHelper->getTotalCodePointCount());
+        }
+        // Advance to the children nodes.
+        parentPos = nodeReader->getHeadPos();
+        readingHelper->readChildNode();
+    }
+    if (readingHelper->isError()) {
+        // The dictionary is invalid.
+        return false;
+    }
+    int pos = readingHelper->getPosOfLastForwardLinkField();
+    return createAndInsertNodeIntoPtNodeArray(parentPos,
+            wordCodePoints + readingHelper->getPrevTotalCodePointCount(),
+            codePointCount - readingHelper->getPrevTotalCodePointCount(),
+            probability, &pos);
+}
+
+bool DynamicPatriciaTrieWritingHelper::addBigramWords(const int word0Pos, const int word1Pos,
+        const int probability) {
+    int mMergedNodeCodePoints[MAX_WORD_LENGTH];
+    DynamicPatriciaTrieNodeReader nodeReader(mBuffer, mBigramPolicy, mShortcutPolicy);
+    nodeReader.fetchNodeInfoInBufferFromPtNodePosAndGetNodeCodePoints(word0Pos, MAX_WORD_LENGTH,
+            mMergedNodeCodePoints);
+    // Move node to add bigram entry.
+    const int newNodePos = mBuffer->getTailPosition();
+    if (!markNodeAsMovedAndSetPosition(&nodeReader, newNodePos, newNodePos)) {
+        return false;
+    }
+    int writingPos = newNodePos;
+    // 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;
+    }
+    nodeReader.fetchNodeInfoInBufferFromPtNodePos(newNodePos);
+    if (nodeReader.getBigramsPos() != NOT_A_DICT_POS) {
+        // Insert a new bigram entry into the existing bigram list.
+        int bigramListPos = nodeReader.getBigramsPos();
+        return mBigramPolicy->addNewBigramEntryToBigramList(word1Pos, probability, &bigramListPos);
+    } else {
+        // The PtNode doesn't have a bigram list.
+        // First, Write a bigram entry at the tail position of the PtNode.
+        if (!mBigramPolicy->writeNewBigramEntry(word1Pos, probability, &writingPos)) {
+            return false;
+        }
+        // Then, Mark as the PtNode having bigram list in the flags.
+        const PatriciaTrieReadingUtils::NodeFlags updatedFlags =
+                PatriciaTrieReadingUtils::createAndGetFlags(nodeReader.isBlacklisted(),
+                        nodeReader.isNotAWord(), nodeReader.getProbability() != NOT_A_PROBABILITY,
+                        nodeReader.getShortcutPos() != NOT_A_DICT_POS, true /* hasBigrams */,
+                        nodeReader.getCodePointCount() > 1, CHILDREN_POSITION_FIELD_SIZE);
+        writingPos = newNodePos;
+        // Write updated flags into the moved PtNode's flags field.
+        return DynamicPatriciaTrieWritingUtils::writeFlagsAndAdvancePosition(mBuffer, updatedFlags,
+                &writingPos);
+    }
+}
+
+// Remove a bigram relation from word0Pos to word1Pos.
+bool DynamicPatriciaTrieWritingHelper::removeBigramWords(const int word0Pos, const int word1Pos) {
+    DynamicPatriciaTrieNodeReader nodeReader(mBuffer, mBigramPolicy, mShortcutPolicy);
+    nodeReader.fetchNodeInfoInBufferFromPtNodePos(word0Pos);
+    if (nodeReader.getBigramsPos() == NOT_A_DICT_POS) {
+        return false;
+    }
+    return mBigramPolicy->removeBigram(nodeReader.getBigramsPos(), word1Pos);
+}
+
+void DynamicPatriciaTrieWritingHelper::writeToDictFile(const char *const fileName,
+        const HeaderPolicy *const headerPolicy) {
+    BufferWithExtendableBuffer headerBuffer(0 /* originalBuffer */, 0 /* originalBufferSize */);
+    if (!headerPolicy->writeHeaderToBuffer(&headerBuffer, false /* updatesLastUpdatedTime */)) {
+        return;
+    }
+    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;
+    }
+    BufferWithExtendableBuffer newDictBuffer(0 /* originalBuffer */, 0 /* originalBufferSize */,
+            MAX_DICTIONARY_SIZE);
+    if (!runGC(rootPtNodeArrayPos, &newDictBuffer)) {
+        return;
+    }
+    flushAllToFile(fileName, &headerBuffer, &newDictBuffer);
+}
+
+bool DynamicPatriciaTrieWritingHelper::markNodeAsDeleted(
+        const DynamicPatriciaTrieNodeReader *const nodeToUpdate) {
+    int pos = nodeToUpdate->getHeadPos();
+    const bool usesAdditionalBuffer = mBuffer->isInAdditionalBuffer(pos);
+    const uint8_t *const dictBuf = mBuffer->getBuffer(usesAdditionalBuffer);
+    if (usesAdditionalBuffer) {
+        pos -= mBuffer->getOriginalBufferSize();
+    }
+    // Read original flags
+    const PatriciaTrieReadingUtils::NodeFlags originalFlags =
+            PatriciaTrieReadingUtils::getFlagsAndAdvancePosition(dictBuf, &pos);
+    const PatriciaTrieReadingUtils::NodeFlags updatedFlags =
+            DynamicPatriciaTrieReadingUtils::updateAndGetFlags(originalFlags, false /* isMoved */,
+                    true /* isDeleted */);
+    int writingPos = nodeToUpdate->getHeadPos();
+    // Update flags.
+    return DynamicPatriciaTrieWritingUtils::writeFlagsAndAdvancePosition(mBuffer, updatedFlags,
+            &writingPos);
+}
+
+bool DynamicPatriciaTrieWritingHelper::markNodeAsMovedAndSetPosition(
+        const DynamicPatriciaTrieNodeReader *const originalNode, const int movedPos,
+        const int bigramLinkedNodePos) {
+    int pos = originalNode->getHeadPos();
+    const bool usesAdditionalBuffer = mBuffer->isInAdditionalBuffer(pos);
+    const uint8_t *const dictBuf = mBuffer->getBuffer(usesAdditionalBuffer);
+    if (usesAdditionalBuffer) {
+        pos -= mBuffer->getOriginalBufferSize();
+    }
+    // Read original flags
+    const PatriciaTrieReadingUtils::NodeFlags originalFlags =
+            PatriciaTrieReadingUtils::getFlagsAndAdvancePosition(dictBuf, &pos);
+    const PatriciaTrieReadingUtils::NodeFlags updatedFlags =
+            DynamicPatriciaTrieReadingUtils::updateAndGetFlags(originalFlags, true /* isMoved */,
+                    false /* isDeleted */);
+    int writingPos = originalNode->getHeadPos();
+    // Update flags.
+    if (!DynamicPatriciaTrieWritingUtils::writeFlagsAndAdvancePosition(mBuffer, updatedFlags,
+            &writingPos)) {
+        return false;
+    }
+    // Update moved position, which is stored in the parent offset field.
+    if (!DynamicPatriciaTrieWritingUtils::writeParentPosOffsetAndAdvancePosition(
+            mBuffer, movedPos, originalNode->getHeadPos(), &writingPos)) {
+        return false;
+    }
+    // Update bigram linked node position, which is stored in the children position field.
+    int childrenPosFieldPos = originalNode->getChildrenPosFieldPos();
+    if (!DynamicPatriciaTrieWritingUtils::writeChildrenPositionAndAdvancePosition(
+            mBuffer, bigramLinkedNodePos, &childrenPosFieldPos)) {
+        return false;
+    }
+    if (originalNode->hasChildren()) {
+        // Update children's parent position.
+        DynamicPatriciaTrieReadingHelper readingHelper(mBuffer, mBigramPolicy, mShortcutPolicy);
+        const DynamicPatriciaTrieNodeReader *const nodeReader = readingHelper.getNodeReader();
+        readingHelper.initWithPtNodeArrayPos(originalNode->getChildrenPos());
+        while (!readingHelper.isEnd()) {
+            int parentOffsetFieldPos = nodeReader->getHeadPos()
+                    + DynamicPatriciaTrieWritingUtils::NODE_FLAG_FIELD_SIZE;
+            if (!DynamicPatriciaTrieWritingUtils::writeParentPosOffsetAndAdvancePosition(
+                    mBuffer, movedPos, nodeReader->getHeadPos(), &parentOffsetFieldPos)) {
+                // Parent offset cannot be written because of a bug or a broken dictionary; thus,
+                // we give up to update dictionary.
+                return false;
+            }
+            readingHelper.readNextSiblingNode();
+        }
+    }
+    return true;
+}
+
+// Write new PtNode at writingPos.
+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,
+        int *const writingPos) {
+    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(bufferToWrite,
+            0 /* nodeFlags */, writingPos)) {
+        return false;
+    }
+    // Calculate a parent offset and write the offset.
+    if (!DynamicPatriciaTrieWritingUtils::writeParentPosOffsetAndAdvancePosition(bufferToWrite,
+            parentPos, nodePos, writingPos)) {
+        return false;
+    }
+    // Write code points
+    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(bufferToWrite,
+                probability, writingPos)) {
+            return false;
+        }
+    }
+    // Write children position
+    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(bufferToWrite, &fromPos,
+                writingPos)) {
+            return false;
+        }
+    }
+    // Copy bigram list when the originalBigramListPos is valid dictionary position.
+    int bigramCount = 0;
+    if (originalBigramListPos != NOT_A_DICT_POS) {
+        int fromPos = originalBigramListPos;
+        if (!mBigramPolicy->copyAllBigrams(bufferToWrite, &fromPos, writingPos, &bigramCount)) {
+            return false;
+        }
+    }
+    // Create node flags and write them.
+    PatriciaTrieReadingUtils::NodeFlags nodeFlags =
+            PatriciaTrieReadingUtils::createAndGetFlags(isBlacklisted, isNotAWord,
+                    probability != NOT_A_PROBABILITY /* isTerminal */,
+                    originalShortcutListPos != NOT_A_DICT_POS /* hasShortcutTargets */,
+                    bigramCount > 0 /* hasBigrams */, codePointCount > 1 /* hasMultipleChars */,
+                    CHILDREN_POSITION_FIELD_SIZE);
+    int flagsFieldPos = nodePos;
+    if (!DynamicPatriciaTrieWritingUtils::writeFlagsAndAdvancePosition(bufferToWrite, nodeFlags,
+            &flagsFieldPos)) {
+        return false;
+    }
+    return true;
+}
+
+bool DynamicPatriciaTrieWritingHelper::writePtNodeToBuffer(
+        BufferWithExtendableBuffer *const bufferToWrite, const int parentPos,
+        const int *const codePoints, const int codePointCount, const int probability,
+        int *const writingPos) {
+    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(bufferToWrite, originalNode->isBlacklisted(),
+            originalNode->isNotAWord(), parentPos, codePoints, codePointCount, probability,
+            originalNode->getChildrenPos(), originalNode->getBigramsPos(),
+            originalNode->getShortcutPos(), writingPos);
+}
+
+bool DynamicPatriciaTrieWritingHelper::createAndInsertNodeIntoPtNodeArray(const int parentPos,
+        const int *const nodeCodePoints, const int nodeCodePointCount, const int probability,
+        int *const forwardLinkFieldPos) {
+    const int newPtNodeArrayPos = mBuffer->getTailPosition();
+    if (!DynamicPatriciaTrieWritingUtils::writeForwardLinkPositionAndAdvancePosition(mBuffer,
+            newPtNodeArrayPos, forwardLinkFieldPos)) {
+        return false;
+    }
+    return createNewPtNodeArrayWithAChildPtNode(parentPos, nodeCodePoints, nodeCodePointCount,
+            probability);
+}
+
+bool DynamicPatriciaTrieWritingHelper::setPtNodeProbability(
+        const DynamicPatriciaTrieNodeReader *const originalPtNode, const int probability,
+        const int *const codePoints) {
+    if (originalPtNode->isTerminal()) {
+        // Overwrites the probability.
+        int probabilityFieldPos = originalPtNode->getProbabilityFieldPos();
+        if (!DynamicPatriciaTrieWritingUtils::writeProbabilityAndAdvancePosition(mBuffer,
+                probability, &probabilityFieldPos)) {
+            return false;
+        }
+    } else {
+        // Make the node terminal and write the probability.
+        int movedPos = mBuffer->getTailPosition();
+        if (!markNodeAsMovedAndSetPosition(originalPtNode, movedPos, movedPos)) {
+            return false;
+        }
+        if (!writePtNodeToBufferByCopyingPtNodeInfo(mBuffer, originalPtNode,
+                originalPtNode->getParentPos(), codePoints, originalPtNode->getCodePointCount(),
+                probability, &movedPos)) {
+            return false;
+        }
+    }
+    return true;
+}
+
+bool DynamicPatriciaTrieWritingHelper::createChildrenPtNodeArrayAndAChildPtNode(
+        const DynamicPatriciaTrieNodeReader *const parentNode, const int probability,
+        const int *const codePoints, const int codePointCount) {
+    const int newPtNodeArrayPos = mBuffer->getTailPosition();
+    int childrenPosFieldPos = parentNode->getChildrenPosFieldPos();
+    if (!DynamicPatriciaTrieWritingUtils::writeChildrenPositionAndAdvancePosition(mBuffer,
+            newPtNodeArrayPos, &childrenPosFieldPos)) {
+        return false;
+    }
+    return createNewPtNodeArrayWithAChildPtNode(parentNode->getHeadPos(), codePoints,
+            codePointCount, probability);
+}
+
+bool DynamicPatriciaTrieWritingHelper::createNewPtNodeArrayWithAChildPtNode(
+        const int parentPtNodePos, const int *const nodeCodePoints, const int nodeCodePointCount,
+        const int probability) {
+    int writingPos = mBuffer->getTailPosition();
+    if (!DynamicPatriciaTrieWritingUtils::writePtNodeArraySizeAndAdvancePosition(mBuffer,
+            1 /* arraySize */, &writingPos)) {
+        return false;
+    }
+    if (!writePtNodeToBuffer(mBuffer, parentPtNodePos, nodeCodePoints, nodeCodePointCount,
+            probability, &writingPos)) {
+        return false;
+    }
+    if (!DynamicPatriciaTrieWritingUtils::writeForwardLinkPositionAndAdvancePosition(mBuffer,
+            NOT_A_DICT_POS /* forwardLinkPos */, &writingPos)) {
+        return false;
+    }
+    return true;
+}
+
+// Returns whether the dictionary updating was succeeded or not.
+bool DynamicPatriciaTrieWritingHelper::reallocatePtNodeAndAddNewPtNodes(
+        const DynamicPatriciaTrieNodeReader *const reallocatingPtNode,
+        const int *const reallocatingPtNodeCodePoints, const int overlappingCodePointCount,
+        const int probabilityOfNewPtNode, const int *const newNodeCodePoints,
+        const int newNodeCodePointCount) {
+    // When addsExtraChild is true, split the reallocating PtNode and add new child.
+    // Reallocating PtNode: abcde, newNode: abcxy.
+    // abc (1st, not terminal) __ de (2nd)
+    //                         \_ xy (extra child, terminal)
+    // Otherwise, this method makes 1st part terminal and write probabilityOfNewPtNode.
+    // Reallocating PtNode: abcde, newNode: abc.
+    // abc (1st, terminal) __ de (2nd)
+    const bool addsExtraChild = newNodeCodePointCount > overlappingCodePointCount;
+    const int firstPartOfReallocatedPtNodePos = mBuffer->getTailPosition();
+    int writingPos = firstPartOfReallocatedPtNodePos;
+    // Write the 1st part of the reallocating node. The children position will be updated later
+    // with actual children position.
+    const int newProbability = addsExtraChild ? NOT_A_PROBABILITY : probabilityOfNewPtNode;
+    if (!writePtNodeToBuffer(mBuffer, reallocatingPtNode->getParentPos(),
+            reallocatingPtNodeCodePoints, overlappingCodePointCount, newProbability,
+            &writingPos)) {
+        return false;
+    }
+    const int actualChildrenPos = writingPos;
+    // Create new children PtNode array.
+    const size_t newPtNodeCount = addsExtraChild ? 2 : 1;
+    if (!DynamicPatriciaTrieWritingUtils::writePtNodeArraySizeAndAdvancePosition(mBuffer,
+            newPtNodeCount, &writingPos)) {
+        return false;
+    }
+    // Write the 2nd part of the reallocating node.
+    const int secondPartOfReallocatedPtNodePos = writingPos;
+    if (!writePtNodeToBufferByCopyingPtNodeInfo(mBuffer, reallocatingPtNode,
+            firstPartOfReallocatedPtNodePos,
+            reallocatingPtNodeCodePoints + overlappingCodePointCount,
+            reallocatingPtNode->getCodePointCount() - overlappingCodePointCount,
+            reallocatingPtNode->getProbability(), &writingPos)) {
+        return false;
+    }
+    if (addsExtraChild) {
+        if (!writePtNodeToBuffer(mBuffer, firstPartOfReallocatedPtNodePos,
+                newNodeCodePoints + overlappingCodePointCount,
+                newNodeCodePointCount - overlappingCodePointCount, probabilityOfNewPtNode,
+                &writingPos)) {
+            return false;
+        }
+    }
+    if (!DynamicPatriciaTrieWritingUtils::writeForwardLinkPositionAndAdvancePosition(mBuffer,
+            NOT_A_DICT_POS /* forwardLinkPos */, &writingPos)) {
+        return false;
+    }
+    // Update original reallocatingPtNode as moved.
+    if (!markNodeAsMovedAndSetPosition(reallocatingPtNode, firstPartOfReallocatedPtNodePos,
+            secondPartOfReallocatedPtNodePos)) {
+        return false;
+    }
+    // Load node info. Information of the 1st part will be fetched.
+    DynamicPatriciaTrieNodeReader nodeReader(mBuffer, mBigramPolicy, mShortcutPolicy);
+    nodeReader.fetchNodeInfoInBufferFromPtNodePos(firstPartOfReallocatedPtNodePos);
+    // Update children position.
+    int childrenPosFieldPos = nodeReader.getChildrenPosFieldPos();
+    if (!DynamicPatriciaTrieWritingUtils::writeChildrenPositionAndAdvancePosition(mBuffer,
+            actualChildrenPos, &childrenPosFieldPos)) {
+        return false;
+    }
+    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) {
+    DynamicPatriciaTrieReadingHelper readingHelper(mBuffer, mBigramPolicy, mShortcutPolicy);
+    readingHelper.initWithPtNodeArrayPos(rootPtNodeArrayPos);
+    DynamicPatriciaTrieGcEventListeners
+            ::TraversePolicyToUpdateUnigramProbabilityAndMarkUselessPtNodesAsDeleted
+                    traversePolicyToUpdateUnigramProbabilityAndMarkUselessPtNodesAsDeleted(
+                            this, mBuffer);
+    if (!readingHelper.traverseAllPtNodesInPostorderDepthFirstManner(
+            &traversePolicyToUpdateUnigramProbabilityAndMarkUselessPtNodesAsDeleted)) {
+        return false;
+    }
+
+    readingHelper.initWithPtNodeArrayPos(rootPtNodeArrayPos);
+    DynamicPatriciaTrieGcEventListeners::TraversePolicyToUpdateBigramProbability
+            traversePolicyToUpdateBigramProbability(mBigramPolicy);
+    if (!readingHelper.traverseAllPtNodesInPostorderDepthFirstManner(
+            &traversePolicyToUpdateBigramProbability)) {
+        return false;
+    }
+
+    // Mapping from positions in mBuffer to positions in bufferToWrite.
+    DictPositionRelocationMap dictPositionRelocationMap;
+    readingHelper.initWithPtNodeArrayPos(rootPtNodeArrayPos);
+    DynamicPatriciaTrieGcEventListeners::TraversePolicyToPlaceAndWriteValidPtNodesToBuffer
+            traversePolicyToPlaceAndWriteValidPtNodesToBuffer(this, bufferToWrite,
+                    &dictPositionRelocationMap);
+    if (!readingHelper.traverseAllPtNodesInPtNodeArrayLevelPreorderDepthFirstManner(
+            &traversePolicyToPlaceAndWriteValidPtNodesToBuffer)) {
+        return false;
+    }
+
+    // Create policy instance for the GCed dictionary.
+    DynamicShortcutListPolicy newDictShortcutPolicy(bufferToWrite);
+    DynamicBigramListPolicy newDictBigramPolicy(bufferToWrite, &newDictShortcutPolicy);
+    // Create reading helper for the GCed dictionary.
+    DynamicPatriciaTrieReadingHelper newDictReadingHelper(bufferToWrite, &newDictBigramPolicy,
+            &newDictShortcutPolicy);
+    newDictReadingHelper.initWithPtNodeArrayPos(rootPtNodeArrayPos);
+    DynamicPatriciaTrieGcEventListeners::TraversePolicyToUpdateAllPositionFields
+            traversePolicyToUpdateAllPositionFields(this, &newDictBigramPolicy, bufferToWrite,
+                    &dictPositionRelocationMap);
+    if (!newDictReadingHelper.traverseAllPtNodesInPtNodeArrayLevelPreorderDepthFirstManner(
+            &traversePolicyToUpdateAllPositionFields)) {
+        return false;
+    }
+    return true;
+}
+
+} // 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
new file mode 100644
index 0000000..028fa60
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_helper.h
@@ -0,0 +1,137 @@
+/*
+ * 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.
+ */
+
+#ifndef LATINIME_DYNAMIC_PATRICIA_TRIE_WRITING_HELPER_H
+#define LATINIME_DYNAMIC_PATRICIA_TRIE_WRITING_HELPER_H
+
+#include <cstdio>
+#include <stdint.h>
+
+#include "defines.h"
+#include "utils/hash_map_compat.h"
+
+namespace latinime {
+
+class BufferWithExtendableBuffer;
+class DynamicBigramListPolicy;
+class DynamicPatriciaTrieNodeReader;
+class DynamicPatriciaTrieReadingHelper;
+class DynamicShortcutListPolicy;
+class HeaderPolicy;
+
+class DynamicPatriciaTrieWritingHelper {
+ public:
+    typedef hash_map_compat<int, int> PtNodeArrayPositionRelocationMap;
+    typedef hash_map_compat<int, int> PtNodePositionRelocationMap;
+    struct DictPositionRelocationMap {
+     public:
+        DictPositionRelocationMap()
+                : mPtNodeArrayPositionRelocationMap(), mPtNodePositionRelocationMap() {}
+
+        PtNodeArrayPositionRelocationMap mPtNodeArrayPositionRelocationMap;
+        PtNodePositionRelocationMap mPtNodePositionRelocationMap;
+
+     private:
+        DISALLOW_COPY_AND_ASSIGN(DictPositionRelocationMap);
+    };
+
+    DynamicPatriciaTrieWritingHelper(BufferWithExtendableBuffer *const buffer,
+            DynamicBigramListPolicy *const bigramPolicy,
+            DynamicShortcutListPolicy *const shortcutPolicy)
+            : mBuffer(buffer), mBigramPolicy(bigramPolicy), mShortcutPolicy(shortcutPolicy) {}
+
+    ~DynamicPatriciaTrieWritingHelper() {}
+
+    // Add a word to the dictionary. If the word already exists, update the probability.
+    bool addUnigramWord(DynamicPatriciaTrieReadingHelper *const readingHelper,
+            const int *const wordCodePoints, const int codePointCount, const int probability);
+
+    // Add a bigram relation from word0Pos to word1Pos.
+    bool addBigramWords(const int word0Pos, const int word1Pos, const int probability);
+
+    // Remove a bigram relation from word0Pos to word1Pos.
+    bool removeBigramWords(const int word0Pos, const int word1Pos);
+
+    void writeToDictFile(const char *const fileName, const HeaderPolicy *const headerPolicy);
+
+    void writeToDictFileWithGC(const int rootPtNodeArrayPos, const char *const fileName,
+            const HeaderPolicy *const headerPolicy);
+
+    // CAVEAT: This method must be called only from inner classes of
+    // DynamicPatriciaTrieGcEventListeners.
+    bool markNodeAsDeleted(const DynamicPatriciaTrieNodeReader *const nodeToUpdate);
+
+    // CAVEAT: This method must be called only from this class or inner classes of
+    // DynamicPatriciaTrieGcEventListeners.
+    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);
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(DynamicPatriciaTrieWritingHelper);
+
+    static const int CHILDREN_POSITION_FIELD_SIZE;
+    static const char *const TEMP_FILE_SUFFIX_FOR_WRITING_DICT_FILE;
+    static const size_t MAX_DICTIONARY_SIZE;
+
+    BufferWithExtendableBuffer *const mBuffer;
+    DynamicBigramListPolicy *const mBigramPolicy;
+    DynamicShortcutListPolicy *const mShortcutPolicy;
+
+    bool markNodeAsMovedAndSetPosition(const DynamicPatriciaTrieNodeReader *const nodeToUpdate,
+            const int movedPos, const int bigramLinkedNodePos);
+
+    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(BufferWithExtendableBuffer *const bufferToWrite,
+            const int parentPos, const int *const codePoints, const int codePointCount,
+            const int probability, int *const writingPos);
+
+    bool createAndInsertNodeIntoPtNodeArray(const int parentPos, const int *const nodeCodePoints,
+            const int nodeCodePointCount, const int probability, int *const forwardLinkFieldPos);
+
+    bool setPtNodeProbability(const DynamicPatriciaTrieNodeReader *const originalNode,
+            const int probability, const int *const codePoints);
+
+    bool createChildrenPtNodeArrayAndAChildPtNode(
+            const DynamicPatriciaTrieNodeReader *const parentNode, const int probability,
+            const int *const codePoints, const int codePointCount);
+
+    bool createNewPtNodeArrayWithAChildPtNode(const int parentPos, const int *const nodeCodePoints,
+            const int nodeCodePointCount, const int probability);
+
+    bool reallocatePtNodeAndAddNewPtNodes(
+            const DynamicPatriciaTrieNodeReader *const reallocatingPtNode,
+            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/dynamic_patricia_trie_writing_utils.cpp b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_utils.cpp
new file mode 100644
index 0000000..5a39837
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_utils.cpp
@@ -0,0 +1,137 @@
+/*
+ * 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.
+ */
+
+#include "suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_utils.h"
+
+#include <cstddef>
+#include <cstdlib>
+#include <stdint.h>
+
+#include "suggest/policyimpl/dictionary/utils/buffer_with_extendable_buffer.h"
+
+namespace latinime {
+
+const size_t DynamicPatriciaTrieWritingUtils::MAX_PTNODE_ARRAY_SIZE_TO_USE_SMALL_SIZE_FIELD = 0x7F;
+const size_t DynamicPatriciaTrieWritingUtils::MAX_PTNODE_ARRAY_SIZE = 0x7FFF;
+const int DynamicPatriciaTrieWritingUtils::SMALL_PTNODE_ARRAY_SIZE_FIELD_SIZE = 1;
+const int DynamicPatriciaTrieWritingUtils::LARGE_PTNODE_ARRAY_SIZE_FIELD_SIZE = 2;
+const int DynamicPatriciaTrieWritingUtils::LARGE_PTNODE_ARRAY_SIZE_FIELD_SIZE_FLAG = 0x8000;
+const int DynamicPatriciaTrieWritingUtils::DICT_OFFSET_FIELD_SIZE = 3;
+const int DynamicPatriciaTrieWritingUtils::MAX_DICT_OFFSET_VALUE = 0x7FFFFF;
+const int DynamicPatriciaTrieWritingUtils::MIN_DICT_OFFSET_VALUE = -0x7FFFFF;
+const int DynamicPatriciaTrieWritingUtils::DICT_OFFSET_NEGATIVE_FLAG = 0x800000;
+const int DynamicPatriciaTrieWritingUtils::PROBABILITY_FIELD_SIZE = 1;
+const int DynamicPatriciaTrieWritingUtils::NODE_FLAG_FIELD_SIZE = 1;
+
+/* static */ bool DynamicPatriciaTrieWritingUtils::writeForwardLinkPositionAndAdvancePosition(
+        BufferWithExtendableBuffer *const buffer, const int forwardLinkPos,
+        int *const forwardLinkFieldPos) {
+    return writeDictOffset(buffer, forwardLinkPos, (*forwardLinkFieldPos), forwardLinkFieldPos);
+}
+
+/* static */ bool DynamicPatriciaTrieWritingUtils::writePtNodeArraySizeAndAdvancePosition(
+        BufferWithExtendableBuffer *const buffer, const size_t arraySize,
+        int *const arraySizeFieldPos) {
+    // Currently, all array size field to be created has LARGE_PTNODE_ARRAY_SIZE_FIELD_SIZE to
+    // simplify updating process.
+    // TODO: Use SMALL_PTNODE_ARRAY_SIZE_FIELD_SIZE for small arrays.
+    /*if (arraySize <= MAX_PTNODE_ARRAY_SIZE_TO_USE_SMALL_SIZE_FIELD) {
+        return buffer->writeUintAndAdvancePosition(arraySize, SMALL_PTNODE_ARRAY_SIZE_FIELD_SIZE,
+                arraySizeFieldPos);
+    } else */
+    if (arraySize <= MAX_PTNODE_ARRAY_SIZE) {
+        uint32_t data = arraySize | LARGE_PTNODE_ARRAY_SIZE_FIELD_SIZE_FLAG;
+        return buffer->writeUintAndAdvancePosition(data, LARGE_PTNODE_ARRAY_SIZE_FIELD_SIZE,
+                arraySizeFieldPos);
+    } else {
+        AKLOGI("PtNode array size cannot be written because arraySize is too large: %zd",
+                arraySize);
+        ASSERT(false);
+        return false;
+    }
+}
+
+/* static */ bool DynamicPatriciaTrieWritingUtils::writeFlagsAndAdvancePosition(
+        BufferWithExtendableBuffer *const buffer,
+        const DynamicPatriciaTrieReadingUtils::NodeFlags nodeFlags, int *const nodeFlagsFieldPos) {
+    return buffer->writeUintAndAdvancePosition(nodeFlags, NODE_FLAG_FIELD_SIZE, nodeFlagsFieldPos);
+}
+
+// Note that parentOffset is offset from node's head position.
+/* static */ bool DynamicPatriciaTrieWritingUtils::writeParentPosOffsetAndAdvancePosition(
+        BufferWithExtendableBuffer *const buffer, const int parentPos, const int basePos,
+        int *const parentPosFieldPos) {
+    return writeDictOffset(buffer, parentPos, basePos, parentPosFieldPos);
+}
+
+/* static */ bool DynamicPatriciaTrieWritingUtils::writeCodePointsAndAdvancePosition(
+        BufferWithExtendableBuffer *const buffer, const int *const codePoints,
+        const int codePointCount, int *const codePointFieldPos) {
+    if (codePointCount <= 0) {
+        AKLOGI("code points cannot be written because codePointCount is invalid: %d",
+                codePointCount);
+        ASSERT(false);
+        return false;
+    }
+    const bool hasMultipleCodePoints = codePointCount > 1;
+    return buffer->writeCodePointsAndAdvancePosition(codePoints, codePointCount,
+            hasMultipleCodePoints, codePointFieldPos);
+}
+
+/* static */ bool DynamicPatriciaTrieWritingUtils::writeProbabilityAndAdvancePosition(
+        BufferWithExtendableBuffer *const buffer, const int probability,
+        int *const probabilityFieldPos) {
+    if (probability < 0 || probability > MAX_PROBABILITY) {
+        AKLOGI("probability cannot be written because the probability is invalid: %d",
+                probability);
+        ASSERT(false);
+        return false;
+    }
+    return buffer->writeUintAndAdvancePosition(probability, PROBABILITY_FIELD_SIZE,
+            probabilityFieldPos);
+}
+
+/* static */ bool DynamicPatriciaTrieWritingUtils::writeChildrenPositionAndAdvancePosition(
+        BufferWithExtendableBuffer *const buffer, const int childrenPosition,
+        int *const childrenPositionFieldPos) {
+    return writeDictOffset(buffer, childrenPosition, (*childrenPositionFieldPos),
+            childrenPositionFieldPos);
+}
+
+/* static */ bool DynamicPatriciaTrieWritingUtils::writeDictOffset(
+        BufferWithExtendableBuffer *const buffer, const int targetPos, const int basePos,
+        int *const offsetFieldPos) {
+    int offset = targetPos - basePos;
+    if (targetPos == NOT_A_DICT_POS) {
+        offset = DynamicPatriciaTrieReadingUtils::DICT_OFFSET_INVALID;
+    } else if (offset == 0) {
+        offset = DynamicPatriciaTrieReadingUtils::DICT_OFFSET_ZERO_OFFSET;
+    }
+    if (offset > MAX_DICT_OFFSET_VALUE || offset < MIN_DICT_OFFSET_VALUE) {
+        AKLOGI("offset cannot be written because the offset is too large or too small: %d",
+                offset);
+        ASSERT(false);
+        return false;
+    }
+    uint32_t data = 0;
+    if (offset >= 0) {
+        data = offset;
+    } else {
+        data = abs(offset) | DICT_OFFSET_NEGATIVE_FLAG;
+    }
+    return buffer->writeUintAndAdvancePosition(data, DICT_OFFSET_FIELD_SIZE, offsetFieldPos);
+}
+}
diff --git a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_utils.h b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_utils.h
new file mode 100644
index 0000000..a37e9fb
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_utils.h
@@ -0,0 +1,74 @@
+/*
+ * 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.
+ */
+
+#ifndef LATINIME_DYNAMIC_PATRICIA_TRIE_WRITING_UTILS_H
+#define LATINIME_DYNAMIC_PATRICIA_TRIE_WRITING_UTILS_H
+
+#include <cstddef>
+
+#include "defines.h"
+#include "suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_utils.h"
+
+namespace latinime {
+
+class BufferWithExtendableBuffer;
+
+class DynamicPatriciaTrieWritingUtils {
+ public:
+    static const int NODE_FLAG_FIELD_SIZE;
+
+    static bool writeForwardLinkPositionAndAdvancePosition(
+            BufferWithExtendableBuffer *const buffer, const int forwardLinkPos,
+            int *const forwardLinkFieldPos);
+
+    static bool writePtNodeArraySizeAndAdvancePosition(BufferWithExtendableBuffer *const buffer,
+            const size_t arraySize, int *const arraySizeFieldPos);
+
+    static bool writeFlagsAndAdvancePosition(BufferWithExtendableBuffer *const buffer,
+            const DynamicPatriciaTrieReadingUtils::NodeFlags nodeFlags,
+            int *const nodeFlagsFieldPos);
+
+    static bool writeParentPosOffsetAndAdvancePosition(BufferWithExtendableBuffer *const buffer,
+            const int parentPosition, const int basePos, int *const parentPosFieldPos);
+
+    static bool writeCodePointsAndAdvancePosition(BufferWithExtendableBuffer *const buffer,
+            const int *const codePoints, const int codePointCount, int *const codePointFieldPos);
+
+    static bool writeProbabilityAndAdvancePosition(BufferWithExtendableBuffer *const buffer,
+            const int probability, int *const probabilityFieldPos);
+
+    static bool writeChildrenPositionAndAdvancePosition(BufferWithExtendableBuffer *const buffer,
+            const int childrenPosition, int *const childrenPositionFieldPos);
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(DynamicPatriciaTrieWritingUtils);
+
+    static const size_t MAX_PTNODE_ARRAY_SIZE_TO_USE_SMALL_SIZE_FIELD;
+    static const size_t MAX_PTNODE_ARRAY_SIZE;
+    static const int SMALL_PTNODE_ARRAY_SIZE_FIELD_SIZE;
+    static const int LARGE_PTNODE_ARRAY_SIZE_FIELD_SIZE;
+    static const int LARGE_PTNODE_ARRAY_SIZE_FIELD_SIZE_FLAG;
+    static const int DICT_OFFSET_FIELD_SIZE;
+    static const int MAX_DICT_OFFSET_VALUE;
+    static const int MIN_DICT_OFFSET_VALUE;
+    static const int DICT_OFFSET_NEGATIVE_FLAG;
+    static const int PROBABILITY_FIELD_SIZE;
+
+    static bool writeDictOffset(BufferWithExtendableBuffer *const buffer, const int targetPos,
+            const int basePos, int *const offsetFieldPos);
+};
+} // namespace latinime
+#endif /* LATINIME_DYNAMIC_PATRICIA_TRIE_WRITING_UTILS_H */
diff --git a/native/jni/src/suggest/policyimpl/dictionary/header/header_policy.cpp b/native/jni/src/suggest/policyimpl/dictionary/header/header_policy.cpp
new file mode 100644
index 0000000..47ace23
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/dictionary/header/header_policy.cpp
@@ -0,0 +1,178 @@
+/*
+ * 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.
+ */
+
+#include "suggest/policyimpl/dictionary/header/header_policy.h"
+
+#include <cstddef>
+#include <cstdio>
+#include <ctime>
+
+namespace latinime {
+
+const char *const HeaderPolicy::MULTIPLE_WORDS_DEMOTION_RATE_KEY = "MULTIPLE_WORDS_DEMOTION_RATE";
+const char *const HeaderPolicy::USES_FORGETTING_CURVE_KEY = "USES_FORGETTING_CURVE";
+const char *const HeaderPolicy::LAST_UPDATED_TIME_KEY = "date";
+const float HeaderPolicy::DEFAULT_MULTIPLE_WORD_COST_MULTIPLIER = 1.0f;
+const float HeaderPolicy::MULTIPLE_WORD_COST_MULTIPLIER_SCALE = 100.0f;
+
+// Used for logging. Question mark is used to indicate that the key is not found.
+void HeaderPolicy::readHeaderValueOrQuestionMark(const char *const key, int *outValue,
+        int outValueSize) const {
+    if (outValueSize <= 0) return;
+    if (outValueSize == 1) {
+        outValue[0] = '\0';
+        return;
+    }
+    std::vector<int> keyCodePointVector;
+    insertCharactersIntoVector(key, &keyCodePointVector);
+    HeaderReadWriteUtils::AttributeMap::const_iterator it = mAttributeMap.find(keyCodePointVector);
+    if (it == mAttributeMap.end()) {
+        // The key was not found.
+        outValue[0] = '?';
+        outValue[1] = '\0';
+        return;
+    }
+    const int terminalIndex = min(static_cast<int>(it->second.size()), outValueSize - 1);
+    for (int i = 0; i < terminalIndex; ++i) {
+        outValue[i] = it->second[i];
+    }
+    outValue[terminalIndex] = '\0';
+}
+
+float HeaderPolicy::readMultipleWordCostMultiplier() const {
+    int attributeValue = 0;
+    if (getAttributeValueAsInt(MULTIPLE_WORDS_DEMOTION_RATE_KEY, &attributeValue)) {
+        if (attributeValue <= 0) {
+            return static_cast<float>(MAX_VALUE_FOR_WEIGHTING);
+        }
+        return MULTIPLE_WORD_COST_MULTIPLIER_SCALE / static_cast<float>(attributeValue);
+    } else {
+        return DEFAULT_MULTIPLE_WORD_COST_MULTIPLIER;
+    }
+}
+
+bool HeaderPolicy::readUsesForgettingCurveFlag() const {
+    int attributeValue = 0;
+    if (getAttributeValueAsInt(USES_FORGETTING_CURVE_KEY, &attributeValue)) {
+        return attributeValue != 0;
+    } else {
+        return false;
+    }
+}
+
+// Returns S_INT_MIN when the key is not found or the value is invalid.
+int HeaderPolicy::readLastUpdatedTime() const {
+    int attributeValue = 0;
+    if (getAttributeValueAsInt(LAST_UPDATED_TIME_KEY, &attributeValue)) {
+        return attributeValue;
+    } else {
+        return S_INT_MIN;
+    }
+}
+
+// Returns whether the key is found or not and stores the found value into outValue.
+bool HeaderPolicy::getAttributeValueAsInt(const char *const key, int *const outValue) const {
+    std::vector<int> keyVector;
+    insertCharactersIntoVector(key, &keyVector);
+    HeaderReadWriteUtils::AttributeMap::const_iterator it = mAttributeMap.find(keyVector);
+    if (it == mAttributeMap.end()) {
+        // The key was not found.
+        return false;
+    }
+    *outValue = parseIntAttributeValue(&(it->second));
+    return true;
+}
+
+bool HeaderPolicy::writeHeaderToBuffer(BufferWithExtendableBuffer *const bufferToWrite,
+        const bool updatesLastUpdatedTime) const {
+    int writingPos = 0;
+    if (!HeaderReadWriteUtils::writeDictionaryVersion(bufferToWrite, mDictFormatVersion,
+            &writingPos)) {
+        return false;
+    }
+    if (!HeaderReadWriteUtils::writeDictionaryFlags(bufferToWrite, mDictionaryFlags,
+            &writingPos)) {
+        return false;
+    }
+    // Temporarily writes a dummy header size.
+    int headerSizeFieldPos = writingPos;
+    if (!HeaderReadWriteUtils::writeDictionaryHeaderSize(bufferToWrite, 0 /* size */,
+            &writingPos)) {
+        return false;
+    }
+    if (updatesLastUpdatedTime) {
+        // Set current time as a last updated time.
+        HeaderReadWriteUtils::AttributeMap attributeMapTowrite(mAttributeMap);
+        std::vector<int> updatedTimekey;
+        insertCharactersIntoVector(LAST_UPDATED_TIME_KEY, &updatedTimekey);
+        const time_t currentTime = time(NULL);
+        std::vector<int> updatedTimeValue;
+        char charBuf[LARGEST_INT_DIGIT_COUNT + 1];
+        snprintf(charBuf, LARGEST_INT_DIGIT_COUNT + 1, "%ld", currentTime);
+        insertCharactersIntoVector(charBuf, &updatedTimeValue);
+        attributeMapTowrite[updatedTimekey] = updatedTimeValue;
+        if (!HeaderReadWriteUtils::writeHeaderAttributes(bufferToWrite, &attributeMapTowrite,
+                &writingPos)) {
+            return false;
+        }
+    } else {
+        if (!HeaderReadWriteUtils::writeHeaderAttributes(bufferToWrite, &mAttributeMap,
+                &writingPos)) {
+            return false;
+        }
+    }
+    // Writes an actual header size.
+    if (!HeaderReadWriteUtils::writeDictionaryHeaderSize(bufferToWrite, writingPos,
+            &headerSizeFieldPos)) {
+        return false;
+    }
+    return true;
+}
+
+/* static */ HeaderReadWriteUtils::AttributeMap
+        HeaderPolicy::createAttributeMapAndReadAllAttributes(const uint8_t *const dictBuf) {
+    HeaderReadWriteUtils::AttributeMap attributeMap;
+    HeaderReadWriteUtils::fetchAllHeaderAttributes(dictBuf, &attributeMap);
+    return attributeMap;
+}
+
+/* static */ int HeaderPolicy::parseIntAttributeValue(
+        const std::vector<int> *const attributeValue) {
+    int value = 0;
+    bool isNegative = false;
+    for (size_t i = 0; i < attributeValue->size(); ++i) {
+        if (i == 0 && attributeValue->at(i) == '-') {
+            isNegative = true;
+        } else {
+            if (!isdigit(attributeValue->at(i))) {
+                // If not a number, return S_INT_MIN
+                return S_INT_MIN;
+            }
+            value *= 10;
+            value += attributeValue->at(i) - '0';
+        }
+    }
+    return isNegative ? -value : value;
+}
+
+/* static */ void HeaderPolicy::insertCharactersIntoVector(const char *const characters,
+        std::vector<int> *const vector) {
+    for (int i = 0; characters[i]; ++i) {
+        vector->push_back(characters[i]);
+    }
+}
+
+} // namespace latinime
diff --git a/native/jni/src/suggest/policyimpl/dictionary/header/header_policy.h b/native/jni/src/suggest/policyimpl/dictionary/header/header_policy.h
new file mode 100644
index 0000000..6b396f3
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/dictionary/header/header_policy.h
@@ -0,0 +1,113 @@
+/*
+ * 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.
+ */
+
+#ifndef LATINIME_HEADER_POLICY_H
+#define LATINIME_HEADER_POLICY_H
+
+#include <cctype>
+#include <stdint.h>
+
+#include "defines.h"
+#include "suggest/core/policy/dictionary_header_structure_policy.h"
+#include "suggest/policyimpl/dictionary/header/header_read_write_utils.h"
+#include "suggest/policyimpl/dictionary/utils/format_utils.h"
+
+namespace latinime {
+
+class HeaderPolicy : public DictionaryHeaderStructurePolicy {
+ public:
+    explicit HeaderPolicy(const uint8_t *const dictBuf, const int dictSize)
+            : mDictBuf(dictBuf),
+              mDictFormatVersion(FormatUtils::detectFormatVersion(dictBuf, dictSize)),
+              mDictionaryFlags(HeaderReadWriteUtils::getFlags(dictBuf)),
+              mSize(HeaderReadWriteUtils::getHeaderSize(dictBuf)),
+              mAttributeMap(createAttributeMapAndReadAllAttributes(mDictBuf)),
+              mMultiWordCostMultiplier(readMultipleWordCostMultiplier()),
+              mUsesForgettingCurve(readUsesForgettingCurveFlag()),
+              mLastUpdatedTime(readLastUpdatedTime()) {}
+
+    ~HeaderPolicy() {}
+
+    AK_FORCE_INLINE int getSize() const {
+        return mSize;
+    }
+
+    AK_FORCE_INLINE bool supportsDynamicUpdate() const {
+        return HeaderReadWriteUtils::supportsDynamicUpdate(mDictionaryFlags);
+    }
+
+    AK_FORCE_INLINE bool requiresGermanUmlautProcessing() const {
+        return HeaderReadWriteUtils::requiresGermanUmlautProcessing(mDictionaryFlags);
+    }
+
+    AK_FORCE_INLINE bool requiresFrenchLigatureProcessing() const {
+        return HeaderReadWriteUtils::requiresFrenchLigatureProcessing(mDictionaryFlags);
+    }
+
+    AK_FORCE_INLINE float getMultiWordCostMultiplier() const {
+        return mMultiWordCostMultiplier;
+    }
+
+    AK_FORCE_INLINE bool usesForgettingCurve() const {
+        return mUsesForgettingCurve;
+    }
+
+    AK_FORCE_INLINE int getLastUpdatedTime() const {
+        return mLastUpdatedTime;
+    }
+
+    void readHeaderValueOrQuestionMark(const char *const key,
+            int *outValue, int outValueSize) const;
+
+    bool writeHeaderToBuffer(BufferWithExtendableBuffer *const bufferToWrite,
+            const bool updatesLastUpdatedTime) const;
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(HeaderPolicy);
+
+    static const char *const MULTIPLE_WORDS_DEMOTION_RATE_KEY;
+    static const char *const USES_FORGETTING_CURVE_KEY;
+    static const char *const LAST_UPDATED_TIME_KEY;
+    static const float DEFAULT_MULTIPLE_WORD_COST_MULTIPLIER;
+    static const float MULTIPLE_WORD_COST_MULTIPLIER_SCALE;
+
+    const uint8_t *const mDictBuf;
+    const FormatUtils::FORMAT_VERSION mDictFormatVersion;
+    const HeaderReadWriteUtils::DictionaryFlags mDictionaryFlags;
+    const int mSize;
+    HeaderReadWriteUtils::AttributeMap mAttributeMap;
+    const float mMultiWordCostMultiplier;
+    const bool mUsesForgettingCurve;
+    const int mLastUpdatedTime;
+
+    float readMultipleWordCostMultiplier() const;
+
+    bool readUsesForgettingCurveFlag() const;
+
+    int readLastUpdatedTime() const;
+
+    bool getAttributeValueAsInt(const char *const key, int *const outValue) const;
+
+    static HeaderReadWriteUtils::AttributeMap createAttributeMapAndReadAllAttributes(
+            const uint8_t *const dictBuf);
+
+    static int parseIntAttributeValue(const std::vector<int> *const attributeValue);
+
+    static void insertCharactersIntoVector(
+            const char *const characters, std::vector<int> *const vector);
+};
+} // namespace latinime
+#endif /* LATINIME_HEADER_POLICY_H */
diff --git a/native/jni/src/suggest/policyimpl/dictionary/header/header_read_write_utils.cpp b/native/jni/src/suggest/policyimpl/dictionary/header/header_read_write_utils.cpp
new file mode 100644
index 0000000..80fe886
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/dictionary/header/header_read_write_utils.cpp
@@ -0,0 +1,131 @@
+/*
+ * 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.
+ */
+
+#include "suggest/policyimpl/dictionary/header/header_read_write_utils.h"
+
+#include <vector>
+
+#include "defines.h"
+#include "suggest/policyimpl/dictionary/utils/buffer_with_extendable_buffer.h"
+#include "suggest/policyimpl/dictionary/utils/byte_array_utils.h"
+
+namespace latinime {
+
+const int HeaderReadWriteUtils::MAX_ATTRIBUTE_KEY_LENGTH = 256;
+const int HeaderReadWriteUtils::MAX_ATTRIBUTE_VALUE_LENGTH = 256;
+
+const int HeaderReadWriteUtils::HEADER_MAGIC_NUMBER_SIZE = 4;
+const int HeaderReadWriteUtils::HEADER_DICTIONARY_VERSION_SIZE = 2;
+const int HeaderReadWriteUtils::HEADER_FLAG_SIZE = 2;
+const int HeaderReadWriteUtils::HEADER_SIZE_FIELD_SIZE = 4;
+
+const HeaderReadWriteUtils::DictionaryFlags HeaderReadWriteUtils::NO_FLAGS = 0;
+// Flags for special processing
+// Those *must* match the flags in makedict (FormatSpec#*_PROCESSING_FLAG) or
+// something very bad (like, the apocalypse) will happen. Please update both at the same time.
+const HeaderReadWriteUtils::DictionaryFlags
+        HeaderReadWriteUtils::GERMAN_UMLAUT_PROCESSING_FLAG = 0x1;
+const HeaderReadWriteUtils::DictionaryFlags
+        HeaderReadWriteUtils::SUPPORTS_DYNAMIC_UPDATE_FLAG = 0x2;
+const HeaderReadWriteUtils::DictionaryFlags
+        HeaderReadWriteUtils::FRENCH_LIGATURE_PROCESSING_FLAG = 0x4;
+
+/* static */ int HeaderReadWriteUtils::getHeaderSize(const uint8_t *const dictBuf) {
+    // See the format of the header in the comment in
+    // BinaryDictionaryFormatUtils::detectFormatVersion()
+    return ByteArrayUtils::readUint32(dictBuf, HEADER_MAGIC_NUMBER_SIZE
+            + HEADER_DICTIONARY_VERSION_SIZE + HEADER_FLAG_SIZE);
+}
+
+/* static */ HeaderReadWriteUtils::DictionaryFlags
+        HeaderReadWriteUtils::getFlags(const uint8_t *const dictBuf) {
+    return ByteArrayUtils::readUint16(dictBuf,
+            HEADER_MAGIC_NUMBER_SIZE + HEADER_DICTIONARY_VERSION_SIZE);
+}
+
+/* static */ void HeaderReadWriteUtils::fetchAllHeaderAttributes(const uint8_t *const dictBuf,
+        AttributeMap *const headerAttributes) {
+    const int headerSize = getHeaderSize(dictBuf);
+    int pos = getHeaderOptionsPosition();
+    if (pos == NOT_A_DICT_POS) {
+        // The header doesn't have header options.
+        return;
+    }
+    int keyBuffer[MAX_ATTRIBUTE_KEY_LENGTH];
+    int valueBuffer[MAX_ATTRIBUTE_VALUE_LENGTH];
+    while (pos < headerSize) {
+        const int keyLength = ByteArrayUtils::readStringAndAdvancePosition(dictBuf,
+                MAX_ATTRIBUTE_KEY_LENGTH, keyBuffer, &pos);
+        std::vector<int> key;
+        key.insert(key.end(), keyBuffer, keyBuffer + keyLength);
+        const int valueLength = ByteArrayUtils::readStringAndAdvancePosition(dictBuf,
+                MAX_ATTRIBUTE_VALUE_LENGTH, valueBuffer, &pos);
+        std::vector<int> value;
+        value.insert(value.end(), valueBuffer, valueBuffer + valueLength);
+        headerAttributes->insert(AttributeMap::value_type(key, value));
+    }
+}
+
+/* static */ bool HeaderReadWriteUtils::writeDictionaryVersion(
+        BufferWithExtendableBuffer *const buffer, const FormatUtils::FORMAT_VERSION version,
+        int *const writingPos) {
+    if (!buffer->writeUintAndAdvancePosition(FormatUtils::MAGIC_NUMBER, HEADER_MAGIC_NUMBER_SIZE,
+            writingPos)) {
+        return false;
+    }
+    switch (version) {
+        case FormatUtils::VERSION_2:
+            // Version 2 dictionary writing is not supported.
+            return false;
+        case FormatUtils::VERSION_3:
+            return buffer->writeUintAndAdvancePosition(3 /* data */,
+                    HEADER_DICTIONARY_VERSION_SIZE, writingPos);
+        default:
+            return false;
+    }
+}
+
+/* static */ bool HeaderReadWriteUtils::writeDictionaryFlags(
+        BufferWithExtendableBuffer *const buffer, const DictionaryFlags flags,
+        int *const writingPos) {
+    return buffer->writeUintAndAdvancePosition(flags, HEADER_FLAG_SIZE, writingPos);
+}
+
+/* static */ bool HeaderReadWriteUtils::writeDictionaryHeaderSize(
+        BufferWithExtendableBuffer *const buffer, const int size, int *const writingPos) {
+    return buffer->writeUintAndAdvancePosition(size, HEADER_SIZE_FIELD_SIZE, writingPos);
+}
+
+/* static */ bool HeaderReadWriteUtils::writeHeaderAttributes(
+        BufferWithExtendableBuffer *const buffer, const AttributeMap *const headerAttributes,
+        int *const writingPos) {
+    for (AttributeMap::const_iterator it = headerAttributes->begin();
+            it != headerAttributes->end(); ++it) {
+        // Write a key.
+        if (!buffer->writeCodePointsAndAdvancePosition(&(it->first.at(0)), it->first.size(),
+                true /* writesTerminator */, writingPos)) {
+            return false;
+        }
+        // Write a value.
+        if (!buffer->writeCodePointsAndAdvancePosition(&(it->second.at(0)), it->second.size(),
+                true /* writesTerminator */, writingPos)) {
+            return false;
+        }
+    }
+    return true;
+}
+
+} // namespace latinime
diff --git a/native/jni/src/suggest/policyimpl/dictionary/header/header_read_write_utils.h b/native/jni/src/suggest/policyimpl/dictionary/header/header_read_write_utils.h
new file mode 100644
index 0000000..6cce733
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/dictionary/header/header_read_write_utils.h
@@ -0,0 +1,93 @@
+/*
+ * 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.
+ */
+
+#ifndef LATINIME_HEADER_READ_WRITE_UTILS_H
+#define LATINIME_HEADER_READ_WRITE_UTILS_H
+
+#include <map>
+#include <stdint.h>
+#include <vector>
+
+#include "defines.h"
+#include "suggest/policyimpl/dictionary/utils/format_utils.h"
+
+namespace latinime {
+
+class BufferWithExtendableBuffer;
+
+class HeaderReadWriteUtils {
+ public:
+    typedef uint16_t DictionaryFlags;
+    typedef std::map<std::vector<int>, std::vector<int> > AttributeMap;
+
+    static int getHeaderSize(const uint8_t *const dictBuf);
+
+    static DictionaryFlags getFlags(const uint8_t *const dictBuf);
+
+    static AK_FORCE_INLINE bool supportsDynamicUpdate(const DictionaryFlags flags) {
+        return (flags & SUPPORTS_DYNAMIC_UPDATE_FLAG) != 0;
+    }
+
+    static AK_FORCE_INLINE bool requiresGermanUmlautProcessing(const DictionaryFlags flags) {
+        return (flags & GERMAN_UMLAUT_PROCESSING_FLAG) != 0;
+    }
+
+    static AK_FORCE_INLINE bool requiresFrenchLigatureProcessing(const DictionaryFlags flags) {
+        return (flags & FRENCH_LIGATURE_PROCESSING_FLAG) != 0;
+    }
+
+    static AK_FORCE_INLINE int getHeaderOptionsPosition() {
+        return HEADER_MAGIC_NUMBER_SIZE + HEADER_DICTIONARY_VERSION_SIZE + HEADER_FLAG_SIZE
+                + HEADER_SIZE_FIELD_SIZE;
+    }
+
+    static void fetchAllHeaderAttributes(const uint8_t *const dictBuf,
+            AttributeMap *const headerAttributes);
+
+    static bool writeDictionaryVersion(BufferWithExtendableBuffer *const buffer,
+            const FormatUtils::FORMAT_VERSION version, int *const writingPos);
+
+    static bool writeDictionaryFlags(BufferWithExtendableBuffer *const buffer,
+            const DictionaryFlags flags, int *const writingPos);
+
+    static bool writeDictionaryHeaderSize(BufferWithExtendableBuffer *const buffer,
+            const int size, int *const writingPos);
+
+    static bool writeHeaderAttributes(BufferWithExtendableBuffer *const buffer,
+            const AttributeMap *const headerAttributes, int *const writingPos);
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(HeaderReadWriteUtils);
+
+    static const int MAX_ATTRIBUTE_KEY_LENGTH;
+    static const int MAX_ATTRIBUTE_VALUE_LENGTH;
+
+    static const int HEADER_MAGIC_NUMBER_SIZE;
+    static const int HEADER_DICTIONARY_VERSION_SIZE;
+    static const int HEADER_FLAG_SIZE;
+    static const int HEADER_SIZE_FIELD_SIZE;
+
+    static const DictionaryFlags NO_FLAGS;
+    // Flags for special processing
+    // Those *must* match the flags in makedict (FormatSpec#*_PROCESSING_FLAGS) or
+    // something very bad (like, the apocalypse) will happen. Please update both at the same time.
+    static const DictionaryFlags GERMAN_UMLAUT_PROCESSING_FLAG;
+    static const DictionaryFlags SUPPORTS_DYNAMIC_UPDATE_FLAG;
+    static const DictionaryFlags FRENCH_LIGATURE_PROCESSING_FLAG;
+    static const DictionaryFlags CONTAINS_BIGRAMS_FLAG;
+};
+}
+#endif /* LATINIME_HEADER_READ_WRITE_UTILS_H */
diff --git a/native/jni/src/suggest/policyimpl/dictionary/patricia_trie_policy.cpp b/native/jni/src/suggest/policyimpl/dictionary/patricia_trie_policy.cpp
index 097f7c8..5269795 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/patricia_trie_policy.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/patricia_trie_policy.cpp
@@ -20,55 +20,312 @@
 #include "defines.h"
 #include "suggest/core/dicnode/dic_node.h"
 #include "suggest/core/dicnode/dic_node_vector.h"
-#include "suggest/core/dictionary/binary_dictionary_info.h"
-#include "suggest/core/dictionary/binary_dictionary_terminal_attributes_reading_utils.h"
-#include "suggest/policyimpl/dictionary/binary_format.h"
 #include "suggest/policyimpl/dictionary/patricia_trie_reading_utils.h"
+#include "suggest/policyimpl/dictionary/utils/probability_utils.h"
 
 namespace latinime {
 
-const PatriciaTriePolicy PatriciaTriePolicy::sInstance;
-
 void PatriciaTriePolicy::createAndGetAllChildNodes(const DicNode *const dicNode,
-        const BinaryDictionaryInfo *const binaryDictionaryInfo,
-        const NodeFilter *const nodeFilter, DicNodeVector *const childDicNodes) const {
+        DicNodeVector *const childDicNodes) const {
     if (!dicNode->hasChildren()) {
         return;
     }
     int nextPos = dicNode->getChildrenPos();
-    const int childCount = PatriciaTrieReadingUtils::getGroupCountAndAdvancePosition(
-            binaryDictionaryInfo->getDictRoot(), &nextPos);
+    const int childCount = PatriciaTrieReadingUtils::getPtNodeArraySizeAndAdvancePosition(
+            mDictRoot, &nextPos);
     for (int i = 0; i < childCount; i++) {
-        nextPos = createAndGetLeavingChildNode(dicNode, nextPos, binaryDictionaryInfo,
-                nodeFilter, childDicNodes);
+        nextPos = createAndGetLeavingChildNode(dicNode, nextPos, childDicNodes);
     }
 }
 
+// This retrieves code points and the probability of the word by its terminal position.
+// Due to the fact that words are ordered in the dictionary in a strict breadth-first order,
+// it is possible to check for this with advantageous complexity. For each node, we search
+// for PtNodes with children and compare the children position with the position we look for.
+// When we shoot the position we look for, it means the word we look for is in the children
+// of the previous PtNode. The only tricky part is the fact that if we arrive at the end of a
+// PtNode array with the last PtNode's children position still less than what we are searching for,
+// we must descend the last PtNode's children (for example, if the word we are searching for starts
+// with a z, it's the last PtNode of the root array, so all children addresses will be smaller
+// than the position we look for, and we have to descend the z node).
+/* Parameters :
+ * ptNodePos: the byte position of the terminal PtNode of the word we are searching for (this is
+ *   what is stored as the "bigram position" in each bigram)
+ * outCodePoints: an array to write the found word, with MAX_WORD_LENGTH size.
+ * outUnigramProbability: a pointer to an int to write the probability into.
+ * Return value : the code point count, of 0 if the word was not found.
+ */
+// TODO: Split this function to be more readable
 int PatriciaTriePolicy::getCodePointsAndProbabilityAndReturnCodePointCount(
-        const BinaryDictionaryInfo *const binaryDictionaryInfo,
-        const int nodePos, const int maxCodePointCount, int *const outCodePoints,
+        const int ptNodePos, const int maxCodePointCount, int *const outCodePoints,
         int *const outUnigramProbability) const {
-    return BinaryFormat::getCodePointsAndProbabilityAndReturnCodePointCount(
-            binaryDictionaryInfo->getDictRoot(), nodePos,
-            maxCodePointCount, outCodePoints, outUnigramProbability);
+    int pos = getRootPosition();
+    int wordPos = 0;
+    // One iteration of the outer loop iterates through PtNode arrays. As stated above, we will
+    // only traverse nodes that are actually a part of the terminal we are searching, so each time
+    // we enter this loop we are one depth level further than last time.
+    // The only reason we count nodes is because we want to reduce the probability of infinite
+    // looping in case there is a bug. Since we know there is an upper bound to the depth we are
+    // supposed to traverse, it does not hurt to count iterations.
+    for (int loopCount = maxCodePointCount; loopCount > 0; --loopCount) {
+        int lastCandidatePtNodePos = 0;
+        // Let's loop through PtNodes in this PtNode array searching for either the terminal
+        // or one of its ascendants.
+        for (int ptNodeCount = PatriciaTrieReadingUtils::getPtNodeArraySizeAndAdvancePosition(
+                mDictRoot, &pos); ptNodeCount > 0; --ptNodeCount) {
+            const int startPos = pos;
+            const PatriciaTrieReadingUtils::NodeFlags flags =
+                    PatriciaTrieReadingUtils::getFlagsAndAdvancePosition(mDictRoot, &pos);
+            const int character = PatriciaTrieReadingUtils::getCodePointAndAdvancePosition(
+                    mDictRoot, &pos);
+            if (ptNodePos == startPos) {
+                // We found the position. Copy the rest of the code points in the buffer and return
+                // the length.
+                outCodePoints[wordPos] = character;
+                if (PatriciaTrieReadingUtils::hasMultipleChars(flags)) {
+                    int nextChar = PatriciaTrieReadingUtils::getCodePointAndAdvancePosition(
+                            mDictRoot, &pos);
+                    // We count code points in order to avoid infinite loops if the file is broken
+                    // or if there is some other bug
+                    int charCount = maxCodePointCount;
+                    while (NOT_A_CODE_POINT != nextChar && --charCount > 0) {
+                        outCodePoints[++wordPos] = nextChar;
+                        nextChar = PatriciaTrieReadingUtils::getCodePointAndAdvancePosition(
+                                mDictRoot, &pos);
+                    }
+                }
+                *outUnigramProbability =
+                        PatriciaTrieReadingUtils::readProbabilityAndAdvancePosition(mDictRoot,
+                                &pos);
+                return ++wordPos;
+            }
+            // We need to skip past this PtNode, so skip any remaining code points after the
+            // first and possibly the probability.
+            if (PatriciaTrieReadingUtils::hasMultipleChars(flags)) {
+                PatriciaTrieReadingUtils::skipCharacters(mDictRoot, flags, MAX_WORD_LENGTH, &pos);
+            }
+            if (PatriciaTrieReadingUtils::isTerminal(flags)) {
+                PatriciaTrieReadingUtils::readProbabilityAndAdvancePosition(mDictRoot, &pos);
+            }
+            // The fact that this PtNode has children is very important. Since we already know
+            // that this PtNode does not match, if it has no children we know it is irrelevant
+            // to what we are searching for.
+            const bool hasChildren = PatriciaTrieReadingUtils::hasChildrenInFlags(flags);
+            // We will write in `found' whether we have passed the children position we are
+            // searching for. For example if we search for "beer", the children of b are less
+            // than the address we are searching for and the children of c are greater. When we
+            // come here for c, we realize this is too big, and that we should descend b.
+            bool found;
+            if (hasChildren) {
+                int currentPos = pos;
+                // Here comes the tricky part. First, read the children position.
+                const int childrenPos = PatriciaTrieReadingUtils
+                        ::readChildrenPositionAndAdvancePosition(mDictRoot, flags, &currentPos);
+                if (childrenPos > ptNodePos) {
+                    // If the children pos is greater than the position, it means the previous
+                    // PtNode, which position is stored in lastCandidatePtNodePos, was the right
+                    // one.
+                    found = true;
+                } else if (1 >= ptNodeCount) {
+                    // However if we are on the LAST PtNode of this array, and we have NOT shot the
+                    // position we should descend THIS node. So we trick the lastCandidatePtNodePos
+                    // so that we will descend this PtNode, not the previous one.
+                    lastCandidatePtNodePos = startPos;
+                    found = true;
+                } else {
+                    // Else, we should continue looking.
+                    found = false;
+                }
+            } else {
+                // Even if we don't have children here, we could still be on the last PtNode of /
+                // this array. If this is the case, we should descend the last PtNode that had
+                // children, and their position is already in lastCandidatePtNodePos.
+                found = (1 >= ptNodeCount);
+            }
+
+            if (found) {
+                // Okay, we found the PtNode we should descend. Its position is in
+                // the lastCandidatePtNodePos variable, so we just re-read it.
+                if (0 != lastCandidatePtNodePos) {
+                    const PatriciaTrieReadingUtils::NodeFlags lastFlags =
+                            PatriciaTrieReadingUtils::getFlagsAndAdvancePosition(
+                                    mDictRoot, &lastCandidatePtNodePos);
+                    const int lastChar = PatriciaTrieReadingUtils::getCodePointAndAdvancePosition(
+                            mDictRoot, &lastCandidatePtNodePos);
+                    // We copy all the characters in this PtNode to the buffer
+                    outCodePoints[wordPos] = lastChar;
+                    if (PatriciaTrieReadingUtils::hasMultipleChars(lastFlags)) {
+                        int nextChar = PatriciaTrieReadingUtils::getCodePointAndAdvancePosition(
+                                mDictRoot, &lastCandidatePtNodePos);
+                        int charCount = maxCodePointCount;
+                        while (-1 != nextChar && --charCount > 0) {
+                            outCodePoints[++wordPos] = nextChar;
+                            nextChar = PatriciaTrieReadingUtils::getCodePointAndAdvancePosition(
+                                    mDictRoot, &lastCandidatePtNodePos);
+                        }
+                    }
+                    ++wordPos;
+                    // Now we only need to branch to the children address. Skip the probability if
+                    // it's there, read pos, and break to resume the search at pos.
+                    if (PatriciaTrieReadingUtils::isTerminal(lastFlags)) {
+                        PatriciaTrieReadingUtils::readProbabilityAndAdvancePosition(mDictRoot,
+                                &lastCandidatePtNodePos);
+                    }
+                    pos = PatriciaTrieReadingUtils::readChildrenPositionAndAdvancePosition(
+                            mDictRoot, lastFlags, &lastCandidatePtNodePos);
+                    break;
+                } else {
+                    // Here is a little tricky part: we come here if we found out that all children
+                    // addresses in this PtNode are bigger than the address we are searching for.
+                    // Should we conclude the word is not in the dictionary? No! It could still be
+                    // one of the remaining PtNodes in this array, so we have to keep looking in
+                    // this array until we find it (or we realize it's not there either, in which
+                    // case it's actually not in the dictionary). Pass the end of this PtNode,
+                    // ready to start the next one.
+                    if (PatriciaTrieReadingUtils::hasChildrenInFlags(flags)) {
+                        PatriciaTrieReadingUtils::readChildrenPositionAndAdvancePosition(
+                                mDictRoot, flags, &pos);
+                    }
+                    if (PatriciaTrieReadingUtils::hasShortcutTargets(flags)) {
+                        mShortcutListPolicy.skipAllShortcuts(&pos);
+                    }
+                    if (PatriciaTrieReadingUtils::hasBigrams(flags)) {
+                        mBigramListPolicy.skipAllBigrams(&pos);
+                    }
+                }
+            } else {
+                // If we did not find it, we should record the last children address for the next
+                // iteration.
+                if (hasChildren) lastCandidatePtNodePos = startPos;
+                // Now skip the end of this PtNode (children pos and the attributes if any) so that
+                // our pos is after the end of this PtNode, at the start of the next one.
+                if (PatriciaTrieReadingUtils::hasChildrenInFlags(flags)) {
+                    PatriciaTrieReadingUtils::readChildrenPositionAndAdvancePosition(
+                            mDictRoot, flags, &pos);
+                }
+                if (PatriciaTrieReadingUtils::hasShortcutTargets(flags)) {
+                    mShortcutListPolicy.skipAllShortcuts(&pos);
+                }
+                if (PatriciaTrieReadingUtils::hasBigrams(flags)) {
+                    mBigramListPolicy.skipAllBigrams(&pos);
+                }
+            }
+
+        }
+    }
+    // If we have looked through all the PtNodes and found no match, the ptNodePos is
+    // not the position of a terminal in this dictionary.
+    return 0;
 }
 
-int PatriciaTriePolicy::getTerminalNodePositionOfWord(
-        const BinaryDictionaryInfo *const binaryDictionaryInfo, const int *const inWord,
+// This function gets the position of the terminal node of the exact matching word in the
+// dictionary. If no match is found, it returns NOT_A_DICT_POS.
+int PatriciaTriePolicy::getTerminalNodePositionOfWord(const int *const inWord,
         const int length, const bool forceLowerCaseSearch) const {
-    return BinaryFormat::getTerminalPosition(binaryDictionaryInfo->getDictRoot(), inWord,
-            length, forceLowerCaseSearch);
+    int pos = getRootPosition();
+    int wordPos = 0;
+
+    while (true) {
+        // If we already traversed the tree further than the word is long, there means
+        // there was no match (or we would have found it).
+        if (wordPos >= length) return NOT_A_DICT_POS;
+        int ptNodeCount = PatriciaTrieReadingUtils::getPtNodeArraySizeAndAdvancePosition(mDictRoot,
+                &pos);
+        const int wChar = forceLowerCaseSearch
+                ? CharUtils::toLowerCase(inWord[wordPos]) : inWord[wordPos];
+        while (true) {
+            // If there are no more PtNodes in this array, it means we could not
+            // find a matching character for this depth, therefore there is no match.
+            if (0 >= ptNodeCount) return NOT_A_DICT_POS;
+            const int ptNodePos = pos;
+            const PatriciaTrieReadingUtils::NodeFlags flags =
+                    PatriciaTrieReadingUtils::getFlagsAndAdvancePosition(mDictRoot, &pos);
+            int character = PatriciaTrieReadingUtils::getCodePointAndAdvancePosition(mDictRoot,
+                    &pos);
+            if (character == wChar) {
+                // This is the correct PtNode. Only one PtNode may start with the same char within
+                // a PtNode array, so either we found our match in this array, or there is
+                // no match and we can return NOT_A_DICT_POS. So we will check all the
+                // characters in this PtNode indeed does match.
+                if (PatriciaTrieReadingUtils::hasMultipleChars(flags)) {
+                    character = PatriciaTrieReadingUtils::getCodePointAndAdvancePosition(mDictRoot,
+                            &pos);
+                    while (NOT_A_CODE_POINT != character) {
+                        ++wordPos;
+                        // If we shoot the length of the word we search for, or if we find a single
+                        // character that does not match, as explained above, it means the word is
+                        // not in the dictionary (by virtue of this PtNode being the only one to
+                        // match the word on the first character, but not matching the whole word).
+                        if (wordPos >= length) return NOT_A_DICT_POS;
+                        if (inWord[wordPos] != character) return NOT_A_DICT_POS;
+                        character = PatriciaTrieReadingUtils::getCodePointAndAdvancePosition(
+                                mDictRoot, &pos);
+                    }
+                }
+                // If we come here we know that so far, we do match. Either we are on a terminal
+                // and we match the length, in which case we found it, or we traverse children.
+                // If we don't match the length AND don't have children, then a word in the
+                // dictionary fully matches a prefix of the searched word but not the full word.
+                ++wordPos;
+                if (PatriciaTrieReadingUtils::isTerminal(flags)) {
+                    if (wordPos == length) {
+                        return ptNodePos;
+                    }
+                    PatriciaTrieReadingUtils::readProbabilityAndAdvancePosition(mDictRoot, &pos);
+                }
+                if (!PatriciaTrieReadingUtils::hasChildrenInFlags(flags)) {
+                    return NOT_A_DICT_POS;
+                }
+                // We have children and we are still shorter than the word we are searching for, so
+                // we need to traverse children. Put the pointer on the children position, and
+                // break
+                pos = PatriciaTrieReadingUtils::readChildrenPositionAndAdvancePosition(mDictRoot,
+                        flags, &pos);
+                break;
+            } else {
+                // This PtNode does not match, so skip the remaining part and go to the next.
+                if (PatriciaTrieReadingUtils::hasMultipleChars(flags)) {
+                    PatriciaTrieReadingUtils::skipCharacters(mDictRoot, flags, MAX_WORD_LENGTH,
+                            &pos);
+                }
+                if (PatriciaTrieReadingUtils::isTerminal(flags)) {
+                    PatriciaTrieReadingUtils::readProbabilityAndAdvancePosition(mDictRoot, &pos);
+                }
+                if (PatriciaTrieReadingUtils::hasChildrenInFlags(flags)) {
+                    PatriciaTrieReadingUtils::readChildrenPositionAndAdvancePosition(mDictRoot,
+                            flags, &pos);
+                }
+                if (PatriciaTrieReadingUtils::hasShortcutTargets(flags)) {
+                    mShortcutListPolicy.skipAllShortcuts(&pos);
+                }
+                if (PatriciaTrieReadingUtils::hasBigrams(flags)) {
+                    mBigramListPolicy.skipAllBigrams(&pos);
+                }
+            }
+            --ptNodeCount;
+        }
+    }
 }
 
-int PatriciaTriePolicy::getUnigramProbability(
-        const BinaryDictionaryInfo *const binaryDictionaryInfo, const int nodePos) const {
-    if (nodePos == NOT_A_VALID_WORD_POS) {
+int PatriciaTriePolicy::getProbability(const int unigramProbability,
+        const int bigramProbability) const {
+    if (unigramProbability == NOT_A_PROBABILITY) {
+        return NOT_A_PROBABILITY;
+    } else if (bigramProbability == NOT_A_PROBABILITY) {
+        return ProbabilityUtils::backoff(unigramProbability);
+    } else {
+        return ProbabilityUtils::computeProbabilityForBigram(unigramProbability,
+                bigramProbability);
+    }
+}
+
+int PatriciaTriePolicy::getUnigramProbabilityOfPtNode(const int ptNodePos) const {
+    if (ptNodePos == NOT_A_DICT_POS) {
         return NOT_A_PROBABILITY;
     }
-    const uint8_t *const dictRoot = binaryDictionaryInfo->getDictRoot();
-    int pos = nodePos;
+    int pos = ptNodePos;
     const PatriciaTrieReadingUtils::NodeFlags flags =
-            PatriciaTrieReadingUtils::getFlagsAndAdvancePosition(dictRoot, &pos);
+            PatriciaTrieReadingUtils::getFlagsAndAdvancePosition(mDictRoot, &pos);
     if (!PatriciaTrieReadingUtils::isTerminal(flags)) {
         return NOT_A_PROBABILITY;
     }
@@ -79,90 +336,80 @@
         // for shortcuts).
         return NOT_A_PROBABILITY;
     }
-    PatriciaTrieReadingUtils::skipCharacters(dictRoot, flags, MAX_WORD_LENGTH, &pos);
-    return PatriciaTrieReadingUtils::readProbabilityAndAdvancePosition(dictRoot, &pos);
+    PatriciaTrieReadingUtils::skipCharacters(mDictRoot, flags, MAX_WORD_LENGTH, &pos);
+    return getProbability(PatriciaTrieReadingUtils::readProbabilityAndAdvancePosition(
+            mDictRoot, &pos), NOT_A_PROBABILITY);
 }
 
-int PatriciaTriePolicy::getShortcutPositionOfNode(
-        const BinaryDictionaryInfo *const binaryDictionaryInfo,
-        const int nodePos) const {
-    if (nodePos == NOT_A_VALID_WORD_POS) {
+int PatriciaTriePolicy::getShortcutPositionOfPtNode(const int ptNodePos) const {
+    if (ptNodePos == NOT_A_DICT_POS) {
         return NOT_A_DICT_POS;
     }
-    const uint8_t *const dictRoot = binaryDictionaryInfo->getDictRoot();
-    int pos = nodePos;
+    int pos = ptNodePos;
     const PatriciaTrieReadingUtils::NodeFlags flags =
-            PatriciaTrieReadingUtils::getFlagsAndAdvancePosition(dictRoot, &pos);
+            PatriciaTrieReadingUtils::getFlagsAndAdvancePosition(mDictRoot, &pos);
     if (!PatriciaTrieReadingUtils::hasShortcutTargets(flags)) {
         return NOT_A_DICT_POS;
     }
-    PatriciaTrieReadingUtils::skipCharacters(dictRoot, flags, MAX_WORD_LENGTH, &pos);
+    PatriciaTrieReadingUtils::skipCharacters(mDictRoot, flags, MAX_WORD_LENGTH, &pos);
     if (PatriciaTrieReadingUtils::isTerminal(flags)) {
-        PatriciaTrieReadingUtils::readProbabilityAndAdvancePosition(dictRoot, &pos);
+        PatriciaTrieReadingUtils::readProbabilityAndAdvancePosition(mDictRoot, &pos);
     }
     if (PatriciaTrieReadingUtils::hasChildrenInFlags(flags)) {
-        PatriciaTrieReadingUtils::readChildrenPositionAndAdvancePosition(dictRoot, flags, &pos);
+        PatriciaTrieReadingUtils::readChildrenPositionAndAdvancePosition(mDictRoot, flags, &pos);
     }
     return pos;
 }
 
-int PatriciaTriePolicy::getBigramsPositionOfNode(
-        const BinaryDictionaryInfo *const binaryDictionaryInfo,
-        const int nodePos) const {
-    if (nodePos == NOT_A_VALID_WORD_POS) {
+int PatriciaTriePolicy::getBigramsPositionOfPtNode(const int ptNodePos) const {
+    if (ptNodePos == NOT_A_DICT_POS) {
         return NOT_A_DICT_POS;
     }
-    const uint8_t *const dictRoot = binaryDictionaryInfo->getDictRoot();
-    int pos = nodePos;
+    int pos = ptNodePos;
     const PatriciaTrieReadingUtils::NodeFlags flags =
-            PatriciaTrieReadingUtils::getFlagsAndAdvancePosition(dictRoot, &pos);
+            PatriciaTrieReadingUtils::getFlagsAndAdvancePosition(mDictRoot, &pos);
     if (!PatriciaTrieReadingUtils::hasBigrams(flags)) {
         return NOT_A_DICT_POS;
     }
-    PatriciaTrieReadingUtils::skipCharacters(dictRoot, flags, MAX_WORD_LENGTH, &pos);
+    PatriciaTrieReadingUtils::skipCharacters(mDictRoot, flags, MAX_WORD_LENGTH, &pos);
     if (PatriciaTrieReadingUtils::isTerminal(flags)) {
-        PatriciaTrieReadingUtils::readProbabilityAndAdvancePosition(dictRoot, &pos);
+        PatriciaTrieReadingUtils::readProbabilityAndAdvancePosition(mDictRoot, &pos);
     }
     if (PatriciaTrieReadingUtils::hasChildrenInFlags(flags)) {
-        PatriciaTrieReadingUtils::readChildrenPositionAndAdvancePosition(dictRoot, flags, &pos);
+        PatriciaTrieReadingUtils::readChildrenPositionAndAdvancePosition(mDictRoot, flags, &pos);
     }
     if (PatriciaTrieReadingUtils::hasShortcutTargets(flags)) {
-        BinaryDictionaryTerminalAttributesReadingUtils::skipShortcuts(binaryDictionaryInfo, &pos);
+        mShortcutListPolicy.skipAllShortcuts(&pos);;
     }
     return pos;
 }
 
 int PatriciaTriePolicy::createAndGetLeavingChildNode(const DicNode *const dicNode,
-        const int nodePos, const BinaryDictionaryInfo *const binaryDictionaryInfo,
-        const NodeFilter *const childrenFilter, DicNodeVector *childDicNodes) const {
-    const uint8_t *const dictRoot = binaryDictionaryInfo->getDictRoot();
-    int pos = nodePos;
+        const int ptNodePos, DicNodeVector *childDicNodes) const {
+    int pos = ptNodePos;
     const PatriciaTrieReadingUtils::NodeFlags flags =
-            PatriciaTrieReadingUtils::getFlagsAndAdvancePosition(dictRoot, &pos);
+            PatriciaTrieReadingUtils::getFlagsAndAdvancePosition(mDictRoot, &pos);
     int mergedNodeCodePoints[MAX_WORD_LENGTH];
     const int mergedNodeCodePointCount = PatriciaTrieReadingUtils::getCharsAndAdvancePosition(
-            dictRoot, flags, MAX_WORD_LENGTH, mergedNodeCodePoints, &pos);
+            mDictRoot, flags, MAX_WORD_LENGTH, mergedNodeCodePoints, &pos);
     const int probability = (PatriciaTrieReadingUtils::isTerminal(flags))?
-            PatriciaTrieReadingUtils::readProbabilityAndAdvancePosition(dictRoot, &pos)
+            PatriciaTrieReadingUtils::readProbabilityAndAdvancePosition(mDictRoot, &pos)
                     : NOT_A_PROBABILITY;
     const int childrenPos = PatriciaTrieReadingUtils::hasChildrenInFlags(flags) ?
             PatriciaTrieReadingUtils::readChildrenPositionAndAdvancePosition(
-                    dictRoot, flags, &pos) : NOT_A_DICT_POS;
+                    mDictRoot, flags, &pos) : NOT_A_DICT_POS;
     if (PatriciaTrieReadingUtils::hasShortcutTargets(flags)) {
-        BinaryDictionaryTerminalAttributesReadingUtils::skipShortcuts(binaryDictionaryInfo, &pos);
+        getShortcutsStructurePolicy()->skipAllShortcuts(&pos);
     }
     if (PatriciaTrieReadingUtils::hasBigrams(flags)) {
-        BinaryDictionaryTerminalAttributesReadingUtils::skipExistingBigrams(
-                binaryDictionaryInfo, &pos);
+        getBigramsStructurePolicy()->skipAllBigrams(&pos);
     }
-    if (!childrenFilter->isFilteredOut(mergedNodeCodePoints[0])) {
-        childDicNodes->pushLeavingChild(dicNode, nodePos, childrenPos, probability,
-                PatriciaTrieReadingUtils::isTerminal(flags),
-                PatriciaTrieReadingUtils::hasChildrenInFlags(flags),
-                PatriciaTrieReadingUtils::isBlacklisted(flags) ||
-                        PatriciaTrieReadingUtils::isNotAWord(flags),
-                mergedNodeCodePointCount, mergedNodeCodePoints);
-    }
+    childDicNodes->pushLeavingChild(dicNode, ptNodePos, childrenPos, probability,
+            PatriciaTrieReadingUtils::isTerminal(flags),
+            PatriciaTrieReadingUtils::hasChildrenInFlags(flags),
+            PatriciaTrieReadingUtils::isBlacklisted(flags) ||
+                    PatriciaTrieReadingUtils::isNotAWord(flags),
+            mergedNodeCodePointCount, mergedNodeCodePoints);
     return pos;
 }
 
diff --git a/native/jni/src/suggest/policyimpl/dictionary/patricia_trie_policy.h b/native/jni/src/suggest/policyimpl/dictionary/patricia_trie_policy.h
index 71f256e..19155f9 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/patricia_trie_policy.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/patricia_trie_policy.h
@@ -17,15 +17,29 @@
 #ifndef LATINIME_PATRICIA_TRIE_POLICY_H
 #define LATINIME_PATRICIA_TRIE_POLICY_H
 
+#include <stdint.h>
+
 #include "defines.h"
-#include "suggest/core/policy/dictionary_structure_policy.h"
+#include "suggest/core/policy/dictionary_structure_with_buffer_policy.h"
+#include "suggest/policyimpl/dictionary/bigram/bigram_list_policy.h"
+#include "suggest/policyimpl/dictionary/header/header_policy.h"
+#include "suggest/policyimpl/dictionary/shortcut/shortcut_list_policy.h"
+#include "suggest/policyimpl/dictionary/utils/mmapped_buffer.h"
 
 namespace latinime {
 
-class PatriciaTriePolicy : public DictionaryStructurePolicy {
+class DicNode;
+class DicNodeVector;
+
+class PatriciaTriePolicy : public DictionaryStructureWithBufferPolicy {
  public:
-    static AK_FORCE_INLINE const PatriciaTriePolicy *getInstance() {
-        return &sInstance;
+    PatriciaTriePolicy(const MmappedBuffer *const buffer)
+            : mBuffer(buffer), mHeaderPolicy(mBuffer->getBuffer(), buffer->getBufferSize()),
+              mDictRoot(mBuffer->getBuffer() + mHeaderPolicy.getSize()),
+              mBigramListPolicy(mDictRoot), mShortcutListPolicy(mDictRoot) {}
+
+    ~PatriciaTriePolicy() {
+        delete mBuffer;
     }
 
     AK_FORCE_INLINE int getRootPosition() const {
@@ -33,37 +47,82 @@
     }
 
     void createAndGetAllChildNodes(const DicNode *const dicNode,
-            const BinaryDictionaryInfo *const binaryDictionaryInfo,
-            const NodeFilter *const nodeFilter, DicNodeVector *const childDicNodes) const;
+            DicNodeVector *const childDicNodes) const;
 
     int getCodePointsAndProbabilityAndReturnCodePointCount(
-            const BinaryDictionaryInfo *const binaryDictionaryInfo,
             const int terminalNodePos, const int maxCodePointCount, int *const outCodePoints,
             int *const outUnigramProbability) const;
 
-    int getTerminalNodePositionOfWord(
-            const BinaryDictionaryInfo *const binaryDictionaryInfo, const int *const inWord,
+    int getTerminalNodePositionOfWord(const int *const inWord,
             const int length, const bool forceLowerCaseSearch) const;
 
-    int getUnigramProbability(const BinaryDictionaryInfo *const binaryDictionaryInfo,
-            const int nodePos) const;
+    int getProbability(const int unigramProbability, const int bigramProbability) const;
 
-    int getShortcutPositionOfNode(const BinaryDictionaryInfo *const binaryDictionaryInfo,
-            const int nodePos) const;
+    int getUnigramProbabilityOfPtNode(const int ptNodePos) const;
 
-    int getBigramsPositionOfNode(const BinaryDictionaryInfo *const binaryDictionaryInfo,
-            const int nodePos) const;
+    int getShortcutPositionOfPtNode(const int ptNodePos) const;
+
+    int getBigramsPositionOfPtNode(const int ptNodePos) const;
+
+    const DictionaryHeaderStructurePolicy *getHeaderStructurePolicy() const {
+        return &mHeaderPolicy;
+    }
+
+    const DictionaryBigramsStructurePolicy *getBigramsStructurePolicy() const {
+        return &mBigramListPolicy;
+    }
+
+    const DictionaryShortcutsStructurePolicy *getShortcutsStructurePolicy() const {
+        return &mShortcutListPolicy;
+    }
+
+    bool addUnigramWord(const int *const word, const int length, const int probability) {
+        // This method should not be called for non-updatable dictionary.
+        AKLOGI("Warning: addUnigramWord() is called for non-updatable dictionary.");
+        return false;
+    }
+
+    bool addBigramWords(const int *const word0, const int length0, const int *const word1,
+            const int length1, const int probability) {
+        // This method should not be called for non-updatable dictionary.
+        AKLOGI("Warning: addBigramWords() is called for non-updatable dictionary.");
+        return false;
+    }
+
+    bool removeBigramWords(const int *const word0, const int length0, const int *const word1,
+            const int length1) {
+        // This method should not be called for non-updatable dictionary.
+        AKLOGI("Warning: removeBigramWords() is called for non-updatable dictionary.");
+        return false;
+    }
+
+    void flush(const char *const filePath) {
+        // This method should not be called for non-updatable dictionary.
+        AKLOGI("Warning: flush() is called for non-updatable dictionary.");
+    }
+
+    void flushWithGC(const char *const filePath) {
+        // This method should not be called for non-updatable dictionary.
+        AKLOGI("Warning: flushWithGC() is called for non-updatable dictionary.");
+    }
+
+    bool needsToRunGC() const {
+        // This method should not be called for non-updatable dictionary.
+        AKLOGI("Warning: needsToRunGC() is called for non-updatable dictionary.");
+        return false;
+    }
 
  private:
-    DISALLOW_COPY_AND_ASSIGN(PatriciaTriePolicy);
-    static const PatriciaTriePolicy sInstance;
+    DISALLOW_IMPLICIT_CONSTRUCTORS(PatriciaTriePolicy);
 
-    PatriciaTriePolicy() {}
-    ~PatriciaTriePolicy() {}
+    const MmappedBuffer *const mBuffer;
+    const HeaderPolicy mHeaderPolicy;
+    const uint8_t *const mDictRoot;
+    const BigramListPolicy mBigramListPolicy;
+    const ShortcutListPolicy mShortcutListPolicy;
 
-    int createAndGetLeavingChildNode(const DicNode *const dicNode, const int nodePos,
-            const BinaryDictionaryInfo *const binaryDictionaryInfo,
-            const NodeFilter *const nodeFilter, DicNodeVector *const childDicNodes) const;
+    int createAndGetLeavingChildNode(const DicNode *const dicNode, const int ptNodePos,
+            DicNodeVector *const childDicNodes) const;
 };
 } // namespace latinime
 #endif // LATINIME_PATRICIA_TRIE_POLICY_H
diff --git a/native/jni/src/suggest/policyimpl/dictionary/patricia_trie_reading_utils.cpp b/native/jni/src/suggest/policyimpl/dictionary/patricia_trie_reading_utils.cpp
index 89e981d..1316b42 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/patricia_trie_reading_utils.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/patricia_trie_reading_utils.cpp
@@ -17,21 +17,21 @@
 #include "suggest/policyimpl/dictionary/patricia_trie_reading_utils.h"
 
 #include "defines.h"
-#include "suggest/core/dictionary/byte_array_utils.h"
+#include "suggest/policyimpl/dictionary/utils/byte_array_utils.h"
 
 namespace latinime {
 
 typedef PatriciaTrieReadingUtils PtReadingUtils;
 
-const PtReadingUtils::NodeFlags PtReadingUtils::MASK_GROUP_ADDRESS_TYPE = 0xC0;
-const PtReadingUtils::NodeFlags PtReadingUtils::FLAG_GROUP_ADDRESS_TYPE_NOADDRESS = 0x00;
-const PtReadingUtils::NodeFlags PtReadingUtils::FLAG_GROUP_ADDRESS_TYPE_ONEBYTE = 0x40;
-const PtReadingUtils::NodeFlags PtReadingUtils::FLAG_GROUP_ADDRESS_TYPE_TWOBYTES = 0x80;
-const PtReadingUtils::NodeFlags PtReadingUtils::FLAG_GROUP_ADDRESS_TYPE_THREEBYTES = 0xC0;
+const PtReadingUtils::NodeFlags PtReadingUtils::MASK_CHILDREN_POSITION_TYPE = 0xC0;
+const PtReadingUtils::NodeFlags PtReadingUtils::FLAG_CHILDREN_POSITION_TYPE_NOPOSITION = 0x00;
+const PtReadingUtils::NodeFlags PtReadingUtils::FLAG_CHILDREN_POSITION_TYPE_ONEBYTE = 0x40;
+const PtReadingUtils::NodeFlags PtReadingUtils::FLAG_CHILDREN_POSITION_TYPE_TWOBYTES = 0x80;
+const PtReadingUtils::NodeFlags PtReadingUtils::FLAG_CHILDREN_POSITION_TYPE_THREEBYTES = 0xC0;
 
 // Flag for single/multiple char group
 const PtReadingUtils::NodeFlags PtReadingUtils::FLAG_HAS_MULTIPLE_CHARS = 0x20;
-// Flag for terminal groups
+// Flag for terminal PtNodes
 const PtReadingUtils::NodeFlags PtReadingUtils::FLAG_IS_TERMINAL = 0x10;
 // Flag for shortcut targets presence
 const PtReadingUtils::NodeFlags PtReadingUtils::FLAG_HAS_SHORTCUT_TARGETS = 0x08;
@@ -42,18 +42,75 @@
 // Flag for blacklist
 const PtReadingUtils::NodeFlags PtReadingUtils::FLAG_IS_BLACKLISTED = 0x01;
 
+/* static */ int PtReadingUtils::getPtNodeArraySizeAndAdvancePosition(
+        const uint8_t *const buffer, int *const pos) {
+    const uint8_t firstByte = ByteArrayUtils::readUint8AndAdvancePosition(buffer, pos);
+    if (firstByte < 0x80) {
+        return firstByte;
+    } else {
+        return ((firstByte & 0x7F) << 8) ^ ByteArrayUtils::readUint8AndAdvancePosition(
+                buffer, pos);
+    }
+}
+
+/* static */ PtReadingUtils::NodeFlags PtReadingUtils::getFlagsAndAdvancePosition(
+        const uint8_t *const buffer, int *const pos) {
+    return ByteArrayUtils::readUint8AndAdvancePosition(buffer, pos);
+}
+
+/* static */ int PtReadingUtils::getCodePointAndAdvancePosition(const uint8_t *const buffer,
+        int *const pos) {
+    return ByteArrayUtils::readCodePointAndAdvancePosition(buffer, pos);
+}
+
+// Returns the number of read characters.
+/* static */ int PtReadingUtils::getCharsAndAdvancePosition(const uint8_t *const buffer,
+        const NodeFlags flags, const int maxLength, int *const outBuffer, int *const pos) {
+    int length = 0;
+    if (hasMultipleChars(flags)) {
+        length = ByteArrayUtils::readStringAndAdvancePosition(buffer, maxLength, outBuffer,
+                pos);
+    } else {
+        if (maxLength > 0) {
+            outBuffer[0] = getCodePointAndAdvancePosition(buffer, pos);
+            length = 1;
+        }
+    }
+    return length;
+}
+
+// Returns the number of skipped characters.
+/* static */ int PtReadingUtils::skipCharacters(const uint8_t *const buffer, const NodeFlags flags,
+        const int maxLength, int *const pos) {
+    if (hasMultipleChars(flags)) {
+        return ByteArrayUtils::advancePositionToBehindString(buffer, maxLength, pos);
+    } else {
+        if (maxLength > 0) {
+            getCodePointAndAdvancePosition(buffer, pos);
+            return 1;
+        } else {
+            return 0;
+        }
+    }
+}
+
+/* static */ int PtReadingUtils::readProbabilityAndAdvancePosition(const uint8_t *const buffer,
+        int *const pos) {
+    return ByteArrayUtils::readUint8AndAdvancePosition(buffer, pos);
+}
+
 /* static */ int PtReadingUtils::readChildrenPositionAndAdvancePosition(
         const uint8_t *const buffer, const NodeFlags flags, int *const pos) {
     const int base = *pos;
     int offset = 0;
-    switch (MASK_GROUP_ADDRESS_TYPE & flags) {
-        case FLAG_GROUP_ADDRESS_TYPE_ONEBYTE:
+    switch (MASK_CHILDREN_POSITION_TYPE & flags) {
+        case FLAG_CHILDREN_POSITION_TYPE_ONEBYTE:
             offset = ByteArrayUtils::readUint8AndAdvancePosition(buffer, pos);
             break;
-        case FLAG_GROUP_ADDRESS_TYPE_TWOBYTES:
+        case FLAG_CHILDREN_POSITION_TYPE_TWOBYTES:
             offset = ByteArrayUtils::readUint16AndAdvancePosition(buffer, pos);
             break;
-        case FLAG_GROUP_ADDRESS_TYPE_THREEBYTES:
+        case FLAG_CHILDREN_POSITION_TYPE_THREEBYTES:
             offset = ByteArrayUtils::readUint24AndAdvancePosition(buffer, pos);
             break;
         default:
diff --git a/native/jni/src/suggest/policyimpl/dictionary/patricia_trie_reading_utils.h b/native/jni/src/suggest/policyimpl/dictionary/patricia_trie_reading_utils.h
index 002c3f1..8420ee9 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/patricia_trie_reading_utils.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/patricia_trie_reading_utils.h
@@ -20,7 +20,6 @@
 #include <stdint.h>
 
 #include "defines.h"
-#include "suggest/core/dictionary/byte_array_utils.h"
 
 namespace latinime {
 
@@ -28,62 +27,21 @@
  public:
     typedef uint8_t NodeFlags;
 
-    static AK_FORCE_INLINE int getGroupCountAndAdvancePosition(
-            const uint8_t *const buffer, int *const pos) {
-        const uint8_t firstByte = ByteArrayUtils::readUint8AndAdvancePosition(buffer, pos);
-        if (firstByte < 0x80) {
-            return firstByte;
-        } else {
-            return ((firstByte & 0x7F) << 8) ^ ByteArrayUtils::readUint8AndAdvancePosition(
-                    buffer, pos);
-        }
-    }
+    static int getPtNodeArraySizeAndAdvancePosition(const uint8_t *const buffer, int *const pos);
 
-    static AK_FORCE_INLINE NodeFlags getFlagsAndAdvancePosition(const uint8_t *const buffer,
-            int *const pos) {
-        return ByteArrayUtils::readUint8AndAdvancePosition(buffer, pos);
-    }
+    static NodeFlags getFlagsAndAdvancePosition(const uint8_t *const buffer, int *const pos);
 
-    static AK_FORCE_INLINE int getCodePointAndAdvancePosition(const uint8_t *const buffer,
-            int *const pos) {
-        return ByteArrayUtils::readCodePointAndAdvancePosition(buffer, pos);
-    }
+    static int getCodePointAndAdvancePosition(const uint8_t *const buffer, int *const pos);
 
     // Returns the number of read characters.
-    static AK_FORCE_INLINE int getCharsAndAdvancePosition(const uint8_t *const buffer,
-            const NodeFlags flags, const int maxLength, int *const outBuffer, int *const pos) {
-        int length = 0;
-        if (hasMultipleChars(flags)) {
-            length = ByteArrayUtils::readStringAndAdvancePosition(buffer, maxLength, outBuffer,
-                    pos);
-        } else {
-            if (maxLength > 0) {
-                outBuffer[0] = getCodePointAndAdvancePosition(buffer, pos);
-                length = 1;
-            }
-        }
-        return length;
-    }
+    static int getCharsAndAdvancePosition(const uint8_t *const buffer, const NodeFlags flags,
+            const int maxLength, int *const outBuffer, int *const pos);
 
     // Returns the number of skipped characters.
-    static AK_FORCE_INLINE int skipCharacters(const uint8_t *const buffer, const NodeFlags flags,
-            const int maxLength, int *const pos) {
-        if (hasMultipleChars(flags)) {
-            return ByteArrayUtils::advancePositionToBehindString(buffer, maxLength, pos);
-        } else {
-            if (maxLength > 0) {
-                getCodePointAndAdvancePosition(buffer, pos);
-                return 1;
-            } else {
-                return 0;
-            }
-        }
-    }
+    static int skipCharacters(const uint8_t *const buffer, const NodeFlags flags,
+            const int maxLength, int *const pos);
 
-    static AK_FORCE_INLINE int readProbabilityAndAdvancePosition(const uint8_t *const buffer,
-            int *const pos) {
-        return ByteArrayUtils::readUint8AndAdvancePosition(buffer, pos);
-    }
+    static int readProbabilityAndAdvancePosition(const uint8_t *const buffer, int *const pos);
 
     static int readChildrenPositionAndAdvancePosition(const uint8_t *const buffer,
             const NodeFlags flags, int *const pos);
@@ -116,17 +74,40 @@
     }
 
     static AK_FORCE_INLINE bool hasChildrenInFlags(const NodeFlags flags) {
-        return FLAG_GROUP_ADDRESS_TYPE_NOADDRESS != (MASK_GROUP_ADDRESS_TYPE & flags);
+        return FLAG_CHILDREN_POSITION_TYPE_NOPOSITION != (MASK_CHILDREN_POSITION_TYPE & flags);
+    }
+
+    static AK_FORCE_INLINE NodeFlags createAndGetFlags(const bool isBlacklisted,
+            const bool isNotAWord, const bool isTerminal, const bool hasShortcutTargets,
+            const bool hasBigrams, const bool hasMultipleChars,
+            const int childrenPositionFieldSize) {
+        NodeFlags nodeFlags = 0;
+        nodeFlags = isBlacklisted ? (nodeFlags | FLAG_IS_BLACKLISTED) : nodeFlags;
+        nodeFlags = isNotAWord ? (nodeFlags | FLAG_IS_NOT_A_WORD) : nodeFlags;
+        nodeFlags = isTerminal ? (nodeFlags | FLAG_IS_TERMINAL) : nodeFlags;
+        nodeFlags = hasShortcutTargets ? (nodeFlags | FLAG_HAS_SHORTCUT_TARGETS) : nodeFlags;
+        nodeFlags = hasBigrams ? (nodeFlags | FLAG_HAS_BIGRAMS) : nodeFlags;
+        nodeFlags = hasMultipleChars ? (nodeFlags | FLAG_HAS_MULTIPLE_CHARS) : nodeFlags;
+        if (childrenPositionFieldSize == 1) {
+            nodeFlags |= FLAG_CHILDREN_POSITION_TYPE_ONEBYTE;
+        } else if (childrenPositionFieldSize == 2) {
+            nodeFlags |= FLAG_CHILDREN_POSITION_TYPE_TWOBYTES;
+        } else if (childrenPositionFieldSize == 3) {
+            nodeFlags |= FLAG_CHILDREN_POSITION_TYPE_THREEBYTES;
+        } else {
+            nodeFlags |= FLAG_CHILDREN_POSITION_TYPE_NOPOSITION;
+        }
+        return nodeFlags;
     }
 
  private:
     DISALLOW_IMPLICIT_CONSTRUCTORS(PatriciaTrieReadingUtils);
 
-    static const NodeFlags MASK_GROUP_ADDRESS_TYPE;
-    static const NodeFlags FLAG_GROUP_ADDRESS_TYPE_NOADDRESS;
-    static const NodeFlags FLAG_GROUP_ADDRESS_TYPE_ONEBYTE;
-    static const NodeFlags FLAG_GROUP_ADDRESS_TYPE_TWOBYTES;
-    static const NodeFlags FLAG_GROUP_ADDRESS_TYPE_THREEBYTES;
+    static const NodeFlags MASK_CHILDREN_POSITION_TYPE;
+    static const NodeFlags FLAG_CHILDREN_POSITION_TYPE_NOPOSITION;
+    static const NodeFlags FLAG_CHILDREN_POSITION_TYPE_ONEBYTE;
+    static const NodeFlags FLAG_CHILDREN_POSITION_TYPE_TWOBYTES;
+    static const NodeFlags FLAG_CHILDREN_POSITION_TYPE_THREEBYTES;
 
     static const NodeFlags FLAG_HAS_MULTIPLE_CHARS;
     static const NodeFlags FLAG_IS_TERMINAL;
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
new file mode 100644
index 0000000..bd3211f
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/dictionary/shortcut/dynamic_shortcut_list_policy.h
@@ -0,0 +1,123 @@
+/*
+ * 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.
+ */
+
+#ifndef LATINIME_DYNAMIC_SHORTCUT_LIST_POLICY_H
+#define LATINIME_DYNAMIC_SHORTCUT_LIST_POLICY_H
+
+#include <stdint.h>
+
+#include "defines.h"
+#include "suggest/core/policy/dictionary_shortcuts_structure_policy.h"
+#include "suggest/policyimpl/dictionary/shortcut/shortcut_list_reading_utils.h"
+#include "suggest/policyimpl/dictionary/utils/buffer_with_extendable_buffer.h"
+
+namespace latinime {
+
+/*
+ * This is a dynamic version of ShortcutListPolicy and supports an additional buffer.
+ */
+class DynamicShortcutListPolicy : public DictionaryShortcutsStructurePolicy {
+ public:
+    explicit DynamicShortcutListPolicy(const BufferWithExtendableBuffer *const buffer)
+            : mBuffer(buffer) {}
+
+    ~DynamicShortcutListPolicy() {}
+
+    int getStartPos(const int pos) const {
+        if (pos == NOT_A_DICT_POS) {
+            return NOT_A_DICT_POS;
+        }
+        return pos + ShortcutListReadingUtils::getShortcutListSizeFieldSize();
+    }
+
+    void getNextShortcut(const int maxCodePointCount, int *const outCodePoint,
+            int *const outCodePointCount, bool *const outIsWhitelist, bool *const outHasNext,
+            int *const pos) const {
+        const bool usesAdditionalBuffer = mBuffer->isInAdditionalBuffer(*pos);
+        const uint8_t *const buffer = mBuffer->getBuffer(usesAdditionalBuffer);
+        if (usesAdditionalBuffer) {
+            *pos -= mBuffer->getOriginalBufferSize();
+        }
+        const ShortcutListReadingUtils::ShortcutFlags flags =
+                ShortcutListReadingUtils::getFlagsAndForwardPointer(buffer, pos);
+        if (outHasNext) {
+            *outHasNext = ShortcutListReadingUtils::hasNext(flags);
+        }
+        if (outIsWhitelist) {
+            *outIsWhitelist = ShortcutListReadingUtils::isWhitelist(flags);
+        }
+        if (outCodePoint) {
+            *outCodePointCount = ShortcutListReadingUtils::readShortcutTarget(
+                    buffer, maxCodePointCount, outCodePoint, pos);
+        }
+        if (usesAdditionalBuffer) {
+            *pos += mBuffer->getOriginalBufferSize();
+        }
+    }
+
+    void skipAllShortcuts(int *const pos) const {
+        const bool usesAdditionalBuffer = mBuffer->isInAdditionalBuffer(*pos);
+        const uint8_t *const buffer = mBuffer->getBuffer(usesAdditionalBuffer);
+        if (usesAdditionalBuffer) {
+            *pos -= mBuffer->getOriginalBufferSize();
+        }
+        const int shortcutListSize = ShortcutListReadingUtils
+                ::getShortcutListSizeAndForwardPointer(buffer, pos);
+        *pos += shortcutListSize;
+        if (usesAdditionalBuffer) {
+            *pos += mBuffer->getOriginalBufferSize();
+        }
+    }
+
+    // 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);
+        if (usesAdditionalBuffer) {
+            *fromPos -= mBuffer->getOriginalBufferSize();
+        }
+        const int shortcutListSize = ShortcutListReadingUtils
+                ::getShortcutListSizeAndForwardPointer(mBuffer->getBuffer(usesAdditionalBuffer),
+                        fromPos);
+        // Copy shortcut list size.
+        if (!bufferToWrite->writeUintAndAdvancePosition(
+                shortcutListSize + ShortcutListReadingUtils::getShortcutListSizeFieldSize(),
+                ShortcutListReadingUtils::getShortcutListSizeFieldSize(), toPos)) {
+            return false;
+        }
+        // Copy shortcut list.
+        for (int i = 0; i < shortcutListSize; ++i) {
+            const uint8_t data = ByteArrayUtils::readUint8AndAdvancePosition(
+                    mBuffer->getBuffer(usesAdditionalBuffer), fromPos);
+            if (!bufferToWrite->writeUintAndAdvancePosition(data, 1 /* size */, toPos)) {
+                return false;
+            }
+        }
+        if (usesAdditionalBuffer) {
+            *fromPos += mBuffer->getOriginalBufferSize();
+        }
+        return true;
+    }
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(DynamicShortcutListPolicy);
+
+    const BufferWithExtendableBuffer *const mBuffer;
+};
+} // namespace latinime
+#endif // LATINIME_DYNAMIC_SHORTCUT_LIST_POLICY_H
diff --git a/native/jni/src/suggest/policyimpl/dictionary/shortcut/shortcut_list_policy.h b/native/jni/src/suggest/policyimpl/dictionary/shortcut/shortcut_list_policy.h
new file mode 100644
index 0000000..d73f739
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/dictionary/shortcut/shortcut_list_policy.h
@@ -0,0 +1,73 @@
+/*
+ * 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.
+ */
+
+#ifndef LATINIME_SHORTCUT_LIST_POLICY_H
+#define LATINIME_SHORTCUT_LIST_POLICY_H
+
+#include <stdint.h>
+
+#include "defines.h"
+#include "suggest/core/policy/dictionary_shortcuts_structure_policy.h"
+#include "suggest/policyimpl/dictionary/shortcut/shortcut_list_reading_utils.h"
+
+namespace latinime {
+
+class ShortcutListPolicy : public DictionaryShortcutsStructurePolicy {
+ public:
+    explicit ShortcutListPolicy(const uint8_t *const shortcutBuf)
+            : mShortcutsBuf(shortcutBuf) {}
+
+    ~ShortcutListPolicy() {}
+
+    int getStartPos(const int pos) const {
+        if (pos == NOT_A_DICT_POS) {
+            return NOT_A_DICT_POS;
+        }
+        int listPos = pos;
+        ShortcutListReadingUtils::getShortcutListSizeAndForwardPointer(mShortcutsBuf, &listPos);
+        return listPos;
+    }
+
+    void getNextShortcut(const int maxCodePointCount, int *const outCodePoint,
+            int *const outCodePointCount, bool *const outIsWhitelist, bool *const outHasNext,
+            int *const pos) const {
+        const ShortcutListReadingUtils::ShortcutFlags flags =
+                ShortcutListReadingUtils::getFlagsAndForwardPointer(mShortcutsBuf, pos);
+        if (outHasNext) {
+            *outHasNext = ShortcutListReadingUtils::hasNext(flags);
+        }
+        if (outIsWhitelist) {
+            *outIsWhitelist = ShortcutListReadingUtils::isWhitelist(flags);
+        }
+        if (outCodePoint) {
+            *outCodePointCount = ShortcutListReadingUtils::readShortcutTarget(
+                        mShortcutsBuf, maxCodePointCount, outCodePoint, pos);
+        }
+    }
+
+    void skipAllShortcuts(int *const pos) const {
+        const int shortcutListSize = ShortcutListReadingUtils
+                ::getShortcutListSizeAndForwardPointer(mShortcutsBuf, pos);
+        *pos += shortcutListSize;
+    }
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(ShortcutListPolicy);
+
+    const uint8_t *const mShortcutsBuf;
+};
+} // namespace latinime
+#endif // LATINIME_SHORTCUT_LIST_POLICY_H
diff --git a/native/jni/src/suggest/policyimpl/dictionary/shortcut/shortcut_list_reading_utils.cpp b/native/jni/src/suggest/policyimpl/dictionary/shortcut/shortcut_list_reading_utils.cpp
new file mode 100644
index 0000000..847dcde
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/dictionary/shortcut/shortcut_list_reading_utils.cpp
@@ -0,0 +1,51 @@
+/*
+ * 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.
+ */
+
+#include "suggest/policyimpl/dictionary/shortcut/shortcut_list_reading_utils.h"
+
+#include "suggest/policyimpl/dictionary/utils/byte_array_utils.h"
+
+namespace latinime {
+
+// Flag for presence of more attributes
+const ShortcutListReadingUtils::ShortcutFlags
+        ShortcutListReadingUtils::FLAG_ATTRIBUTE_HAS_NEXT = 0x80;
+// Mask for attribute probability, stored on 4 bits inside the flags byte.
+const ShortcutListReadingUtils::ShortcutFlags
+        ShortcutListReadingUtils::MASK_ATTRIBUTE_PROBABILITY = 0x0F;
+const int ShortcutListReadingUtils::SHORTCUT_LIST_SIZE_FIELD_SIZE = 2;
+// The numeric value of the shortcut probability that means 'whitelist'.
+const int ShortcutListReadingUtils::WHITELIST_SHORTCUT_PROBABILITY = 15;
+
+/* static */ ShortcutListReadingUtils::ShortcutFlags
+        ShortcutListReadingUtils::getFlagsAndForwardPointer(const uint8_t *const dictRoot,
+                int *const pos) {
+    return ByteArrayUtils::readUint8AndAdvancePosition(dictRoot, pos);
+}
+
+/* static */ int ShortcutListReadingUtils::getShortcutListSizeAndForwardPointer(
+        const uint8_t *const dictRoot, int *const pos) {
+    // readUint16andAdvancePosition() returns an offset *including* the uint16 field itself.
+    return ByteArrayUtils::readUint16AndAdvancePosition(dictRoot, pos)
+            - SHORTCUT_LIST_SIZE_FIELD_SIZE;
+}
+
+/* static */ int ShortcutListReadingUtils::readShortcutTarget(
+        const uint8_t *const dictRoot, const int maxLength,  int *const outWord, int *const pos) {
+    return ByteArrayUtils::readStringAndAdvancePosition(dictRoot, maxLength, outWord, pos);
+}
+
+} // namespace latinime
diff --git a/native/jni/src/suggest/policyimpl/dictionary/shortcut/shortcut_list_reading_utils.h b/native/jni/src/suggest/policyimpl/dictionary/shortcut/shortcut_list_reading_utils.h
new file mode 100644
index 0000000..a83ed5a
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/dictionary/shortcut/shortcut_list_reading_utils.h
@@ -0,0 +1,69 @@
+/*
+ * 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.
+ */
+
+#ifndef LATINIME_SHORTCUT_LIST_READING_UTILS_H
+#define LATINIME_SHORTCUT_LIST_READING_UTILS_H
+
+#include <stdint.h>
+
+#include "defines.h"
+
+namespace latinime {
+
+class ShortcutListReadingUtils {
+ public:
+    typedef uint8_t ShortcutFlags;
+
+    static ShortcutFlags getFlagsAndForwardPointer(const uint8_t *const dictRoot, int *const pos);
+
+    static AK_FORCE_INLINE int getProbabilityFromFlags(const ShortcutFlags flags) {
+        return flags & MASK_ATTRIBUTE_PROBABILITY;
+    }
+
+    static AK_FORCE_INLINE bool hasNext(const ShortcutFlags flags) {
+        return (flags & FLAG_ATTRIBUTE_HAS_NEXT) != 0;
+    }
+
+    // This method returns the size of the shortcut list region excluding the shortcut list size
+    // field at the beginning.
+    static int getShortcutListSizeAndForwardPointer(const uint8_t *const dictRoot, int *const pos);
+
+    static AK_FORCE_INLINE int getShortcutListSizeFieldSize() {
+        return SHORTCUT_LIST_SIZE_FIELD_SIZE;
+    }
+
+    static AK_FORCE_INLINE void skipShortcuts(const uint8_t *const dictRoot, int *const pos) {
+        const int shortcutListSize = getShortcutListSizeAndForwardPointer(dictRoot, pos);
+        *pos += shortcutListSize;
+    }
+
+    static AK_FORCE_INLINE bool isWhitelist(const ShortcutFlags flags) {
+        return getProbabilityFromFlags(flags) == WHITELIST_SHORTCUT_PROBABILITY;
+    }
+
+    static int readShortcutTarget(const uint8_t *const dictRoot, const int maxLength,
+            int *const outWord, int *const pos);
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(ShortcutListReadingUtils);
+
+    static const ShortcutFlags FLAG_ATTRIBUTE_HAS_NEXT;
+    static const ShortcutFlags MASK_ATTRIBUTE_PROBABILITY;
+    static const int SHORTCUT_LIST_SIZE_FIELD_SIZE;
+    static const int WHITELIST_SHORTCUT_PROBABILITY;
+};
+} // namespace latinime
+#endif // LATINIME_SHORTCUT_LIST_READING_UTILS_H
diff --git a/native/jni/src/suggest/policyimpl/dictionary/utils/buffer_with_extendable_buffer.cpp b/native/jni/src/suggest/policyimpl/dictionary/utils/buffer_with_extendable_buffer.cpp
new file mode 100644
index 0000000..f692882
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/dictionary/utils/buffer_with_extendable_buffer.cpp
@@ -0,0 +1,103 @@
+/*
+ * 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.
+ */
+
+#include "suggest/policyimpl/dictionary/utils/buffer_with_extendable_buffer.h"
+
+namespace latinime {
+
+const size_t BufferWithExtendableBuffer::MAX_ADDITIONAL_BUFFER_SIZE = 1024 * 1024;
+const int BufferWithExtendableBuffer::NEAR_BUFFER_LIMIT_THRESHOLD_PERCENTILE = 90;
+// TODO: Needs to allocate larger memory corresponding to the current vector size.
+const size_t BufferWithExtendableBuffer::EXTEND_ADDITIONAL_BUFFER_SIZE_STEP = 128 * 1024;
+
+bool BufferWithExtendableBuffer::writeUintAndAdvancePosition(const uint32_t data, const int size,
+        int *const pos) {
+    if (!(size >= 1 && size <= 4)) {
+        AKLOGI("writeUintAndAdvancePosition() is called with invalid size: %d", size);
+        ASSERT(false);
+        return false;
+    }
+    if (!checkAndPrepareWriting(*pos, size)) {
+        return false;
+    }
+    const bool usesAdditionalBuffer = isInAdditionalBuffer(*pos);
+    uint8_t *const buffer = usesAdditionalBuffer ? &mAdditionalBuffer[0] : mOriginalBuffer;
+    if (usesAdditionalBuffer) {
+        *pos -= mOriginalBufferSize;
+    }
+    ByteArrayUtils::writeUintAndAdvancePosition(buffer, data, size, pos);
+    if (usesAdditionalBuffer) {
+        *pos += mOriginalBufferSize;
+    }
+    return true;
+}
+
+bool BufferWithExtendableBuffer::writeCodePointsAndAdvancePosition(const int *const codePoints,
+        const int codePointCount, const bool writesTerminator ,int *const pos) {
+    const size_t size = ByteArrayUtils::calculateRequiredByteCountToStoreCodePoints(
+            codePoints, codePointCount, writesTerminator);
+    if (!checkAndPrepareWriting(*pos, size)) {
+        return false;
+    }
+    const bool usesAdditionalBuffer = isInAdditionalBuffer(*pos);
+    uint8_t *const buffer = usesAdditionalBuffer ? &mAdditionalBuffer[0] : mOriginalBuffer;
+    if (usesAdditionalBuffer) {
+        *pos -= mOriginalBufferSize;
+    }
+    ByteArrayUtils::writeCodePointsAndAdvancePosition(buffer, codePoints, codePointCount,
+            writesTerminator, pos);
+    if (usesAdditionalBuffer) {
+        *pos += mOriginalBufferSize;
+    }
+    return true;
+}
+
+bool BufferWithExtendableBuffer::extendBuffer() {
+    const size_t sizeAfterExtending =
+            mAdditionalBuffer.size() + EXTEND_ADDITIONAL_BUFFER_SIZE_STEP;
+    if (sizeAfterExtending > mMaxAdditionalBufferSize) {
+        return false;
+    }
+    mAdditionalBuffer.resize(mAdditionalBuffer.size() + EXTEND_ADDITIONAL_BUFFER_SIZE_STEP);
+    return true;
+}
+
+bool BufferWithExtendableBuffer::checkAndPrepareWriting(const int pos, const int size) {
+    if (isInAdditionalBuffer(pos)) {
+        const int tailPosition = getTailPosition();
+        if (pos == tailPosition) {
+            // Append data to the tail.
+            if (pos + size > static_cast<int>(mAdditionalBuffer.size()) + mOriginalBufferSize) {
+                // Need to extend buffer.
+                if (!extendBuffer()) {
+                    return false;
+                }
+            }
+            mUsedAdditionalBufferSize += size;
+        } else if (pos + size > tailPosition) {
+            // The access will beyond the tail of used region.
+            return false;
+        }
+    } else {
+        if (pos < 0 || mOriginalBufferSize < pos + size) {
+            // Invalid position or violate the boundary.
+            return false;
+        }
+    }
+    return true;
+}
+
+}
diff --git a/native/jni/src/suggest/policyimpl/dictionary/utils/buffer_with_extendable_buffer.h b/native/jni/src/suggest/policyimpl/dictionary/utils/buffer_with_extendable_buffer.h
new file mode 100644
index 0000000..17d2e39
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/dictionary/utils/buffer_with_extendable_buffer.h
@@ -0,0 +1,103 @@
+/*
+ * 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.
+ */
+
+#ifndef LATINIME_BUFFER_WITH_EXTENDABLE_BUFFER_H
+#define LATINIME_BUFFER_WITH_EXTENDABLE_BUFFER_H
+
+#include <cstddef>
+#include <stdint.h>
+#include <vector>
+
+#include "defines.h"
+#include "suggest/policyimpl/dictionary/utils/byte_array_utils.h"
+
+namespace latinime {
+
+// This is used as a buffer that can be extended for updatable dictionaries.
+// To optimize performance, raw pointer is directly used for reading buffer. The position has to be
+// adjusted to access additional buffer. On the other hand, this class does not provide writable
+// raw pointer but provides several methods that handle boundary checking for writing data.
+class BufferWithExtendableBuffer {
+ public:
+    BufferWithExtendableBuffer(uint8_t *const originalBuffer, const int originalBufferSize,
+            const int maxAdditionalBufferSize = MAX_ADDITIONAL_BUFFER_SIZE)
+            : mOriginalBuffer(originalBuffer), mOriginalBufferSize(originalBufferSize),
+              mAdditionalBuffer(EXTEND_ADDITIONAL_BUFFER_SIZE_STEP), mUsedAdditionalBufferSize(0),
+              mMaxAdditionalBufferSize(maxAdditionalBufferSize) {}
+
+    AK_FORCE_INLINE int getTailPosition() const {
+        return mOriginalBufferSize + mUsedAdditionalBufferSize;
+    }
+
+    /**
+     * For reading.
+     */
+    AK_FORCE_INLINE bool isInAdditionalBuffer(const int position) const {
+        return position >= mOriginalBufferSize;
+    }
+
+    // TODO: Resolve the issue that the address can be changed when the vector is resized.
+    // CAVEAT!: Be careful about array out of bound access with buffers
+    AK_FORCE_INLINE const uint8_t *getBuffer(const bool usesAdditionalBuffer) const {
+        if (usesAdditionalBuffer) {
+            return &mAdditionalBuffer[0];
+        } else {
+            return mOriginalBuffer;
+        }
+    }
+
+    AK_FORCE_INLINE int getOriginalBufferSize() const {
+        return mOriginalBufferSize;
+    }
+
+    AK_FORCE_INLINE bool isNearSizeLimit() const {
+        return mAdditionalBuffer.size() >= ((mMaxAdditionalBufferSize
+                * NEAR_BUFFER_LIMIT_THRESHOLD_PERCENTILE) / 100);
+    }
+
+    /**
+     * For writing.
+     *
+     * Writing is allowed for original buffer, already written region of additional buffer and the
+     * tail of additional buffer.
+     */
+    bool writeUintAndAdvancePosition(const uint32_t data, const int size, int *const pos);
+
+    bool writeCodePointsAndAdvancePosition(const int *const codePoints, const int codePointCount,
+            const bool writesTerminator, int *const pos);
+
+ private:
+    DISALLOW_COPY_AND_ASSIGN(BufferWithExtendableBuffer);
+
+    static const size_t MAX_ADDITIONAL_BUFFER_SIZE;
+    static const int NEAR_BUFFER_LIMIT_THRESHOLD_PERCENTILE;
+    static const size_t EXTEND_ADDITIONAL_BUFFER_SIZE_STEP;
+
+    uint8_t *const mOriginalBuffer;
+    const int mOriginalBufferSize;
+    std::vector<uint8_t> mAdditionalBuffer;
+    int mUsedAdditionalBufferSize;
+    const size_t mMaxAdditionalBufferSize;
+
+    // Return if the buffer is successfully extended or not.
+    bool extendBuffer();
+
+    // Returns if it is possible to write size-bytes from pos. When pos is at the tail position of
+    // the additional buffer, try extending the buffer.
+    bool checkAndPrepareWriting(const int pos, const int size);
+};
+}
+#endif /* LATINIME_BUFFER_WITH_EXTENDABLE_BUFFER_H */
diff --git a/native/jni/src/suggest/core/dictionary/byte_array_utils.cpp b/native/jni/src/suggest/policyimpl/dictionary/utils/byte_array_utils.cpp
similarity index 78%
rename from native/jni/src/suggest/core/dictionary/byte_array_utils.cpp
rename to native/jni/src/suggest/policyimpl/dictionary/utils/byte_array_utils.cpp
index 68b1d5d..1833e88 100644
--- a/native/jni/src/suggest/core/dictionary/byte_array_utils.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/utils/byte_array_utils.cpp
@@ -14,11 +14,12 @@
  * limitations under the License.
  */
 
-#include "suggest/core/dictionary/byte_array_utils.h"
+#include "suggest/policyimpl/dictionary/utils/byte_array_utils.h"
 
 namespace latinime {
 
-const uint8_t ByteArrayUtils::MINIMAL_ONE_BYTE_CHARACTER_VALUE = 0x20;
+const uint8_t ByteArrayUtils::MINIMUM_ONE_BYTE_CHARACTER_VALUE = 0x20;
+const uint8_t ByteArrayUtils::MAXIMUM_ONE_BYTE_CHARACTER_VALUE = 0xFF;
 const uint8_t ByteArrayUtils::CHARACTER_ARRAY_TERMINATOR = 0x1F;
 
 } // namespace latinime
diff --git a/native/jni/src/suggest/policyimpl/dictionary/utils/byte_array_utils.h b/native/jni/src/suggest/policyimpl/dictionary/utils/byte_array_utils.h
new file mode 100644
index 0000000..0c15768
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/dictionary/utils/byte_array_utils.h
@@ -0,0 +1,261 @@
+/*
+ * 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.
+ */
+
+#ifndef LATINIME_BYTE_ARRAY_UTILS_H
+#define LATINIME_BYTE_ARRAY_UTILS_H
+
+#include <stdint.h>
+
+#include "defines.h"
+
+namespace latinime {
+
+/**
+ * Utility methods for reading byte arrays.
+ */
+class ByteArrayUtils {
+ public:
+    /**
+     * Integer writing
+     *
+     * Each method write a corresponding size integer in a big endian manner.
+     */
+    static AK_FORCE_INLINE void writeUintAndAdvancePosition(uint8_t *const buffer,
+            const uint32_t data, const int size, int *const pos) {
+        // size must be in 1 to 4.
+        ASSERT(size >= 1 && size <= 4);
+        switch (size) {
+            case 1:
+                ByteArrayUtils::writeUint8AndAdvancePosition(buffer, data, pos);
+                return;
+            case 2:
+                ByteArrayUtils::writeUint16AndAdvancePosition(buffer, data, pos);
+                return;
+            case 3:
+                ByteArrayUtils::writeUint24AndAdvancePosition(buffer, data, pos);
+                return;
+            case 4:
+                ByteArrayUtils::writeUint32AndAdvancePosition(buffer, data, pos);
+                return;
+            default:
+                break;
+        }
+    }
+
+    /**
+     * Integer reading
+     *
+     * Each method read a corresponding size integer in a big endian manner.
+     */
+    static AK_FORCE_INLINE uint32_t readUint32(const uint8_t *const buffer, const int pos) {
+        return (buffer[pos] << 24) ^ (buffer[pos + 1] << 16)
+                ^ (buffer[pos + 2] << 8) ^ buffer[pos + 3];
+    }
+
+    static AK_FORCE_INLINE uint32_t readUint24(const uint8_t *const buffer, const int pos) {
+        return (buffer[pos] << 16) ^ (buffer[pos + 1] << 8) ^ buffer[pos + 2];
+    }
+
+    static AK_FORCE_INLINE uint16_t readUint16(const uint8_t *const buffer, const int pos) {
+        return (buffer[pos] << 8) ^ buffer[pos + 1];
+    }
+
+    static AK_FORCE_INLINE uint8_t readUint8(const uint8_t *const buffer, const int pos) {
+        return buffer[pos];
+    }
+
+    static AK_FORCE_INLINE uint32_t readUint32AndAdvancePosition(
+            const uint8_t *const buffer, int *const pos) {
+        const uint32_t value = readUint32(buffer, *pos);
+        *pos += 4;
+        return value;
+    }
+
+    static AK_FORCE_INLINE int readSint24AndAdvancePosition(
+            const uint8_t *const buffer, int *const pos) {
+        const uint8_t value = readUint8(buffer, *pos);
+        if (value < 0x80) {
+            return readUint24AndAdvancePosition(buffer, pos);
+        } else {
+            (*pos)++;
+            return -(((value & 0x7F) << 16) ^ readUint16AndAdvancePosition(buffer, pos));
+        }
+    }
+
+    static AK_FORCE_INLINE uint32_t readUint24AndAdvancePosition(
+            const uint8_t *const buffer, int *const pos) {
+        const uint32_t value = readUint24(buffer, *pos);
+        *pos += 3;
+        return value;
+    }
+
+    static AK_FORCE_INLINE uint16_t readUint16AndAdvancePosition(
+            const uint8_t *const buffer, int *const pos) {
+        const uint16_t value = readUint16(buffer, *pos);
+        *pos += 2;
+        return value;
+    }
+
+    static AK_FORCE_INLINE uint8_t readUint8AndAdvancePosition(
+            const uint8_t *const buffer, int *const pos) {
+        return buffer[(*pos)++];
+    }
+
+    /**
+     * Code Point Reading
+     *
+     * 1 byte = bbbbbbbb match
+     * case 000xxxxx: xxxxx << 16 + next byte << 8 + next byte
+     * else: if 00011111 (= 0x1F) : this is the terminator. This is a relevant choice because
+     *       unicode code points range from 0 to 0x10FFFF, so any 3-byte value starting with
+     *       00011111 would be outside unicode.
+     * else: iso-latin-1 code
+     * This allows for the whole unicode range to be encoded, including chars outside of
+     * the BMP. Also everything in the iso-latin-1 charset is only 1 byte, except control
+     * characters which should never happen anyway (and still work, but take 3 bytes).
+     */
+    static AK_FORCE_INLINE int readCodePoint(const uint8_t *const buffer, const int pos) {
+        int p = pos;
+        return readCodePointAndAdvancePosition(buffer, &p);
+    }
+
+    static AK_FORCE_INLINE int readCodePointAndAdvancePosition(
+            const uint8_t *const buffer, int *const pos) {
+        const uint8_t firstByte = readUint8(buffer, *pos);
+        if (firstByte < MINIMUM_ONE_BYTE_CHARACTER_VALUE) {
+            if (firstByte == CHARACTER_ARRAY_TERMINATOR) {
+                *pos += 1;
+                return NOT_A_CODE_POINT;
+            } else {
+                return readUint24AndAdvancePosition(buffer, pos);
+            }
+        } else {
+            *pos += 1;
+            return firstByte;
+        }
+    }
+
+    /**
+     * String (array of code points) Reading
+     *
+     * Reads code points until the terminator is found.
+     */
+    // Returns the length of the string.
+    static int readStringAndAdvancePosition(const uint8_t *const buffer,
+            const int maxLength, int *const outBuffer, int *const pos) {
+        int length = 0;
+        int codePoint = readCodePointAndAdvancePosition(buffer, pos);
+        while (NOT_A_CODE_POINT != codePoint && length < maxLength) {
+            outBuffer[length++] = codePoint;
+            codePoint = readCodePointAndAdvancePosition(buffer, pos);
+        }
+        return length;
+    }
+
+    // Advances the position and returns the length of the string.
+    static int advancePositionToBehindString(
+            const uint8_t *const buffer, const int maxLength, int *const pos) {
+        int length = 0;
+        int codePoint = readCodePointAndAdvancePosition(buffer, pos);
+        while (NOT_A_CODE_POINT != codePoint && length < maxLength) {
+            codePoint = readCodePointAndAdvancePosition(buffer, pos);
+            length++;
+        }
+        return length;
+    }
+
+    /**
+     * String (array of code points) Writing
+     */
+    static void writeCodePointsAndAdvancePosition(uint8_t *const buffer,
+            const int *const codePoints, const int codePointCount, const bool writesTerminator,
+            int *const pos) {
+        for (int i = 0; i < codePointCount; ++i) {
+            const int codePoint = codePoints[i];
+            if (codePoint == NOT_A_CODE_POINT || codePoint == CHARACTER_ARRAY_TERMINATOR) {
+                break;
+            } else if (codePoint < MINIMUM_ONE_BYTE_CHARACTER_VALUE
+                    || codePoint > MAXIMUM_ONE_BYTE_CHARACTER_VALUE) {
+                // three bytes character.
+                writeUint24AndAdvancePosition(buffer, codePoint, pos);
+            } else {
+                // one byte character.
+                writeUint8AndAdvancePosition(buffer, codePoint, pos);
+            }
+        }
+        if (writesTerminator) {
+            writeUint8AndAdvancePosition(buffer, CHARACTER_ARRAY_TERMINATOR, pos);
+        }
+    }
+
+    static int calculateRequiredByteCountToStoreCodePoints(const int *const codePoints,
+            const int codePointCount, const bool writesTerminator) {
+        int byteCount = 0;
+        for (int i = 0; i < codePointCount; ++i) {
+            const int codePoint = codePoints[i];
+            if (codePoint == NOT_A_CODE_POINT || codePoint == CHARACTER_ARRAY_TERMINATOR) {
+                break;
+            } else if (codePoint < MINIMUM_ONE_BYTE_CHARACTER_VALUE
+                    || codePoint > MAXIMUM_ONE_BYTE_CHARACTER_VALUE) {
+                // three bytes character.
+                byteCount += 3;
+            } else {
+                // one byte character.
+                byteCount += 1;
+            }
+        }
+        if (writesTerminator) {
+            // The terminator is one byte.
+            byteCount += 1;
+        }
+        return byteCount;
+    }
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(ByteArrayUtils);
+
+    static const uint8_t MINIMUM_ONE_BYTE_CHARACTER_VALUE;
+    static const uint8_t MAXIMUM_ONE_BYTE_CHARACTER_VALUE;
+    static const uint8_t CHARACTER_ARRAY_TERMINATOR;
+
+    static AK_FORCE_INLINE void writeUint32AndAdvancePosition(uint8_t *const buffer,
+            const uint32_t data, int *const pos) {
+        buffer[(*pos)++] = (data >> 24) & 0xFF;
+        buffer[(*pos)++] = (data >> 16) & 0xFF;
+        buffer[(*pos)++] = (data >> 8) & 0xFF;
+        buffer[(*pos)++] = data & 0xFF;
+    }
+
+    static AK_FORCE_INLINE void writeUint24AndAdvancePosition(uint8_t *const buffer,
+            const uint32_t data, int *const pos) {
+        buffer[(*pos)++] = (data >> 16) & 0xFF;
+        buffer[(*pos)++] = (data >> 8) & 0xFF;
+        buffer[(*pos)++] = data & 0xFF;
+    }
+
+    static AK_FORCE_INLINE void writeUint16AndAdvancePosition(uint8_t *const buffer,
+            const uint16_t data, int *const pos) {
+        buffer[(*pos)++] = (data >> 8) & 0xFF;
+        buffer[(*pos)++] = data & 0xFF;
+    }
+
+    static AK_FORCE_INLINE void writeUint8AndAdvancePosition(uint8_t *const buffer,
+            const uint8_t data, int *const pos) {
+        buffer[(*pos)++] = data & 0xFF;
+    }
+};
+} // namespace latinime
+#endif /* LATINIME_BYTE_ARRAY_UTILS_H */
diff --git a/native/jni/src/suggest/policyimpl/dictionary/utils/format_utils.cpp b/native/jni/src/suggest/policyimpl/dictionary/utils/format_utils.cpp
new file mode 100644
index 0000000..1d77d5c
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/dictionary/utils/format_utils.cpp
@@ -0,0 +1,56 @@
+/*
+ * 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.
+ */
+
+#include "suggest/policyimpl/dictionary/utils/format_utils.h"
+
+#include "suggest/policyimpl/dictionary/utils/byte_array_utils.h"
+
+namespace latinime {
+
+const uint32_t FormatUtils::MAGIC_NUMBER = 0x9BC13AFE;
+
+// Magic number (4 bytes), version (2 bytes), flags (2 bytes), header size (4 bytes) = 12
+const int FormatUtils::DICTIONARY_MINIMUM_SIZE = 12;
+
+/* static */ FormatUtils::FORMAT_VERSION FormatUtils::detectFormatVersion(
+        const uint8_t *const dict, const int dictSize) {
+    // The magic number is stored big-endian.
+    // If the dictionary is less than 4 bytes, we can't even read the magic number, so we don't
+    // understand this format.
+    if (dictSize < DICTIONARY_MINIMUM_SIZE) {
+        return UNKNOWN_VERSION;
+    }
+    const uint32_t magicNumber = ByteArrayUtils::readUint32(dict, 0);
+    switch (magicNumber) {
+        case MAGIC_NUMBER:
+            // Version 2 header is as follows:
+            // Magic number (4 bytes) 0x9B 0xC1 0x3A 0xFE
+            // Dictionary format version number (2 bytes)
+            // Options (2 bytes)
+            // Header size (4 bytes) : integer, big endian
+            if (ByteArrayUtils::readUint16(dict, 4) == 2) {
+                return VERSION_2;
+            } else if (ByteArrayUtils::readUint16(dict, 4) == 3) {
+                return VERSION_3;
+            } else {
+                return UNKNOWN_VERSION;
+            }
+        default:
+            return UNKNOWN_VERSION;
+    }
+}
+
+} // namespace latinime
diff --git a/native/jni/src/suggest/core/dictionary/binary_dictionary_format_utils.h b/native/jni/src/suggest/policyimpl/dictionary/utils/format_utils.h
similarity index 60%
rename from native/jni/src/suggest/core/dictionary/binary_dictionary_format_utils.h
rename to native/jni/src/suggest/policyimpl/dictionary/utils/format_utils.h
index 830684c..79ed0de 100644
--- a/native/jni/src/suggest/core/dictionary/binary_dictionary_format_utils.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/utils/format_utils.h
@@ -14,24 +14,19 @@
  * limitations under the License.
  */
 
-#ifndef LATINIME_BINARY_DICTIONARY_FORMAT_UTILS_H
-#define LATINIME_BINARY_DICTIONARY_FORMAT_UTILS_H
+#ifndef LATINIME_FORMAT_UTILS_H
+#define LATINIME_FORMAT_UTILS_H
 
 #include <stdint.h>
 
 #include "defines.h"
-#include "suggest/core/dictionary/byte_array_utils.h"
 
 namespace latinime {
 
 /**
  * Methods to handle binary dictionary format version.
- *
- * Currently, we have a file with a similar name, binary_format.h. binary_format.h contains binary
- * reading methods and utility methods for various purposes.
- * On the other hand, this file deals with only about dictionary format version.
  */
-class BinaryDictionaryFormatUtils {
+class FormatUtils {
  public:
     enum FORMAT_VERSION {
         VERSION_2,
@@ -39,14 +34,16 @@
         UNKNOWN_VERSION
     };
 
+    // 32 bit magic number is stored at the beginning of the dictionary header to reject
+    // unsupported or obsolete dictionary formats.
+    static const uint32_t MAGIC_NUMBER;
+
     static FORMAT_VERSION detectFormatVersion(const uint8_t *const dict, const int dictSize);
 
  private:
-    DISALLOW_IMPLICIT_CONSTRUCTORS(BinaryDictionaryFormatUtils);
+    DISALLOW_IMPLICIT_CONSTRUCTORS(FormatUtils);
 
     static const int DICTIONARY_MINIMUM_SIZE;
-    static const uint32_t HEADER_VERSION_2_MAGIC_NUMBER;
-    static const int HEADER_VERSION_2_MINIMUM_SIZE;
 };
 } // namespace latinime
-#endif /* LATINIME_BINARY_DICTIONARY_FORMAT_UTILS_H */
+#endif /* LATINIME_FORMAT_UTILS_H */
diff --git a/native/jni/src/suggest/policyimpl/dictionary/utils/mmapped_buffer.h b/native/jni/src/suggest/policyimpl/dictionary/utils/mmapped_buffer.h
new file mode 100644
index 0000000..6b69116
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/dictionary/utils/mmapped_buffer.h
@@ -0,0 +1,102 @@
+/*
+ * 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.
+ */
+
+#ifndef LATINIME_MMAPPED_BUFFER_H
+#define LATINIME_MMAPPED_BUFFER_H
+
+#include <cerrno>
+#include <fcntl.h>
+#include <stdint.h>
+#include <sys/mman.h>
+#include <unistd.h>
+
+#include "defines.h"
+
+namespace latinime {
+
+class MmappedBuffer {
+ public:
+    static MmappedBuffer* openBuffer(const char *const path, const int bufferOffset,
+            const int bufferSize, const bool isUpdatable) {
+        const int openMode = isUpdatable ? O_RDWR : O_RDONLY;
+        const int mmapFd = open(path, openMode);
+        if (mmapFd < 0) {
+            AKLOGE("DICT: Can't open the source. path=%s errno=%d", path, errno);
+            return 0;
+        }
+        const int pagesize = getpagesize();
+        const int offset = bufferOffset % pagesize;
+        int alignedOffset = bufferOffset - offset;
+        int alignedSize = bufferSize + offset;
+        const int protMode = isUpdatable ? PROT_READ | PROT_WRITE : PROT_READ;
+        void *const mmappedBuffer = mmap(0, alignedSize, protMode, MAP_PRIVATE, mmapFd,
+                alignedOffset);
+        if (mmappedBuffer == MAP_FAILED) {
+            AKLOGE("DICT: Can't mmap dictionary. errno=%d", errno);
+            close(mmapFd);
+            return 0;
+        }
+        uint8_t *const buffer = static_cast<uint8_t *>(mmappedBuffer) + offset;
+        if (!buffer) {
+            AKLOGE("DICT: buffer is null");
+            close(mmapFd);
+            return 0;
+        }
+        return new MmappedBuffer(buffer, bufferSize, mmappedBuffer, alignedSize, mmapFd,
+                isUpdatable);
+    }
+
+    ~MmappedBuffer() {
+        int ret = munmap(mMmappedBuffer, mAlignedSize);
+        if (ret != 0) {
+            AKLOGE("DICT: Failure in munmap. ret=%d errno=%d", ret, errno);
+        }
+        ret = close(mMmapFd);
+        if (ret != 0) {
+            AKLOGE("DICT: Failure in close. ret=%d errno=%d", ret, errno);
+        }
+    }
+
+    AK_FORCE_INLINE uint8_t *getBuffer() const {
+        return mBuffer;
+    }
+
+    AK_FORCE_INLINE int getBufferSize() const {
+        return mBufferSize;
+    }
+
+    AK_FORCE_INLINE bool isUpdatable() const {
+        return mIsUpdatable;
+    }
+
+ private:
+    AK_FORCE_INLINE MmappedBuffer(uint8_t *const buffer, const int bufferSize,
+            void *const mmappedBuffer, const int alignedSize, const int mmapFd,
+            const bool isUpdatable)
+            : mBuffer(buffer), mBufferSize(bufferSize), mMmappedBuffer(mmappedBuffer),
+              mAlignedSize(alignedSize), mMmapFd(mmapFd), mIsUpdatable(isUpdatable) {}
+
+    DISALLOW_IMPLICIT_CONSTRUCTORS(MmappedBuffer);
+
+    uint8_t *const mBuffer;
+    const int mBufferSize;
+    void *const mMmappedBuffer;
+    const int mAlignedSize;
+    const int mMmapFd;
+    const bool mIsUpdatable;
+};
+}
+#endif /* LATINIME_MMAPPED_BUFFER_H */
diff --git a/native/jni/src/suggest/core/dictionary/probability_utils.h b/native/jni/src/suggest/policyimpl/dictionary/utils/probability_utils.h
similarity index 96%
rename from native/jni/src/suggest/core/dictionary/probability_utils.h
rename to native/jni/src/suggest/policyimpl/dictionary/utils/probability_utils.h
index f450087..21fe355 100644
--- a/native/jni/src/suggest/core/dictionary/probability_utils.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/utils/probability_utils.h
@@ -41,7 +41,7 @@
         // the unigram probability to be the median value of the 17th step from the top. A value of
         // 0 for the bigram probability represents the middle of the 16th step from the top,
         // while a value of 15 represents the middle of the top step.
-        // See makedict.BinaryDictInputOutput for details.
+        // See makedict.BinaryDictEncoder#makeBigramFlags for details.
         const float stepSize = static_cast<float>(MAX_PROBABILITY - unigramProbability)
                 / (1.5f + MAX_BIGRAM_ENCODED_PROBABILITY);
         return unigramProbability
diff --git a/native/jni/src/suggest/policyimpl/typing/typing_weighting.h b/native/jni/src/suggest/policyimpl/typing/typing_weighting.h
index 7cddb08..b6aa858 100644
--- a/native/jni/src/suggest/policyimpl/typing/typing_weighting.h
+++ b/native/jni/src/suggest/policyimpl/typing/typing_weighting.h
@@ -155,7 +155,8 @@
     float getNewWordBigramLanguageCost(const DicTraverseSession *const traverseSession,
             const DicNode *const dicNode,
             MultiBigramMap *const multiBigramMap) const {
-        return DicNodeUtils::getBigramNodeImprobability(traverseSession->getBinaryDictionaryInfo(),
+        return DicNodeUtils::getBigramNodeImprobability(
+                traverseSession->getDictionaryStructurePolicy(),
                 dicNode, multiBigramMap) * ScoringParams::DISTANCE_WEIGHT_LANGUAGE;
     }
 
diff --git a/native/jni/src/utils/autocorrection_threshold_utils.cpp b/native/jni/src/utils/autocorrection_threshold_utils.cpp
index 3406e0f..1f8ee08 100644
--- a/native/jni/src/utils/autocorrection_threshold_utils.cpp
+++ b/native/jni/src/utils/autocorrection_threshold_utils.cpp
@@ -83,9 +83,12 @@
         return 0.0f;
     }
 
+    if (score <= 0 || distance >= afterLength) {
+        // normalizedScore must be 0.0f (the minimum value) if the score is less than or equal to 0,
+        // or if the edit distance is larger than or equal to afterLength.
+        return 0.0f;
+    }
     // add a weight based on edit distance.
-    // distance <= max(afterLength, beforeLength) == afterLength,
-    // so, 0 <= distance / afterLength <= 1
     const float weight = 1.0f - static_cast<float>(distance) / static_cast<float>(afterLength);
 
     // TODO: Revise the following logic thoroughly by referring to...
diff --git a/tests/src/com/android/inputmethod/keyboard/internal/MockKeyboardSwitcher.java b/tests/src/com/android/inputmethod/keyboard/internal/MockKeyboardSwitcher.java
index db39976..6e3e37a 100644
--- a/tests/src/com/android/inputmethod/keyboard/internal/MockKeyboardSwitcher.java
+++ b/tests/src/com/android/inputmethod/keyboard/internal/MockKeyboardSwitcher.java
@@ -120,6 +120,11 @@
     }
 
     @Override
+    public void setEmojiKeyboard() {
+        // Just ignore.
+    }
+
+    @Override
     public void requestUpdatingShiftState() {
         mState.onUpdateShiftState(mAutoCapsState, RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE);
     }
diff --git a/tests/src/com/android/inputmethod/latin/BinaryDictionaryTests.java b/tests/src/com/android/inputmethod/latin/BinaryDictionaryTests.java
new file mode 100644
index 0000000..96a2217
--- /dev/null
+++ b/tests/src/com/android/inputmethod/latin/BinaryDictionaryTests.java
@@ -0,0 +1,653 @@
+/*
+ * 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;
+
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.util.Pair;
+
+import com.android.inputmethod.latin.makedict.CodePointUtils;
+import com.android.inputmethod.latin.makedict.DictEncoder;
+import com.android.inputmethod.latin.makedict.FormatSpec;
+import com.android.inputmethod.latin.makedict.FusionDictionary;
+import com.android.inputmethod.latin.makedict.FusionDictionary.PtNodeArray;
+import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
+import com.android.inputmethod.latin.makedict.Ver3DictEncoder;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Random;
+
+@LargeTest
+public class BinaryDictionaryTests extends AndroidTestCase {
+    private static final FormatSpec.FormatOptions FORMAT_OPTIONS =
+            new FormatSpec.FormatOptions(3 /* version */, true /* supportsDynamicUpdate */);
+    private static final String TEST_DICT_FILE_EXTENSION = ".testDict";
+    private static final String TEST_LOCALE = "test";
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    private File createEmptyDictionaryAndGetFile(final String filename) throws IOException,
+            UnsupportedFormatException {
+        final FusionDictionary dict = new FusionDictionary(new PtNodeArray(),
+                new FusionDictionary.DictionaryOptions(new HashMap<String,String>(), false, false));
+        final File file = File.createTempFile(filename, TEST_DICT_FILE_EXTENSION,
+                getContext().getCacheDir());
+        final DictEncoder dictEncoder = new Ver3DictEncoder(file);
+        dictEncoder.writeDictionary(dict, FORMAT_OPTIONS);
+        return file;
+    }
+
+    public void testIsValidDictionary() {
+        File dictFile = null;
+        try {
+            dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary");
+        } catch (IOException e) {
+            fail("IOException while writing an initial dictionary : " + e);
+        } catch (UnsupportedFormatException e) {
+            fail("UnsupportedFormatException while writing an initial dictionary : " + e);
+        }
+        BinaryDictionary binaryDictionary = new BinaryDictionary(dictFile.getAbsolutePath(),
+                0 /* offset */, dictFile.length(), true /* useFullEditDistance */,
+                Locale.getDefault(), TEST_LOCALE, true /* isUpdatable */);
+        assertTrue("binaryDictionary must be valid for existing valid dictionary file.",
+                binaryDictionary.isValidDictionary());
+        binaryDictionary.close();
+        assertFalse("binaryDictionary must be invalid after closing.",
+                binaryDictionary.isValidDictionary());
+        dictFile.delete();
+        binaryDictionary = new BinaryDictionary(dictFile.getAbsolutePath(), 0 /* offset */,
+                dictFile.length(), true /* useFullEditDistance */, Locale.getDefault(),
+                TEST_LOCALE, true /* isUpdatable */);
+        assertFalse("binaryDictionary must be invalid for not existing dictionary file.",
+                binaryDictionary.isValidDictionary());
+        binaryDictionary.close();
+    }
+
+    public void testAddUnigramWord() {
+        File dictFile = null;
+        try {
+            dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary");
+        } catch (IOException e) {
+            fail("IOException while writing an initial dictionary : " + e);
+        } catch (UnsupportedFormatException e) {
+            fail("UnsupportedFormatException while writing an initial dictionary : " + e);
+        }
+        BinaryDictionary binaryDictionary = new BinaryDictionary(dictFile.getAbsolutePath(),
+                0 /* offset */, dictFile.length(), true /* useFullEditDistance */,
+                Locale.getDefault(), TEST_LOCALE, true /* isUpdatable */);
+
+        final int probability = 100;
+        binaryDictionary.addUnigramWord("aaa", probability);
+        // Reallocate and create.
+        binaryDictionary.addUnigramWord("aab", probability);
+        // Insert into children.
+        binaryDictionary.addUnigramWord("aac", probability);
+        // Make terminal.
+        binaryDictionary.addUnigramWord("aa", probability);
+        // Create children.
+        binaryDictionary.addUnigramWord("aaaa", probability);
+        // Reallocate and make termianl.
+        binaryDictionary.addUnigramWord("a", probability);
+
+        final int updatedProbability = 200;
+        // Update.
+        binaryDictionary.addUnigramWord("aaa", updatedProbability);
+
+        assertEquals(probability, binaryDictionary.getFrequency("aab"));
+        assertEquals(probability, binaryDictionary.getFrequency("aac"));
+        assertEquals(probability, binaryDictionary.getFrequency("aa"));
+        assertEquals(probability, binaryDictionary.getFrequency("aaaa"));
+        assertEquals(probability, binaryDictionary.getFrequency("a"));
+        assertEquals(updatedProbability, binaryDictionary.getFrequency("aaa"));
+
+        dictFile.delete();
+    }
+
+    public void testRandomlyAddUnigramWord() {
+        final int wordCount = 1000;
+        final int codePointSetSize = 50;
+        final int seed = 123456789;
+
+        File dictFile = null;
+        try {
+            dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary");
+        } catch (IOException e) {
+            fail("IOException while writing an initial dictionary : " + e);
+        } catch (UnsupportedFormatException e) {
+            fail("UnsupportedFormatException while writing an initial dictionary : " + e);
+        }
+        BinaryDictionary binaryDictionary = new BinaryDictionary(dictFile.getAbsolutePath(),
+                0 /* offset */, dictFile.length(), true /* useFullEditDistance */,
+                Locale.getDefault(), TEST_LOCALE, true /* isUpdatable */);
+
+        final HashMap<String, Integer> probabilityMap = new HashMap<String, Integer>();
+        // Test a word that isn't contained within the dictionary.
+        final Random random = new Random(seed);
+        final int[] codePointSet = CodePointUtils.generateCodePointSet(codePointSetSize, random);
+        for (int i = 0; i < wordCount; ++i) {
+            final String word = CodePointUtils.generateWord(random, codePointSet);
+            probabilityMap.put(word, random.nextInt(0xFF));
+        }
+        for (String word : probabilityMap.keySet()) {
+            binaryDictionary.addUnigramWord(word, probabilityMap.get(word));
+        }
+        for (String word : probabilityMap.keySet()) {
+            assertEquals(word, (int)probabilityMap.get(word), binaryDictionary.getFrequency(word));
+        }
+        dictFile.delete();
+    }
+
+    public void testAddBigramWords() {
+        File dictFile = null;
+        try {
+            dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary");
+        } catch (IOException e) {
+            fail("IOException while writing an initial dictionary : " + e);
+        } catch (UnsupportedFormatException e) {
+            fail("UnsupportedFormatException while writing an initial dictionary : " + e);
+        }
+        BinaryDictionary binaryDictionary = new BinaryDictionary(dictFile.getAbsolutePath(),
+                0 /* offset */, dictFile.length(), true /* useFullEditDistance */,
+                Locale.getDefault(), TEST_LOCALE, true /* isUpdatable */);
+
+        final int unigramProbability = 100;
+        final int bigramProbability = 10;
+        final int updatedBigramProbability = 15;
+        binaryDictionary.addUnigramWord("aaa", unigramProbability);
+        binaryDictionary.addUnigramWord("abb", unigramProbability);
+        binaryDictionary.addUnigramWord("bcc", unigramProbability);
+        binaryDictionary.addBigramWords("aaa", "abb", bigramProbability);
+        binaryDictionary.addBigramWords("aaa", "bcc", bigramProbability);
+        binaryDictionary.addBigramWords("abb", "aaa", bigramProbability);
+        binaryDictionary.addBigramWords("abb", "bcc", bigramProbability);
+
+        final int probability = binaryDictionary.calculateProbability(unigramProbability,
+                bigramProbability);
+        assertEquals(true, binaryDictionary.isValidBigram("aaa", "abb"));
+        assertEquals(true, binaryDictionary.isValidBigram("aaa", "bcc"));
+        assertEquals(true, binaryDictionary.isValidBigram("abb", "aaa"));
+        assertEquals(true, binaryDictionary.isValidBigram("abb", "bcc"));
+        assertEquals(probability, binaryDictionary.getBigramProbability("aaa", "abb"));
+        assertEquals(probability, binaryDictionary.getBigramProbability("aaa", "bcc"));
+        assertEquals(probability, binaryDictionary.getBigramProbability("abb", "aaa"));
+        assertEquals(probability, binaryDictionary.getBigramProbability("abb", "bcc"));
+
+        binaryDictionary.addBigramWords("aaa", "abb", updatedBigramProbability);
+        final int updatedProbability = binaryDictionary.calculateProbability(unigramProbability,
+                updatedBigramProbability);
+        assertEquals(updatedProbability, binaryDictionary.getBigramProbability("aaa", "abb"));
+
+        assertEquals(false, binaryDictionary.isValidBigram("bcc", "aaa"));
+        assertEquals(false, binaryDictionary.isValidBigram("bcc", "bbc"));
+        assertEquals(false, binaryDictionary.isValidBigram("aaa", "aaa"));
+        assertEquals(Dictionary.NOT_A_PROBABILITY,
+                binaryDictionary.getBigramProbability("bcc", "aaa"));
+        assertEquals(Dictionary.NOT_A_PROBABILITY,
+                binaryDictionary.getBigramProbability("bcc", "bbc"));
+        assertEquals(Dictionary.NOT_A_PROBABILITY,
+                binaryDictionary.getBigramProbability("aaa", "aaa"));
+
+        // Testing bigram link.
+        binaryDictionary.addUnigramWord("abcde", unigramProbability);
+        binaryDictionary.addUnigramWord("fghij", unigramProbability);
+        binaryDictionary.addBigramWords("abcde", "fghij", bigramProbability);
+        binaryDictionary.addUnigramWord("fgh", unigramProbability);
+        binaryDictionary.addUnigramWord("abc", unigramProbability);
+        binaryDictionary.addUnigramWord("f", unigramProbability);
+        assertEquals(probability, binaryDictionary.getBigramProbability("abcde", "fghij"));
+        assertEquals(Dictionary.NOT_A_PROBABILITY,
+                binaryDictionary.getBigramProbability("abcde", "fgh"));
+        binaryDictionary.addBigramWords("abcde", "fghij", updatedBigramProbability);
+        assertEquals(updatedProbability, binaryDictionary.getBigramProbability("abcde", "fghij"));
+
+        dictFile.delete();
+    }
+
+    public void testRandomlyAddBigramWords() {
+        final int wordCount = 100;
+        final int bigramCount = 1000;
+        final int codePointSetSize = 50;
+        final int seed = 11111;
+
+        File dictFile = null;
+        try {
+            dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary");
+        } catch (IOException e) {
+            fail("IOException while writing an initial dictionary : " + e);
+        } catch (UnsupportedFormatException e) {
+            fail("UnsupportedFormatException while writing an initial dictionary : " + e);
+        }
+        BinaryDictionary binaryDictionary = new BinaryDictionary(dictFile.getAbsolutePath(),
+                0 /* offset */, dictFile.length(), true /* useFullEditDistance */,
+                Locale.getDefault(), TEST_LOCALE, true /* isUpdatable */);
+        final ArrayList<String> words = new ArrayList<String>();
+        // Test a word that isn't contained within the dictionary.
+        final Random random = new Random(seed);
+        final int[] codePointSet = CodePointUtils.generateCodePointSet(codePointSetSize, random);
+        final int[] unigramProbabilities = new int[wordCount];
+        for (int i = 0; i < wordCount; ++i) {
+            final String word = CodePointUtils.generateWord(random, codePointSet);
+            words.add(word);
+            final int unigramProbability = random.nextInt(0xFF);
+            unigramProbabilities[i] = unigramProbability;
+            binaryDictionary.addUnigramWord(word, unigramProbability);
+        }
+
+        final int[][] probabilities = new int[wordCount][wordCount];
+
+        for (int i = 0; i < wordCount; ++i) {
+            for (int j = 0; j < wordCount; ++j) {
+                probabilities[i][j] = Dictionary.NOT_A_PROBABILITY;
+            }
+        }
+
+        for (int i = 0; i < bigramCount; i++) {
+            final int word0Index = random.nextInt(wordCount);
+            final int word1Index = random.nextInt(wordCount);
+            final String word0 = words.get(word0Index);
+            final String word1 = words.get(word1Index);
+            final int bigramProbability = random.nextInt(0xF);
+            probabilities[word0Index][word1Index] = binaryDictionary.calculateProbability(
+                    unigramProbabilities[word1Index], bigramProbability);
+            binaryDictionary.addBigramWords(word0, word1, bigramProbability);
+        }
+
+        for (int i = 0; i < words.size(); i++) {
+            for (int j = 0; j < words.size(); j++) {
+                assertEquals(probabilities[i][j],
+                        binaryDictionary.getBigramProbability(words.get(i), words.get(j)));
+            }
+        }
+
+        dictFile.delete();
+    }
+
+    public void testRemoveBigramWords() {
+        File dictFile = null;
+        try {
+            dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary");
+        } catch (IOException e) {
+            fail("IOException while writing an initial dictionary : " + e);
+        } catch (UnsupportedFormatException e) {
+            fail("UnsupportedFormatException while writing an initial dictionary : " + e);
+        }
+        BinaryDictionary binaryDictionary = new BinaryDictionary(dictFile.getAbsolutePath(),
+                0 /* offset */, dictFile.length(), true /* useFullEditDistance */,
+                Locale.getDefault(), TEST_LOCALE, true /* isUpdatable */);
+        final int unigramProbability = 100;
+        final int bigramProbability = 10;
+        binaryDictionary.addUnigramWord("aaa", unigramProbability);
+        binaryDictionary.addUnigramWord("abb", unigramProbability);
+        binaryDictionary.addUnigramWord("bcc", unigramProbability);
+        binaryDictionary.addBigramWords("aaa", "abb", bigramProbability);
+        binaryDictionary.addBigramWords("aaa", "bcc", bigramProbability);
+        binaryDictionary.addBigramWords("abb", "aaa", bigramProbability);
+        binaryDictionary.addBigramWords("abb", "bcc", bigramProbability);
+
+        assertEquals(true, binaryDictionary.isValidBigram("aaa", "abb"));
+        assertEquals(true, binaryDictionary.isValidBigram("aaa", "bcc"));
+        assertEquals(true, binaryDictionary.isValidBigram("abb", "aaa"));
+        assertEquals(true, binaryDictionary.isValidBigram("abb", "bcc"));
+
+        binaryDictionary.removeBigramWords("aaa", "abb");
+        assertEquals(false, binaryDictionary.isValidBigram("aaa", "abb"));
+        binaryDictionary.addBigramWords("aaa", "abb", bigramProbability);
+        assertEquals(true, binaryDictionary.isValidBigram("aaa", "abb"));
+
+
+        binaryDictionary.removeBigramWords("aaa", "bcc");
+        assertEquals(false, binaryDictionary.isValidBigram("aaa", "bcc"));
+        binaryDictionary.removeBigramWords("abb", "aaa");
+        assertEquals(false, binaryDictionary.isValidBigram("abb", "aaa"));
+        binaryDictionary.removeBigramWords("abb", "bcc");
+        assertEquals(false, binaryDictionary.isValidBigram("abb", "bcc"));
+
+        binaryDictionary.removeBigramWords("aaa", "abb");
+        // Test remove non-existing bigram operation.
+        binaryDictionary.removeBigramWords("aaa", "abb");
+        binaryDictionary.removeBigramWords("bcc", "aaa");
+
+        dictFile.delete();
+    }
+
+    public void testFlushDictionary() {
+        File dictFile = null;
+        try {
+            dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary");
+        } catch (IOException e) {
+            fail("IOException while writing an initial dictionary : " + e);
+        } catch (UnsupportedFormatException e) {
+            fail("UnsupportedFormatException while writing an initial dictionary : " + e);
+        }
+        BinaryDictionary binaryDictionary = new BinaryDictionary(dictFile.getAbsolutePath(),
+                0 /* offset */, dictFile.length(), true /* useFullEditDistance */,
+                Locale.getDefault(), TEST_LOCALE, true /* isUpdatable */);
+
+        final int probability = 100;
+        binaryDictionary.addUnigramWord("aaa", probability);
+        binaryDictionary.addUnigramWord("abcd", probability);
+        // Close without flushing.
+        binaryDictionary.close();
+
+        binaryDictionary = new BinaryDictionary(dictFile.getAbsolutePath(),
+                0 /* offset */, dictFile.length(), true /* useFullEditDistance */,
+                Locale.getDefault(), TEST_LOCALE, true /* isUpdatable */);
+
+        assertEquals(Dictionary.NOT_A_PROBABILITY, binaryDictionary.getFrequency("aaa"));
+        assertEquals(Dictionary.NOT_A_PROBABILITY, binaryDictionary.getFrequency("abcd"));
+
+        binaryDictionary.addUnigramWord("aaa", probability);
+        binaryDictionary.addUnigramWord("abcd", probability);
+        binaryDictionary.flush();
+        binaryDictionary.close();
+
+        binaryDictionary = new BinaryDictionary(dictFile.getAbsolutePath(),
+                0 /* offset */, dictFile.length(), true /* useFullEditDistance */,
+                Locale.getDefault(), TEST_LOCALE, true /* isUpdatable */);
+
+        assertEquals(probability, binaryDictionary.getFrequency("aaa"));
+        assertEquals(probability, binaryDictionary.getFrequency("abcd"));
+        binaryDictionary.addUnigramWord("bcde", probability);
+        binaryDictionary.flush();
+        binaryDictionary.close();
+
+        binaryDictionary = new BinaryDictionary(dictFile.getAbsolutePath(),
+                0 /* offset */, dictFile.length(), true /* useFullEditDistance */,
+                Locale.getDefault(), TEST_LOCALE, true /* isUpdatable */);
+        assertEquals(probability, binaryDictionary.getFrequency("bcde"));
+        binaryDictionary.close();
+
+        dictFile.delete();
+    }
+
+    public void testFlushWithGCDictionary() {
+        File dictFile = null;
+        try {
+            dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary");
+        } catch (IOException e) {
+            fail("IOException while writing an initial dictionary : " + e);
+        } catch (UnsupportedFormatException e) {
+            fail("UnsupportedFormatException while writing an initial dictionary : " + e);
+        }
+        BinaryDictionary binaryDictionary = new BinaryDictionary(dictFile.getAbsolutePath(),
+                0 /* offset */, dictFile.length(), true /* useFullEditDistance */,
+                Locale.getDefault(), TEST_LOCALE, true /* isUpdatable */);
+
+        final int unigramProbability = 100;
+        final int bigramProbability = 10;
+        binaryDictionary.addUnigramWord("aaa", unigramProbability);
+        binaryDictionary.addUnigramWord("abb", unigramProbability);
+        binaryDictionary.addUnigramWord("bcc", unigramProbability);
+        binaryDictionary.addBigramWords("aaa", "abb", bigramProbability);
+        binaryDictionary.addBigramWords("aaa", "bcc", bigramProbability);
+        binaryDictionary.addBigramWords("abb", "aaa", bigramProbability);
+        binaryDictionary.addBigramWords("abb", "bcc", bigramProbability);
+        binaryDictionary.flushWithGC();
+        binaryDictionary.close();
+
+        binaryDictionary = new BinaryDictionary(dictFile.getAbsolutePath(),
+                0 /* offset */, dictFile.length(), true /* useFullEditDistance */,
+                Locale.getDefault(), TEST_LOCALE, true /* isUpdatable */);
+        final int probability = binaryDictionary.calculateProbability(unigramProbability,
+                bigramProbability);
+        assertEquals(unigramProbability, binaryDictionary.getFrequency("aaa"));
+        assertEquals(unigramProbability, binaryDictionary.getFrequency("abb"));
+        assertEquals(unigramProbability, binaryDictionary.getFrequency("bcc"));
+        assertEquals(probability, binaryDictionary.getBigramProbability("aaa", "abb"));
+        assertEquals(probability, binaryDictionary.getBigramProbability("aaa", "bcc"));
+        assertEquals(probability, binaryDictionary.getBigramProbability("abb", "aaa"));
+        assertEquals(probability, binaryDictionary.getBigramProbability("abb", "bcc"));
+        assertEquals(false, binaryDictionary.isValidBigram("bcc", "aaa"));
+        assertEquals(false, binaryDictionary.isValidBigram("bcc", "bbc"));
+        assertEquals(false, binaryDictionary.isValidBigram("aaa", "aaa"));
+        binaryDictionary.flushWithGC();
+        binaryDictionary.close();
+
+        dictFile.delete();
+    }
+
+    // TODO: Evaluate performance of GC
+    public void testAddBigramWordsAndFlashWithGC() {
+        final int wordCount = 100;
+        final int bigramCount = 1000;
+        final int codePointSetSize = 30;
+        // TODO: Use various seeds such as a current timestamp to make this test more random.
+        final int seed = 314159265;
+
+        File dictFile = null;
+        try {
+            dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary");
+        } catch (IOException e) {
+            fail("IOException while writing an initial dictionary : " + e);
+        } catch (UnsupportedFormatException e) {
+            fail("UnsupportedFormatException while writing an initial dictionary : " + e);
+        }
+
+        BinaryDictionary binaryDictionary = new BinaryDictionary(dictFile.getAbsolutePath(),
+                0 /* offset */, dictFile.length(), true /* useFullEditDistance */,
+                Locale.getDefault(), TEST_LOCALE, true /* isUpdatable */);
+        final ArrayList<String> words = new ArrayList<String>();
+        // Test a word that isn't contained within the dictionary.
+        final Random random = new Random(seed);
+        final int[] codePointSet = CodePointUtils.generateCodePointSet(codePointSetSize, random);
+        final int[] unigramProbabilities = new int[wordCount];
+        for (int i = 0; i < wordCount; ++i) {
+            final String word = CodePointUtils.generateWord(random, codePointSet);
+            words.add(word);
+            final int unigramProbability = random.nextInt(0xFF);
+            unigramProbabilities[i] = unigramProbability;
+            binaryDictionary.addUnigramWord(word, unigramProbability);
+        }
+
+        final int[][] probabilities = new int[wordCount][wordCount];
+
+        for (int i = 0; i < wordCount; ++i) {
+            for (int j = 0; j < wordCount; ++j) {
+                probabilities[i][j] = Dictionary.NOT_A_PROBABILITY;
+            }
+        }
+
+        for (int i = 0; i < bigramCount; i++) {
+            final int word0Index = random.nextInt(wordCount);
+            final int word1Index = random.nextInt(wordCount);
+            final String word0 = words.get(word0Index);
+            final String word1 = words.get(word1Index);
+            final int bigramProbability = random.nextInt(0xF);
+            probabilities[word0Index][word1Index] = binaryDictionary.calculateProbability(
+                    unigramProbabilities[word1Index], bigramProbability);
+            binaryDictionary.addBigramWords(word0, word1, bigramProbability);
+        }
+
+        binaryDictionary.flushWithGC();
+        binaryDictionary.close();
+        binaryDictionary = new BinaryDictionary(dictFile.getAbsolutePath(),
+                0 /* offset */, dictFile.length(), true /* useFullEditDistance */,
+                Locale.getDefault(), TEST_LOCALE, true /* isUpdatable */);
+
+        for (int i = 0; i < words.size(); i++) {
+            for (int j = 0; j < words.size(); j++) {
+                assertEquals(probabilities[i][j],
+                        binaryDictionary.getBigramProbability(words.get(i), words.get(j)));
+            }
+        }
+        dictFile.delete();
+    }
+
+    public void testRandomOperetionsAndFlashWithGC() {
+        final int flashWithGCIterationCount = 50;
+        final int operationCountInEachIteration = 200;
+        final int initialUnigramCount = 100;
+        final float addUnigramProb = 0.5f;
+        final float addBigramProb = 0.8f;
+        final float removeBigramProb = 0.2f;
+        final int codePointSetSize = 30;
+        final int seed = 141421356;
+
+        final Random random = new Random(seed);
+
+        File dictFile = null;
+        try {
+            dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary");
+        } catch (IOException e) {
+            fail("IOException while writing an initial dictionary : " + e);
+        } catch (UnsupportedFormatException e) {
+            fail("UnsupportedFormatException while writing an initial dictionary : " + e);
+        }
+
+        BinaryDictionary binaryDictionary = new BinaryDictionary(dictFile.getAbsolutePath(),
+                0 /* offset */, dictFile.length(), true /* useFullEditDistance */,
+                Locale.getDefault(), TEST_LOCALE, true /* isUpdatable */);
+        final ArrayList<String> words = new ArrayList<String>();
+        final ArrayList<Pair<String, String>> bigramWords = new ArrayList<Pair<String,String>>();
+        final int[] codePointSet = CodePointUtils.generateCodePointSet(codePointSetSize, random);
+        final HashMap<String, Integer> unigramProbabilities = new HashMap<String, Integer>();
+        final HashMap<Pair<String, String>, Integer> bigramProbabilities =
+                new HashMap<Pair<String, String>, Integer>();
+        for (int i = 0; i < initialUnigramCount; ++i) {
+            final String word = CodePointUtils.generateWord(random, codePointSet);
+            words.add(word);
+            final int unigramProbability = random.nextInt(0xFF);
+            unigramProbabilities.put(word, unigramProbability);
+            binaryDictionary.addUnigramWord(word, unigramProbability);
+        }
+        binaryDictionary.flushWithGC();
+        binaryDictionary.close();
+
+        for (int gcCount = 0; gcCount < flashWithGCIterationCount; gcCount++) {
+            binaryDictionary = new BinaryDictionary(dictFile.getAbsolutePath(),
+                    0 /* offset */, dictFile.length(), true /* useFullEditDistance */,
+                    Locale.getDefault(), TEST_LOCALE, true /* isUpdatable */);
+            for (int opCount = 0; opCount < operationCountInEachIteration; opCount++) {
+                // Add unigram.
+                if (random.nextFloat() < addUnigramProb) {
+                    final String word = CodePointUtils.generateWord(random, codePointSet);
+                    words.add(word);
+                    final int unigramProbability = random.nextInt(0xFF);
+                    unigramProbabilities.put(word, unigramProbability);
+                    binaryDictionary.addUnigramWord(word, unigramProbability);
+                }
+                // Add bigram.
+                if (random.nextFloat() < addBigramProb && words.size() > 2) {
+                    final int word0Index = random.nextInt(words.size());
+                    int word1Index = random.nextInt(words.size() - 1);
+                    if (word0Index <= word1Index) {
+                        word1Index++;
+                    }
+                    final String word0 = words.get(word0Index);
+                    final String word1 = words.get(word1Index);
+                    final int bigramProbability = random.nextInt(0xF);
+                    final Pair<String, String> bigram = new Pair<String, String>(word0, word1);
+                    bigramWords.add(bigram);
+                    bigramProbabilities.put(bigram, bigramProbability);
+                    binaryDictionary.addBigramWords(word0, word1, bigramProbability);
+                }
+                // Remove bigram.
+                if (random.nextFloat() < removeBigramProb && !bigramWords.isEmpty()) {
+                    final int bigramIndex = random.nextInt(bigramWords.size());
+                    final Pair<String, String> bigram = bigramWords.get(bigramIndex);
+                    bigramWords.remove(bigramIndex);
+                    bigramProbabilities.remove(bigram);
+                    binaryDictionary.removeBigramWords(bigram.first, bigram.second);
+                }
+            }
+
+            // Test whether the all unigram operations are collectlly handled.
+            for (int i = 0; i < words.size(); i++) {
+                final String word = words.get(i);
+                final int unigramProbability = unigramProbabilities.get(word);
+                assertEquals(word, unigramProbability, binaryDictionary.getFrequency(word));
+            }
+            // Test whether the all bigram operations are collectlly handled.
+            for (int i = 0; i < bigramWords.size(); i++) {
+                final Pair<String, String> bigram = bigramWords.get(i);
+                final int unigramProbability = unigramProbabilities.get(bigram.second);
+                final int probability;
+                if (bigramProbabilities.containsKey(bigram)) {
+                    final int bigramProbability = bigramProbabilities.get(bigram);
+                    probability = binaryDictionary.calculateProbability(unigramProbability,
+                            bigramProbability);
+                } else {
+                    probability = Dictionary.NOT_A_PROBABILITY;
+                }
+                assertEquals(probability,
+                        binaryDictionary.getBigramProbability(bigram.first, bigram.second));
+            }
+            binaryDictionary.flushWithGC();
+            binaryDictionary.close();
+        }
+
+        dictFile.delete();
+    }
+
+    public void testAddManyUnigramsAndFlushWithGC() {
+        final int flashWithGCIterationCount = 3;
+        final int codePointSetSize = 50;
+        final int seed = 22360679;
+
+        final Random random = new Random(seed);
+
+        File dictFile = null;
+        try {
+            dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary");
+        } catch (IOException e) {
+            fail("IOException while writing an initial dictionary : " + e);
+        } catch (UnsupportedFormatException e) {
+            fail("UnsupportedFormatException while writing an initial dictionary : " + e);
+        }
+
+        final ArrayList<String> words = new ArrayList<String>();
+        final HashMap<String, Integer> unigramProbabilities = new HashMap<String, Integer>();
+        final int[] codePointSet = CodePointUtils.generateCodePointSet(codePointSetSize, random);
+
+        BinaryDictionary binaryDictionary;
+        for (int i = 0; i < flashWithGCIterationCount; i++) {
+            binaryDictionary = new BinaryDictionary(dictFile.getAbsolutePath(),
+                    0 /* offset */, dictFile.length(), true /* useFullEditDistance */,
+                    Locale.getDefault(), TEST_LOCALE, true /* isUpdatable */);
+            while(!binaryDictionary.needsToRunGC()) {
+                final String word = CodePointUtils.generateWord(random, codePointSet);
+                words.add(word);
+                final int unigramProbability = random.nextInt(0xFF);
+                unigramProbabilities.put(word, unigramProbability);
+                binaryDictionary.addUnigramWord(word, unigramProbability);
+            }
+
+            for (int j = 0; j < words.size(); j++) {
+                final String word = words.get(j);
+                final int unigramProbability = unigramProbabilities.get(word);
+                assertEquals(word, unigramProbability, binaryDictionary.getFrequency(word));
+            }
+
+            binaryDictionary.flushWithGC();
+            binaryDictionary.close();
+        }
+
+        dictFile.delete();
+    }
+}
diff --git a/tests/src/com/android/inputmethod/latin/ExpandableDictionaryTests.java b/tests/src/com/android/inputmethod/latin/ExpandableDictionaryTests.java
new file mode 100644
index 0000000..ecf3af7
--- /dev/null
+++ b/tests/src/com/android/inputmethod/latin/ExpandableDictionaryTests.java
@@ -0,0 +1,55 @@
+/*
+ * 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;
+
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+/**
+ * Unit test for ExpandableDictionary
+ */
+@SmallTest
+public class ExpandableDictionaryTests extends AndroidTestCase {
+
+    private final static int UNIGRAM_FREQ = 50;
+
+    public void testAddWordAndGetWordFrequency() {
+        final ExpandableDictionary dict = new ExpandableDictionary(Dictionary.TYPE_USER);
+
+        // Add words
+        dict.addWord("abcde", "abcde", UNIGRAM_FREQ);
+        dict.addWord("abcef", null, UNIGRAM_FREQ + 1);
+
+        // Check words
+        assertFalse(dict.isValidWord("abcde"));
+        assertEquals(UNIGRAM_FREQ, dict.getWordFrequency("abcde"));
+        assertTrue(dict.isValidWord("abcef"));
+        assertEquals(UNIGRAM_FREQ+1, dict.getWordFrequency("abcef"));
+
+        dict.addWord("abc", null, UNIGRAM_FREQ + 2);
+        assertTrue(dict.isValidWord("abc"));
+        assertEquals(UNIGRAM_FREQ + 2, dict.getWordFrequency("abc"));
+
+        // Add existing word with lower frequency
+        dict.addWord("abc", null, UNIGRAM_FREQ);
+        assertEquals(UNIGRAM_FREQ + 2, dict.getWordFrequency("abc"));
+
+        // Add existing word with higher frequency
+        dict.addWord("abc", null, UNIGRAM_FREQ + 3);
+        assertEquals(UNIGRAM_FREQ + 3, dict.getWordFrequency("abc"));
+    }
+}
diff --git a/tests/src/com/android/inputmethod/latin/FusionDictionaryTests.java b/tests/src/com/android/inputmethod/latin/FusionDictionaryTests.java
index 65dfd2d..cadd0f8 100644
--- a/tests/src/com/android/inputmethod/latin/FusionDictionaryTests.java
+++ b/tests/src/com/android/inputmethod/latin/FusionDictionaryTests.java
@@ -20,7 +20,7 @@
 import android.test.suitebuilder.annotation.SmallTest;
 
 import com.android.inputmethod.latin.makedict.FusionDictionary;
-import com.android.inputmethod.latin.makedict.FusionDictionary.Node;
+import com.android.inputmethod.latin.makedict.FusionDictionary.PtNodeArray;
 
 import java.util.HashMap;
 
@@ -30,21 +30,21 @@
 @SmallTest
 public class FusionDictionaryTests extends AndroidTestCase {
     public void testFindWordInTree() {
-        FusionDictionary dict = new FusionDictionary(new Node(),
+        FusionDictionary dict = new FusionDictionary(new PtNodeArray(),
                 new FusionDictionary.DictionaryOptions(new HashMap<String,String>(), false, false));
 
         dict.add("abc", 10, null, false /* isNotAWord */);
-        assertNull(FusionDictionary.findWordInTree(dict.mRoot, "aaa"));
-        assertNotNull(FusionDictionary.findWordInTree(dict.mRoot, "abc"));
+        assertNull(FusionDictionary.findWordInTree(dict.mRootNodeArray, "aaa"));
+        assertNotNull(FusionDictionary.findWordInTree(dict.mRootNodeArray, "abc"));
 
         dict.add("aa", 10, null, false /* isNotAWord */);
-        assertNull(FusionDictionary.findWordInTree(dict.mRoot, "aaa"));
-        assertNotNull(FusionDictionary.findWordInTree(dict.mRoot, "aa"));
+        assertNull(FusionDictionary.findWordInTree(dict.mRootNodeArray, "aaa"));
+        assertNotNull(FusionDictionary.findWordInTree(dict.mRootNodeArray, "aa"));
 
         dict.add("babcd", 10, null, false /* isNotAWord */);
         dict.add("bacde", 10, null, false /* isNotAWord */);
-        assertNull(FusionDictionary.findWordInTree(dict.mRoot, "ba"));
-        assertNotNull(FusionDictionary.findWordInTree(dict.mRoot, "babcd"));
-        assertNotNull(FusionDictionary.findWordInTree(dict.mRoot, "bacde"));
+        assertNull(FusionDictionary.findWordInTree(dict.mRootNodeArray, "ba"));
+        assertNotNull(FusionDictionary.findWordInTree(dict.mRootNodeArray, "babcd"));
+        assertNotNull(FusionDictionary.findWordInTree(dict.mRootNodeArray, "bacde"));
     }
 }
diff --git a/tests/src/com/android/inputmethod/latin/InputLogicTests.java b/tests/src/com/android/inputmethod/latin/InputLogicTests.java
index d27a7a9..cc2569f 100644
--- a/tests/src/com/android/inputmethod/latin/InputLogicTests.java
+++ b/tests/src/com/android/inputmethod/latin/InputLogicTests.java
@@ -17,6 +17,7 @@
 package com.android.inputmethod.latin;
 
 import android.test.suitebuilder.annotation.LargeTest;
+import android.view.inputmethod.BaseInputConnection;
 
 @LargeTest
 public class InputLogicTests extends InputTestsBase {
@@ -133,6 +134,13 @@
         assertEquals("simple auto-correct", EXPECTED_RESULT, mEditText.getText().toString());
     }
 
+    public void testAutoCorrectWithQuote() {
+        final String STRING_TO_TYPE = "didn' ";
+        final String EXPECTED_RESULT = "didn't ";
+        type(STRING_TO_TYPE);
+        assertEquals("auto-correct with quote", EXPECTED_RESULT, mEditText.getText().toString());
+    }
+
     public void testAutoCorrectWithPeriod() {
         final String STRING_TO_TYPE = "tgis.";
         final String EXPECTED_RESULT = "this.";
@@ -290,5 +298,47 @@
         }
         assertEquals("delete whole composing word", "", mEditText.getText().toString());
     }
+
+    public void testResumeSuggestionOnBackspace() {
+        final String WORD_TO_TYPE = "and this ";
+        type(WORD_TO_TYPE);
+        assertEquals("resume suggestion on backspace", -1,
+                BaseInputConnection.getComposingSpanStart(mEditText.getText()));
+        assertEquals("resume suggestion on backspace", -1,
+                BaseInputConnection.getComposingSpanEnd(mEditText.getText()));
+        type(Constants.CODE_DELETE);
+        assertEquals("resume suggestion on backspace", 4,
+                BaseInputConnection.getComposingSpanStart(mEditText.getText()));
+        assertEquals("resume suggestion on backspace", 8,
+                BaseInputConnection.getComposingSpanEnd(mEditText.getText()));
+    }
+
+    private void helperTestComposing(final String wordToType, final boolean shouldBeComposing) {
+        mEditText.setText("");
+        type(wordToType);
+        assertEquals("start composing inside text", shouldBeComposing ? 0 : -1,
+                BaseInputConnection.getComposingSpanStart(mEditText.getText()));
+        assertEquals("start composing inside text", shouldBeComposing ? wordToType.length() : -1,
+                BaseInputConnection.getComposingSpanEnd(mEditText.getText()));
+    }
+
+    public void testStartComposing() {
+        // Should start composing on a letter
+        helperTestComposing("a", true);
+        type("  "); // To reset the composing state
+        // Should not start composing on quote
+        helperTestComposing("'", false);
+        type("  ");
+        helperTestComposing("'-", false);
+        type("  ");
+        // Should not start composing on dash
+        helperTestComposing("-", false);
+        type("  ");
+        helperTestComposing("-'", false);
+        type("  ");
+        helperTestComposing("a-", true);
+        type("  ");
+        helperTestComposing("a'", true);
+    }
     // TODO: Add some tests for non-BMP characters
 }
diff --git a/tests/src/com/android/inputmethod/latin/InputLogicTestsLanguageWithoutSpaces.java b/tests/src/com/android/inputmethod/latin/InputLogicTestsLanguageWithoutSpaces.java
new file mode 100644
index 0000000..0f0ebaf
--- /dev/null
+++ b/tests/src/com/android/inputmethod/latin/InputLogicTestsLanguageWithoutSpaces.java
@@ -0,0 +1,105 @@
+/*
+ * 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;
+
+import android.test.suitebuilder.annotation.LargeTest;
+import android.view.inputmethod.BaseInputConnection;
+
+import com.android.inputmethod.latin.suggestions.SuggestionStripView;
+
+@LargeTest
+public class InputLogicTestsLanguageWithoutSpaces extends InputTestsBase {
+    public void testAutoCorrectForLanguageWithoutSpaces() {
+        final String STRING_TO_TYPE = "tgis is";
+        final String EXPECTED_RESULT = "thisis";
+        changeKeyboardLocaleAndDictLocale("th", "en_US");
+        type(STRING_TO_TYPE);
+        assertEquals("simple auto-correct for language without spaces", EXPECTED_RESULT,
+                mEditText.getText().toString());
+    }
+
+    public void testRevertAutoCorrectForLanguageWithoutSpaces() {
+        final String STRING_TO_TYPE = "tgis ";
+        final String EXPECTED_INTERMEDIATE_RESULT = "this";
+        final String EXPECTED_FINAL_RESULT = "tgis";
+        changeKeyboardLocaleAndDictLocale("th", "en_US");
+        type(STRING_TO_TYPE);
+        assertEquals("simple auto-correct for language without spaces",
+                EXPECTED_INTERMEDIATE_RESULT, mEditText.getText().toString());
+        type(Constants.CODE_DELETE);
+        assertEquals("simple auto-correct for language without spaces",
+                EXPECTED_FINAL_RESULT, mEditText.getText().toString());
+        // Check we are back to composing the word
+        assertEquals("don't resume suggestion on backspace", 0,
+                BaseInputConnection.getComposingSpanStart(mEditText.getText()));
+        assertEquals("don't resume suggestion on backspace", 4,
+                BaseInputConnection.getComposingSpanEnd(mEditText.getText()));
+    }
+
+    public void testDontResumeSuggestionOnBackspace() {
+        final String WORD_TO_TYPE = "and this ";
+        changeKeyboardLocaleAndDictLocale("th", "en_US");
+        type(WORD_TO_TYPE);
+        assertEquals("don't resume suggestion on backspace", -1,
+                BaseInputConnection.getComposingSpanStart(mEditText.getText()));
+        assertEquals("don't resume suggestion on backspace", -1,
+                BaseInputConnection.getComposingSpanEnd(mEditText.getText()));
+        type(" ");
+        type(Constants.CODE_DELETE);
+        assertEquals("don't resume suggestion on backspace", -1,
+                BaseInputConnection.getComposingSpanStart(mEditText.getText()));
+        assertEquals("don't resume suggestion on backspace", -1,
+                BaseInputConnection.getComposingSpanEnd(mEditText.getText()));
+    }
+
+    public void testStartComposingInsideText() {
+        final String WORD_TO_TYPE = "abcdefgh ";
+        final int typedLength = WORD_TO_TYPE.length() - 1; // -1 because space gets eaten
+        final int CURSOR_POS = 4;
+        changeKeyboardLocaleAndDictLocale("th", "en_US");
+        type(WORD_TO_TYPE);
+        mLatinIME.onUpdateSelection(0, 0, typedLength, typedLength, -1, -1);
+        mInputConnection.setSelection(CURSOR_POS, CURSOR_POS);
+        mLatinIME.onUpdateSelection(typedLength, typedLength,
+                CURSOR_POS, CURSOR_POS, -1, -1);
+        sleep(DELAY_TO_WAIT_FOR_PREDICTIONS);
+        runMessages();
+        assertEquals("start composing inside text", -1,
+                BaseInputConnection.getComposingSpanStart(mEditText.getText()));
+        assertEquals("start composing inside text", -1,
+                BaseInputConnection.getComposingSpanEnd(mEditText.getText()));
+        type("xxxx");
+        assertEquals("start composing inside text", 4,
+                BaseInputConnection.getComposingSpanStart(mEditText.getText()));
+        assertEquals("start composing inside text", 8,
+                BaseInputConnection.getComposingSpanEnd(mEditText.getText()));
+    }
+
+    public void testPredictions() {
+        final String WORD_TO_TYPE = "Barack ";
+        changeKeyboardLocaleAndDictLocale("th", "en_US");
+        type(WORD_TO_TYPE);
+        sleep(DELAY_TO_WAIT_FOR_PREDICTIONS);
+        runMessages();
+        // Make sure there is no space
+        assertEquals("predictions in lang without spaces", "Barack",
+                mEditText.getText().toString());
+        // Test the first prediction is displayed
+        assertEquals("predictions in lang without spaces", "Obama",
+                mLatinIME.getFirstSuggestedWord());
+    }
+}
diff --git a/tests/src/com/android/inputmethod/latin/InputPointersTests.java b/tests/src/com/android/inputmethod/latin/InputPointersTests.java
index f0b6acc..5095f96 100644
--- a/tests/src/com/android/inputmethod/latin/InputPointersTests.java
+++ b/tests/src/com/android/inputmethod/latin/InputPointersTests.java
@@ -244,4 +244,20 @@
                     expecteds[i + expectedPos], actuals[i + actualPos]);
         }
     }
+
+    public void testShift() {
+        final InputPointers src = new InputPointers(DEFAULT_CAPACITY);
+        final int limit = 100;
+        final int shiftAmount = 20;
+        for (int i = 0; i < limit; i++) {
+            src.addPointer(i, i * 2, i * 3, i * 4);
+        }
+        src.shift(shiftAmount);
+        for (int i = 0; i < limit - shiftAmount; ++i) {
+            assertEquals("xCoordinates at " + i, i + shiftAmount, src.getXCoordinates()[i]);
+            assertEquals("yCoordinates at " + i, (i + shiftAmount) * 2, src.getYCoordinates()[i]);
+            assertEquals("pointerIds at " + i, (i + shiftAmount) * 3, src.getPointerIds()[i]);
+            assertEquals("times at " + i, (i + shiftAmount) * 4, src.getTimes()[i]);
+        }
+    }
 }
diff --git a/tests/src/com/android/inputmethod/latin/InputTestsBase.java b/tests/src/com/android/inputmethod/latin/InputTestsBase.java
index eb4f706..234bb1b 100644
--- a/tests/src/com/android/inputmethod/latin/InputTestsBase.java
+++ b/tests/src/com/android/inputmethod/latin/InputTestsBase.java
@@ -44,8 +44,10 @@
 
     private static final String PREF_DEBUG_MODE = "debug_mode";
 
-    // The message that sets the underline is posted with a 100 ms delay
+    // The message that sets the underline is posted with a 200 ms delay
     protected static final int DELAY_TO_WAIT_FOR_UNDERLINE = 200;
+    // The message that sets predictions is posted with a 200 ms delay
+    protected static final int DELAY_TO_WAIT_FOR_PREDICTIONS = 200;
 
     protected LatinIME mLatinIME;
     protected Keyboard mKeyboard;
@@ -204,17 +206,16 @@
         // view and only delegates to the parts of the code that care. So we don't include them here
         // to keep these tests as pinpoint as possible and avoid bringing it too many dependencies,
         // but keep them in mind if something breaks. Commenting them out as is should work.
-        //mLatinIME.onPressKey(codePoint);
-        for (final Key key : mKeyboard.mKeys) {
-            if (key.mCode == codePoint) {
-                final int x = key.mX + key.mWidth / 2;
-                final int y = key.mY + key.mHeight / 2;
-                mLatinIME.onCodeInput(codePoint, x, y);
-                return;
-            }
+        //mLatinIME.onPressKey(codePoint, 0 /* repeatCount */, true /* isSinglePointer */);
+        final Key key = mKeyboard.getKey(codePoint);
+        if (key != null) {
+            final int x = key.getX() + key.getWidth() / 2;
+            final int y = key.getY() + key.getHeight() / 2;
+            mLatinIME.onCodeInput(codePoint, x, y);
+            return;
         }
         mLatinIME.onCodeInput(codePoint, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
-        //mLatinIME.onReleaseKey(codePoint, false);
+        //mLatinIME.onReleaseKey(codePoint, false /* withSliding */);
     }
 
     protected void type(final String stringToType) {
@@ -234,9 +235,6 @@
                 --remainingAttempts;
             }
         }
-        if (!mLatinIME.hasMainDictionary()) {
-            throw new RuntimeException("Can't initialize the main dictionary");
-        }
     }
 
     protected void changeLanguage(final String locale) {
@@ -248,9 +246,21 @@
         waitForDictionaryToBeLoaded();
     }
 
+    protected void changeKeyboardLocaleAndDictLocale(final String keyboardLocale,
+            final String dictLocale) {
+        changeLanguage(keyboardLocale);
+        if (!keyboardLocale.equals(dictLocale)) {
+            mLatinIME.replaceMainDictionaryForTest(
+                    LocaleUtils.constructLocaleFromString(dictLocale));
+        }
+        waitForDictionaryToBeLoaded();
+    }
+
     protected void pickSuggestionManually(final int index, final String suggestion) {
         mLatinIME.pickSuggestionManually(index, new SuggestedWordInfo(suggestion, 1,
-                SuggestedWordInfo.KIND_CORRECTION, "main"));
+                SuggestedWordInfo.KIND_CORRECTION, null /* sourceDict */,
+                SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */,
+                SuggestedWordInfo.NOT_A_CONFIDENCE /* autoCommitFirstWordConfidence */));
     }
 
     // Helper to avoid writing the try{}catch block each time
diff --git a/tests/src/com/android/inputmethod/latin/SuggestedWordsTests.java b/tests/src/com/android/inputmethod/latin/SuggestedWordsTests.java
index 8d0fe01..a594baf 100644
--- a/tests/src/com/android/inputmethod/latin/SuggestedWordsTests.java
+++ b/tests/src/com/android/inputmethod/latin/SuggestedWordsTests.java
@@ -34,9 +34,14 @@
         final int NUMBER_OF_ADDED_SUGGESTIONS = 5;
         final ArrayList<SuggestedWordInfo> list = CollectionUtils.newArrayList();
         list.add(new SuggestedWordInfo(TYPED_WORD, TYPED_WORD_FREQ,
-                SuggestedWordInfo.KIND_TYPED, ""));
+                SuggestedWordInfo.KIND_TYPED, null /* sourceDict */,
+                SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */,
+                SuggestedWordInfo.NOT_A_CONFIDENCE /* autoCommitFirstWordConfidence */));
         for (int i = 0; i < NUMBER_OF_ADDED_SUGGESTIONS; ++i) {
-            list.add(new SuggestedWordInfo("" + i, 1, SuggestedWordInfo.KIND_CORRECTION, ""));
+            list.add(new SuggestedWordInfo("" + i, 1, SuggestedWordInfo.KIND_CORRECTION,
+                    null /* sourceDict */,
+                    SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */,
+                    SuggestedWordInfo.NOT_A_CONFIDENCE /* autoCommitFirstWordConfidence */));
         }
 
         final SuggestedWords words = new SuggestedWords(
@@ -59,4 +64,37 @@
         assertEquals("0", wordsWithoutTyped.getWord(0));
         assertEquals(SuggestedWordInfo.KIND_CORRECTION, wordsWithoutTyped.getInfo(0).mKind);
     }
+
+    // Helper for testGetTransformedWordInfo
+    private SuggestedWordInfo createWordInfo(final String s) {
+        // Use 100 as the frequency because the numerical value does not matter as
+        // long as it's > 1 and < INT_MAX.
+        return new SuggestedWordInfo(s, 100,
+                SuggestedWordInfo.KIND_TYPED, null /* sourceDict */,
+                SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */,
+                SuggestedWordInfo.NOT_A_CONFIDENCE /* autoCommitFirstWordConfidence */);
+    }
+
+    // Helper for testGetTransformedWordInfo
+    private SuggestedWordInfo transformWordInfo(final String info,
+            final int trailingSingleQuotesCount) {
+        return Suggest.getTransformedSuggestedWordInfo(createWordInfo(info),
+                Locale.ENGLISH, false /* isAllUpperCase */, false /* isFirstCharCapitalized */,
+                trailingSingleQuotesCount);
+    }
+
+    public void testGetTransformedSuggestedWordInfo() {
+        SuggestedWordInfo result = transformWordInfo("word", 0);
+        assertEquals(result.mWord, "word");
+        result = transformWordInfo("word", 1);
+        assertEquals(result.mWord, "word'");
+        result = transformWordInfo("word", 3);
+        assertEquals(result.mWord, "word'''");
+        result = transformWordInfo("didn't", 0);
+        assertEquals(result.mWord, "didn't");
+        result = transformWordInfo("didn't", 1);
+        assertEquals(result.mWord, "didn't");
+        result = transformWordInfo("didn't", 3);
+        assertEquals(result.mWord, "didn't''");
+    }
 }
diff --git a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderEncoderTests.java b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderEncoderTests.java
new file mode 100644
index 0000000..a4d9426
--- /dev/null
+++ b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderEncoderTests.java
@@ -0,0 +1,684 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.makedict;
+
+import android.test.AndroidTestCase;
+import android.test.MoreAsserts;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.util.Log;
+import android.util.SparseArray;
+
+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;
+import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
+import com.android.inputmethod.latin.utils.ByteArrayDictBuffer;
+import com.android.inputmethod.latin.utils.CollectionUtils;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map.Entry;
+import java.util.Random;
+import java.util.Set;
+import java.util.TreeMap;
+
+/**
+ * Unit tests for BinaryDictDecoderUtils and BinaryDictEncoderUtils.
+ */
+@LargeTest
+public class BinaryDictDecoderEncoderTests extends AndroidTestCase {
+    private static final String TAG = BinaryDictDecoderEncoderTests.class.getSimpleName();
+    private static final int DEFAULT_MAX_UNIGRAMS = 100;
+    private static final int DEFAULT_CODE_POINT_SET_SIZE = 50;
+    private static final int UNIGRAM_FREQ = 10;
+    private static final int BIGRAM_FREQ = 50;
+    private static final int TOLERANCE_OF_BIGRAM_FREQ = 5;
+    private static final int NUM_OF_NODES_HAVING_SHORTCUTS = 50;
+    private static final int NUM_OF_SHORTCUTS = 5;
+
+    private static final int USE_BYTE_ARRAY = 1;
+    private static final int USE_BYTE_BUFFER = 2;
+
+    private static final ArrayList<String> sWords = CollectionUtils.newArrayList();
+    private static final SparseArray<List<Integer>> sEmptyBigrams =
+            CollectionUtils.newSparseArray();
+    private static final SparseArray<List<Integer>> sStarBigrams = CollectionUtils.newSparseArray();
+    private static final SparseArray<List<Integer>> sChainBigrams =
+            CollectionUtils.newSparseArray();
+    private static final HashMap<String, List<String>> sShortcuts = CollectionUtils.newHashMap();
+
+    private static final FormatSpec.FormatOptions VERSION2 = new FormatSpec.FormatOptions(2);
+    private static final FormatSpec.FormatOptions VERSION3_WITHOUT_DYNAMIC_UPDATE =
+            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";
+
+    public BinaryDictDecoderEncoderTests() {
+        this(System.currentTimeMillis(), DEFAULT_MAX_UNIGRAMS);
+    }
+
+    public BinaryDictDecoderEncoderTests(final long seed, final int maxUnigrams) {
+        super();
+        Log.e(TAG, "Testing dictionary: seed is " + seed);
+        final Random random = new Random(seed);
+        sWords.clear();
+        final int[] codePointSet = CodePointUtils.generateCodePointSet(DEFAULT_CODE_POINT_SET_SIZE,
+                random);
+        generateWords(maxUnigrams, random, codePointSet);
+
+        for (int i = 0; i < sWords.size(); ++i) {
+            sChainBigrams.put(i, new ArrayList<Integer>());
+            if (i > 0) {
+                sChainBigrams.get(i - 1).add(i);
+            }
+        }
+
+        sStarBigrams.put(0, new ArrayList<Integer>());
+        for (int i = 1; i < sWords.size(); ++i) {
+            sStarBigrams.get(0).add(i);
+        }
+
+        sShortcuts.clear();
+        for (int i = 0; i < NUM_OF_NODES_HAVING_SHORTCUTS; ++i) {
+            final int from = Math.abs(random.nextInt()) % sWords.size();
+            sShortcuts.put(sWords.get(from), new ArrayList<String>());
+            for (int j = 0; j < NUM_OF_SHORTCUTS; ++j) {
+                final int to = Math.abs(random.nextInt()) % sWords.size();
+                sShortcuts.get(sWords.get(from)).add(sWords.get(to));
+            }
+        }
+    }
+
+    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) {
+            wordSet.add(CodePointUtils.generateWord(random, codePointSet));
+        }
+        sWords.addAll(wordSet);
+    }
+
+    /**
+     * Adds unigrams to the dictionary.
+     */
+    private void addUnigrams(final int number, final FusionDictionary dict,
+            final List<String> words, final HashMap<String, List<String>> shortcutMap) {
+        for (int i = 0; i < number; ++i) {
+            final String word = words.get(i);
+            final ArrayList<WeightedString> shortcuts = CollectionUtils.newArrayList();
+            if (shortcutMap != null && shortcutMap.containsKey(word)) {
+                for (final String shortcut : shortcutMap.get(word)) {
+                    shortcuts.add(new WeightedString(shortcut, UNIGRAM_FREQ));
+                }
+            }
+            dict.add(word, UNIGRAM_FREQ, (shortcutMap == null) ? null : shortcuts,
+                    false /* isNotAWord */);
+        }
+    }
+
+    private void addBigrams(final FusionDictionary dict,
+            final List<String> words,
+            final SparseArray<List<Integer>> bigrams) {
+        for (int i = 0; i < bigrams.size(); ++i) {
+            final int w1 = bigrams.keyAt(i);
+            for (int w2 : bigrams.valueAt(i)) {
+                dict.setBigram(words.get(w1), words.get(w2), BIGRAM_FREQ);
+            }
+        }
+    }
+
+//    The following is useful to dump the dictionary into a textual file, but it can't compile
+//    on-device, so it's commented out.
+//    private void dumpToCombinedFileForDebug(final FusionDictionary dict, final String filename)
+//            throws IOException {
+//        com.android.inputmethod.latin.dicttool.CombinedInputOutput.writeDictionaryCombined(
+//                new java.io.FileWriter(new File(filename)), dict);
+//    }
+
+    private long timeWritingDictToFile(final File file, final FusionDictionary dict,
+            final FormatSpec.FormatOptions formatOptions) {
+
+        long now = -1, diff = -1;
+
+        try {
+            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
+            // function above
+            // dumpToCombinedFileForDebug(file, "/tmp/foo");
+            dictEncoder.writeDictionary(dict, formatOptions);
+            diff = System.currentTimeMillis() - now;
+        } catch (IOException e) {
+            Log.e(TAG, "IO exception while writing file", e);
+        } catch (UnsupportedFormatException e) {
+            Log.e(TAG, "UnsupportedFormatException", e);
+        }
+
+        return diff;
+    }
+
+    private void checkDictionary(final FusionDictionary dict, final List<String> words,
+            final SparseArray<List<Integer>> bigrams,
+            final HashMap<String, List<String>> shortcutMap) {
+        assertNotNull(dict);
+
+        // check unigram
+        for (final String word : words) {
+            final PtNode ptNode = FusionDictionary.findWordInTree(dict.mRootNodeArray, word);
+            assertNotNull(ptNode);
+        }
+
+        // check bigram
+        for (int i = 0; i < bigrams.size(); ++i) {
+            final int w1 = bigrams.keyAt(i);
+            for (final int w2 : bigrams.valueAt(i)) {
+                final PtNode ptNode = FusionDictionary.findWordInTree(dict.mRootNodeArray,
+                        words.get(w1));
+                assertNotNull(words.get(w1) + "," + words.get(w2), ptNode.getBigram(words.get(w2)));
+            }
+        }
+
+        // check shortcut
+        if (shortcutMap != null) {
+            for (final Entry<String, List<String>> entry : shortcutMap.entrySet()) {
+                assertTrue(words.contains(entry.getKey()));
+                final PtNode ptNode = FusionDictionary.findWordInTree(dict.mRootNodeArray,
+                        entry.getKey());
+                for (final String word : entry.getValue()) {
+                    assertNotNull("shortcut not found: " + entry.getKey() + ", " + word,
+                            ptNode.getShortcut(word));
+                }
+            }
+        }
+    }
+
+    private String outputOptions(final int bufferType,
+            final FormatSpec.FormatOptions formatOptions) {
+        String result = " : buffer type = "
+                + ((bufferType == USE_BYTE_BUFFER) ? "byte buffer" : "byte array");
+        result += " : version = " + formatOptions.mVersion;
+        return result + ", supportsDynamicUpdate = " + formatOptions.mSupportsDynamicUpdate;
+    }
+
+    private DictionaryOptions getDictionaryOptions(final String id, final String version) {
+        final DictionaryOptions options = new DictionaryOptions(new HashMap<String, String>(),
+                false, false);
+        options.mAttributes.put("version", version);
+        options.mAttributes.put("dictionary", id);
+        return options;
+    }
+
+    private File setUpDictionaryFile(final String name, final String version) {
+        File file = null;
+        try {
+            file = new File(getContext().getCacheDir(), name + "." + version
+                    + TEST_DICT_FILE_EXTENSION);
+            file.createNewFile();
+        } catch (IOException e) {
+            // do nothing
+        }
+        assertTrue("Failed to create the dictionary file.", file.exists());
+        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 FormatOptions formatOptions, final DictionaryOptions dictOptions) {
+        long now, diff = -1;
+
+        FusionDictionary dict = null;
+        try {
+            final DictDecoder dictDecoder = getDictDecoder(file, bufferType, formatOptions,
+                    dictOptions);
+            now = System.currentTimeMillis();
+            dict = dictDecoder.readDictionaryBinary(null, false /* deleteDictIfBroken */);
+            diff  = System.currentTimeMillis() - now;
+        } catch (IOException e) {
+            Log.e(TAG, "IOException while reading dictionary", e);
+        } catch (UnsupportedFormatException e) {
+            Log.e(TAG, "Unsupported format", e);
+        }
+
+        checkDictionary(dict, words, bigrams, shortcutMap);
+        return diff;
+    }
+
+    // Tests for readDictionaryBinary and writeDictionaryBinary
+    private String runReadAndWrite(final List<String> words,
+            final SparseArray<List<Integer>> bigrams, final HashMap<String, List<String>> shortcuts,
+            final int bufferType, final FormatSpec.FormatOptions formatOptions,
+            final String message) {
+
+        final String dictName = "runReadAndWrite";
+        final String dictVersion = Long.toString(System.currentTimeMillis());
+        final File file = setUpDictionaryFile(dictName, dictVersion);
+
+        final FusionDictionary dict = new FusionDictionary(new PtNodeArray(),
+                getDictionaryOptions(dictName, dictVersion));
+        addUnigrams(words.size(), dict, words, shortcuts);
+        addBigrams(dict, words, bigrams);
+        checkDictionary(dict, words, bigrams, shortcuts);
+
+        final long write = timeWritingDictToFile(file, dict, formatOptions);
+        final long read = timeReadingAndCheckDict(file, words, bigrams, shortcuts, bufferType,
+                formatOptions, dict.mOptions);
+
+        return "PROF: read=" + read + "ms, write=" + write + "ms :" + message
+                + " : " + outputOptions(bufferType, formatOptions);
+    }
+
+    private void runReadAndWriteTests(final List<String> results, final int bufferType,
+            final FormatSpec.FormatOptions formatOptions) {
+        results.add(runReadAndWrite(sWords, sEmptyBigrams, null /* shortcuts */, bufferType,
+                formatOptions, "unigram"));
+        results.add(runReadAndWrite(sWords, sChainBigrams, null /* shortcuts */, bufferType,
+                formatOptions, "chain"));
+        results.add(runReadAndWrite(sWords, sStarBigrams, null /* shortcuts */, bufferType,
+                formatOptions, "star"));
+        results.add(runReadAndWrite(sWords, sEmptyBigrams, sShortcuts, bufferType, formatOptions,
+                "unigram with shortcuts"));
+        results.add(runReadAndWrite(sWords, sChainBigrams, sShortcuts, bufferType, formatOptions,
+                "chain with shortcuts"));
+        results.add(runReadAndWrite(sWords, sStarBigrams, sShortcuts, bufferType, formatOptions,
+                "star with shortcuts"));
+    }
+
+    // Unit test for CharEncoding.readString and CharEncoding.writeString.
+    public void testCharEncoding() {
+        // the max length of a word in sWords is less than 50.
+        // See generateWords.
+        final byte[] buffer = new byte[50 * 3];
+        final DictBuffer dictBuffer = new ByteArrayDictBuffer(buffer);
+        for (final String word : sWords) {
+            Log.d("testReadAndWriteString", "write : " + word);
+            Arrays.fill(buffer, (byte)0);
+            CharEncoding.writeString(buffer, 0, word);
+            dictBuffer.position(0);
+            final String str = CharEncoding.readString(dictBuffer);
+            assertEquals(word, str);
+        }
+    }
+
+    public void testReadAndWriteWithByteBuffer() {
+        final List<String> results = CollectionUtils.newArrayList();
+
+        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);
+        }
+    }
+
+    public void testReadAndWriteWithByteArray() {
+        final List<String> results = CollectionUtils.newArrayList();
+
+        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);
+        }
+    }
+
+    // Tests for readUnigramsAndBigramsBinary
+
+    private void checkWordMap(final List<String> expectedWords,
+            final SparseArray<List<Integer>> expectedBigrams,
+            final TreeMap<Integer, String> resultWords,
+            final TreeMap<Integer, Integer> resultFrequencies,
+            final TreeMap<Integer, ArrayList<PendingAttribute>> resultBigrams) {
+        // check unigrams
+        final Set<String> actualWordsSet = new HashSet<String>(resultWords.values());
+        final Set<String> expectedWordsSet = new HashSet<String>(expectedWords);
+        assertEquals(actualWordsSet, expectedWordsSet);
+
+        for (int freq : resultFrequencies.values()) {
+            assertEquals(freq, UNIGRAM_FREQ);
+        }
+
+        // check bigrams
+        final HashMap<String, List<String>> expBigrams = new HashMap<String, List<String>>();
+        for (int i = 0; i < expectedBigrams.size(); ++i) {
+            final String word1 = expectedWords.get(expectedBigrams.keyAt(i));
+            for (int w2 : expectedBigrams.valueAt(i)) {
+                if (expBigrams.get(word1) == null) {
+                    expBigrams.put(word1, new ArrayList<String>());
+                }
+                expBigrams.get(word1).add(expectedWords.get(w2));
+            }
+        }
+
+        final HashMap<String, List<String>> actBigrams = new HashMap<String, List<String>>();
+        for (Entry<Integer, ArrayList<PendingAttribute>> entry : resultBigrams.entrySet()) {
+            final String word1 = resultWords.get(entry.getKey());
+            final int unigramFreq = resultFrequencies.get(entry.getKey());
+            for (PendingAttribute attr : entry.getValue()) {
+                final String word2 = resultWords.get(attr.mAddress);
+                if (actBigrams.get(word1) == null) {
+                    actBigrams.put(word1, new ArrayList<String>());
+                }
+                actBigrams.get(word1).add(word2);
+
+                final int bigramFreq = BinaryDictIOUtils.reconstructBigramFrequency(
+                        unigramFreq, attr.mFrequency);
+                assertTrue(Math.abs(bigramFreq - BIGRAM_FREQ) < TOLERANCE_OF_BIGRAM_FREQ);
+            }
+        }
+
+        assertEquals(actBigrams, expBigrams);
+    }
+
+    private long timeAndCheckReadUnigramsAndBigramsBinary(final File file, final List<String> words,
+            final SparseArray<List<Integer>> bigrams, final int bufferType,
+            final FormatOptions formatOptions, final DictionaryOptions dictOptions) {
+        FileInputStream inStream = null;
+
+        final TreeMap<Integer, String> resultWords = CollectionUtils.newTreeMap();
+        final TreeMap<Integer, ArrayList<PendingAttribute>> resultBigrams =
+                CollectionUtils.newTreeMap();
+        final TreeMap<Integer, Integer> resultFreqs = CollectionUtils.newTreeMap();
+
+        long now = -1, diff = -1;
+        try {
+            final DictDecoder dictDecoder = getDictDecoder(file, bufferType, formatOptions,
+                    dictOptions);
+            now = System.currentTimeMillis();
+            dictDecoder.readUnigramsAndBigramsBinary(resultWords, resultFreqs, resultBigrams);
+            diff = System.currentTimeMillis() - now;
+        } catch (IOException e) {
+            Log.e(TAG, "IOException", e);
+        } catch (UnsupportedFormatException e) {
+            Log.e(TAG, "UnsupportedFormatException", e);
+        } finally {
+            if (inStream != null) {
+                try {
+                    inStream.close();
+                } catch (IOException e) {
+                    // do nothing
+                }
+            }
+        }
+
+        checkWordMap(words, bigrams, resultWords, resultFreqs, resultBigrams);
+        return diff;
+    }
+
+    private String runReadUnigramsAndBigramsBinary(final ArrayList<String> words,
+            final SparseArray<List<Integer>> bigrams, final int bufferType,
+            final FormatSpec.FormatOptions formatOptions, final String message) {
+        final String dictName = "runReadUnigrams";
+        final String dictVersion = Long.toString(System.currentTimeMillis());
+        final File file = setUpDictionaryFile(dictName, dictVersion);
+
+        // making the dictionary from lists of words.
+        final FusionDictionary dict = new FusionDictionary(new PtNodeArray(),
+                getDictionaryOptions(dictName, dictVersion));
+        addUnigrams(words.size(), dict, words, null /* shortcutMap */);
+        addBigrams(dict, words, bigrams);
+
+        timeWritingDictToFile(file, dict, formatOptions);
+
+        long wordMap = timeAndCheckReadUnigramsAndBigramsBinary(file, words, bigrams, bufferType,
+                formatOptions, dict.mOptions);
+        long fullReading = timeReadingAndCheckDict(file, words, bigrams, null /* shortcutMap */,
+                bufferType, formatOptions, dict.mOptions);
+
+        return "readDictionaryBinary=" + fullReading + ", readUnigramsAndBigramsBinary=" + wordMap
+                + " : " + message + " : " + outputOptions(bufferType, formatOptions);
+    }
+
+    private void runReadUnigramsAndBigramsTests(final ArrayList<String> results,
+            final int bufferType, final FormatSpec.FormatOptions formatOptions) {
+        results.add(runReadUnigramsAndBigramsBinary(sWords, sEmptyBigrams, bufferType,
+                formatOptions, "unigram"));
+        results.add(runReadUnigramsAndBigramsBinary(sWords, sChainBigrams, bufferType,
+                formatOptions, "chain"));
+        results.add(runReadUnigramsAndBigramsBinary(sWords, sStarBigrams, bufferType,
+                formatOptions, "star"));
+    }
+
+    public void testReadUnigramsAndBigramsBinaryWithByteBuffer() {
+        final ArrayList<String> results = CollectionUtils.newArrayList();
+
+        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);
+        }
+    }
+
+    public void testReadUnigramsAndBigramsBinaryWithByteArray() {
+        final ArrayList<String> results = CollectionUtils.newArrayList();
+
+        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);
+        }
+    }
+
+    // Tests for getTerminalPosition
+    private String getWordFromBinary(final DictDecoder dictDecoder, final int address) {
+        if (dictDecoder.getPosition() != 0) dictDecoder.setPosition(0);
+
+        FileHeader fileHeader = null;
+        try {
+            fileHeader = dictDecoder.readHeader();
+        } catch (IOException e) {
+            return null;
+        } catch (UnsupportedFormatException e) {
+            return null;
+        }
+        if (fileHeader == null) return null;
+        return BinaryDictDecoderUtils.getWordAtPosition(dictDecoder, fileHeader.mHeaderSize,
+                address, fileHeader.mFormatOptions).mWord;
+    }
+
+    private long checkGetTerminalPosition(final DictDecoder dictDecoder, final String word,
+            int index, boolean contained) {
+        final int expectedFrequency = (UNIGRAM_FREQ + index) % 255;
+        long diff = -1;
+        int position = -1;
+        try {
+            final long now = System.nanoTime();
+            position = dictDecoder.getTerminalPosition(word);
+            diff = System.nanoTime() - now;
+        } catch (IOException e) {
+            Log.e(TAG, "IOException while getTerminalPosition", e);
+        } catch (UnsupportedFormatException e) {
+            Log.e(TAG, "UnsupportedFormatException while getTerminalPosition", e);
+        }
+
+        assertEquals(FormatSpec.NOT_VALID_WORD != position, contained);
+        if (contained) assertEquals(getWordFromBinary(dictDecoder, position), word);
+        return diff;
+    }
+
+    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);
+
+        final FusionDictionary dict = new FusionDictionary(new PtNodeArray(),
+                getDictionaryOptions(dictName, dictVersion));
+        addUnigrams(sWords.size(), dict, sWords, null /* shortcutMap */);
+        addBigrams(dict, words, bigrams);
+        timeWritingDictToFile(file, dict, formatOptions);
+
+        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.isDictBufferOpen());
+
+        try {
+            // too long word
+            final String longWord = "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz";
+            assertEquals(FormatSpec.NOT_VALID_WORD, dictDecoder.getTerminalPosition(longWord));
+
+            // null
+            assertEquals(FormatSpec.NOT_VALID_WORD, dictDecoder.getTerminalPosition(null));
+
+            // empty string
+            assertEquals(FormatSpec.NOT_VALID_WORD, dictDecoder.getTerminalPosition(""));
+        } catch (IOException e) {
+        } catch (UnsupportedFormatException e) {
+        }
+
+        // Test a word that is contained within the dictionary.
+        long sum = 0;
+        for (int i = 0; i < sWords.size(); ++i) {
+            final long time = checkGetTerminalPosition(dictDecoder, sWords.get(i), i, true);
+            sum += time == -1 ? 0 : time;
+        }
+        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());
+        final int[] codePointSet = CodePointUtils.generateCodePointSet(DEFAULT_CODE_POINT_SET_SIZE,
+                random);
+        for (int i = 0; i < 1000; ++i) {
+            final String word = CodePointUtils.generateWord(random, codePointSet);
+            if (sWords.indexOf(word) != -1) continue;
+            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);
+        }
+    }
+
+    public void testDeleteWord() {
+        final String dictName = "testDeleteWord";
+        final String dictVersion = Long.toString(System.currentTimeMillis());
+        final File file = setUpDictionaryFile(dictName, dictVersion);
+
+        final FusionDictionary dict = new FusionDictionary(new PtNodeArray(),
+                new FusionDictionary.DictionaryOptions(
+                        new HashMap<String, String>(), false, false));
+        addUnigrams(sWords.size(), dict, sWords, null /* shortcutMap */);
+        timeWritingDictToFile(file, dict, VERSION3_WITH_DYNAMIC_UPDATE);
+
+        final Ver3DictDecoder dictDecoder = new Ver3DictDecoder(file, DictDecoder.USE_BYTEARRAY);
+        try {
+            dictDecoder.openDictBuffer();
+        } catch (IOException e) {
+            // ignore
+            Log.e(TAG, "IOException while opening the buffer", e);
+        }
+        assertTrue("Can't get the buffer", dictDecoder.isDictBufferOpen());
+
+        try {
+            MoreAsserts.assertNotEqual(FormatSpec.NOT_VALID_WORD,
+                    dictDecoder.getTerminalPosition(sWords.get(0)));
+            DynamicBinaryDictIOUtils.deleteWord(dictDecoder, sWords.get(0));
+            assertEquals(FormatSpec.NOT_VALID_WORD,
+                    dictDecoder.getTerminalPosition(sWords.get(0)));
+
+            MoreAsserts.assertNotEqual(FormatSpec.NOT_VALID_WORD,
+                    dictDecoder.getTerminalPosition(sWords.get(5)));
+            DynamicBinaryDictIOUtils.deleteWord(dictDecoder, sWords.get(5));
+            assertEquals(FormatSpec.NOT_VALID_WORD,
+                    dictDecoder.getTerminalPosition(sWords.get(5)));
+        } catch (IOException e) {
+        } catch (UnsupportedFormatException e) {
+        }
+    }
+}
diff --git a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOTests.java b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOTests.java
deleted file mode 100644
index ef4ed33..0000000
--- a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOTests.java
+++ /dev/null
@@ -1,635 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.latin.makedict;
-
-import android.test.AndroidTestCase;
-import android.test.MoreAsserts;
-import android.test.suitebuilder.annotation.LargeTest;
-import android.util.Log;
-import android.util.SparseArray;
-
-import com.android.inputmethod.latin.makedict.BinaryDictInputOutput.FusionDictionaryBufferInterface;
-import com.android.inputmethod.latin.makedict.FormatSpec.FileHeader;
-import com.android.inputmethod.latin.makedict.FusionDictionary.CharGroup;
-import com.android.inputmethod.latin.makedict.FusionDictionary.Node;
-import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
-import com.android.inputmethod.latin.utils.ByteArrayWrapper;
-import com.android.inputmethod.latin.utils.CollectionUtils;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.channels.FileChannel;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Random;
-import java.util.Set;
-
-/**
- * Unit tests for BinaryDictInputOutput
- */
-@LargeTest
-public class BinaryDictIOTests extends AndroidTestCase {
-    private static final String TAG = BinaryDictIOTests.class.getSimpleName();
-    private static final int DEFAULT_MAX_UNIGRAMS = 100;
-    private static final int DEFAULT_CODE_POINT_SET_SIZE = 50;
-    private static final int UNIGRAM_FREQ = 10;
-    private static final int BIGRAM_FREQ = 50;
-    private static final int TOLERANCE_OF_BIGRAM_FREQ = 5;
-
-    private static final int USE_BYTE_ARRAY = 1;
-    private static final int USE_BYTE_BUFFER = 2;
-
-    private static final List<String> sWords = CollectionUtils.newArrayList();
-    private static final SparseArray<List<Integer>> sEmptyBigrams =
-            CollectionUtils.newSparseArray();
-    private static final SparseArray<List<Integer>> sStarBigrams = CollectionUtils.newSparseArray();
-    private static final SparseArray<List<Integer>> sChainBigrams =
-            CollectionUtils.newSparseArray();
-
-    private static final FormatSpec.FormatOptions VERSION2 = new FormatSpec.FormatOptions(2);
-    private static final FormatSpec.FormatOptions VERSION3_WITHOUT_DYNAMIC_UPDATE =
-            new FormatSpec.FormatOptions(3, false /* supportsDynamicUpdate */);
-    private static final FormatSpec.FormatOptions VERSION3_WITH_DYNAMIC_UPDATE =
-            new FormatSpec.FormatOptions(3, true /* supportsDynamicUpdate */);
-
-    public BinaryDictIOTests() {
-        this(System.currentTimeMillis(), DEFAULT_MAX_UNIGRAMS);
-    }
-
-    public BinaryDictIOTests(final long seed, final int maxUnigrams) {
-        super();
-        Log.e(TAG, "Testing dictionary: seed is " + seed);
-        final Random random = new Random(seed);
-        sWords.clear();
-        final int[] codePointSet = generateCodePointSet(DEFAULT_CODE_POINT_SET_SIZE, random);
-        generateWords(maxUnigrams, random, codePointSet);
-
-        for (int i = 0; i < sWords.size(); ++i) {
-            sChainBigrams.put(i, new ArrayList<Integer>());
-            if (i > 0) {
-                sChainBigrams.get(i - 1).add(i);
-            }
-        }
-
-        sStarBigrams.put(0, new ArrayList<Integer>());
-        for (int i = 1; i < sWords.size(); ++i) {
-            sStarBigrams.get(0).add(i);
-        }
-    }
-
-    private int[] generateCodePointSet(final int codePointSetSize, final Random random) {
-        final int[] codePointSet = new int[codePointSetSize];
-        for (int i = codePointSet.length - 1; i >= 0; ) {
-            final int r = Math.abs(random.nextInt());
-            if (r < 0) continue;
-            // Don't insert 0~0x20, but insert any other code point.
-            // Code points are in the range 0~0x10FFFF.
-            final int candidateCodePoint = (int)(0x20 + r % (Character.MAX_CODE_POINT - 0x20));
-            // Code points between MIN_ and MAX_SURROGATE are not valid on their own.
-            if (candidateCodePoint >= Character.MIN_SURROGATE
-                    && candidateCodePoint <= Character.MAX_SURROGATE) continue;
-            codePointSet[i] = candidateCodePoint;
-            --i;
-        }
-        return codePointSet;
-    }
-
-    // Utilities for test
-
-    /**
-     * Makes new buffer according to BUFFER_TYPE.
-     */
-    private FusionDictionaryBufferInterface getBuffer(final File file, final int bufferType) {
-        FileInputStream inStream = null;
-        try {
-            inStream = new FileInputStream(file);
-            if (bufferType == USE_BYTE_ARRAY) {
-                final byte[] array = new byte[(int)file.length()];
-                inStream.read(array);
-                return new ByteArrayWrapper(array);
-            } else if (bufferType == USE_BYTE_BUFFER){
-                final ByteBuffer buffer = inStream.getChannel().map(
-                        FileChannel.MapMode.READ_ONLY, 0, file.length());
-                return new BinaryDictInputOutput.ByteBufferWrapper(buffer);
-            }
-        } catch (IOException e) {
-            Log.e(TAG, "IOException while making buffer", e);
-        } finally {
-            if (inStream != null) {
-                try {
-                    inStream.close();
-                } catch (IOException e) {
-                    Log.e(TAG, "IOException while closing stream", e);
-                }
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Generates a random word.
-     */
-    private String generateWord(final Random random, final int[] codePointSet) {
-        StringBuilder builder = new StringBuilder();
-        // 8 * 4 = 32 chars max, but we do it the following way so as to bias the random toward
-        // longer words. This should be closer to natural language, and more importantly, it will
-        // exercise the algorithms in dicttool much more.
-        final int count = 1 + (Math.abs(random.nextInt()) % 5)
-                + (Math.abs(random.nextInt()) % 5)
-                + (Math.abs(random.nextInt()) % 5)
-                + (Math.abs(random.nextInt()) % 5)
-                + (Math.abs(random.nextInt()) % 5)
-                + (Math.abs(random.nextInt()) % 5)
-                + (Math.abs(random.nextInt()) % 5)
-                + (Math.abs(random.nextInt()) % 5);
-        while (builder.length() < count) {
-            builder.appendCodePoint(codePointSet[Math.abs(random.nextInt()) % codePointSet.length]);
-        }
-        return builder.toString();
-    }
-
-    private void generateWords(final int number, final Random random, final int[] codePointSet) {
-        final Set<String> wordSet = CollectionUtils.newHashSet();
-        while (wordSet.size() < number) {
-            wordSet.add(generateWord(random, codePointSet));
-        }
-        sWords.addAll(wordSet);
-    }
-
-    /**
-     * Adds unigrams to the dictionary.
-     */
-    private void addUnigrams(final int number, final FusionDictionary dict,
-            final List<String> words, final Map<String, List<String>> shortcutMap) {
-        for (int i = 0; i < number; ++i) {
-            final String word = words.get(i);
-            final ArrayList<WeightedString> shortcuts = CollectionUtils.newArrayList();
-            if (shortcutMap != null && shortcutMap.containsKey(word)) {
-                for (final String shortcut : shortcutMap.get(word)) {
-                    shortcuts.add(new WeightedString(shortcut, UNIGRAM_FREQ));
-                }
-            }
-            dict.add(word, UNIGRAM_FREQ, (shortcutMap == null) ? null : shortcuts,
-                    false /* isNotAWord */);
-        }
-    }
-
-    private void addBigrams(final FusionDictionary dict,
-            final List<String> words,
-            final SparseArray<List<Integer>> bigrams) {
-        for (int i = 0; i < bigrams.size(); ++i) {
-            final int w1 = bigrams.keyAt(i);
-            for (int w2 : bigrams.valueAt(i)) {
-                dict.setBigram(words.get(w1), words.get(w2), BIGRAM_FREQ);
-            }
-        }
-    }
-
-//    The following is useful to dump the dictionary into a textual file, but it can't compile
-//    on-device, so it's commented out.
-//    private void dumpToCombinedFileForDebug(final FusionDictionary dict, final String filename)
-//            throws IOException {
-//        com.android.inputmethod.latin.dicttool.CombinedInputOutput.writeDictionaryCombined(
-//                new java.io.FileWriter(new File(filename)), dict);
-//    }
-
-    private long timeWritingDictToFile(final File file, final FusionDictionary dict,
-            final FormatSpec.FormatOptions formatOptions) {
-
-        long now = -1, diff = -1;
-
-        try {
-            final FileOutputStream out = new FileOutputStream(file);
-
-            now = System.currentTimeMillis();
-            // If you need to dump the dict to a textual file, uncomment the line below and the
-            // function above
-            // dumpToCombinedFileForDebug(file, "/tmp/foo");
-            BinaryDictInputOutput.writeDictionaryBinary(out, dict, formatOptions);
-            diff = System.currentTimeMillis() - now;
-
-            out.flush();
-            out.close();
-        } catch (IOException e) {
-            Log.e(TAG, "IO exception while writing file", e);
-        } catch (UnsupportedFormatException e) {
-            Log.e(TAG, "UnsupportedFormatException", e);
-        }
-
-        return diff;
-    }
-
-    private void checkDictionary(final FusionDictionary dict, final List<String> words,
-            final SparseArray<List<Integer>> bigrams, final Map<String, List<String>> shortcutMap) {
-        assertNotNull(dict);
-
-        // check unigram
-        for (final String word : words) {
-            final CharGroup cg = FusionDictionary.findWordInTree(dict.mRoot, word);
-            assertNotNull(cg);
-        }
-
-        // check bigram
-        for (int i = 0; i < bigrams.size(); ++i) {
-            final int w1 = bigrams.keyAt(i);
-            for (final int w2 : bigrams.valueAt(i)) {
-                final CharGroup cg = FusionDictionary.findWordInTree(dict.mRoot, words.get(w1));
-                assertNotNull(words.get(w1) + "," + words.get(w2), cg.getBigram(words.get(w2)));
-            }
-        }
-
-        // check shortcut
-        if (shortcutMap != null) {
-            for (final Map.Entry<String, List<String>> entry : shortcutMap.entrySet()) {
-                final CharGroup group = FusionDictionary.findWordInTree(dict.mRoot, entry.getKey());
-                for (final String word : entry.getValue()) {
-                    assertNotNull("shortcut not found: " + entry.getKey() + ", " + word,
-                            group.getShortcut(word));
-                }
-            }
-        }
-    }
-
-    private String outputOptions(final int bufferType,
-            final FormatSpec.FormatOptions formatOptions) {
-        String result = " : buffer type = "
-                + ((bufferType == USE_BYTE_BUFFER) ? "byte buffer" : "byte array");
-        result += " : version = " + formatOptions.mVersion;
-        return result + ", supportsDynamicUpdate = " + formatOptions.mSupportsDynamicUpdate;
-    }
-
-    // Tests for readDictionaryBinary and writeDictionaryBinary
-
-    private long timeReadingAndCheckDict(final File file, final List<String> words,
-            final SparseArray<List<Integer>> bigrams, final Map<String, List<String>> shortcutMap,
-            final int bufferType) {
-        long now, diff = -1;
-        final FusionDictionaryBufferInterface buffer = getBuffer(file, bufferType);
-        assertNotNull(buffer);
-
-        FusionDictionary dict = null;
-        try {
-            now = System.currentTimeMillis();
-            dict = BinaryDictInputOutput.readDictionaryBinary(buffer, null);
-            diff  = System.currentTimeMillis() - now;
-        } catch (IOException e) {
-            Log.e(TAG, "IOException while reading dictionary", e);
-        } catch (UnsupportedFormatException e) {
-            Log.e(TAG, "Unsupported format", e);
-        }
-
-        checkDictionary(dict, words, bigrams, shortcutMap);
-        return diff;
-    }
-
-    // Tests for readDictionaryBinary and writeDictionaryBinary
-    private String runReadAndWrite(final List<String> words,
-            final SparseArray<List<Integer>> bigrams, final Map<String, List<String>> shortcuts,
-            final int bufferType, final FormatSpec.FormatOptions formatOptions,
-            final String message) {
-        File file = null;
-        try {
-            file = File.createTempFile("runReadAndWrite", ".dict", getContext().getCacheDir());
-        } catch (IOException e) {
-            Log.e(TAG, "IOException", e);
-        }
-        assertNotNull(file);
-
-        final FusionDictionary dict = new FusionDictionary(new Node(),
-                new FusionDictionary.DictionaryOptions(new HashMap<String,String>(), false, false));
-        addUnigrams(words.size(), dict, words, shortcuts);
-        addBigrams(dict, words, bigrams);
-        checkDictionary(dict, words, bigrams, shortcuts);
-
-        final long write = timeWritingDictToFile(file, dict, formatOptions);
-        final long read = timeReadingAndCheckDict(file, words, bigrams, shortcuts, bufferType);
-
-        return "PROF: read=" + read + "ms, write=" + write + "ms :" + message
-                + " : " + outputOptions(bufferType, formatOptions);
-    }
-
-    private void runReadAndWriteTests(final List<String> results, final int bufferType,
-            final FormatSpec.FormatOptions formatOptions) {
-        results.add(runReadAndWrite(sWords, sEmptyBigrams, null /* shortcuts */, bufferType,
-                formatOptions, "unigram"));
-        results.add(runReadAndWrite(sWords, sChainBigrams, null /* shortcuts */, bufferType,
-                formatOptions, "chain"));
-        results.add(runReadAndWrite(sWords, sStarBigrams, null /* shortcuts */, bufferType,
-                formatOptions, "star"));
-    }
-
-    public void testReadAndWriteWithByteBuffer() {
-        final List<String> results = CollectionUtils.newArrayList();
-
-        runReadAndWriteTests(results, USE_BYTE_BUFFER, VERSION2);
-        runReadAndWriteTests(results, USE_BYTE_BUFFER, VERSION3_WITHOUT_DYNAMIC_UPDATE);
-        runReadAndWriteTests(results, USE_BYTE_BUFFER, VERSION3_WITH_DYNAMIC_UPDATE);
-
-        for (final String result : results) {
-            Log.d(TAG, result);
-        }
-    }
-
-    public void testReadAndWriteWithByteArray() {
-        final List<String> results = CollectionUtils.newArrayList();
-
-        runReadAndWriteTests(results, USE_BYTE_ARRAY, VERSION2);
-        runReadAndWriteTests(results, USE_BYTE_ARRAY, VERSION3_WITHOUT_DYNAMIC_UPDATE);
-        runReadAndWriteTests(results, USE_BYTE_ARRAY, VERSION3_WITH_DYNAMIC_UPDATE);
-
-        for (final String result : results) {
-            Log.d(TAG, result);
-        }
-    }
-
-    // Tests for readUnigramsAndBigramsBinary
-
-    private void checkWordMap(final List<String> expectedWords,
-            final SparseArray<List<Integer>> expectedBigrams,
-            final Map<Integer, String> resultWords,
-            final Map<Integer, Integer> resultFrequencies,
-            final Map<Integer, ArrayList<PendingAttribute>> resultBigrams) {
-        // check unigrams
-        final Set<String> actualWordsSet = new HashSet<String>(resultWords.values());
-        final Set<String> expectedWordsSet = new HashSet<String>(expectedWords);
-        assertEquals(actualWordsSet, expectedWordsSet);
-
-        for (int freq : resultFrequencies.values()) {
-            assertEquals(freq, UNIGRAM_FREQ);
-        }
-
-        // check bigrams
-        final Map<String, List<String>> expBigrams = new HashMap<String, List<String>>();
-        for (int i = 0; i < expectedBigrams.size(); ++i) {
-            final String word1 = expectedWords.get(expectedBigrams.keyAt(i));
-            for (int w2 : expectedBigrams.valueAt(i)) {
-                if (expBigrams.get(word1) == null) {
-                    expBigrams.put(word1, new ArrayList<String>());
-                }
-                expBigrams.get(word1).add(expectedWords.get(w2));
-            }
-        }
-
-        final Map<String, List<String>> actBigrams = new HashMap<String, List<String>>();
-        for (Entry<Integer, ArrayList<PendingAttribute>> entry : resultBigrams.entrySet()) {
-            final String word1 = resultWords.get(entry.getKey());
-            final int unigramFreq = resultFrequencies.get(entry.getKey());
-            for (PendingAttribute attr : entry.getValue()) {
-                final String word2 = resultWords.get(attr.mAddress);
-                if (actBigrams.get(word1) == null) {
-                    actBigrams.put(word1, new ArrayList<String>());
-                }
-                actBigrams.get(word1).add(word2);
-
-                final int bigramFreq = BinaryDictInputOutput.reconstructBigramFrequency(
-                        unigramFreq, attr.mFrequency);
-                assertTrue(Math.abs(bigramFreq - BIGRAM_FREQ) < TOLERANCE_OF_BIGRAM_FREQ);
-            }
-        }
-
-        assertEquals(actBigrams, expBigrams);
-    }
-
-    private long timeAndCheckReadUnigramsAndBigramsBinary(final File file, final List<String> words,
-            final SparseArray<List<Integer>> bigrams, final int bufferType) {
-        FileInputStream inStream = null;
-
-        final Map<Integer, String> resultWords = CollectionUtils.newTreeMap();
-        final Map<Integer, ArrayList<PendingAttribute>> resultBigrams =
-                CollectionUtils.newTreeMap();
-        final Map<Integer, Integer> resultFreqs = CollectionUtils.newTreeMap();
-
-        long now = -1, diff = -1;
-        final FusionDictionaryBufferInterface buffer = getBuffer(file, bufferType);
-        assertNotNull("Can't get buffer.", buffer);
-        try {
-            now = System.currentTimeMillis();
-            BinaryDictIOUtils.readUnigramsAndBigramsBinary(buffer, resultWords, resultFreqs,
-                    resultBigrams);
-            diff = System.currentTimeMillis() - now;
-        } catch (IOException e) {
-            Log.e(TAG, "IOException", e);
-        } catch (UnsupportedFormatException e) {
-            Log.e(TAG, "UnsupportedFormatException", e);
-        } finally {
-            if (inStream != null) {
-                try {
-                    inStream.close();
-                } catch (IOException e) {
-                    // do nothing
-                }
-            }
-        }
-
-        checkWordMap(words, bigrams, resultWords, resultFreqs, resultBigrams);
-        return diff;
-    }
-
-    private String runReadUnigramsAndBigramsBinary(final List<String> words,
-            final SparseArray<List<Integer>> bigrams, final int bufferType,
-            final FormatSpec.FormatOptions formatOptions, final String message) {
-        File file = null;
-        try {
-            file = File.createTempFile("runReadUnigrams", ".dict", getContext().getCacheDir());
-        } catch (IOException e) {
-            Log.e(TAG, "IOException", e);
-        }
-        assertNotNull(file);
-
-        // making the dictionary from lists of words.
-        final FusionDictionary dict = new FusionDictionary(new Node(),
-                new FusionDictionary.DictionaryOptions(
-                        new HashMap<String, String>(), false, false));
-        addUnigrams(words.size(), dict, words, null /* shortcutMap */);
-        addBigrams(dict, words, bigrams);
-
-        timeWritingDictToFile(file, dict, formatOptions);
-
-        long wordMap = timeAndCheckReadUnigramsAndBigramsBinary(file, words, bigrams, bufferType);
-        long fullReading = timeReadingAndCheckDict(file, words, bigrams, null /* shortcutMap */,
-                bufferType);
-
-        return "readDictionaryBinary=" + fullReading + ", readUnigramsAndBigramsBinary=" + wordMap
-                + " : " + message + " : " + outputOptions(bufferType, formatOptions);
-    }
-
-    private void runReadUnigramsAndBigramsTests(final List<String> results, final int bufferType,
-            final FormatSpec.FormatOptions formatOptions) {
-        results.add(runReadUnigramsAndBigramsBinary(sWords, sEmptyBigrams, bufferType,
-                formatOptions, "unigram"));
-        results.add(runReadUnigramsAndBigramsBinary(sWords, sChainBigrams, bufferType,
-                formatOptions, "chain"));
-        results.add(runReadUnigramsAndBigramsBinary(sWords, sChainBigrams, bufferType,
-                formatOptions, "star"));
-    }
-
-    public void testReadUnigramsAndBigramsBinaryWithByteBuffer() {
-        final List<String> results = CollectionUtils.newArrayList();
-
-        runReadUnigramsAndBigramsTests(results, USE_BYTE_BUFFER, VERSION2);
-        runReadUnigramsAndBigramsTests(results, USE_BYTE_BUFFER, VERSION3_WITHOUT_DYNAMIC_UPDATE);
-        runReadUnigramsAndBigramsTests(results, USE_BYTE_BUFFER, VERSION3_WITH_DYNAMIC_UPDATE);
-
-        for (final String result : results) {
-            Log.d(TAG, result);
-        }
-    }
-
-    public void testReadUnigramsAndBigramsBinaryWithByteArray() {
-        final List<String> results = CollectionUtils.newArrayList();
-
-        runReadUnigramsAndBigramsTests(results, USE_BYTE_ARRAY, VERSION2);
-        runReadUnigramsAndBigramsTests(results, USE_BYTE_ARRAY, VERSION3_WITHOUT_DYNAMIC_UPDATE);
-        runReadUnigramsAndBigramsTests(results, USE_BYTE_ARRAY, VERSION3_WITH_DYNAMIC_UPDATE);
-
-        for (final String result : results) {
-            Log.d(TAG, result);
-        }
-    }
-
-    // Tests for getTerminalPosition
-    private String getWordFromBinary(final FusionDictionaryBufferInterface buffer,
-            final int address) {
-        if (buffer.position() != 0) buffer.position(0);
-
-        FileHeader header = null;
-        try {
-            header = BinaryDictInputOutput.readHeader(buffer);
-        } catch (IOException e) {
-            return null;
-        } catch (UnsupportedFormatException e) {
-            return null;
-        }
-        if (header == null) return null;
-        return BinaryDictInputOutput.getWordAtAddress(buffer, header.mHeaderSize,
-                address - header.mHeaderSize, header.mFormatOptions).mWord;
-    }
-
-    private long runGetTerminalPosition(final FusionDictionaryBufferInterface buffer,
-            final String word, int index, boolean contained) {
-        final int expectedFrequency = (UNIGRAM_FREQ + index) % 255;
-        long diff = -1;
-        int position = -1;
-        try {
-            final long now = System.nanoTime();
-            position = BinaryDictIOUtils.getTerminalPosition(buffer, word);
-            diff = System.nanoTime() - now;
-        } catch (IOException e) {
-            Log.e(TAG, "IOException while getTerminalPosition", e);
-        } catch (UnsupportedFormatException e) {
-            Log.e(TAG, "UnsupportedFormatException while getTerminalPosition", e);
-        }
-
-        assertEquals(FormatSpec.NOT_VALID_WORD != position, contained);
-        if (contained) assertEquals(getWordFromBinary(buffer, position), word);
-        return diff;
-    }
-
-    public void testGetTerminalPosition() {
-        File file = null;
-        try {
-            file = File.createTempFile("testGetTerminalPosition", ".dict",
-                    getContext().getCacheDir());
-        } catch (IOException e) {
-            // do nothing
-        }
-        assertNotNull(file);
-
-        final FusionDictionary dict = new FusionDictionary(new Node(),
-                new FusionDictionary.DictionaryOptions(
-                        new HashMap<String, String>(), false, false));
-        addUnigrams(sWords.size(), dict, sWords, null /* shortcutMap */);
-        timeWritingDictToFile(file, dict, VERSION3_WITH_DYNAMIC_UPDATE);
-
-        final FusionDictionaryBufferInterface buffer = getBuffer(file, USE_BYTE_ARRAY);
-
-        try {
-            // too long word
-            final String longWord = "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz";
-            assertEquals(FormatSpec.NOT_VALID_WORD,
-                    BinaryDictIOUtils.getTerminalPosition(buffer, longWord));
-
-            // null
-            assertEquals(FormatSpec.NOT_VALID_WORD,
-                    BinaryDictIOUtils.getTerminalPosition(buffer, null));
-
-            // empty string
-            assertEquals(FormatSpec.NOT_VALID_WORD,
-                    BinaryDictIOUtils.getTerminalPosition(buffer, ""));
-        } catch (IOException e) {
-        } catch (UnsupportedFormatException e) {
-        }
-
-        // Test a word that is contained within the dictionary.
-        long sum = 0;
-        for (int i = 0; i < sWords.size(); ++i) {
-            final long time = runGetTerminalPosition(buffer, sWords.get(i), i, true);
-            sum += time == -1 ? 0 : time;
-        }
-        Log.d(TAG, "per a search : " + (((double)sum) / sWords.size() / 1000000));
-
-        // Test a word that isn't contained within the dictionary.
-        final Random random = new Random((int)System.currentTimeMillis());
-        final int[] codePointSet = generateCodePointSet(DEFAULT_CODE_POINT_SET_SIZE, random);
-        for (int i = 0; i < 1000; ++i) {
-            final String word = generateWord(random, codePointSet);
-            if (sWords.indexOf(word) != -1) continue;
-            runGetTerminalPosition(buffer, word, i, false);
-        }
-    }
-
-    public void testDeleteWord() {
-        File file = null;
-        try {
-            file = File.createTempFile("testDeleteWord", ".dict", getContext().getCacheDir());
-        } catch (IOException e) {
-            // do nothing
-        }
-        assertNotNull(file);
-
-        final FusionDictionary dict = new FusionDictionary(new Node(),
-                new FusionDictionary.DictionaryOptions(
-                        new HashMap<String, String>(), false, false));
-        addUnigrams(sWords.size(), dict, sWords, null /* shortcutMap */);
-        timeWritingDictToFile(file, dict, VERSION3_WITH_DYNAMIC_UPDATE);
-
-        final FusionDictionaryBufferInterface buffer = getBuffer(file, USE_BYTE_ARRAY);
-
-        try {
-            MoreAsserts.assertNotEqual(FormatSpec.NOT_VALID_WORD,
-                    BinaryDictIOUtils.getTerminalPosition(buffer, sWords.get(0)));
-            BinaryDictIOUtils.deleteWord(buffer, sWords.get(0));
-            assertEquals(FormatSpec.NOT_VALID_WORD,
-                    BinaryDictIOUtils.getTerminalPosition(buffer, sWords.get(0)));
-
-            MoreAsserts.assertNotEqual(FormatSpec.NOT_VALID_WORD,
-                    BinaryDictIOUtils.getTerminalPosition(buffer, sWords.get(5)));
-            BinaryDictIOUtils.deleteWord(buffer, sWords.get(5));
-            assertEquals(FormatSpec.NOT_VALID_WORD,
-                    BinaryDictIOUtils.getTerminalPosition(buffer, sWords.get(5)));
-        } catch (IOException e) {
-        } catch (UnsupportedFormatException e) {
-        }
-    }
-}
diff --git a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtilsTests.java b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtilsTests.java
index 9331da4..a837494 100644
--- a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtilsTests.java
+++ b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtilsTests.java
@@ -21,20 +21,16 @@
 import android.test.suitebuilder.annotation.LargeTest;
 import android.util.Log;
 
-import com.android.inputmethod.latin.makedict.BinaryDictInputOutput.ByteBufferWrapper;
-import com.android.inputmethod.latin.makedict.BinaryDictInputOutput.FusionDictionaryBufferInterface;
+import com.android.inputmethod.latin.makedict.BinaryDictDecoderUtils.DictBuffer;
 import com.android.inputmethod.latin.makedict.FormatSpec.FileHeader;
-import com.android.inputmethod.latin.makedict.FusionDictionary.Node;
+import com.android.inputmethod.latin.makedict.FusionDictionary.PtNodeArray;
 import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
 import com.android.inputmethod.latin.utils.CollectionUtils;
 
 import java.io.BufferedOutputStream;
 import java.io.File;
-import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
-import java.io.RandomAccessFile;
-import java.nio.channels.FileChannel;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Random;
@@ -49,6 +45,8 @@
     public static final int DEFAULT_MAX_UNIGRAMS = 1500;
     private final int mMaxUnigrams;
 
+    private static final String TEST_DICT_FILE_EXTENSION = ".testDict";
+
     private static final String[] CHARACTERS = {
         "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m",
         "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z",
@@ -86,8 +84,8 @@
         return builder.toString();
     }
 
-    private static void printCharGroup(final CharGroupInfo info) {
-        Log.d(TAG, "    CharGroup at " + info.mOriginalAddress);
+    private static void printPtNode(final PtNodeInfo info) {
+        Log.d(TAG, "    PtNode at " + info.mOriginalAddress);
         Log.d(TAG, "        flags = " + info.mFlags);
         Log.d(TAG, "        parentAddress = " + info.mParentAddress);
         Log.d(TAG, "        characters = " + new String(info.mCharacters, 0,
@@ -111,70 +109,75 @@
         Log.d(TAG, "    end address = " + info.mEndAddress);
     }
 
-    private static void printNode(final FusionDictionaryBufferInterface buffer,
+    private static void printNode(final Ver3DictDecoder dictDecoder,
             final FormatSpec.FormatOptions formatOptions) {
-        Log.d(TAG, "Node at " + buffer.position());
-        final int count = BinaryDictInputOutput.readCharGroupCount(buffer);
-        Log.d(TAG, "    charGroupCount = " + count);
+        final DictBuffer dictBuffer = dictDecoder.getDictBuffer();
+        Log.d(TAG, "Node at " + dictBuffer.position());
+        final int count = BinaryDictDecoderUtils.readPtNodeCount(dictBuffer);
+        Log.d(TAG, "    ptNodeCount = " + count);
         for (int i = 0; i < count; ++i) {
-            final CharGroupInfo currentInfo = BinaryDictInputOutput.readCharGroup(buffer,
-                    buffer.position(), formatOptions);
-            printCharGroup(currentInfo);
+            final PtNodeInfo currentInfo = dictDecoder.readPtNode(dictBuffer.position(),
+                    formatOptions);
+            printPtNode(currentInfo);
         }
         if (formatOptions.mSupportsDynamicUpdate) {
-            final int forwardLinkAddress = buffer.readUnsignedInt24();
+            final int forwardLinkAddress = dictBuffer.readUnsignedInt24();
             Log.d(TAG, "    forwardLinkAddress = " + forwardLinkAddress);
         }
     }
 
-    private static void printBinaryFile(final FusionDictionaryBufferInterface buffer)
+    @SuppressWarnings("unused")
+    private static void printBinaryFile(final Ver3DictDecoder dictDecoder)
             throws IOException, UnsupportedFormatException {
-        FileHeader header = BinaryDictInputOutput.readHeader(buffer);
-        while (buffer.position() < buffer.limit()) {
-            printNode(buffer, header.mFormatOptions);
+        final FileHeader fileHeader = dictDecoder.readHeader();
+        final DictBuffer dictBuffer = dictDecoder.getDictBuffer();
+        while (dictBuffer.position() < dictBuffer.limit()) {
+            printNode(dictDecoder, fileHeader.mFormatOptions);
         }
     }
 
     private int getWordPosition(final File file, final String word) {
         int position = FormatSpec.NOT_VALID_WORD;
-        FileInputStream inStream = null;
+
         try {
-            inStream = new FileInputStream(file);
-            final FusionDictionaryBufferInterface buffer = new ByteBufferWrapper(
-                    inStream.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, file.length()));
-            position = BinaryDictIOUtils.getTerminalPosition(buffer, word);
+            final Ver3DictDecoder dictDecoder = new Ver3DictDecoder(file,
+                    DictDecoder.USE_READONLY_BYTEBUFFER);
+            position = dictDecoder.getTerminalPosition(word);
         } catch (IOException e) {
         } catch (UnsupportedFormatException e) {
-        } finally {
-            if (inStream != null) {
-                try {
-                    inStream.close();
-                } catch (IOException e) {
-                    // do nothing
-                }
-            }
         }
         return position;
     }
 
-    private CharGroupInfo findWordFromFile(final File file, final String word) {
-        FileInputStream inStream = null;
-        CharGroupInfo info = null;
+    /**
+     * Find a word using the DictDecoder.
+     *
+     * @param dictDecoder the dict decoder
+     * @param word the word searched
+     * @return the found ptNodeInfo
+     * @throws IOException
+     * @throws UnsupportedFormatException
+     */
+    private static PtNodeInfo findWordByBinaryDictReader(final DictDecoder dictDecoder,
+            final String word) throws IOException, UnsupportedFormatException {
+        int position = dictDecoder.getTerminalPosition(word);
+        if (position != FormatSpec.NOT_VALID_WORD) {
+            dictDecoder.setPosition(0);
+            final FileHeader header = dictDecoder.readHeader();
+            dictDecoder.setPosition(position);
+            return dictDecoder.readPtNode(position, header.mFormatOptions);
+        }
+        return null;
+    }
+
+    private PtNodeInfo findWordFromFile(final File file, final String word) {
+        final DictDecoder dictDecoder = FormatSpec.getDictDecoder(file);
+        PtNodeInfo info = null;
         try {
-            inStream = new FileInputStream(file);
-            final FusionDictionaryBufferInterface buffer = new ByteBufferWrapper(
-                    inStream.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, file.length()));
-            info = BinaryDictIOUtils.findWordFromBuffer(buffer, word);
+            dictDecoder.openDictBuffer();
+            info = findWordByBinaryDictReader(dictDecoder, word);
         } catch (IOException e) {
         } catch (UnsupportedFormatException e) {
-        } finally {
-            if (inStream != null) {
-                try {
-                    inStream.close();
-                } catch (IOException e) {
-                    // do nothing
-                }
-            }
         }
         return info;
     }
@@ -183,42 +186,34 @@
     private long insertAndCheckWord(final File file, final String word, final int frequency,
             final boolean exist, final ArrayList<WeightedString> bigrams,
             final ArrayList<WeightedString> shortcuts) {
-        RandomAccessFile raFile = null;
         BufferedOutputStream outStream = null;
-        FusionDictionaryBufferInterface buffer = null;
         long amountOfTime = -1;
         try {
-            raFile = new RandomAccessFile(file, "rw");
-            buffer = new ByteBufferWrapper(raFile.getChannel().map(
-                    FileChannel.MapMode.READ_WRITE, 0, file.length()));
+            final Ver3DictDecoder dictDecoder = new Ver3DictDecoder(file,
+                    DictDecoder.USE_WRITABLE_BYTEBUFFER);
+            dictDecoder.openDictBuffer();
             outStream = new BufferedOutputStream(new FileOutputStream(file, true));
 
             if (!exist) {
                 assertEquals(FormatSpec.NOT_VALID_WORD, getWordPosition(file, word));
             }
             final long now = System.nanoTime();
-            BinaryDictIOUtils.insertWord(buffer, outStream, word, frequency, bigrams, shortcuts,
-                    false, false);
+            DynamicBinaryDictIOUtils.insertWord(dictDecoder, outStream, word, frequency, bigrams,
+                    shortcuts, false, false);
             amountOfTime = System.nanoTime() - now;
             outStream.flush();
             MoreAsserts.assertNotEqual(FormatSpec.NOT_VALID_WORD, getWordPosition(file, word));
             outStream.close();
-            raFile.close();
         } catch (IOException e) {
+            Log.e(TAG, "Raised an IOException while inserting a word", e);
         } catch (UnsupportedFormatException e) {
+            Log.e(TAG, "Raised an UnsupportedFormatException error while inserting a word", e);
         } finally {
             if (outStream != null) {
                 try {
                     outStream.close();
                 } catch (IOException e) {
-                    // do nothing
-                }
-            }
-            if (raFile != null) {
-                try {
-                    raFile.close();
-                } catch (IOException e) {
-                    // do nothing
+                    Log.e(TAG, "Failed to close the output stream", e);
                 }
             }
         }
@@ -226,65 +221,48 @@
     }
 
     private void deleteWord(final File file, final String word) {
-        RandomAccessFile raFile = null;
-        FusionDictionaryBufferInterface buffer = null;
         try {
-            raFile = new RandomAccessFile(file, "rw");
-            buffer = new ByteBufferWrapper(raFile.getChannel().map(
-                    FileChannel.MapMode.READ_WRITE, 0, file.length()));
-            BinaryDictIOUtils.deleteWord(buffer, word);
+            final Ver3DictDecoder dictDecoder = new Ver3DictDecoder(file,
+                    DictDecoder.USE_WRITABLE_BYTEBUFFER);
+            dictDecoder.openDictBuffer();
+            DynamicBinaryDictIOUtils.deleteWord(dictDecoder, word);
         } catch (IOException e) {
         } catch (UnsupportedFormatException e) {
-        } finally {
-            if (raFile != null) {
-                try {
-                    raFile.close();
-                } catch (IOException e) {
-                    // do nothing
-                }
-            }
         }
     }
 
     private void checkReverseLookup(final File file, final String word, final int position) {
-        FileInputStream inStream = null;
+
         try {
-            inStream = new FileInputStream(file);
-            final FusionDictionaryBufferInterface buffer = new ByteBufferWrapper(
-                    inStream.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, file.length()));
-            final FileHeader header = BinaryDictInputOutput.readHeader(buffer);
-            assertEquals(word, BinaryDictInputOutput.getWordAtAddress(buffer, header.mHeaderSize,
-                    position - header.mHeaderSize, header.mFormatOptions).mWord);
+            final DictDecoder dictDecoder = FormatSpec.getDictDecoder(file);
+            final FileHeader fileHeader = dictDecoder.readHeader();
+            assertEquals(word,
+                    BinaryDictDecoderUtils.getWordAtPosition(dictDecoder, fileHeader.mHeaderSize,
+                            position, fileHeader.mFormatOptions).mWord);
         } catch (IOException e) {
+            Log.e(TAG, "Raised an IOException while looking up a word", e);
         } catch (UnsupportedFormatException e) {
-        } finally {
-            if (inStream != null) {
-                try {
-                    inStream.close();
-                } catch (IOException e) {
-                    // do nothing
-                }
-            }
+            Log.e(TAG, "Raised an UnsupportedFormatException error while looking up a word", e);
         }
     }
 
     public void testInsertWord() {
         File file = null;
         try {
-            file = File.createTempFile("testInsertWord", ".dict", getContext().getCacheDir());
+            file = File.createTempFile("testInsertWord", TEST_DICT_FILE_EXTENSION,
+                    getContext().getCacheDir());
         } catch (IOException e) {
             fail("IOException while creating temporary file: " + e);
         }
 
         // set an initial dictionary.
-        final FusionDictionary dict = new FusionDictionary(new Node(),
+        final FusionDictionary dict = new FusionDictionary(new PtNodeArray(),
                 new FusionDictionary.DictionaryOptions(new HashMap<String,String>(), false, false));
         dict.add("abcd", 10, null, false);
 
         try {
-            final FileOutputStream out = new FileOutputStream(file);
-            BinaryDictInputOutput.writeDictionaryBinary(out, dict, FORMAT_OPTIONS);
-            out.close();
+            final DictEncoder dictEncoder = new Ver3DictEncoder(file);
+            dictEncoder.writeDictionary(dict, FORMAT_OPTIONS);
         } catch (IOException e) {
             fail("IOException while writing an initial dictionary : " + e);
         } catch (UnsupportedFormatException e) {
@@ -321,22 +299,21 @@
     public void testInsertWordWithBigrams() {
         File file = null;
         try {
-            file = File.createTempFile("testInsertWordWithBigrams", ".dict",
+            file = File.createTempFile("testInsertWordWithBigrams", TEST_DICT_FILE_EXTENSION,
                     getContext().getCacheDir());
         } catch (IOException e) {
             fail("IOException while creating temporary file: " + e);
         }
 
         // set an initial dictionary.
-        final FusionDictionary dict = new FusionDictionary(new Node(),
+        final FusionDictionary dict = new FusionDictionary(new PtNodeArray(),
                 new FusionDictionary.DictionaryOptions(new HashMap<String,String>(), false, false));
         dict.add("abcd", 10, null, false);
         dict.add("efgh", 15, null, false);
 
         try {
-            final FileOutputStream out = new FileOutputStream(file);
-            BinaryDictInputOutput.writeDictionaryBinary(out, dict, FORMAT_OPTIONS);
-            out.close();
+            final DictEncoder dictEncoder = new Ver3DictEncoder(file); 
+            dictEncoder.writeDictionary(dict, FORMAT_OPTIONS);
         } catch (IOException e) {
             fail("IOException while writing an initial dictionary : " + e);
         } catch (UnsupportedFormatException e) {
@@ -349,7 +326,7 @@
         insertAndCheckWord(file, "banana", 0, false, null, null);
         insertAndCheckWord(file, "recursive", 60, true, banana, null);
 
-        final CharGroupInfo info = findWordFromFile(file, "recursive");
+        final PtNodeInfo info = findWordFromFile(file, "recursive");
         int bananaPos = getWordPosition(file, "banana");
         assertNotNull(info.mBigrams);
         assertEquals(info.mBigrams.size(), 1);
@@ -359,21 +336,21 @@
     public void testRandomWords() {
         File file = null;
         try {
-            file = File.createTempFile("testRandomWord", ".dict", getContext().getCacheDir());
+            file = File.createTempFile("testRandomWord", TEST_DICT_FILE_EXTENSION,
+                    getContext().getCacheDir());
         } catch (IOException e) {
         }
         assertNotNull(file);
 
         // set an initial dictionary.
-        final FusionDictionary dict = new FusionDictionary(new Node(),
+        final FusionDictionary dict = new FusionDictionary(new PtNodeArray(),
                 new FusionDictionary.DictionaryOptions(new HashMap<String, String>(), false,
                         false));
         dict.add("initial", 10, null, false);
 
         try {
-            final FileOutputStream out = new FileOutputStream(file);
-            BinaryDictInputOutput.writeDictionaryBinary(out, dict, FORMAT_OPTIONS);
-            out.close();
+            final DictEncoder dictEncoder = new Ver3DictEncoder(file);
+            dictEncoder.writeDictionary(dict, FORMAT_OPTIONS);
         } catch (IOException e) {
             assertTrue(false);
         } catch (UnsupportedFormatException e) {
diff --git a/tests/src/com/android/inputmethod/latin/makedict/CodePointUtils.java b/tests/src/com/android/inputmethod/latin/makedict/CodePointUtils.java
new file mode 100644
index 0000000..36b958a
--- /dev/null
+++ b/tests/src/com/android/inputmethod/latin/makedict/CodePointUtils.java
@@ -0,0 +1,65 @@
+/*
+ * 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 java.util.Random;
+
+// Utility methods related with code points used for tests.
+public class CodePointUtils {
+    private CodePointUtils() {
+        // This utility class is not publicly instantiable.
+    }
+
+    public static int[] generateCodePointSet(final int codePointSetSize, final Random random) {
+        final int[] codePointSet = new int[codePointSetSize];
+        for (int i = codePointSet.length - 1; i >= 0; ) {
+            final int r = Math.abs(random.nextInt());
+            if (r < 0) continue;
+            // Don't insert 0~0x20, but insert any other code point.
+            // Code points are in the range 0~0x10FFFF.
+            final int candidateCodePoint = 0x20 + r % (Character.MAX_CODE_POINT - 0x20);
+            // Code points between MIN_ and MAX_SURROGATE are not valid on their own.
+            if (candidateCodePoint >= Character.MIN_SURROGATE
+                    && candidateCodePoint <= Character.MAX_SURROGATE) continue;
+            codePointSet[i] = candidateCodePoint;
+            --i;
+        }
+        return codePointSet;
+    }
+
+    /**
+     * Generates a random word.
+     */
+    public static String generateWord(final Random random, final int[] codePointSet) {
+        StringBuilder builder = new StringBuilder();
+        // 8 * 4 = 32 chars max, but we do it the following way so as to bias the random toward
+        // longer words. This should be closer to natural language, and more importantly, it will
+        // exercise the algorithms in dicttool much more.
+        final int count = 1 + (Math.abs(random.nextInt()) % 5)
+                + (Math.abs(random.nextInt()) % 5)
+                + (Math.abs(random.nextInt()) % 5)
+                + (Math.abs(random.nextInt()) % 5)
+                + (Math.abs(random.nextInt()) % 5)
+                + (Math.abs(random.nextInt()) % 5)
+                + (Math.abs(random.nextInt()) % 5)
+                + (Math.abs(random.nextInt()) % 5);
+        while (builder.length() < count) {
+            builder.appendCodePoint(codePointSet[Math.abs(random.nextInt()) % codePointSet.length]);
+        }
+        return builder.toString();
+    }
+}
diff --git a/tests/src/com/android/inputmethod/latin/makedict/Ver3DictDecoderTests.java b/tests/src/com/android/inputmethod/latin/makedict/Ver3DictDecoderTests.java
new file mode 100644
index 0000000..9611599
--- /dev/null
+++ b/tests/src/com/android/inputmethod/latin/makedict/Ver3DictDecoderTests.java
@@ -0,0 +1,150 @@
+/*
+ * 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.latin.makedict.BinaryDictDecoderUtils.DictBuffer;
+import com.android.inputmethod.latin.makedict.DictDecoder.DictionaryBufferFactory;
+import com.android.inputmethod.latin.makedict.DictDecoder.DictionaryBufferFromByteArrayFactory;
+import com.android.inputmethod.latin.makedict.DictDecoder.
+        DictionaryBufferFromReadOnlyByteBufferFactory;
+import com.android.inputmethod.latin.makedict.DictDecoder.
+        DictionaryBufferFromWritableByteBufferFactory;
+
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+/**
+ * Unit tests for Ver3DictDecoder
+ */
+public class Ver3DictDecoderTests extends AndroidTestCase {
+    private static final String TAG = Ver3DictDecoderTests.class.getSimpleName();
+
+    private final byte[] data = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
+
+    // Utilities for testing
+    public void writeDataToFile(final File file) {
+        FileOutputStream outStream = null;
+        try {
+            outStream = new FileOutputStream(file);
+            outStream.write(data);
+        } catch (IOException e) {
+            fail ("Can't write data to the test file");
+        } finally {
+            if (outStream != null) {
+                try {
+                    outStream.close();
+                } catch (IOException e) {
+                    Log.e(TAG, "Failed to close the output stream", e);
+                }
+            }
+        }
+    }
+
+    @SuppressWarnings("null")
+    public void runTestOpenBuffer(final String testName, final DictionaryBufferFactory factory) {
+        File testFile = null;
+        try {
+            testFile = File.createTempFile(testName, ".tmp", getContext().getCacheDir());
+        } catch (IOException e) {
+            Log.e(TAG, "IOException while the creating temporary file", e);
+        }
+
+        assertNotNull(testFile);
+        final Ver3DictDecoder dictDecoder = new Ver3DictDecoder(testFile, factory);
+        try {
+            dictDecoder.openDictBuffer();
+        } catch (Exception e) {
+            Log.e(TAG, "Failed to open the buffer", e);
+        }
+
+        writeDataToFile(testFile);
+
+        try {
+            dictDecoder.openDictBuffer();
+        } catch (Exception e) {
+            Log.e(TAG, "Raised the exception while opening buffer", e);
+        }
+
+        assertEquals(testFile.length(), dictDecoder.getDictBuffer().capacity());
+    }
+
+    public void testOpenBufferWithByteBuffer() {
+        runTestOpenBuffer("testOpenBufferWithByteBuffer",
+                new DictionaryBufferFromReadOnlyByteBufferFactory());
+    }
+
+    public void testOpenBufferWithByteArray() {
+        runTestOpenBuffer("testOpenBufferWithByteArray",
+                new DictionaryBufferFromByteArrayFactory());
+    }
+
+    public void testOpenBufferWithWritableByteBuffer() {
+        runTestOpenBuffer("testOpenBufferWithWritableByteBuffer",
+                new DictionaryBufferFromWritableByteBufferFactory());
+    }
+
+    @SuppressWarnings("null")
+    public void runTestGetBuffer(final String testName, final DictionaryBufferFactory factory) {
+        File testFile = null;
+        try {
+            testFile = File.createTempFile(testName, ".tmp", getContext().getCacheDir());
+        } catch (IOException e) {
+            Log.e(TAG, "IOException while the creating temporary file", e);
+        }
+
+        final Ver3DictDecoder dictDecoder = new Ver3DictDecoder(testFile, factory);
+
+        // the default return value of getBuffer() must be null.
+        assertNull("the default return value of getBuffer() is not null",
+                dictDecoder.getDictBuffer());
+
+        writeDataToFile(testFile);
+        assertTrue(testFile.exists());
+        Log.d(TAG, "file length = " + testFile.length());
+
+        DictBuffer dictBuffer = null;
+        try {
+            dictBuffer = dictDecoder.openAndGetDictBuffer();
+        } catch (IOException e) {
+            Log.e(TAG, "Failed to open and get the buffer", e);
+        }
+        assertNotNull("the buffer must not be null", dictBuffer);
+
+        for (int i = 0; i < data.length; ++i) {
+            assertEquals(data[i], dictBuffer.readUnsignedByte());
+        }
+    }
+
+    public void testGetBufferWithByteBuffer() {
+        runTestGetBuffer("testGetBufferWithByteBuffer",
+                new DictionaryBufferFromReadOnlyByteBufferFactory());
+    }
+
+    public void testGetBufferWithByteArray() {
+        runTestGetBuffer("testGetBufferWithByteArray",
+                new DictionaryBufferFromByteArrayFactory());
+    }
+
+    public void testGetBufferWithWritableByteBuffer() {
+        runTestGetBuffer("testGetBufferWithWritableByteBuffer",
+                new DictionaryBufferFromWritableByteBufferFactory());
+    }
+}
diff --git a/tests/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryTests.java b/tests/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryTests.java
index b3e2ee0..d605cdb 100644
--- a/tests/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryTests.java
+++ b/tests/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryTests.java
@@ -22,6 +22,7 @@
 import android.test.suitebuilder.annotation.LargeTest;
 import android.util.Log;
 
+import com.android.inputmethod.latin.ExpandableBinaryDictionary;
 import com.android.inputmethod.latin.utils.CollectionUtils;
 
 import java.io.File;
@@ -29,6 +30,7 @@
 import java.util.List;
 import java.util.Random;
 import java.util.Set;
+import java.util.concurrent.TimeUnit;
 
 /**
  * Unit tests for UserHistoryDictionary
@@ -43,6 +45,9 @@
         "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"
     };
 
+    private static final int MIN_USER_HISTORY_DICTIONARY_FILE_SIZE = 1000;
+    private static final int WAIT_TERMINATING_IN_MILLISECONDS = 100;
+
     @Override
     public void setUp() {
         mPrefs = PreferenceManager.getDefaultSharedPreferences(getContext());
@@ -73,48 +78,74 @@
     private void addToDict(final UserHistoryPredictionDictionary dict, final List<String> words) {
         String prevWord = null;
         for (String word : words) {
-            dict.forceAddWordForTest(prevWord, word, true);
+            dict.addToPersonalizationPredictionDictionary(prevWord, word, true);
             prevWord = word;
         }
     }
 
+    /**
+     * @param checksContents if true, checks whether written words are actually in the dictionary
+     * or not.
+     */
+    private void addAndWriteRandomWords(final String testFilenameSuffix, final int numberOfWords,
+            final Random random, final boolean checksContents) {
+        final List<String> words = generateWords(numberOfWords, random);
+        final UserHistoryPredictionDictionary dict =
+                PersonalizationHelper.getUserHistoryPredictionDictionary(getContext(),
+                        testFilenameSuffix /* locale */, mPrefs);
+        // Add random words to the user history dictionary.
+        addToDict(dict, words);
+        if (checksContents) {
+            try {
+                Thread.sleep(TimeUnit.MILLISECONDS.convert(5L, TimeUnit.SECONDS));
+            } catch (InterruptedException e) {
+            }
+            // Limit word count to check when using a Java on memory dictionary.
+            final int wordCountToCheck =
+                    ExpandableBinaryDictionary.ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE ?
+                            numberOfWords : 10;
+            for (int i = 0; i < wordCountToCheck; ++i) {
+                final String word = words.get(i);
+                // This may fail as long as we use tryLock on inserting the bigram words
+                assertTrue(dict.isInDictionaryForTests(word));
+            }
+        }
+        // write to file.
+        dict.close();
+    }
+
     public void testRandomWords() {
         File dictFile = null;
+        Log.d(TAG, "This test can be used for profiling.");
+        Log.d(TAG, "Usage: please set UserHistoryDictionary.PROFILE_SAVE_RESTORE to true.");
+        final String testFilenameSuffix = "testRandomWords" + System.currentTimeMillis();
+        final int numberOfWords = 1000;
+        final Random random = new Random(123456);
+
         try {
-            Log.d(TAG, "This test can be used for profiling.");
-            Log.d(TAG, "Usage: please set UserHistoryDictionary.PROFILE_SAVE_RESTORE to true.");
-            final int numberOfWords = 1000;
-            final Random random = new Random(123456);
-            List<String> words = generateWords(numberOfWords, random);
-
-            final String locale = "testRandomWords";
-            final String fileName = "UserHistoryDictionary." + locale + ".dict";
-            dictFile = new File(getContext().getFilesDir(), fileName);
-            final UserHistoryPredictionDictionary dict =
-                    PersonalizationDictionaryHelper.getUserHistoryPredictionDictionary(
-                            getContext(), locale, mPrefs);
-            dict.mIsTest = true;
-
-            addToDict(dict, words);
-
-            try {
-                Log.d(TAG, "waiting for adding the word ...");
-                Thread.sleep(2000);
-            } catch (InterruptedException e) {
-                Log.d(TAG, "InterruptedException: " + e);
-            }
-
-            // write to file
-            dict.close();
-
-            try {
-                Log.d(TAG, "waiting for writing ...");
-                Thread.sleep(5000);
-            } catch (InterruptedException e) {
-                Log.d(TAG, "InterruptedException: " + e);
-            }
+            addAndWriteRandomWords(testFilenameSuffix, numberOfWords, random,
+                    true /* checksContents */);
         } finally {
+            try {
+                final UserHistoryPredictionDictionary dict =
+                        PersonalizationHelper.getUserHistoryPredictionDictionary(getContext(),
+                                testFilenameSuffix, mPrefs);
+                Log.d(TAG, "waiting for writing ...");
+                dict.shutdownExecutorForTests();
+                while (!dict.isTerminatedForTests()) {
+                    Thread.sleep(WAIT_TERMINATING_IN_MILLISECONDS);
+                }
+            } catch (InterruptedException e) {
+                Log.d(TAG, "InterruptedException: " + e);
+            }
+
+            final String fileName = UserHistoryPredictionDictionary.NAME + "." + testFilenameSuffix
+                    + ExpandableBinaryDictionary.DICT_FILE_EXTENSION;
+            dictFile = new File(getContext().getFilesDir(), fileName);
+
             if (dictFile != null) {
+                assertTrue(dictFile.exists());
+                assertTrue(dictFile.length() >= MIN_USER_HISTORY_DICTIONARY_FILE_SIZE);
                 dictFile.delete();
             }
         }
@@ -122,52 +153,94 @@
 
     public void testStressTestForSwitchingLanguagesAndAddingWords() {
         final int numberOfLanguages = 2;
-        final int numberOfLanguageSwitching = 100;
-        final int numberOfWordsIntertedForEachLanguageSwitch = 100;
+        final int numberOfLanguageSwitching = 80;
+        final int numberOfWordsInsertedForEachLanguageSwitch = 100;
 
         final File dictFiles[] = new File[numberOfLanguages];
+        final String testFilenameSuffixes[] = new String[numberOfLanguages];
         try {
             final Random random = new Random(123456);
 
-            // Create locales for this test.
-            String locales[] = new String[numberOfLanguages];
+            // Create filename suffixes for this test.
             for (int i = 0; i < numberOfLanguages; i++) {
-                locales[i] = "testSwitchingLanguages" + i;
-                final String fileName = "UserHistoryDictionary." + locales[i] + ".dict";
+                testFilenameSuffixes[i] = "testSwitchingLanguages" + i;
+                final String fileName = UserHistoryPredictionDictionary.NAME + "." +
+                        testFilenameSuffixes[i] + ExpandableBinaryDictionary.DICT_FILE_EXTENSION;
                 dictFiles[i] = new File(getContext().getFilesDir(), fileName);
             }
 
-            final long now = System.currentTimeMillis();
+            final long start = System.currentTimeMillis();
 
             for (int i = 0; i < numberOfLanguageSwitching; i++) {
                 final int index = i % numberOfLanguages;
-                // Switch languages to locales[index].
-                final UserHistoryPredictionDictionary dict =
-                        PersonalizationDictionaryHelper.getUserHistoryPredictionDictionary(
-                                getContext(), locales[index], mPrefs);
-                final List<String> words = generateWords(
-                        numberOfWordsIntertedForEachLanguageSwitch, random);
-                // Add random words to the user history dictionary.
-                addToDict(dict, words);
-                // write to file
-                dict.close();
+                // Switch languages to testFilenameSuffixes[index].
+                addAndWriteRandomWords(testFilenameSuffixes[index],
+                        numberOfWordsInsertedForEachLanguageSwitch, random,
+                        false /* checksContents */);
             }
 
             final long end = System.currentTimeMillis();
             Log.d(TAG, "testStressTestForSwitchingLanguageAndAddingWords took "
-                    + (end - now) + " ms");
+                    + (end - start) + " ms");
+        } finally {
             try {
                 Log.d(TAG, "waiting for writing ...");
-                Thread.sleep(5000);
+                for (int i = 0; i < numberOfLanguages; i++) {
+                    final UserHistoryPredictionDictionary dict =
+                            PersonalizationHelper.getUserHistoryPredictionDictionary(getContext(),
+                                    testFilenameSuffixes[i], mPrefs);
+                    dict.shutdownExecutorForTests();
+                    while (!dict.isTerminatedForTests()) {
+                        Thread.sleep(WAIT_TERMINATING_IN_MILLISECONDS);
+                    }
+                }
             } catch (InterruptedException e) {
                 Log.d(TAG, "InterruptedException: " + e);
             }
-        } finally {
             for (final File file : dictFiles) {
                 if (file != null) {
+                    assertTrue(file.exists());
+                    assertTrue(file.length() >= MIN_USER_HISTORY_DICTIONARY_FILE_SIZE);
                     file.delete();
                 }
             }
         }
     }
+
+    public void testAddManyWords() {
+        File dictFile = null;
+        final String testFilenameSuffix = "testRandomWords" + System.currentTimeMillis();
+        final int numberOfWords =
+                ExpandableBinaryDictionary.ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE ?
+                        10000 : 1000;
+        final Random random = new Random(123456);
+
+        UserHistoryPredictionDictionary dict =
+                PersonalizationHelper.getUserHistoryPredictionDictionary(getContext(),
+                        testFilenameSuffix, mPrefs);
+        try {
+            addAndWriteRandomWords(testFilenameSuffix, numberOfWords, random,
+                    true /* checksContents */);
+            dict.close();
+        } finally {
+            try {
+                Log.d(TAG, "waiting for writing ...");
+                dict.shutdownExecutorForTests();
+                while (!dict.isTerminatedForTests()) {
+                    Thread.sleep(WAIT_TERMINATING_IN_MILLISECONDS);
+                }
+            } catch (InterruptedException e) {
+                Log.d(TAG, "InterruptedException: ", e);
+            }
+            final String fileName = UserHistoryPredictionDictionary.NAME + "." + testFilenameSuffix
+                    + ExpandableBinaryDictionary.DICT_FILE_EXTENSION;
+            dictFile = new File(getContext().getFilesDir(), fileName);
+            if (dictFile != null) {
+                assertTrue(dictFile.exists());
+                assertTrue(dictFile.length() >= MIN_USER_HISTORY_DICTIONARY_FILE_SIZE);
+                dictFile.delete();
+            }
+        }
+    }
+
 }
diff --git a/tests/src/com/android/inputmethod/latin/utils/AsyncResultHolderTests.java b/tests/src/com/android/inputmethod/latin/utils/AsyncResultHolderTests.java
new file mode 100644
index 0000000..7fd1679
--- /dev/null
+++ b/tests/src/com/android/inputmethod/latin/utils/AsyncResultHolderTests.java
@@ -0,0 +1,73 @@
+/*
+ * 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.utils;
+
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.util.Log;
+
+@MediumTest
+public class AsyncResultHolderTests extends AndroidTestCase {
+    private static final String TAG = AsyncResultHolderTests.class.getSimpleName();
+
+    private static final int TIMEOUT_IN_MILLISECONDS = 500;
+    private static final int MARGIN_IN_MILLISECONDS = 250;
+    private static final int DEFAULT_VALUE = 2;
+    private static final int SET_VALUE = 1;
+
+    private <T> void setAfterGivenTime(final AsyncResultHolder<T> holder, final T value,
+            final long time) {
+        new Thread(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    Thread.sleep(time);
+                } catch (InterruptedException e) {
+                    Log.d(TAG, "Exception while sleeping", e);
+                }
+                holder.set(value);
+            }
+        }).start();
+    }
+
+    public void testGetWithoutSet() {
+        final AsyncResultHolder<Integer> holder = new AsyncResultHolder<Integer>();
+        final int resultValue = holder.get(DEFAULT_VALUE, TIMEOUT_IN_MILLISECONDS);
+        assertEquals(DEFAULT_VALUE, resultValue);
+    }
+
+    public void testGetBeforeSet() {
+        final AsyncResultHolder<Integer> holder = new AsyncResultHolder<Integer>();
+        setAfterGivenTime(holder, SET_VALUE, TIMEOUT_IN_MILLISECONDS + MARGIN_IN_MILLISECONDS);
+        final int resultValue = holder.get(DEFAULT_VALUE, TIMEOUT_IN_MILLISECONDS);
+        assertEquals(DEFAULT_VALUE, resultValue);
+    }
+
+    public void testGetAfterSet() {
+        final AsyncResultHolder<Integer> holder = new AsyncResultHolder<Integer>();
+        holder.set(SET_VALUE);
+        final int resultValue = holder.get(DEFAULT_VALUE, TIMEOUT_IN_MILLISECONDS);
+        assertEquals(SET_VALUE, resultValue);
+    }
+
+    public void testGetBeforeTimeout() {
+        final AsyncResultHolder<Integer> holder = new AsyncResultHolder<Integer>();
+        setAfterGivenTime(holder, SET_VALUE, TIMEOUT_IN_MILLISECONDS - MARGIN_IN_MILLISECONDS);
+        final int resultValue = holder.get(DEFAULT_VALUE, TIMEOUT_IN_MILLISECONDS);
+        assertEquals(SET_VALUE, resultValue);
+    }
+}
diff --git a/tests/src/com/android/inputmethod/latin/utils/PrioritizedSerialExecutorTests.java b/tests/src/com/android/inputmethod/latin/utils/PrioritizedSerialExecutorTests.java
new file mode 100644
index 0000000..e075548
--- /dev/null
+++ b/tests/src/com/android/inputmethod/latin/utils/PrioritizedSerialExecutorTests.java
@@ -0,0 +1,105 @@
+/*
+ * 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.utils;
+
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.util.Log;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Unit tests for PrioritizedSerialExecutor.
+ * TODO: Add more detailed tests to make use of priorities, etc.
+ */
+@MediumTest
+public class PrioritizedSerialExecutorTests extends AndroidTestCase {
+    private static final String TAG = PrioritizedSerialExecutorTests.class.getSimpleName();
+
+    private static final int NUM_OF_TASKS = 10;
+    private static final int DELAY_FOR_WAITING_TASKS_MILLISECONDS = 500;
+
+    public void testExecute() {
+        final PrioritizedSerialExecutor executor = new PrioritizedSerialExecutor();
+        final AtomicInteger v = new AtomicInteger(0);
+        for (int i = 0; i < NUM_OF_TASKS; ++i) {
+            executor.execute(new Runnable() {
+                @Override
+                public void run() {
+                    v.incrementAndGet();
+                }
+            });
+        }
+        try {
+            Thread.sleep(DELAY_FOR_WAITING_TASKS_MILLISECONDS);
+        } catch (InterruptedException e) {
+            Log.d(TAG, "Exception while sleeping.", e);
+        }
+
+        assertEquals(NUM_OF_TASKS, v.get());
+    }
+
+    public void testExecutePrioritized() {
+        final PrioritizedSerialExecutor executor = new PrioritizedSerialExecutor();
+        final AtomicInteger v = new AtomicInteger(0);
+        for (int i = 0; i < NUM_OF_TASKS; ++i) {
+            executor.executePrioritized(new Runnable() {
+                @Override
+                public void run() {
+                    v.incrementAndGet();
+                }
+            });
+        }
+        try {
+            Thread.sleep(DELAY_FOR_WAITING_TASKS_MILLISECONDS);
+        } catch (InterruptedException e) {
+            Log.d(TAG, "Exception while sleeping.", e);
+        }
+
+        assertEquals(NUM_OF_TASKS, v.get());
+    }
+
+    public void testExecuteCombined() {
+        final PrioritizedSerialExecutor executor = new PrioritizedSerialExecutor();
+        final AtomicInteger v = new AtomicInteger(0);
+        for (int i = 0; i < NUM_OF_TASKS; ++i) {
+            executor.execute(new Runnable() {
+                @Override
+                public void run() {
+                    v.incrementAndGet();
+                }
+            });
+        }
+
+        for (int i = 0; i < NUM_OF_TASKS; ++i) {
+            executor.executePrioritized(new Runnable() {
+                @Override
+                public void run() {
+                    v.incrementAndGet();
+                }
+            });
+        }
+
+        try {
+            Thread.sleep(DELAY_FOR_WAITING_TASKS_MILLISECONDS);
+        } catch (InterruptedException e) {
+            Log.d(TAG, "Exception while sleeping.", e);
+        }
+
+        assertEquals(2 * NUM_OF_TASKS, v.get());
+    }
+}
diff --git a/tests/src/com/android/inputmethod/latin/utils/ResizableIntArrayTests.java b/tests/src/com/android/inputmethod/latin/utils/ResizableIntArrayTests.java
index cfff61e..cad80d5 100644
--- a/tests/src/com/android/inputmethod/latin/utils/ResizableIntArrayTests.java
+++ b/tests/src/com/android/inputmethod/latin/utils/ResizableIntArrayTests.java
@@ -340,4 +340,18 @@
                     expecteds[i + expectedPos], actuals[i + actualPos]);
         }
     }
+
+    public void testShift() {
+        final ResizableIntArray src = new ResizableIntArray(DEFAULT_CAPACITY);
+        final int limit = DEFAULT_CAPACITY * 10;
+        final int shiftAmount = 20;
+        for (int i = 0; i < limit; ++i) {
+            src.add(i, i);
+            assertEquals("length after add at " + i, i + 1, src.getLength());
+        }
+        src.shift(shiftAmount);
+        for (int i = 0; i < limit - shiftAmount; ++i) {
+            assertEquals("value at " + i, i + shiftAmount, src.get(i));
+        }
+    }
 }
diff --git a/tests/src/com/android/inputmethod/latin/utils/StringUtilsTests.java b/tests/src/com/android/inputmethod/latin/utils/StringUtilsTests.java
index 9ee8e38..eb9fb98 100644
--- a/tests/src/com/android/inputmethod/latin/utils/StringUtilsTests.java
+++ b/tests/src/com/android/inputmethod/latin/utils/StringUtilsTests.java
@@ -16,9 +16,18 @@
 
 package com.android.inputmethod.latin.utils;
 
+import com.android.inputmethod.latin.settings.SettingsValues;
+
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
+import android.text.style.SuggestionSpan;
+import android.text.style.URLSpan;
+import android.text.SpannableStringBuilder;
+import android.text.Spannable;
+import android.text.Spanned;
 
+import java.util.Arrays;
+import java.util.List;
 import java.util.Locale;
 
 @SmallTest
@@ -183,6 +192,18 @@
         assertTrue(StringUtils.isIdenticalAfterDowncase(""));
     }
 
+    public void testLooksValidForDictionaryInsertion() {
+        final SettingsValues settings =
+                SettingsValues.makeDummySettingsValuesForTest(Locale.ENGLISH);
+        assertTrue(StringUtils.looksValidForDictionaryInsertion("aochaueo", settings));
+        assertFalse(StringUtils.looksValidForDictionaryInsertion("", settings));
+        assertTrue(StringUtils.looksValidForDictionaryInsertion("ao-ch'aueo", settings));
+        assertFalse(StringUtils.looksValidForDictionaryInsertion("2908743256", settings));
+        assertTrue(StringUtils.looksValidForDictionaryInsertion("31aochaueo", settings));
+        assertFalse(StringUtils.looksValidForDictionaryInsertion("akeo  raeoch oerch .", settings));
+        assertFalse(StringUtils.looksValidForDictionaryInsertion("!!!", settings));
+    }
+
     private static void checkCapitalize(final String src, final String dst, final String separators,
             final Locale locale) {
         assertEquals(dst, StringUtils.capitalizeEachWord(src, separators, locale));
@@ -242,4 +263,56 @@
         // code for now True is acceptable.
         assertTrue(StringUtils.lastPartLooksLikeURL(".abc/def"));
     }
+
+    public void testHexStringUtils() {
+        final byte[] bytes = new byte[] { (byte)0x01, (byte)0x11, (byte)0x22, (byte)0x33,
+                (byte)0x55, (byte)0x88, (byte)0xEE };
+        final String bytesStr = StringUtils.byteArrayToHexString(bytes);
+        final byte[] bytes2 = StringUtils.hexStringToByteArray(bytesStr);
+        for (int i = 0; i < bytes.length; ++i) {
+            assertTrue(bytes[i] == bytes2[i]);
+        }
+        final String bytesStr2 = StringUtils.byteArrayToHexString(bytes2);
+        assertTrue(bytesStr.equals(bytesStr2));
+    }
+
+    public void testJsonStringUtils() {
+        final Object[] objs = new Object[] { 1, "aaa", "bbb", 3 };
+        final List<Object> objArray = Arrays.asList(objs);
+        final String str = StringUtils.listToJsonStr(objArray);
+        final List<Object> newObjArray = StringUtils.jsonStrToList(str);
+        for (int i = 0; i < objs.length; ++i) {
+            assertEquals(objs[i], newObjArray.get(i));
+        }
+    }
+
+    public void testConcatWithSuggestionSpansOnly() {
+        SpannableStringBuilder s = new SpannableStringBuilder("test string\ntest string\n"
+                + "test string\ntest string\ntest string\ntest string\ntest string\ntest string\n"
+                + "test string\ntest string\n");
+        final int N = 10;
+        for (int i = 0; i < N; ++i) {
+            // Put a PARAGRAPH-flagged span that should not be found in the result.
+            s.setSpan(new SuggestionSpan(getContext(),
+                    new String[] {"" + i}, Spannable.SPAN_PARAGRAPH),
+                    i * 12, i * 12 + 12, Spannable.SPAN_PARAGRAPH);
+            // Put a normal suggestion span that should be found in the result.
+            s.setSpan(new SuggestionSpan(getContext(), new String[] {"" + i}, 0), i, i * 2, 0);
+            // Put a URL span than should not be found in the result.
+            s.setSpan(new URLSpan("http://a"), i, i * 2, 0);
+        }
+
+        final CharSequence a = s.subSequence(0, 15);
+        final CharSequence b = s.subSequence(15, s.length());
+        final Spanned result =
+                (Spanned)StringUtils.concatWithNonParagraphSuggestionSpansOnly(a, b);
+
+        Object[] spans = result.getSpans(0, result.length(), SuggestionSpan.class);
+        for (int i = 0; i < spans.length; i++) {
+            final int flags = result.getSpanFlags(spans[i]);
+            assertEquals("Should not find a span with PARAGRAPH flag",
+                    flags & Spannable.SPAN_PARAGRAPH, 0);
+            assertTrue("Should be a SuggestionSpan", spans[i] instanceof SuggestionSpan);
+        }
+    }
 }
diff --git a/tests/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtilsTests.java b/tests/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtilsTests.java
index baebda2..856b2db 100644
--- a/tests/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtilsTests.java
+++ b/tests/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtilsTests.java
@@ -214,7 +214,7 @@
                         SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(FR_CA));
                 assertEquals("de   ", "Allemand",
                         SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(DE));
-                assertEquals("zz   ", "Alphabet (QWERTY)",
+                assertEquals("zz   ", "Alphabet latin (QWERTY)",
                         SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(ZZ));
                 return null;
             }
@@ -236,7 +236,7 @@
                         SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(EN_UK_DVORAK));
                 assertEquals("es_US colemak","Espagnol (États-Unis) (Colemak)",
                         SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(ES_US_COLEMAK));
-                assertEquals("zz pc",    "Alphabet (PC)",
+                assertEquals("zz pc",    "Alphabet latin (PC)",
                         SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(ZZ_PC));
                 return null;
             }
diff --git a/tests/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtilsTests.java b/tests/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtilsTests.java
index b679839..3eabe2b 100644
--- a/tests/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtilsTests.java
+++ b/tests/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtilsTests.java
@@ -21,18 +21,19 @@
 import android.test.suitebuilder.annotation.LargeTest;
 import android.util.Log;
 
+import com.android.inputmethod.latin.makedict.DictDecoder;
+import com.android.inputmethod.latin.makedict.DictEncoder;
 import com.android.inputmethod.latin.makedict.FormatSpec;
 import com.android.inputmethod.latin.makedict.FusionDictionary;
-import com.android.inputmethod.latin.makedict.FusionDictionary.CharGroup;
+import com.android.inputmethod.latin.makedict.FusionDictionary.PtNode;
+import com.android.inputmethod.latin.makedict.Ver3DictDecoder;
+import com.android.inputmethod.latin.makedict.Ver3DictEncoder;
 import com.android.inputmethod.latin.personalization.UserHistoryDictionaryBigramList;
-import com.android.inputmethod.latin.utils.ByteArrayWrapper;
 import com.android.inputmethod.latin.utils.UserHistoryDictIOUtils.BigramDictionaryInterface;
 import com.android.inputmethod.latin.utils.UserHistoryDictIOUtils.OnAddWordListener;
 
 import java.io.File;
-import java.io.FileInputStream;
 import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -50,6 +51,7 @@
     private static final int BIGRAM_FREQUENCY = 100;
     private static final ArrayList<String> NOT_HAVE_BIGRAM = new ArrayList<String>();
     private static final FormatSpec.FormatOptions FORMAT_OPTIONS = new FormatSpec.FormatOptions(2);
+    private static final String TEST_DICT_FILE_EXTENSION = ".testDict";
 
     /**
      * Return same frequency for all words and bigrams
@@ -87,12 +89,12 @@
 
     private void checkWordInFusionDict(final FusionDictionary dict, final String word,
             final ArrayList<String> expectedBigrams) {
-        final CharGroup group = FusionDictionary.findWordInTree(dict.mRoot, word);
-        assertNotNull(group);
-        assertTrue(group.isTerminal());
+        final PtNode ptNode = FusionDictionary.findWordInTree(dict.mRootNodeArray, word);
+        assertNotNull(ptNode);
+        assertTrue(ptNode.isTerminal());
 
         for (final String bigram : expectedBigrams) {
-            assertNotNull(group.getBigram(bigram));
+            assertNotNull(ptNode.getBigram(bigram));
         }
     }
 
@@ -136,38 +138,20 @@
 
     private void writeDictToFile(final File file,
             final UserHistoryDictionaryBigramList bigramList) {
-        try {
-            final FileOutputStream out = new FileOutputStream(file);
-            UserHistoryDictIOUtils.writeDictionaryBinary(out, this, bigramList, FORMAT_OPTIONS);
-            out.flush();
-            out.close();
-        } catch (IOException e) {
-            Log.e(TAG, "IO exception while writing file", e);
-        }
+        final DictEncoder dictEncoder = new Ver3DictEncoder(file);
+        UserHistoryDictIOUtils.writeDictionary(dictEncoder, this, bigramList, FORMAT_OPTIONS);
     }
 
     private void readDictFromFile(final File file, final OnAddWordListener listener) {
-        FileInputStream inStream = null;
-
+        final DictDecoder dictDecoder = FormatSpec.getDictDecoder(file, DictDecoder.USE_BYTEARRAY);
         try {
-            inStream = new FileInputStream(file);
-            final byte[] buffer = new byte[(int)file.length()];
-            inStream.read(buffer);
-
-            UserHistoryDictIOUtils.readDictionaryBinary(new ByteArrayWrapper(buffer), listener);
+            dictDecoder.openDictBuffer();
         } catch (FileNotFoundException e) {
             Log.e(TAG, "file not found", e);
         } catch (IOException e) {
             Log.e(TAG, "IOException", e);
-        } finally {
-            if (inStream != null) {
-                try {
-                    inStream.close();
-                } catch (IOException e) {
-                    // do nothing
-                }
-            }
         }
+        UserHistoryDictIOUtils.readDictionaryBinary(dictDecoder, listener);
     }
 
     public void testGenerateFusionDictionary() {
@@ -190,7 +174,8 @@
 
         File file = null;
         try {
-            file = File.createTempFile("testReadAndWrite", ".dict", getContext().getCacheDir());
+            file = File.createTempFile("testReadAndWrite", TEST_DICT_FILE_EXTENSION,
+                    getContext().getCacheDir());
         } catch (IOException e) {
             Log.d(TAG, "IOException while creating a temporary file", e);
         }
diff --git a/tools/dicttool/Android.mk b/tools/dicttool/Android.mk
index d06be58..3d09c05 100644
--- a/tools/dicttool/Android.mk
+++ b/tools/dicttool/Android.mk
@@ -13,27 +13,34 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-LOCAL_PATH := $(call my-dir)
+LATINIME_DICTTOOL_AOSP_LOCAL_PATH := $(call my-dir)
+LOCAL_PATH := $(LATINIME_DICTTOOL_AOSP_LOCAL_PATH)
+LATINIME_HOST_NATIVE_LIBNAME := liblatinime-aosp-dicttool-host
+include $(LOCAL_PATH)/NativeLib.mk
+
+######################################
+LOCAL_PATH := $(LATINIME_DICTTOOL_AOSP_LOCAL_PATH)
 include $(CLEAR_VARS)
 
-BUILD_TOP := ../../../../..
-LATINIME_DIR := $(BUILD_TOP)/packages/inputmethods/LatinIME
-LATINIME_BASE_SOURCE_DIRECTORY := $(LATINIME_DIR)/java/src/com/android/inputmethod
-LATINIME_CORE_SOURCE_DIRECTORY := $(LATINIME_BASE_SOURCE_DIRECTORY)/latin
+LATINIME_LOCAL_DIR := ../..
+LATINIME_BASE_SOURCE_DIRECTORY := $(LATINIME_LOCAL_DIR)/java/src/com/android/inputmethod
 LATINIME_ANNOTATIONS_SOURCE_DIRECTORY := $(LATINIME_BASE_SOURCE_DIRECTORY)/annotations
+LATINIME_CORE_SOURCE_DIRECTORY := $(LATINIME_BASE_SOURCE_DIRECTORY)/latin
 MAKEDICT_CORE_SOURCE_DIRECTORY := $(LATINIME_CORE_SOURCE_DIRECTORY)/makedict
-DICTTOOL_COMPAT_TESTS_DIRECTORY := compat
-DICTTOOL_ONDEVICE_TESTS_DIRECTORY := \
-        $(LATINIME_DIR)/tests/src/com/android/inputmethod/latin/makedict/
-
 USED_TARGETTED_UTILS := \
-        $(LATINIME_CORE_SOURCE_DIRECTORY)/utils/ByteArrayWrapper.java \
-        $(LATINIME_CORE_SOURCE_DIRECTORY)/utils/CollectionUtils.java
+        $(LATINIME_CORE_SOURCE_DIRECTORY)/utils/ByteArrayDictBuffer.java \
+        $(LATINIME_CORE_SOURCE_DIRECTORY)/utils/CollectionUtils.java \
+        $(LATINIME_CORE_SOURCE_DIRECTORY)/utils/JniUtils.java
+
+DICTTOOL_ONDEVICE_TESTS_DIRECTORY := \
+        $(LATINIME_LOCAL_DIR)/tests/src/com/android/inputmethod/latin/makedict/
+DICTTOOL_COMPAT_TESTS_DIRECTORY := compat
 
 LOCAL_MAIN_SRC_FILES := $(call all-java-files-under, $(MAKEDICT_CORE_SOURCE_DIRECTORY))
 LOCAL_TOOL_SRC_FILES := $(call all-java-files-under, src)
 LOCAL_ANNOTATIONS_SRC_FILES := \
         $(call all-java-files-under, $(LATINIME_ANNOTATIONS_SOURCE_DIRECTORY))
+
 LOCAL_SRC_FILES := $(LOCAL_TOOL_SRC_FILES) \
         $(filter-out $(addprefix %/, $(notdir $(LOCAL_TOOL_SRC_FILES))), $(LOCAL_MAIN_SRC_FILES)) \
         $(LOCAL_ANNOTATIONS_SRC_FILES) \
@@ -44,9 +51,13 @@
         $(USED_TARGETTED_UTILS)
 
 LOCAL_JAVA_LIBRARIES := junit
-
+LOCAL_ADDITIONAL_DEPENDENCIES := $(LATINIME_HOST_NATIVE_LIBNAME)
 LOCAL_JAR_MANIFEST := etc/manifest.txt
 LOCAL_MODULE := dicttool_aosp
 
 include $(BUILD_HOST_JAVA_LIBRARY)
 include $(LOCAL_PATH)/etc/Android.mk
+
+# Clear our private variables
+LATINIME_DICTTOOL_AOSP_LOCAL_PATH :=
+LATINIME_LOCAL_DIR :=
diff --git a/tools/dicttool/NativeLib.mk b/tools/dicttool/NativeLib.mk
new file mode 100644
index 0000000..a3d3c02
--- /dev/null
+++ b/tools/dicttool/NativeLib.mk
@@ -0,0 +1,51 @@
+#
+# 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.
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+# Need to define the name of the library in the caller in LATINIME_HOST_NATIVE_LIBNAME
+
+LATINIME_DIR_RELATIVE_TO_DICTTOOL := ../..
+
+ifneq ($(strip $(HOST_JDK_IS_64BIT_VERSION)),)
+LOCAL_CFLAGS += -m64
+LOCAL_LDFLAGS += -m64
+endif #HOST_JDK_IS_64BIT_VERSION
+
+LOCAL_CFLAGS += -DHOST_TOOL -fPIC
+LOCAL_NO_DEFAULT_COMPILER_FLAGS := true
+
+LATINIME_NATIVE_JNI_DIR := $(LATINIME_DIR_RELATIVE_TO_DICTTOOL)/native/jni
+LATINIME_NATIVE_SRC_DIR := $(LATINIME_DIR_RELATIVE_TO_DICTTOOL)/native/jni/src
+LOCAL_C_INCLUDES := $(LOCAL_PATH)/$(LATINIME_NATIVE_SRC_DIR)
+# Used in jni_common.cpp to avoid registering useless methods.
+
+LATIN_IME_JNI_SRC_FILES := \
+    com_android_inputmethod_latin_makedict_Ver3DictDecoder.cpp \
+    jni_common.cpp
+
+LATIN_IME_CORE_SRC_FILES :=
+
+LOCAL_SRC_FILES := \
+    $(addprefix $(LATINIME_NATIVE_JNI_DIR)/, $(LATIN_IME_JNI_SRC_FILES)) \
+    $(addprefix $(LATINIME_NATIVE_SRC_DIR)/, $(LATIN_IME_CORE_SRC_FILES))
+
+LOCAL_MODULE := $(LATINIME_HOST_NATIVE_LIBNAME)
+
+include $(BUILD_HOST_SHARED_LIBRARY)
+
+# Clear our private variables
+LATINIME_DIR_RELATIVE_TO_DICTTOOL := ../..
diff --git a/tools/dicttool/compat/android/util/Log.java b/tools/dicttool/compat/android/util/Log.java
index d9df3a4..b3b6dd8 100644
--- a/tools/dicttool/compat/android/util/Log.java
+++ b/tools/dicttool/compat/android/util/Log.java
@@ -25,13 +25,13 @@
     public static void d(final String tag, final String message) {
         System.out.println(tag + " : " + message);
     }
-    public static void d(final String tag, final String message, final Exception e) {
+    public static void d(final String tag, final String message, final Throwable e) {
         System.out.println(tag + " : " + message + " : " + e);
     }
     public static void e(final String tag, final String message) {
         d(tag, message);
     }
-    public static void e(final String tag, final String message, final Exception e) {
-        e(tag, message, e);
+    public static void e(final String tag, final String message, final Throwable e) {
+        d(tag, message, e);
     }
 }
diff --git a/native/jni/src/suggest/core/dictionary/byte_array_utils.cpp b/tools/dicttool/compat/com/android/inputmethod/latin/define/JniLibName.java
similarity index 71%
copy from native/jni/src/suggest/core/dictionary/byte_array_utils.cpp
copy to tools/dicttool/compat/com/android/inputmethod/latin/define/JniLibName.java
index 68b1d5d..c68bdaa 100644
--- a/native/jni/src/suggest/core/dictionary/byte_array_utils.cpp
+++ b/tools/dicttool/compat/com/android/inputmethod/latin/define/JniLibName.java
@@ -14,11 +14,12 @@
  * limitations under the License.
  */
 
-#include "suggest/core/dictionary/byte_array_utils.h"
+package com.android.inputmethod.latin.define;
 
-namespace latinime {
+public final class JniLibName {
+    private JniLibName() {
+        // This class is not publicly instantiable.
+    }
 
-const uint8_t ByteArrayUtils::MINIMAL_ONE_BYTE_CHARACTER_VALUE = 0x20;
-const uint8_t ByteArrayUtils::CHARACTER_ARRAY_TERMINATOR = 0x1F;
-
-} // namespace latinime
+    public static final String JNI_LIB_NAME = "latinime-dicttool-host";
+}
diff --git a/tools/dicttool/etc/dicttool_aosp b/tools/dicttool/etc/dicttool_aosp
index cc7111a..65a1c3a 100755
--- a/tools/dicttool/etc/dicttool_aosp
+++ b/tools/dicttool/etc/dicttool_aosp
@@ -69,4 +69,4 @@
 fi
 
 # might need more memory, e.g. -Xmx128M
-exec java -ea -classpath "$libpath":"$jarpath" "$classname" "$@"
+exec java -ea -classpath "$libpath":"$jarpath" -Djava.library.path="$libdir" "$classname" "$@"
diff --git a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/BinaryDictOffdeviceUtils.java b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/BinaryDictOffdeviceUtils.java
index c2c77d6..fa80385 100644
--- a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/BinaryDictOffdeviceUtils.java
+++ b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/BinaryDictOffdeviceUtils.java
@@ -16,7 +16,9 @@
 
 package com.android.inputmethod.latin.dicttool;
 
-import com.android.inputmethod.latin.makedict.BinaryDictInputOutput;
+import com.android.inputmethod.latin.makedict.BinaryDictDecoderUtils;
+import com.android.inputmethod.latin.makedict.DictDecoder;
+import com.android.inputmethod.latin.makedict.FormatSpec;
 import com.android.inputmethod.latin.makedict.FusionDictionary;
 import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
 
@@ -30,8 +32,6 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
-import java.nio.ByteBuffer;
-import java.nio.channels.FileChannel;
 import java.util.ArrayList;
 
 import javax.xml.parsers.ParserConfigurationException;
@@ -98,7 +98,7 @@
         // over and over, ending in a stack overflow. Hence we limit the depth at which we try
         // decoding the file.
         if (depth > MAX_DECODE_DEPTH) return null;
-        if (BinaryDictInputOutput.isBinaryDictionary(src)) {
+        if (BinaryDictDecoderUtils.isBinaryDictionary(src)) {
             spec.mFile = src;
             return spec;
         }
@@ -185,16 +185,14 @@
                     crash(filename, new RuntimeException(
                             filename + " does not seem to be a dictionary file"));
                 } else {
-                    final FileInputStream inStream = new FileInputStream(decodedSpec.mFile);
-                    final ByteBuffer buffer = inStream.getChannel().map(
-                            FileChannel.MapMode.READ_ONLY, 0, decodedSpec.mFile.length());
+                    final DictDecoder dictDecoder = FormatSpec.getDictDecoder(file,
+                            DictDecoder.USE_BYTEARRAY);
                     if (report) {
                         System.out.println("Format : Binary dictionary format");
                         System.out.println("Packaging : " + decodedSpec.describeChain());
                         System.out.println("Uncompressed size : " + decodedSpec.mFile.length());
                     }
-                    return BinaryDictInputOutput.readDictionaryBinary(
-                            new BinaryDictInputOutput.ByteBufferWrapper(buffer), null);
+                    return dictDecoder.readDictionaryBinary(null, false /* deleteDictIfBroken */);
                 }
             }
         } catch (IOException e) {
diff --git a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/CombinedInputOutput.java b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/CombinedInputOutput.java
index 092ee76..4b67169 100644
--- a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/CombinedInputOutput.java
+++ b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/CombinedInputOutput.java
@@ -19,7 +19,7 @@
 import com.android.inputmethod.latin.makedict.FormatSpec;
 import com.android.inputmethod.latin.makedict.FusionDictionary;
 import com.android.inputmethod.latin.makedict.FusionDictionary.DictionaryOptions;
-import com.android.inputmethod.latin.makedict.FusionDictionary.Node;
+import com.android.inputmethod.latin.makedict.FusionDictionary.PtNodeArray;
 import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
 import com.android.inputmethod.latin.makedict.Word;
 
@@ -117,7 +117,7 @@
         final boolean processLigatures =
                 FRENCH_LIGATURE_PROCESSING_OPTION.equals(attributes.get(OPTIONS_TAG));
         attributes.remove(OPTIONS_TAG);
-        final FusionDictionary dict = new FusionDictionary(new Node(), new DictionaryOptions(
+        final FusionDictionary dict = new FusionDictionary(new PtNodeArray(), new DictionaryOptions(
                 attributes, processUmlauts, processLigatures));
 
         String line;
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 cc890f6..5c7e8b4 100644
--- a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/DictionaryMaker.java
+++ b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/DictionaryMaker.java
@@ -16,21 +16,22 @@
 
 package com.android.inputmethod.latin.dicttool;
 
-import com.android.inputmethod.latin.makedict.BinaryDictInputOutput;
+import com.android.inputmethod.latin.makedict.BinaryDictDecoderUtils;
+import com.android.inputmethod.latin.makedict.DictDecoder;
+import com.android.inputmethod.latin.makedict.DictEncoder;
 import com.android.inputmethod.latin.makedict.FormatSpec;
 import com.android.inputmethod.latin.makedict.FusionDictionary;
 import com.android.inputmethod.latin.makedict.MakedictLog;
 import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
+import com.android.inputmethod.latin.makedict.Ver3DictEncoder;
+import com.android.inputmethod.latin.makedict.Ver4DictEncoder;
 
 import java.io.BufferedWriter;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
 import java.io.FileWriter;
 import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.channels.FileChannel;
 import java.util.Arrays;
 import java.util.LinkedList;
 
@@ -44,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";
@@ -127,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.";
         }
 
@@ -159,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 {
@@ -176,7 +177,7 @@
                                 inputUnigramXml = filename;
                             } else if (CombinedInputOutput.isCombinedDictionary(filename)) {
                                 inputCombined = filename;
-                            } else if (BinaryDictInputOutput.isBinaryDictionary(filename)) {
+                            } else if (BinaryDictDecoderUtils.isBinaryDictionary(filename)) {
                                 inputBinary = filename;
                             } else {
                                 throw new IllegalArgumentException(
@@ -198,7 +199,7 @@
                     }
                 } else {
                     if (null == inputBinary && null == inputUnigramXml) {
-                        if (BinaryDictInputOutput.isBinaryDictionary(arg)) {
+                        if (BinaryDictDecoderUtils.isBinaryDictionary(arg)) {
                             inputBinary = arg;
                         } else if (CombinedInputOutput.isCombinedDictionary(arg)) {
                             inputCombined = arg;
@@ -265,24 +266,9 @@
      */
     private static FusionDictionary readBinaryFile(final String binaryFilename)
             throws FileNotFoundException, IOException, UnsupportedFormatException {
-        FileInputStream inStream = null;
-
-        try {
-            final File file = new File(binaryFilename);
-            inStream = new FileInputStream(file);
-            final ByteBuffer buffer = inStream.getChannel().map(
-                    FileChannel.MapMode.READ_ONLY, 0, file.length());
-            return BinaryDictInputOutput.readDictionaryBinary(
-                    new BinaryDictInputOutput.ByteBufferWrapper(buffer), null);
-        } finally {
-            if (inStream != null) {
-                try {
-                    inStream.close();
-                } catch (IOException e) {
-                    // do nothing
-                }
-            }
-        }
+        final File file = new File(binaryFilename);
+        final DictDecoder dictDecoder = FormatSpec.getDictDecoder(file);
+        return dictDecoder.readDictionaryBinary(null, false /* deleteDictIfBroken */);
     }
 
     /**
@@ -371,8 +357,13 @@
             throws FileNotFoundException, IOException, UnsupportedFormatException {
         final File outputFile = new File(outputFilename);
         final FormatSpec.FormatOptions formatOptions = new FormatSpec.FormatOptions(version);
-        BinaryDictInputOutput.writeDictionaryBinary(new FileOutputStream(outputFilename), dict,
-                formatOptions);
+        final DictEncoder dictEncoder;
+        if (version == 4) {
+            dictEncoder = new Ver4DictEncoder(outputFile);
+        } else {
+            dictEncoder = new Ver3DictEncoder(outputFile);
+        }
+        dictEncoder.writeDictionary(dict, formatOptions);
     }
 
     /**
diff --git a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Diff.java b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Diff.java
index 5c3e87e..66fd084 100644
--- a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Diff.java
+++ b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Diff.java
@@ -17,7 +17,7 @@
 package com.android.inputmethod.latin.dicttool;
 
 import com.android.inputmethod.latin.makedict.FusionDictionary;
-import com.android.inputmethod.latin.makedict.FusionDictionary.CharGroup;
+import com.android.inputmethod.latin.makedict.FusionDictionary.PtNode;
 import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
 import com.android.inputmethod.latin.makedict.Word;
 
@@ -121,7 +121,8 @@
     private static void diffWords(final FusionDictionary dict0, final FusionDictionary dict1) {
         boolean hasDifferences = false;
         for (final Word word0 : dict0) {
-            final CharGroup word1 = FusionDictionary.findWordInTree(dict1.mRoot, word0.mWord);
+            final PtNode word1 = FusionDictionary.findWordInTree(dict1.mRootNodeArray,
+                    word0.mWord);
             if (null == word1) {
                 // This word is not in dict1
                 System.out.println("Deleted: " + word0.mWord + " " + word0.mFrequency);
@@ -150,7 +151,8 @@
             }
         }
         for (final Word word1 : dict1) {
-            final CharGroup word0 = FusionDictionary.findWordInTree(dict0.mRoot, word1.mWord);
+            final PtNode word0 = FusionDictionary.findWordInTree(dict0.mRootNodeArray,
+                    word1.mWord);
             if (null == word0) {
                 // This word is not in dict0
                 System.out.println("Added: " + word1.mWord + " " + word1.mFrequency);
diff --git a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Info.java b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Info.java
index f289454..350f427 100644
--- a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Info.java
+++ b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Info.java
@@ -18,7 +18,7 @@
 
 import com.android.inputmethod.latin.makedict.FormatSpec;
 import com.android.inputmethod.latin.makedict.FusionDictionary;
-import com.android.inputmethod.latin.makedict.FusionDictionary.CharGroup;
+import com.android.inputmethod.latin.makedict.FusionDictionary.PtNode;
 import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
 import com.android.inputmethod.latin.makedict.Word;
 
@@ -65,20 +65,20 @@
 
     private static void showWordInfo(final FusionDictionary dict, final String word,
             final boolean plumbing) {
-        final CharGroup group = FusionDictionary.findWordInTree(dict.mRoot, word);
-        if (null == group) {
+        final PtNode ptNode = FusionDictionary.findWordInTree(dict.mRootNodeArray, word);
+        if (null == ptNode) {
             System.out.println(word + " is not in the dictionary");
             return;
         }
         System.out.println("Word: " + word);
-        System.out.println("  Freq: " + group.getFrequency());
-        if (group.getIsNotAWord()) {
+        System.out.println("  Freq: " + ptNode.getFrequency());
+        if (ptNode.getIsNotAWord()) {
             System.out.println("  Is not a word");
         }
-        if (group.getIsBlacklistEntry()) {
+        if (ptNode.getIsBlacklistEntry()) {
             System.out.println("  Is a blacklist entry");
         }
-        final ArrayList<WeightedString> shortcutTargets = group.getShortcutTargets();
+        final ArrayList<WeightedString> shortcutTargets = ptNode.getShortcutTargets();
         if (null == shortcutTargets || shortcutTargets.isEmpty()) {
             System.out.println("  No shortcuts");
         } else {
@@ -88,7 +88,7 @@
                                 ? "whitelist" : shortcutTarget.mFrequency) + ")");
             }
         }
-        final ArrayList<WeightedString> bigrams = group.getBigrams();
+        final ArrayList<WeightedString> bigrams = ptNode.getBigrams();
         if (null == bigrams || bigrams.isEmpty()) {
             System.out.println("  No bigrams");
         } else {
diff --git a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Test.java b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Test.java
index 972b6e7..9174238 100644
--- a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Test.java
+++ b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Test.java
@@ -16,9 +16,9 @@
 
 package com.android.inputmethod.latin.dicttool;
 
-import com.android.inputmethod.latin.makedict.BinaryDictIOTests;
+import com.android.inputmethod.latin.makedict.BinaryDictDecoderEncoderTests;
+import com.android.inputmethod.latin.makedict.BinaryDictEncoderFlattenTreeTests;
 import com.android.inputmethod.latin.makedict.BinaryDictIOUtilsTests;
-import com.android.inputmethod.latin.makedict.BinaryDictInputOutputTest;
 import com.android.inputmethod.latin.makedict.FusionDictionaryTest;
 
 import java.lang.reflect.Constructor;
@@ -37,9 +37,9 @@
     private static final Class<?>[] sClassesToTest = {
         BinaryDictOffdeviceUtilsTests.class,
         FusionDictionaryTest.class,
-        BinaryDictInputOutputTest.class,
-        BinaryDictIOUtilsTests.class,
-        BinaryDictIOTests.class
+        BinaryDictDecoderEncoderTests.class,
+        BinaryDictEncoderFlattenTreeTests.class,
+        BinaryDictIOUtilsTests.class
     };
     private ArrayList<Method> mAllTestMethods = new ArrayList<Method>();
     private ArrayList<String> mUsedTestMethods = new ArrayList<String>();
diff --git a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/XmlDictInputOutput.java b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/XmlDictInputOutput.java
index 1fd2cba..4e99bf9 100644
--- a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/XmlDictInputOutput.java
+++ b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/XmlDictInputOutput.java
@@ -18,7 +18,7 @@
 
 import com.android.inputmethod.latin.makedict.FusionDictionary;
 import com.android.inputmethod.latin.makedict.FusionDictionary.DictionaryOptions;
-import com.android.inputmethod.latin.makedict.FusionDictionary.Node;
+import com.android.inputmethod.latin.makedict.FusionDictionary.PtNodeArray;
 import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
 import com.android.inputmethod.latin.makedict.Word;
 
@@ -124,8 +124,8 @@
                         GERMAN_UMLAUT_PROCESSING_OPTION.equals(optionsString);
                 final boolean processLigatures =
                         FRENCH_LIGATURE_PROCESSING_OPTION.equals(optionsString);
-                mDictionary = new FusionDictionary(new Node(), new DictionaryOptions(attributes,
-                        processUmlauts, processLigatures));
+                mDictionary = new FusionDictionary(new PtNodeArray(),
+                        new DictionaryOptions(attributes, processUmlauts, processLigatures));
             } else {
                 mState = UNKNOWN;
             }
diff --git a/tools/dicttool/tests/com/android/inputmethod/latin/dicttool/BinaryDictOffdeviceUtilsTests.java b/tools/dicttool/tests/com/android/inputmethod/latin/dicttool/BinaryDictOffdeviceUtilsTests.java
index 554bd24..1eff497 100644
--- a/tools/dicttool/tests/com/android/inputmethod/latin/dicttool/BinaryDictOffdeviceUtilsTests.java
+++ b/tools/dicttool/tests/com/android/inputmethod/latin/dicttool/BinaryDictOffdeviceUtilsTests.java
@@ -16,35 +16,34 @@
 
 package com.android.inputmethod.latin.dicttool;
 
-import com.android.inputmethod.latin.makedict.BinaryDictInputOutput;
+import com.android.inputmethod.latin.makedict.DictDecoder;
+import com.android.inputmethod.latin.makedict.DictEncoder;
+import com.android.inputmethod.latin.makedict.FormatSpec;
 import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions;
 import com.android.inputmethod.latin.makedict.FusionDictionary;
 import com.android.inputmethod.latin.makedict.FusionDictionary.DictionaryOptions;
-import com.android.inputmethod.latin.makedict.FusionDictionary.Node;
+import com.android.inputmethod.latin.makedict.FusionDictionary.PtNodeArray;
 import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
+import com.android.inputmethod.latin.makedict.Ver3DictEncoder;
 
 import junit.framework.TestCase;
 
 import java.io.File;
 import java.io.BufferedOutputStream;
-import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.OutputStream;
-import java.nio.ByteBuffer;
-import java.nio.channels.FileChannel;
-import java.util.ArrayList;
 import java.util.HashMap;
 
 /**
- * Unit tests for BinaryDictOffdeviceUtilsTests
+ * Unit tests for BinaryDictOffdeviceUtils
  */
 public class BinaryDictOffdeviceUtilsTests extends TestCase {
     private static final int TEST_FREQ = 37; // Some arbitrary value unlikely to happen by chance
 
     public void testGetRawDictWorks() throws IOException, UnsupportedFormatException {
         // Create a thrice-compressed dictionary file.
-        final FusionDictionary dict = new FusionDictionary(new Node(),
+        final FusionDictionary dict = new FusionDictionary(new PtNodeArray(),
                 new DictionaryOptions(new HashMap<String, String>(),
                         false /* germanUmlautProcessing */, false /* frenchLigatureProcessing */));
         dict.add("foo", TEST_FREQ, null, false /* isNotAWord */);
@@ -55,12 +54,13 @@
 
         final File dst = File.createTempFile("testGetRawDict", ".tmp");
         dst.deleteOnExit();
+
         final OutputStream out = Compress.getCompressedStream(
                 Compress.getCompressedStream(
                         Compress.getCompressedStream(
                                 new BufferedOutputStream(new FileOutputStream(dst)))));
-
-        BinaryDictInputOutput.writeDictionaryBinary(out, dict, new FormatOptions(2, false));
+        final DictEncoder dictEncoder = new Ver3DictEncoder(out);
+        dictEncoder.writeDictionary(dict, new FormatOptions(2, false));
 
         // Test for an actually compressed dictionary and its contents
         final BinaryDictOffdeviceUtils.DecoderChainSpec decodeSpec =
@@ -69,14 +69,13 @@
             assertEquals("Wrong decode spec", BinaryDictOffdeviceUtils.COMPRESSION, step);
         }
         assertEquals("Wrong decode spec", 3, decodeSpec.mDecoderSpec.size());
-        final FileInputStream inStream = new FileInputStream(decodeSpec.mFile);
-        final ByteBuffer buffer = inStream.getChannel().map(
-                FileChannel.MapMode.READ_ONLY, 0, decodeSpec.mFile.length());
-        final FusionDictionary resultDict = BinaryDictInputOutput.readDictionaryBinary(
-                new BinaryDictInputOutput.ByteBufferWrapper(buffer),
-                null /* dict : an optional dictionary to add words to, or null */);
+        final DictDecoder dictDecoder = FormatSpec.getDictDecoder(decodeSpec.mFile);
+        final FusionDictionary resultDict = dictDecoder.readDictionaryBinary(
+                null /* dict : an optional dictionary to add words to, or null */,
+                false /* deleteDictIfBroken */);
         assertEquals("Dictionary can't be read back correctly",
-                resultDict.findWordInTree(resultDict.mRoot, "foo").getFrequency(), TEST_FREQ);
+                FusionDictionary.findWordInTree(resultDict.mRootNodeArray, "foo").getFrequency(),
+                TEST_FREQ);
     }
 
     public void testGetRawDictFails() throws IOException {
diff --git a/tools/dicttool/tests/com/android/inputmethod/latin/makedict/BinaryDictInputOutputTest.java b/tools/dicttool/tests/com/android/inputmethod/latin/makedict/BinaryDictEncoderFlattenTreeTests.java
similarity index 78%
rename from tools/dicttool/tests/com/android/inputmethod/latin/makedict/BinaryDictInputOutputTest.java
rename to tools/dicttool/tests/com/android/inputmethod/latin/makedict/BinaryDictEncoderFlattenTreeTests.java
index 0969028..5505823 100644
--- a/tools/dicttool/tests/com/android/inputmethod/latin/makedict/BinaryDictInputOutputTest.java
+++ b/tools/dicttool/tests/com/android/inputmethod/latin/makedict/BinaryDictEncoderFlattenTreeTests.java
@@ -17,7 +17,7 @@
 package com.android.inputmethod.latin.makedict;
 
 import com.android.inputmethod.latin.makedict.FusionDictionary.DictionaryOptions;
-import com.android.inputmethod.latin.makedict.FusionDictionary.Node;
+import com.android.inputmethod.latin.makedict.FusionDictionary.PtNodeArray;
 
 import junit.framework.TestCase;
 
@@ -25,13 +25,13 @@
 import java.util.HashMap;
 
 /**
- * Unit tests for BinaryDictInputOutput.
+ * Unit tests for BinaryDictEncoderUtils.flattenTree().
  */
-public class BinaryDictInputOutputTest extends TestCase {
+public class BinaryDictEncoderFlattenTreeTests extends TestCase {
     // Test the flattened array contains the expected number of nodes, and
     // that it does not contain any duplicates.
     public void testFlattenNodes() {
-        final FusionDictionary dict = new FusionDictionary(new Node(),
+        final FusionDictionary dict = new FusionDictionary(new PtNodeArray(),
                 new DictionaryOptions(new HashMap<String, String>(),
                         false /* germanUmlautProcessing */, false /* frenchLigatureProcessing */));
         dict.add("foo", 1, null, false /* isNotAWord */);
@@ -39,10 +39,11 @@
         dict.add("ftb", 1, null, false /* isNotAWord */);
         dict.add("bar", 1, null, false /* isNotAWord */);
         dict.add("fool", 1, null, false /* isNotAWord */);
-        final ArrayList<Node> result = BinaryDictInputOutput.flattenTree(dict.mRoot);
+        final ArrayList<PtNodeArray> result =
+                BinaryDictEncoderUtils.flattenTree(dict.mRootNodeArray);
         assertEquals(4, result.size());
         while (!result.isEmpty()) {
-            final Node n = result.remove(0);
+            final PtNodeArray n = result.remove(0);
             assertFalse("Flattened array contained the same node twice", result.contains(n));
         }
     }
diff --git a/tools/dicttool/tests/com/android/inputmethod/latin/makedict/FusionDictionaryTest.java b/tools/dicttool/tests/com/android/inputmethod/latin/makedict/FusionDictionaryTest.java
index 7607113..659650a 100644
--- a/tools/dicttool/tests/com/android/inputmethod/latin/makedict/FusionDictionaryTest.java
+++ b/tools/dicttool/tests/com/android/inputmethod/latin/makedict/FusionDictionaryTest.java
@@ -17,9 +17,9 @@
 package com.android.inputmethod.latin.makedict;
 
 import com.android.inputmethod.latin.makedict.FusionDictionary;
-import com.android.inputmethod.latin.makedict.FusionDictionary.CharGroup;
+import com.android.inputmethod.latin.makedict.FusionDictionary.PtNode;
 import com.android.inputmethod.latin.makedict.FusionDictionary.DictionaryOptions;
-import com.android.inputmethod.latin.makedict.FusionDictionary.Node;
+import com.android.inputmethod.latin.makedict.FusionDictionary.PtNodeArray;
 import com.android.inputmethod.latin.makedict.Word;
 
 import junit.framework.TestCase;
@@ -29,7 +29,7 @@
 import java.util.Random;
 
 /**
- * Unit tests for BinaryDictInputOutput.
+ * Unit tests for FusionDictionary.
  */
 public class FusionDictionaryTest extends TestCase {
     private static final ArrayList<String> sWords = new ArrayList<String>();
@@ -72,8 +72,8 @@
         assertNotNull(dict);
         for (final String word : words) {
             if (--limit < 0) return;
-            final CharGroup cg = FusionDictionary.findWordInTree(dict.mRoot, word);
-            assertNotNull(cg);
+            final PtNode ptNode = FusionDictionary.findWordInTree(dict.mRootNodeArray, word);
+            assertNotNull(ptNode);
         }
     }
 
@@ -95,7 +95,7 @@
     // Test the flattened array contains the expected number of nodes, and
     // that it does not contain any duplicates.
     public void testFusion() {
-        final FusionDictionary dict = new FusionDictionary(new Node(),
+        final FusionDictionary dict = new FusionDictionary(new PtNodeArray(),
                 new DictionaryOptions(new HashMap<String, String>(),
                         false /* germanUmlautProcessing */, false /* frenchLigatureProcessing */));
         final long time = System.currentTimeMillis();
diff --git a/tools/dicttool/tests/etc/test-dicttool.sh b/tools/dicttool/tests/etc/test-dicttool.sh
index 0921207..5eb44fc 100755
--- a/tools/dicttool/tests/etc/test-dicttool.sh
+++ b/tools/dicttool/tests/etc/test-dicttool.sh
@@ -24,5 +24,5 @@
 find out -name "dicttool_aosp*" -exec rm -rf {} \; > /dev/null 2>&1
 mmm -j8 external/junit
 DICTTOOL_UNITTEST=true mmm -j8 packages/inputmethods/LatinIME/tools/dicttool
-java -classpath ${ANDROID_HOST_OUT}/framework/junit.jar:${ANDROID_HOST_OUT}/framework/dicttool_aosp.jar junit.textui.TestRunner com.android.inputmethod.latin.makedict.BinaryDictInputOutputTest
+java -classpath ${ANDROID_HOST_OUT}/framework/junit.jar:${ANDROID_HOST_OUT}/framework/dicttool_aosp.jar junit.textui.TestRunner com.android.inputmethod.latin.makedict.BinaryDictEncoderFlattenTreeTests
 java -classpath ${ANDROID_HOST_OUT}/framework/junit.jar:${ANDROID_HOST_OUT}/framework/dicttool_aosp.jar junit.textui.TestRunner com.android.inputmethod.latin.dicttool.BinaryDictOffdeviceUtilsTests
diff --git a/tools/maketext/Android.mk b/tools/make-keyboard-text/Android.mk
similarity index 95%
rename from tools/maketext/Android.mk
rename to tools/make-keyboard-text/Android.mk
index 77914ca..8760148 100644
--- a/tools/maketext/Android.mk
+++ b/tools/make-keyboard-text/Android.mk
@@ -19,7 +19,7 @@
 LOCAL_SRC_FILES += $(call all-java-files-under,src)
 LOCAL_JAR_MANIFEST := etc/manifest.txt
 LOCAL_JAVA_RESOURCE_DIRS := res
-LOCAL_MODULE := maketext
+LOCAL_MODULE := make-keyboard-text
 
 include $(BUILD_HOST_JAVA_LIBRARY)
 include $(LOCAL_PATH)/etc/Android.mk
diff --git a/tools/maketext/etc/Android.mk b/tools/make-keyboard-text/etc/Android.mk
similarity index 93%
rename from tools/maketext/etc/Android.mk
rename to tools/make-keyboard-text/etc/Android.mk
index 475676b..0fbf4ff 100644
--- a/tools/maketext/etc/Android.mk
+++ b/tools/make-keyboard-text/etc/Android.mk
@@ -15,6 +15,6 @@
 LOCAL_PATH := $(call my-dir)
 include $(CLEAR_VARS)
 
-LOCAL_PREBUILT_EXECUTABLES := maketext
+LOCAL_PREBUILT_EXECUTABLES := make-keyboard-text
 
 include $(BUILD_HOST_PREBUILT)
diff --git a/tools/maketext/etc/maketext b/tools/make-keyboard-text/etc/make-keyboard-text
similarity index 97%
rename from tools/maketext/etc/maketext
rename to tools/make-keyboard-text/etc/make-keyboard-text
index 0edd360..156f9ec 100755
--- a/tools/maketext/etc/maketext
+++ b/tools/make-keyboard-text/etc/make-keyboard-text
@@ -33,7 +33,7 @@
 prog="${progdir}"/`basename "${prog}"`
 cd "${oldwd}"
 
-jarfile=maketext.jar
+jarfile=make-keyboard-text.jar
 frameworkdir="$progdir"
 if [ ! -r "$frameworkdir/$jarfile" ]
 then
diff --git a/tools/make-keyboard-text/etc/manifest.txt b/tools/make-keyboard-text/etc/manifest.txt
new file mode 100644
index 0000000..8ad4db0
--- /dev/null
+++ b/tools/make-keyboard-text/etc/manifest.txt
@@ -0,0 +1 @@
+Main-Class: com.android.inputmethod.keyboard.tools.MakeKeyboardText
diff --git a/tools/maketext/res/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.tmpl b/tools/make-keyboard-text/res/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.tmpl
similarity index 91%
rename from tools/maketext/res/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.tmpl
rename to tools/make-keyboard-text/res/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.tmpl
index 479a766..4cd9c23 100644
--- a/tools/maketext/res/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.tmpl
+++ b/tools/make-keyboard-text/res/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.tmpl
@@ -27,18 +27,18 @@
 /**
  * !!!!! DO NOT EDIT THIS FILE !!!!!
  *
- * This file is generated by tools/maketext. The base template file is
- *   tools/maketext/res/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.tmpl
+ * This file is generated by tools/make-keyboard-text. The base template file is
+ *   tools/make-keyboard-text/res/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.tmpl
  *
  * This file must be updated when any text resources in keyboard layout files have been changed.
  * These text resources are referred as "!text/<resource_name>" in keyboard XML definitions,
  * and should be defined in
- *   tools/maketext/res/values-<locale>/donottranslate-more-keys.xml
+ *   tools/make-keyboard-text/res/values-<locale>/donottranslate-more-keys.xml
  *
  * To update this file, please run the following commands.
  *   $ cd $ANDROID_BUILD_TOP
- *   $ mmm packages/inputmethods/LatinIME/tools/maketext
- *   $ maketext -java packages/inputmethods/LatinIME/java/src
+ *   $ mmm packages/inputmethods/LatinIME/tools/make-keyboard-text
+ *   $ make-keyboard-text -java packages/inputmethods/LatinIME/java/src
  *
  * The updated source file will be generated to the following path (this file).
  *   packages/inputmethods/LatinIME/java/src/com/android/inputmethod/keyboard/internal/
diff --git a/tools/maketext/res/values-af/donottranslate-more-keys.xml b/tools/make-keyboard-text/res/values-af/donottranslate-more-keys.xml
similarity index 100%
rename from tools/maketext/res/values-af/donottranslate-more-keys.xml
rename to tools/make-keyboard-text/res/values-af/donottranslate-more-keys.xml
diff --git a/tools/maketext/res/values-ar/donottranslate-more-keys.xml b/tools/make-keyboard-text/res/values-ar/donottranslate-more-keys.xml
similarity index 98%
rename from tools/maketext/res/values-ar/donottranslate-more-keys.xml
rename to tools/make-keyboard-text/res/values-ar/donottranslate-more-keys.xml
index cace240..8b86b1b 100644
--- a/tools/maketext/res/values-ar/donottranslate-more-keys.xml
+++ b/tools/make-keyboard-text/res/values-ar/donottranslate-more-keys.xml
@@ -81,7 +81,7 @@
     <!-- U+061F: "؟" ARABIC QUESTION MARK
          U+060C: "،" ARABIC COMMA
          U+061B: "؛" ARABIC SEMICOLON -->
-    <string name="more_keys_for_punctuation">"!fixedColumnOrder!8,\",\',#,-,:,!,&#x060C;,&#x061F;,\@,&amp;,\\%,+,&#x061B;,/,(,)"</string>
+    <string name="more_keys_for_punctuation">"!fixedColumnOrder!8,\",\',#,-,:,!,&#x060C;,&#x061F;,\@,&amp;,\\%,+,&#x061B;,/,(|),)|("</string>
     <string name="more_keys_for_apostrophe">"&#x061F;,&#x061B;,!,:,-,/,\',\""</string>
     <!-- U+266A: "♪" EIGHTH NOTE -->
     <string name="more_keys_for_bullet">&#x266A;</string>
diff --git a/tools/maketext/res/values-az/donottranslate-more-keys.xml b/tools/make-keyboard-text/res/values-az/donottranslate-more-keys.xml
similarity index 100%
rename from tools/maketext/res/values-az/donottranslate-more-keys.xml
rename to tools/make-keyboard-text/res/values-az/donottranslate-more-keys.xml
diff --git a/tools/maketext/res/values-be/donottranslate-more-keys.xml b/tools/make-keyboard-text/res/values-be/donottranslate-more-keys.xml
similarity index 100%
rename from tools/maketext/res/values-be/donottranslate-more-keys.xml
rename to tools/make-keyboard-text/res/values-be/donottranslate-more-keys.xml
diff --git a/tools/maketext/res/values-bg/donottranslate-more-keys.xml b/tools/make-keyboard-text/res/values-bg/donottranslate-more-keys.xml
similarity index 100%
rename from tools/maketext/res/values-bg/donottranslate-more-keys.xml
rename to tools/make-keyboard-text/res/values-bg/donottranslate-more-keys.xml
diff --git a/tools/maketext/res/values-ca/donottranslate-more-keys.xml b/tools/make-keyboard-text/res/values-ca/donottranslate-more-keys.xml
similarity index 98%
rename from tools/maketext/res/values-ca/donottranslate-more-keys.xml
rename to tools/make-keyboard-text/res/values-ca/donottranslate-more-keys.xml
index 8624dfb..9728c99 100644
--- a/tools/maketext/res/values-ca/donottranslate-more-keys.xml
+++ b/tools/make-keyboard-text/res/values-ca/donottranslate-more-keys.xml
@@ -71,7 +71,7 @@
          U+0142: "ł" LATIN SMALL LETTER L WITH STROKE -->
     <string name="more_keys_for_l">l&#x00B7;l,&#x0142;</string>
     <!-- U+00B7: "·" MIDDLE DOT -->
-    <string name="more_keys_for_punctuation">"!fixedColumnOrder!9,&#x00B7;,\",\',#,-,:,!,\\,,\?,\@,&amp;,\\%,+,;,/,(,)"</string>
+    <string name="more_keys_for_punctuation">"!fixedColumnOrder!4,&#x00B7;,!,\\,,\?,:,;,\@"</string>
     <string name="more_keys_for_tablet_period">\?,&#x00B7;</string>
     <!-- U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA -->
     <string name="keylabel_for_spanish_row2_10">&#x00E7;</string>
diff --git a/tools/maketext/res/values-cs/donottranslate-more-keys.xml b/tools/make-keyboard-text/res/values-cs/donottranslate-more-keys.xml
similarity index 100%
rename from tools/maketext/res/values-cs/donottranslate-more-keys.xml
rename to tools/make-keyboard-text/res/values-cs/donottranslate-more-keys.xml
diff --git a/tools/maketext/res/values-da/donottranslate-more-keys.xml b/tools/make-keyboard-text/res/values-da/donottranslate-more-keys.xml
similarity index 100%
rename from tools/maketext/res/values-da/donottranslate-more-keys.xml
rename to tools/make-keyboard-text/res/values-da/donottranslate-more-keys.xml
diff --git a/tools/maketext/res/values-de/donottranslate-more-keys.xml b/tools/make-keyboard-text/res/values-de/donottranslate-more-keys.xml
similarity index 100%
rename from tools/maketext/res/values-de/donottranslate-more-keys.xml
rename to tools/make-keyboard-text/res/values-de/donottranslate-more-keys.xml
diff --git a/tools/maketext/res/values-el/donottranslate-more-keys.xml b/tools/make-keyboard-text/res/values-el/donottranslate-more-keys.xml
similarity index 100%
rename from tools/maketext/res/values-el/donottranslate-more-keys.xml
rename to tools/make-keyboard-text/res/values-el/donottranslate-more-keys.xml
diff --git a/tools/maketext/res/values-en/donottranslate-more-keys.xml b/tools/make-keyboard-text/res/values-en/donottranslate-more-keys.xml
similarity index 100%
rename from tools/maketext/res/values-en/donottranslate-more-keys.xml
rename to tools/make-keyboard-text/res/values-en/donottranslate-more-keys.xml
diff --git a/tools/maketext/res/values-eo/donottranslate-more-keys.xml b/tools/make-keyboard-text/res/values-eo/donottranslate-more-keys.xml
similarity index 100%
rename from tools/maketext/res/values-eo/donottranslate-more-keys.xml
rename to tools/make-keyboard-text/res/values-eo/donottranslate-more-keys.xml
diff --git a/tools/maketext/res/values-es/donottranslate-more-keys.xml b/tools/make-keyboard-text/res/values-es/donottranslate-more-keys.xml
similarity index 98%
rename from tools/maketext/res/values-es/donottranslate-more-keys.xml
rename to tools/make-keyboard-text/res/values-es/donottranslate-more-keys.xml
index 0e58c14..8494296 100644
--- a/tools/maketext/res/values-es/donottranslate-more-keys.xml
+++ b/tools/make-keyboard-text/res/values-es/donottranslate-more-keys.xml
@@ -71,7 +71,7 @@
     <string name="keylabel_for_spanish_row2_10">&#x00F1;</string>
     <!-- U+00A1: "¡" INVERTED EXCLAMATION MARK
          U+00BF: "¿" INVERTED QUESTION MARK -->
-    <string name="more_keys_for_punctuation">"!fixedColumnOrder!9,&#x00A1;,\",\',#,-,:,!,\\,,\?,&#x00BF;,\@,&amp;,\\%,+,;,/,(,)"</string>
+    <string name="more_keys_for_punctuation">"!fixedColumnOrder!4,;,!,\\,,\?,:,&#x00A1;,\@,&#x00BF;"</string>
     <!-- U+00A1: "¡" INVERTED EXCLAMATION MARK -->
     <string name="more_keys_for_tablet_comma">"!,&#x00A1;"</string>
     <!-- U+00BF: "¿" INVERTED QUESTION MARK -->
diff --git a/tools/maketext/res/values-et/donottranslate-more-keys.xml b/tools/make-keyboard-text/res/values-et/donottranslate-more-keys.xml
similarity index 100%
rename from tools/maketext/res/values-et/donottranslate-more-keys.xml
rename to tools/make-keyboard-text/res/values-et/donottranslate-more-keys.xml
diff --git a/tools/maketext/res/values-fa/donottranslate-more-keys.xml b/tools/make-keyboard-text/res/values-fa/donottranslate-more-keys.xml
similarity index 97%
rename from tools/maketext/res/values-fa/donottranslate-more-keys.xml
rename to tools/make-keyboard-text/res/values-fa/donottranslate-more-keys.xml
index 7c8496d..5a03c80 100644
--- a/tools/maketext/res/values-fa/donottranslate-more-keys.xml
+++ b/tools/make-keyboard-text/res/values-fa/donottranslate-more-keys.xml
@@ -86,10 +86,12 @@
     <string name="keylabel_for_apostrophe">&#x060C;</string>
     <string name="keyhintlabel_for_apostrophe">&#x061F;</string>
     <string name="more_keys_for_apostrophe">"!fixedColumnOrder!4,:,!,&#x061F;,&#x061B;,-,/,&#x00AB;|&#x00BB;,&#x00BB;|&#x00AB;"</string>
+    <!-- U+FDFC: "﷼" RIAL SIGN -->
+    <string name="keylabel_for_currency">&#xFDFC;</string>
     <!-- U+061F: "؟" ARABIC QUESTION MARK
          U+060C: "،" ARABIC COMMA
          U+061B: "؛" ARABIC SEMICOLON -->
-    <string name="more_keys_for_punctuation">"!fixedColumnOrder!8,\",\',#,-,:,!,&#x060C;,&#x061F;,\@,&amp;,\\%,+,&#x061B;,/,(,)"</string>
+    <string name="more_keys_for_punctuation">"!fixedColumnOrder!8,\",\',#,-,:,!,&#x060C;,&#x061F;,\@,&amp;,\\%,+,&#x061B;,/,(|),)|("</string>
     <!-- U+266A: "♪" EIGHTH NOTE -->
     <string name="more_keys_for_bullet">&#x266A;</string>
     <!-- U+2605: "★" BLACK STAR
diff --git a/tools/maketext/res/values-fi/donottranslate-more-keys.xml b/tools/make-keyboard-text/res/values-fi/donottranslate-more-keys.xml
similarity index 100%
rename from tools/maketext/res/values-fi/donottranslate-more-keys.xml
rename to tools/make-keyboard-text/res/values-fi/donottranslate-more-keys.xml
diff --git a/tools/maketext/res/values-fr/donottranslate-more-keys.xml b/tools/make-keyboard-text/res/values-fr/donottranslate-more-keys.xml
similarity index 100%
rename from tools/maketext/res/values-fr/donottranslate-more-keys.xml
rename to tools/make-keyboard-text/res/values-fr/donottranslate-more-keys.xml
diff --git a/tools/maketext/res/values-hi/donottranslate-more-keys.xml b/tools/make-keyboard-text/res/values-hi/donottranslate-more-keys.xml
similarity index 97%
rename from tools/maketext/res/values-hi/donottranslate-more-keys.xml
rename to tools/make-keyboard-text/res/values-hi/donottranslate-more-keys.xml
index 98ad2cb..b0d010f 100644
--- a/tools/maketext/res/values-hi/donottranslate-more-keys.xml
+++ b/tools/make-keyboard-text/res/values-hi/donottranslate-more-keys.xml
@@ -59,5 +59,5 @@
     <string name="additional_more_keys_for_symbols_9">9</string>
     <string name="additional_more_keys_for_symbols_0">0</string>
     <!-- U+20B9: "₹" INDIAN RUPEE SIGN -->
-    <string name="keylabel_for_currency_generic">&#x20B9;</string>
+    <string name="keylabel_for_currency">&#x20B9;</string>
 </resources>
diff --git a/tools/maketext/res/values-hr/donottranslate-more-keys.xml b/tools/make-keyboard-text/res/values-hr/donottranslate-more-keys.xml
similarity index 100%
rename from tools/maketext/res/values-hr/donottranslate-more-keys.xml
rename to tools/make-keyboard-text/res/values-hr/donottranslate-more-keys.xml
diff --git a/tools/maketext/res/values-hu/donottranslate-more-keys.xml b/tools/make-keyboard-text/res/values-hu/donottranslate-more-keys.xml
similarity index 100%
rename from tools/maketext/res/values-hu/donottranslate-more-keys.xml
rename to tools/make-keyboard-text/res/values-hu/donottranslate-more-keys.xml
diff --git a/tools/make-keyboard-text/res/values-hy/donottranslate-more-keys.xml b/tools/make-keyboard-text/res/values-hy/donottranslate-more-keys.xml
new file mode 100644
index 0000000..2f34128
--- /dev/null
+++ b/tools/make-keyboard-text/res/values-hy/donottranslate-more-keys.xml
@@ -0,0 +1,40 @@
+<?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">
+    <!-- U+058A: "֊" ARMENIAN HYPHEN -->
+    <!-- U+055C: "՜" ARMENIAN EXCLAMATION MARK -->
+    <!-- U+055D: "՝" ARMENIAN COMMA -->
+    <!-- U+055E: "՞" ARMENIAN QUESTION MARK -->
+    <!-- U+0559: "ՙ" ARMENIAN MODIFIER LETTER LEFT HALF RING -->
+    <!-- U+055A: "՚" ARMENIAN APOSTROPHE -->
+    <!-- U+055B: "՛" ARMENIAN EMPHASIS MARK -->
+    <!-- U+055F: "՟" ARMENIAN ABBREVIATION MARK -->
+    <string name="more_keys_for_punctuation">"!fixedColumnOrder!8,!,?,\\,,.,&#x058A;,&#x055C;,&#x055D;,&#x055E;,:,;,\@,&#x0559;,&#x055A;,&#x055B;,&#x055F;"</string>
+    <!-- U+055E: "՞" ARMENIAN QUESTION MARK -->
+    <!-- U+00BF: "¿" INVERTED QUESTION MARK -->
+    <string name="more_keys_for_symbols_question">&#x055E;,&#x00BF;</string>
+    <!-- U+055C: "՜" ARMENIAN EXCLAMATION MARK -->
+    <!-- U+00A1: "¡" INVERTED EXCLAMATION MARK -->
+    <string name="more_keys_for_symbols_exclamation">&#x055C;,&#x00A1;</string>
+    <!-- U+058F: "֏" ARMENIAN DRAM SIGN -->
+    <!-- TODO: Enable this when we have glyph for the following letter
+         <string name="keylabel_for_currency">&#x058F;</string>
+    -->
+</resources>
diff --git a/tools/maketext/res/values-is/donottranslate-more-keys.xml b/tools/make-keyboard-text/res/values-is/donottranslate-more-keys.xml
similarity index 100%
rename from tools/maketext/res/values-is/donottranslate-more-keys.xml
rename to tools/make-keyboard-text/res/values-is/donottranslate-more-keys.xml
diff --git a/tools/maketext/res/values-it/donottranslate-more-keys.xml b/tools/make-keyboard-text/res/values-it/donottranslate-more-keys.xml
similarity index 100%
rename from tools/maketext/res/values-it/donottranslate-more-keys.xml
rename to tools/make-keyboard-text/res/values-it/donottranslate-more-keys.xml
diff --git a/tools/maketext/res/values-iw/donottranslate-more-keys.xml b/tools/make-keyboard-text/res/values-iw/donottranslate-more-keys.xml
similarity index 89%
rename from tools/maketext/res/values-iw/donottranslate-more-keys.xml
rename to tools/make-keyboard-text/res/values-iw/donottranslate-more-keys.xml
index 64d4227..feaed4c 100644
--- a/tools/maketext/res/values-iw/donottranslate-more-keys.xml
+++ b/tools/make-keyboard-text/res/values-iw/donottranslate-more-keys.xml
@@ -51,4 +51,10 @@
     <string name="double_quotes">&#x201C;,&#x201D;,&#x201E;</string>
     <string name="single_angle_quotes">!text/single_laqm_raqm_rtl</string>
     <string name="double_angle_quotes">!text/double_laqm_raqm_rtl</string>
+    <!-- U+20AA: "₪" NEW SHEQEL SIGN -->
+    <string name="keylabel_for_currency">&#x20AA;</string>
+    <string name="keyhintlabel_for_tablet_comma">!</string>
+    <string name="more_keys_for_tablet_comma">!</string>
+    <string name="keyhintlabel_for_tablet_period">\?</string>
+    <string name="more_keys_for_tablet_period">\?</string>
 </resources>
diff --git a/tools/maketext/res/values-ka/donottranslate-more-keys.xml b/tools/make-keyboard-text/res/values-ka/donottranslate-more-keys.xml
similarity index 100%
rename from tools/maketext/res/values-ka/donottranslate-more-keys.xml
rename to tools/make-keyboard-text/res/values-ka/donottranslate-more-keys.xml
diff --git a/tools/maketext/res/values-kk/donottranslate-more-keys.xml b/tools/make-keyboard-text/res/values-kk/donottranslate-more-keys.xml
similarity index 100%
rename from tools/maketext/res/values-kk/donottranslate-more-keys.xml
rename to tools/make-keyboard-text/res/values-kk/donottranslate-more-keys.xml
diff --git a/tools/maketext/res/values-mn/donottranslate-more-keys.xml b/tools/make-keyboard-text/res/values-km/donottranslate-more-keys.xml
similarity index 64%
copy from tools/maketext/res/values-mn/donottranslate-more-keys.xml
copy to tools/make-keyboard-text/res/values-km/donottranslate-more-keys.xml
index fd1853e..c33831c 100644
--- a/tools/maketext/res/values-mn/donottranslate-more-keys.xml
+++ b/tools/make-keyboard-text/res/values-km/donottranslate-more-keys.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2012, The Android Open Source Project
+** 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.
@@ -19,10 +19,11 @@
 -->
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <!-- Label for "switch to alphabetic" key.
-         U+0410: "А" CYRILLIC CAPITAL LETTER A
-         U+0411: "Б" CYRILLIC CAPITAL LETTER BE
-         U+0412: "В" CYRILLIC CAPITAL LETTER VE -->
-    <string name="label_to_alpha_key">&#x0410;&#x0411;&#x0412;</string>
-    <!-- U+20AE: "₮" TUGRIK SIGN -->
-    <string name="keylabel_for_currency_generic">&#x20AE;</string>
+         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>
diff --git a/tools/maketext/res/values-ky/donottranslate-more-keys.xml b/tools/make-keyboard-text/res/values-ky/donottranslate-more-keys.xml
similarity index 100%
rename from tools/maketext/res/values-ky/donottranslate-more-keys.xml
rename to tools/make-keyboard-text/res/values-ky/donottranslate-more-keys.xml
diff --git a/tools/maketext/res/values-mn/donottranslate-more-keys.xml b/tools/make-keyboard-text/res/values-lo/donottranslate-more-keys.xml
similarity index 66%
copy from tools/maketext/res/values-mn/donottranslate-more-keys.xml
copy to tools/make-keyboard-text/res/values-lo/donottranslate-more-keys.xml
index fd1853e..1d8ffa8 100644
--- a/tools/maketext/res/values-mn/donottranslate-more-keys.xml
+++ b/tools/make-keyboard-text/res/values-lo/donottranslate-more-keys.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2012, The Android Open Source Project
+** 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.
@@ -19,10 +19,10 @@
 -->
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <!-- Label for "switch to alphabetic" key.
-         U+0410: "А" CYRILLIC CAPITAL LETTER A
-         U+0411: "Б" CYRILLIC CAPITAL LETTER BE
-         U+0412: "В" CYRILLIC CAPITAL LETTER VE -->
-    <string name="label_to_alpha_key">&#x0410;&#x0411;&#x0412;</string>
-    <!-- U+20AE: "₮" TUGRIK SIGN -->
-    <string name="keylabel_for_currency_generic">&#x20AE;</string>
+         U+0E81: "ກ" LAO LETTER KO
+         U+0E82: "ຂ" LAO LETTER KHO SUNG
+         U+0E84: "ຄ" LAO LETTER KHO TAM -->
+    <string name="label_to_alpha_key">&#x0E81;&#x0E82;&#x0E84;</string>
+    <!-- U+20AD: "₭" KIP SIGN -->
+    <string name="keylabel_for_currency">&#x20AD;</string>
 </resources>
diff --git a/tools/maketext/res/values-lt/donottranslate-more-keys.xml b/tools/make-keyboard-text/res/values-lt/donottranslate-more-keys.xml
similarity index 100%
rename from tools/maketext/res/values-lt/donottranslate-more-keys.xml
rename to tools/make-keyboard-text/res/values-lt/donottranslate-more-keys.xml
diff --git a/tools/maketext/res/values-lv/donottranslate-more-keys.xml b/tools/make-keyboard-text/res/values-lv/donottranslate-more-keys.xml
similarity index 100%
rename from tools/maketext/res/values-lv/donottranslate-more-keys.xml
rename to tools/make-keyboard-text/res/values-lv/donottranslate-more-keys.xml
diff --git a/tools/maketext/res/values-mk/donottranslate-more-keys.xml b/tools/make-keyboard-text/res/values-mk/donottranslate-more-keys.xml
similarity index 100%
rename from tools/maketext/res/values-mk/donottranslate-more-keys.xml
rename to tools/make-keyboard-text/res/values-mk/donottranslate-more-keys.xml
diff --git a/tools/maketext/res/values-mn/donottranslate-more-keys.xml b/tools/make-keyboard-text/res/values-mn/donottranslate-more-keys.xml
similarity index 93%
rename from tools/maketext/res/values-mn/donottranslate-more-keys.xml
rename to tools/make-keyboard-text/res/values-mn/donottranslate-more-keys.xml
index fd1853e..a7f3666 100644
--- a/tools/maketext/res/values-mn/donottranslate-more-keys.xml
+++ b/tools/make-keyboard-text/res/values-mn/donottranslate-more-keys.xml
@@ -24,5 +24,5 @@
          U+0412: "В" CYRILLIC CAPITAL LETTER VE -->
     <string name="label_to_alpha_key">&#x0410;&#x0411;&#x0412;</string>
     <!-- U+20AE: "₮" TUGRIK SIGN -->
-    <string name="keylabel_for_currency_generic">&#x20AE;</string>
+    <string name="keylabel_for_currency">&#x20AE;</string>
 </resources>
diff --git a/tools/maketext/res/values-nb/donottranslate-more-keys.xml b/tools/make-keyboard-text/res/values-nb/donottranslate-more-keys.xml
similarity index 100%
rename from tools/maketext/res/values-nb/donottranslate-more-keys.xml
rename to tools/make-keyboard-text/res/values-nb/donottranslate-more-keys.xml
diff --git a/tools/maketext/res/values-hi/donottranslate-more-keys.xml b/tools/make-keyboard-text/res/values-ne/donottranslate-more-keys.xml
similarity index 93%
copy from tools/maketext/res/values-hi/donottranslate-more-keys.xml
copy to tools/make-keyboard-text/res/values-ne/donottranslate-more-keys.xml
index 98ad2cb..9205e53 100644
--- a/tools/maketext/res/values-hi/donottranslate-more-keys.xml
+++ b/tools/make-keyboard-text/res/values-ne/donottranslate-more-keys.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2012, The Android Open Source Project
+** 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.
@@ -58,6 +58,6 @@
     <string name="additional_more_keys_for_symbols_8">8</string>
     <string name="additional_more_keys_for_symbols_9">9</string>
     <string name="additional_more_keys_for_symbols_0">0</string>
-    <!-- U+20B9: "₹" INDIAN RUPEE SIGN -->
-    <string name="keylabel_for_currency_generic">&#x20B9;</string>
+    <!-- U+0930/U+0941/U+002E "रु." NEPALESE RUPEE SIGN -->
+    <string name="keylabel_for_currency">&#x0930;&#x0941;&#x002E;</string>
 </resources>
diff --git a/tools/maketext/res/values-nl/donottranslate-more-keys.xml b/tools/make-keyboard-text/res/values-nl/donottranslate-more-keys.xml
similarity index 100%
rename from tools/maketext/res/values-nl/donottranslate-more-keys.xml
rename to tools/make-keyboard-text/res/values-nl/donottranslate-more-keys.xml
diff --git a/tools/maketext/res/values-pl/donottranslate-more-keys.xml b/tools/make-keyboard-text/res/values-pl/donottranslate-more-keys.xml
similarity index 100%
rename from tools/maketext/res/values-pl/donottranslate-more-keys.xml
rename to tools/make-keyboard-text/res/values-pl/donottranslate-more-keys.xml
diff --git a/tools/maketext/res/values-pt/donottranslate-more-keys.xml b/tools/make-keyboard-text/res/values-pt/donottranslate-more-keys.xml
similarity index 100%
rename from tools/maketext/res/values-pt/donottranslate-more-keys.xml
rename to tools/make-keyboard-text/res/values-pt/donottranslate-more-keys.xml
diff --git a/tools/maketext/res/values-rm/donottranslate-more-keys.xml b/tools/make-keyboard-text/res/values-rm/donottranslate-more-keys.xml
similarity index 100%
rename from tools/maketext/res/values-rm/donottranslate-more-keys.xml
rename to tools/make-keyboard-text/res/values-rm/donottranslate-more-keys.xml
diff --git a/tools/maketext/res/values-ro/donottranslate-more-keys.xml b/tools/make-keyboard-text/res/values-ro/donottranslate-more-keys.xml
similarity index 100%
rename from tools/maketext/res/values-ro/donottranslate-more-keys.xml
rename to tools/make-keyboard-text/res/values-ro/donottranslate-more-keys.xml
diff --git a/tools/maketext/res/values-ru/donottranslate-more-keys.xml b/tools/make-keyboard-text/res/values-ru/donottranslate-more-keys.xml
similarity index 100%
rename from tools/maketext/res/values-ru/donottranslate-more-keys.xml
rename to tools/make-keyboard-text/res/values-ru/donottranslate-more-keys.xml
diff --git a/tools/maketext/res/values-sk/donottranslate-more-keys.xml b/tools/make-keyboard-text/res/values-sk/donottranslate-more-keys.xml
similarity index 100%
rename from tools/maketext/res/values-sk/donottranslate-more-keys.xml
rename to tools/make-keyboard-text/res/values-sk/donottranslate-more-keys.xml
diff --git a/tools/maketext/res/values-sl/donottranslate-more-keys.xml b/tools/make-keyboard-text/res/values-sl/donottranslate-more-keys.xml
similarity index 100%
rename from tools/maketext/res/values-sl/donottranslate-more-keys.xml
rename to tools/make-keyboard-text/res/values-sl/donottranslate-more-keys.xml
diff --git a/tools/maketext/res/values-sr/donottranslate-more-keys.xml b/tools/make-keyboard-text/res/values-sr/donottranslate-more-keys.xml
similarity index 100%
rename from tools/maketext/res/values-sr/donottranslate-more-keys.xml
rename to tools/make-keyboard-text/res/values-sr/donottranslate-more-keys.xml
diff --git a/tools/maketext/res/values-sv/donottranslate-more-keys.xml b/tools/make-keyboard-text/res/values-sv/donottranslate-more-keys.xml
similarity index 100%
rename from tools/maketext/res/values-sv/donottranslate-more-keys.xml
rename to tools/make-keyboard-text/res/values-sv/donottranslate-more-keys.xml
diff --git a/tools/maketext/res/values-sw/donottranslate-more-keys.xml b/tools/make-keyboard-text/res/values-sw/donottranslate-more-keys.xml
similarity index 100%
rename from tools/maketext/res/values-sw/donottranslate-more-keys.xml
rename to tools/make-keyboard-text/res/values-sw/donottranslate-more-keys.xml
diff --git a/tools/maketext/res/values-th/donottranslate-more-keys.xml b/tools/make-keyboard-text/res/values-th/donottranslate-more-keys.xml
similarity index 93%
rename from tools/maketext/res/values-th/donottranslate-more-keys.xml
rename to tools/make-keyboard-text/res/values-th/donottranslate-more-keys.xml
index 6350d4b..070c915 100644
--- a/tools/maketext/res/values-th/donottranslate-more-keys.xml
+++ b/tools/make-keyboard-text/res/values-th/donottranslate-more-keys.xml
@@ -24,5 +24,5 @@
          U+0E04: "ค" THAI CHARACTER KHO KHWAI -->
     <string name="label_to_alpha_key">&#x0E01;&#x0E02;&#x0E04;</string>
     <!-- U+0E3F: "฿" THAI CURRENCY SYMBOL BAHT -->
-    <string name="keylabel_for_currency_generic">&#x0E3F;</string>
+    <string name="keylabel_for_currency">&#x0E3F;</string>
 </resources>
diff --git a/tools/maketext/res/values-tl/donottranslate-more-keys.xml b/tools/make-keyboard-text/res/values-tl/donottranslate-more-keys.xml
similarity index 100%
rename from tools/maketext/res/values-tl/donottranslate-more-keys.xml
rename to tools/make-keyboard-text/res/values-tl/donottranslate-more-keys.xml
diff --git a/tools/maketext/res/values-tr/donottranslate-more-keys.xml b/tools/make-keyboard-text/res/values-tr/donottranslate-more-keys.xml
similarity index 100%
rename from tools/maketext/res/values-tr/donottranslate-more-keys.xml
rename to tools/make-keyboard-text/res/values-tr/donottranslate-more-keys.xml
diff --git a/tools/maketext/res/values-uk/donottranslate-more-keys.xml b/tools/make-keyboard-text/res/values-uk/donottranslate-more-keys.xml
similarity index 96%
rename from tools/maketext/res/values-uk/donottranslate-more-keys.xml
rename to tools/make-keyboard-text/res/values-uk/donottranslate-more-keys.xml
index cc05cc6..6ee34e3 100644
--- a/tools/maketext/res/values-uk/donottranslate-more-keys.xml
+++ b/tools/make-keyboard-text/res/values-uk/donottranslate-more-keys.xml
@@ -35,7 +35,7 @@
     <!-- U+044A: "ъ" CYRILLIC SMALL LETTER HARD SIGN -->
     <string name="more_keys_for_cyrillic_soft_sign">&#x044A;</string>
     <!-- U+20B4: "₴" HRYVNIA SIGN -->
-    <string name="keylabel_for_currency_generic">&#x20B4;</string>
+    <string name="keylabel_for_currency">&#x20B4;</string>
     <!-- Label for "switch to alphabetic" key.
          U+0410: "А" CYRILLIC CAPITAL LETTER A
          U+0411: "Б" CYRILLIC CAPITAL LETTER BE
diff --git a/tools/maketext/res/values-vi/donottranslate-more-keys.xml b/tools/make-keyboard-text/res/values-vi/donottranslate-more-keys.xml
similarity index 98%
rename from tools/maketext/res/values-vi/donottranslate-more-keys.xml
rename to tools/make-keyboard-text/res/values-vi/donottranslate-more-keys.xml
index fa98ea9..f01f068 100644
--- a/tools/maketext/res/values-vi/donottranslate-more-keys.xml
+++ b/tools/make-keyboard-text/res/values-vi/donottranslate-more-keys.xml
@@ -93,5 +93,5 @@
     <!-- U+0111: "đ" LATIN SMALL LETTER D WITH STROKE -->
     <string name="more_keys_for_d">&#x0111;</string>
     <!-- U+20AB: "₫" DONG SIGN -->
-    <string name="keylabel_for_currency_generic">&#x20AB;</string>
+    <string name="keylabel_for_currency">&#x20AB;</string>
 </resources>
diff --git a/tools/maketext/res/values-zu/donottranslate-more-keys.xml b/tools/make-keyboard-text/res/values-zu/donottranslate-more-keys.xml
similarity index 100%
rename from tools/maketext/res/values-zu/donottranslate-more-keys.xml
rename to tools/make-keyboard-text/res/values-zu/donottranslate-more-keys.xml
diff --git a/tools/maketext/res/values-zz/donottranslate-more-keys.xml b/tools/make-keyboard-text/res/values-zz/donottranslate-more-keys.xml
similarity index 100%
rename from tools/maketext/res/values-zz/donottranslate-more-keys.xml
rename to tools/make-keyboard-text/res/values-zz/donottranslate-more-keys.xml
diff --git a/tools/maketext/res/values/donottranslate-more-keys.xml b/tools/make-keyboard-text/res/values/donottranslate-more-keys.xml
similarity index 95%
rename from tools/maketext/res/values/donottranslate-more-keys.xml
rename to tools/make-keyboard-text/res/values/donottranslate-more-keys.xml
index 4cf2650..cc09f7f 100644
--- a/tools/maketext/res/values/donottranslate-more-keys.xml
+++ b/tools/make-keyboard-text/res/values/donottranslate-more-keys.xml
@@ -75,9 +75,9 @@
          U+00A5: "¥" YEN SIGN
          U+20B1: "₱" PESO SIGN -->
     <string name="more_keys_for_currency_dollar">&#x00A2;,&#x00A3;,&#x20AC;,&#x00A5;,&#x20B1;</string>
-    <string name="keylabel_for_currency_generic">$</string>
-    <string name="more_keys_for_currency_generic">$,&#x00A2;,&#x20AC;,&#x00A3;,&#x00A5;,&#x20B1;</string>
-    <string name="more_keys_for_punctuation">"!fixedColumnOrder!8,\",\',#,-,:,!,\\,,\?,\@,&amp;,\\%,+,;,/,(,)"</string>
+    <string name="keylabel_for_currency">$</string>
+    <string name="more_keys_for_currency">$,&#x00A2;,&#x20AC;,&#x00A3;,&#x00A5;,&#x20B1;</string>
+    <string name="more_keys_for_punctuation">"!fixedColumnOrder!3,!,\\,,\?,:,;,\@"</string>
     <!-- U+2020: "†" DAGGER
          U+2021: "‡" DOUBLE DAGGER
          U+2605: "★" BLACK STAR -->
@@ -167,10 +167,11 @@
     <!-- U+2030: "‰" PER MILLE SIGN -->
     <string name="more_keys_for_symbols_percent">&#x2030;</string>
     <string name="keylabel_for_tablet_comma">,</string>
-    <string name="keyhintlabel_for_tablet_comma">!</string>
-    <string name="more_keys_for_tablet_comma">!</string>
-    <string name="keyhintlabel_for_tablet_period">\?</string>
-    <string name="more_keys_for_tablet_period">\?</string>
+    <string name="keyhintlabel_for_tablet_comma"></string>
+    <string name="more_keys_for_tablet_comma"></string>
+    <string name="keyhintlabel_for_tablet_period"></string>
+    <!-- U+2026: "…" HORIZONTAL ELLIPSIS -->
+    <string name="more_keys_for_tablet_period">&#x2026;</string>
     <string name="keylabel_for_apostrophe">\'</string>
     <string name="keyhintlabel_for_apostrophe">\"</string>
     <string name="more_keys_for_apostrophe">\"</string>
@@ -189,7 +190,7 @@
     <!-- Label for "switch to more symbol" modifier key.  Must be short to fit on key! -->
     <string name="label_to_more_symbol_key">= \\ &lt;</string>
     <!-- Label for "switch to more symbol" modifier key on tablets.  Must be short to fit on key! -->
-    <string name="label_to_more_symbol_for_tablet_key">~ \\ {</string>
+    <string name="label_to_more_symbol_for_tablet_key">~ [ &lt;</string>
     <!-- Label for "Tab" key.  Must be short to fit on key! -->
     <string name="label_tab_key">Tab</string>
     <!-- Label for "switch to phone numeric" key.  Must be short to fit on key! -->
@@ -202,8 +203,6 @@
     <string name="label_time_am">"AM"</string>
     <!-- Key label for "post meridiem" -->
     <string name="label_time_pm">"PM"</string>
-    <!-- Label for "switch to symbols" key on PC QWERTY layout -->
-    <string name="label_to_symbol_key_pcqwerty">Sym</string>
     <string name="keylabel_for_popular_domain">".com"</string>
     <!-- popular web domains for the locale - most popular, displayed on the keyboard -->
     <string name="more_keys_for_popular_domain">"!hasLabels!,.net,.org,.gov,.edu"</string>
diff --git a/tools/maketext/src/com/android/inputmethod/latin/maketext/ArrayInitializerFormatter.java b/tools/make-keyboard-text/src/com/android/inputmethod/keyboard/tools/ArrayInitializerFormatter.java
similarity index 97%
rename from tools/maketext/src/com/android/inputmethod/latin/maketext/ArrayInitializerFormatter.java
rename to tools/make-keyboard-text/src/com/android/inputmethod/keyboard/tools/ArrayInitializerFormatter.java
index 3365c72..331003e 100644
--- a/tools/maketext/src/com/android/inputmethod/latin/maketext/ArrayInitializerFormatter.java
+++ b/tools/make-keyboard-text/src/com/android/inputmethod/keyboard/tools/ArrayInitializerFormatter.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.inputmethod.latin.maketext;
+package com.android.inputmethod.keyboard.tools;
 
 import java.io.PrintStream;
 
diff --git a/tools/maketext/src/com/android/inputmethod/latin/maketext/JarUtils.java b/tools/make-keyboard-text/src/com/android/inputmethod/keyboard/tools/JarUtils.java
similarity index 98%
rename from tools/maketext/src/com/android/inputmethod/latin/maketext/JarUtils.java
rename to tools/make-keyboard-text/src/com/android/inputmethod/keyboard/tools/JarUtils.java
index 6d6bc0e..a74096e 100644
--- a/tools/maketext/src/com/android/inputmethod/latin/maketext/JarUtils.java
+++ b/tools/make-keyboard-text/src/com/android/inputmethod/keyboard/tools/JarUtils.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.inputmethod.latin.maketext;
+package com.android.inputmethod.keyboard.tools;
 
 import java.io.IOException;
 import java.io.InputStream;
diff --git a/tools/maketext/src/com/android/inputmethod/latin/maketext/LabelText.java b/tools/make-keyboard-text/src/com/android/inputmethod/keyboard/tools/MakeKeyboardText.java
similarity index 88%
rename from tools/maketext/src/com/android/inputmethod/latin/maketext/LabelText.java
rename to tools/make-keyboard-text/src/com/android/inputmethod/keyboard/tools/MakeKeyboardText.java
index 4a92369..36a03f8 100644
--- a/tools/maketext/src/com/android/inputmethod/latin/maketext/LabelText.java
+++ b/tools/make-keyboard-text/src/com/android/inputmethod/keyboard/tools/MakeKeyboardText.java
@@ -14,14 +14,14 @@
  * the License.
  */
 
-package com.android.inputmethod.latin.maketext;
+package com.android.inputmethod.keyboard.tools;
 
 import java.util.Arrays;
 import java.util.LinkedList;
 import java.util.NoSuchElementException;
 import java.util.jar.JarFile;
 
-public class LabelText {
+public class MakeKeyboardText {
     static class Options {
         private static final String OPTION_JAVA = "-java";
 
@@ -31,7 +31,7 @@
             if (message != null) {
                 System.err.println(message);
             }
-            System.err.println("usage: makelabel " + OPTION_JAVA + " <java_output_dir>");
+            System.err.println("usage: make-keyboard-text " + OPTION_JAVA + " <java_output_dir>");
             System.exit(1);
         }
 
@@ -58,7 +58,7 @@
 
     public static void main(final String[] args) {
         final Options options = new Options(args);
-        final JarFile jar = JarUtils.getJarFile(LabelText.class);
+        final JarFile jar = JarUtils.getJarFile(MakeKeyboardText.class);
         final MoreKeysResources resources = new MoreKeysResources(jar);
         resources.writeToJava(options.mJava);
     }
diff --git a/tools/maketext/src/com/android/inputmethod/latin/maketext/MoreKeysResources.java b/tools/make-keyboard-text/src/com/android/inputmethod/keyboard/tools/MoreKeysResources.java
similarity index 99%
rename from tools/maketext/src/com/android/inputmethod/latin/maketext/MoreKeysResources.java
rename to tools/make-keyboard-text/src/com/android/inputmethod/keyboard/tools/MoreKeysResources.java
index fd42702..2643e01 100644
--- a/tools/maketext/src/com/android/inputmethod/latin/maketext/MoreKeysResources.java
+++ b/tools/make-keyboard-text/src/com/android/inputmethod/keyboard/tools/MoreKeysResources.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.inputmethod.latin.maketext;
+package com.android.inputmethod.keyboard.tools;
 
 import java.io.Closeable;
 import java.io.File;
diff --git a/tools/maketext/src/com/android/inputmethod/latin/maketext/StringResource.java b/tools/make-keyboard-text/src/com/android/inputmethod/keyboard/tools/StringResource.java
similarity index 94%
rename from tools/maketext/src/com/android/inputmethod/latin/maketext/StringResource.java
rename to tools/make-keyboard-text/src/com/android/inputmethod/keyboard/tools/StringResource.java
index 568a896..a49b8fe 100644
--- a/tools/maketext/src/com/android/inputmethod/latin/maketext/StringResource.java
+++ b/tools/make-keyboard-text/src/com/android/inputmethod/keyboard/tools/StringResource.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.inputmethod.latin.maketext;
+package com.android.inputmethod.keyboard.tools;
 
 public class StringResource {
     public final String mName;
diff --git a/tools/maketext/src/com/android/inputmethod/latin/maketext/StringResourceMap.java b/tools/make-keyboard-text/src/com/android/inputmethod/keyboard/tools/StringResourceMap.java
similarity index 98%
rename from tools/maketext/src/com/android/inputmethod/latin/maketext/StringResourceMap.java
rename to tools/make-keyboard-text/src/com/android/inputmethod/keyboard/tools/StringResourceMap.java
index ff13342..cc7ff6a 100644
--- a/tools/maketext/src/com/android/inputmethod/latin/maketext/StringResourceMap.java
+++ b/tools/make-keyboard-text/src/com/android/inputmethod/keyboard/tools/StringResourceMap.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.inputmethod.latin.maketext;
+package com.android.inputmethod.keyboard.tools;
 
 import org.xml.sax.Attributes;
 import org.xml.sax.SAXException;
diff --git a/tools/maketext/etc/manifest.txt b/tools/maketext/etc/manifest.txt
deleted file mode 100644
index bfd1a52..0000000
--- a/tools/maketext/etc/manifest.txt
+++ /dev/null
@@ -1 +0,0 @@
-Main-Class: com.android.inputmethod.latin.maketext.LabelText