Merge "Import translations. DO NOT MERGE" into jb-ub-latinimegoogle-bayo
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_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_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_light.png b/java/res/drawable-hdpi/ic_emoji_light.png
deleted file mode 100644
index 2e3638b..0000000
--- a/java/res/drawable-hdpi/ic_emoji_light.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-hdpi/ic_ime_light.png b/java/res/drawable-hdpi/ic_ime_light.png
deleted file mode 100644
index 4fd3ba1..0000000
--- a/java/res/drawable-hdpi/ic_ime_light.png
+++ /dev/null
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_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_ics.9.png b/java/res/drawable-hdpi/keyboard_background_holo.9.png
similarity index 100%
rename from java/res/drawable-hdpi/keyboard_background_ics.9.png
rename to java/res/drawable-hdpi/keyboard_background_holo.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_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_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_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_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-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_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_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_light.png b/java/res/drawable-mdpi/ic_emoji_light.png
deleted file mode 100644
index a319504..0000000
--- a/java/res/drawable-mdpi/ic_emoji_light.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-mdpi/ic_ime_light.png b/java/res/drawable-mdpi/ic_ime_light.png
deleted file mode 100644
index d94ad6f..0000000
--- a/java/res/drawable-mdpi/ic_ime_light.png
+++ /dev/null
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_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_ics.9.png b/java/res/drawable-mdpi/keyboard_background_holo.9.png
similarity index 100%
rename from java/res/drawable-mdpi/keyboard_background_ics.9.png
rename to java/res/drawable-mdpi/keyboard_background_holo.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_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/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_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_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-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_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_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_light.png b/java/res/drawable-xhdpi/ic_emoji_light.png
deleted file mode 100644
index 21bc909..0000000
--- a/java/res/drawable-xhdpi/ic_emoji_light.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xhdpi/ic_ime_light.png b/java/res/drawable-xhdpi/ic_ime_light.png
deleted file mode 100644
index 9d2caed..0000000
--- a/java/res/drawable-xhdpi/ic_ime_light.png
+++ /dev/null
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_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_ics.9.png b/java/res/drawable-xhdpi/keyboard_background_holo.9.png
similarity index 100%
rename from java/res/drawable-xhdpi/keyboard_background_ics.9.png
rename to java/res/drawable-xhdpi/keyboard_background_holo.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_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/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_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_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-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_light.png b/java/res/drawable-xxhdpi/ic_emoji_light.png
deleted file mode 100644
index 7480e52..0000000
--- a/java/res/drawable-xxhdpi/ic_emoji_light.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xxhdpi/ic_ime_light.png b/java/res/drawable-xxhdpi/ic_ime_light.png
deleted file mode 100644
index 0309635..0000000
--- a/java/res/drawable-xxhdpi/ic_ime_light.png
+++ /dev/null
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_background_ics.9.png b/java/res/drawable-xxhdpi/keyboard_background_holo.9.png
similarity index 100%
rename from java/res/drawable-xxhdpi/keyboard_background_ics.9.png
rename to java/res/drawable-xxhdpi/keyboard_background_holo.9.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/layout/emoji_keyboard_view.xml b/java/res/layout/emoji_keyboard_view.xml
index 5fee419..4566a5a 100644
--- a/java/res/layout/emoji_keyboard_view.xml
+++ b/java/res/layout/emoji_keyboard_view.xml
@@ -40,7 +40,7 @@
             <TabWidget
                 android:id="@android:id/tabs"
                 android:layout_width="match_parent"
-                android:layout_height="wrap_content"
+                android:layout_height="match_parent"
                 android:background="@drawable/tab_selected"
                 android:divider="@null"
                 android:tabStripEnabled="true"
@@ -61,17 +61,27 @@
                     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:src="@drawable/sym_keyboard_delete_holo" />
+            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"
@@ -84,7 +94,7 @@
             android:layout_width="0dip"
             android:layout_weight="0.15"
             android:layout_height="match_parent"
-            android:src="@drawable/ic_ime_light" />
+            android:src="@drawable/ic_ime_switcher_dark" />
         <ImageButton
             android:id="@+id/emoji_keyboard_space"
             android:layout_width="0dip"
@@ -95,6 +105,6 @@
             android:layout_width="0dip"
             android:layout_weight="0.15"
             android:layout_height="match_parent"
-            android:src="@drawable/sym_keyboard_return_holo" />
+            android:src="@drawable/sym_keyboard_return_holo_dark" />
     </LinearLayout>
 </com.android.inputmethod.keyboard.EmojiKeyboardView>
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-km/donottranslate.xml b/java/res/values-km/donottranslate.xml
new file mode 100644
index 0000000..a9893fe
--- /dev/null
+++ b/java/res/values-km/donottranslate.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2013, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Whether this language uses spaces between words -->
+    <bool name="current_language_has_spaces">false</bool>
+</resources>
diff --git a/java/res/values/colors.xml b/java/res/values/colors.xml
index ea762f9..3803cb7 100644
--- a/java/res/values/colors.xml
+++ b/java/res/values/colors.xml
@@ -33,7 +33,7 @@
     <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. -->
+    <!-- Color resources for IceCreamSandwich theme. Base color = 33B5E5 -->
     <!-- android:color/holo_blue_light value is #FF33B5E5 -->
     <color name="highlight_color_ics">#FF33B5E5</color>
     <color name="typed_word_color_ics">#D833B5E5</color>
@@ -49,10 +49,21 @@
     <color name="spacebar_text_color_ics">#FFC0C0C0</color>
     <color name="spacebar_text_shadow_color_ics">#80000000</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/values/dimens.xml b/java/res/values/dimens.xml
index 88e327f..4e3b2f5 100644
--- a/java/res/values/dimens.xml
+++ b/java/res/values/dimens.xml
@@ -113,13 +113,14 @@
     <dimen name="gesture_floating_preview_text_offset">73dp</dimen>
     <dimen name="gesture_floating_preview_horizontal_padding">24dp</dimen>
     <dimen name="gesture_floating_preview_vertical_padding">16dp</dimen>
-    <dimen name="gesture_floating_preview_round_radius">3dp</dimen>
+    <dimen name="gesture_floating_preview_round_radius">2dp</dimen>
 
     <!-- Emoji keyboard -->
     <fraction name="emoji_keyboard_key_width">14.2857%p</fraction>
     <fraction name="emoji_keyboard_row_height">33%p</fraction>
     <fraction name="emoji_keyboard_key_letter_size">90%p</fraction>
     <integer name="emoji_keyboard_max_key_count">21</integer>
+    <dimen name="emoji_category_page_id_height">3dp</dimen>
 
     <!-- Inset used in Accessibility mode to avoid accidental key presses when a finger slides off the screen. -->
     <dimen name="accessibility_edge_slop">8dp</dimen>
diff --git a/java/res/values/donottranslate.xml b/java/res/values/donottranslate.xml
index 82c5ce4..42e692d 100644
--- a/java/res/values/donottranslate.xml
+++ b/java/res/values/donottranslate.xml
@@ -158,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/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/themes-gb.xml b/java/res/values/themes-gb.xml
index 7f8dd23..d9ac4ac 100644
--- a/java/res/values/themes-gb.xml
+++ b/java/res/values/themes-gb.xml
@@ -32,25 +32,25 @@
     </style>
     <style name="KeyboardIcons.GB">
         <!-- 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>
-        <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="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</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</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</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</item>
-        <item name="iconZwjKey">@drawable/sym_keyboard_zwj_holo</item>
-        <item name="iconEmojiKey">@drawable/ic_emoji_light</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.GB"
diff --git a/java/res/values/themes-ics.xml b/java/res/values/themes-ics.xml
index 19fb4fd..33dd50c 100644
--- a/java/res/values/themes-ics.xml
+++ b/java/res/values/themes-ics.xml
@@ -35,23 +35,23 @@
         <!-- 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="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</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="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</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>
+        <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"
@@ -69,7 +69,7 @@
         name="KeyboardView.ICS"
         parent="KeyboardView"
     >
-        <item name="android:background">@drawable/keyboard_background_ics</item>
+        <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>
@@ -88,10 +88,10 @@
     >
         <item name="keyPreviewLayout">@layout/key_preview_ics</item>
         <item name="keyPreviewOffset">@dimen/key_preview_offset_ics</item>
-        <item name="gestureFloatingPreviewTextColor">@color/highlight_color_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_ics</item>
-        <item name="slidingKeyInputPreviewColor">@color/highlight_translucent_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>
@@ -135,10 +135,10 @@
     >
         <item name="android:background">@drawable/keyboard_suggest_strip_holo</item>
         <item name="suggestionStripOption">autoCorrectBold|validTypedWordBold</item>
-        <item name="colorValidTypedWord">@color/typed_word_color_ics</item>
-        <item name="colorTypedWord">@color/typed_word_color_ics</item>
-        <item name="colorAutoCorrect">@color/highlight_color_ics</item>
-        <item name="colorSuggested">@color/suggested_word_color_ics</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">
diff --git a/java/res/xml-sw600dp/row_pcqwerty5.xml b/java/res/xml-sw600dp/row_pcqwerty5.xml
index a79d2a8..b854f10 100644
--- a/java/res/xml-sw600dp/row_pcqwerty5.xml
+++ b/java/res/xml-sw600dp/row_pcqwerty5.xml
@@ -53,7 +53,7 @@
                     latin:keyXPos="-9.0%p"
                     latin:keyWidth="9.0%p"
                     latin:backgroundType="functional"
-                    latin:keyboardLayout="@xml/key_symbols_period" />
+                    latin:keyboardLayout="@xml/key_f2" />
             </default>
         </switch>
     </Row>
diff --git a/java/res/xml-sw600dp/rows_khmer.xml b/java/res/xml-sw600dp/rows_khmer.xml
new file mode 100644
index 0000000..2824a5c
--- /dev/null
+++ b/java/res/xml-sw600dp/rows_khmer.xml
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2013, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <include
+        latin:keyboardLayout="@xml/key_styles_common" />
+    <Row
+        latin:keyWidth="7.5%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_khmer1" />
+        <Key
+            latin:keyStyle="deleteKeyStyle"
+            latin:keyWidth="fillRight" />
+        </Row>
+    <Row
+        latin:keyWidth="7.5%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_khmer2" />
+    </Row>
+    <Row
+        latin:keyWidth="7.5%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_khmer3" />
+        <Key
+            latin:keyStyle="enterKeyStyle"
+            latin:keyWidth="fillRight" />
+    </Row>
+    <Row
+        latin:keyWidth="7.5%p"
+    >
+        <Key
+            latin:keyStyle="shiftKeyStyle"
+            latin:keyWidth="10.0%p" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_khmer4" />
+        <switch>
+            <case
+                latin:keyboardLayoutSetElement="alphabetManualShifted|alphabetShiftLocked|alphabetShiftLockShifted"
+            >
+                <Spacer />
+            </case>
+            <default>
+                <include
+                    latin:keyboardLayout="@xml/keys_exclamation_question" />
+            </default>
+        </switch>
+    </Row>
+    <include
+        latin:keyboardLayout="@xml/row_qwerty4" />
+</merge>
diff --git a/java/res/xml-sw600dp/rows_lao.xml b/java/res/xml-sw600dp/rows_lao.xml
index cfe8db9..446d9bd 100644
--- a/java/res/xml-sw600dp/rows_lao.xml
+++ b/java/res/xml-sw600dp/rows_lao.xml
@@ -55,8 +55,17 @@
             latin:keyWidth="10.0%p" />
         <include
             latin:keyboardLayout="@xml/rowkeys_lao4" />
-        <include
-            latin:keyboardLayout="@xml/keys_exclamation_question" />
+        <switch>
+            <case
+                latin:keyboardLayoutSetElement="alphabetManualShifted|alphabetShiftLocked|alphabetShiftLockShifted"
+            >
+                <Spacer />
+            </case>
+            <default>
+                <include
+                    latin:keyboardLayout="@xml/keys_exclamation_question" />
+            </default>
+        </switch>
     </Row>
     <include
         latin:keyboardLayout="@xml/row_qwerty4" />
diff --git a/java/res/xml-sw600dp/rows_thai.xml b/java/res/xml-sw600dp/rows_thai.xml
index cfcaf68..7738c7f 100644
--- a/java/res/xml-sw600dp/rows_thai.xml
+++ b/java/res/xml-sw600dp/rows_thai.xml
@@ -59,8 +59,17 @@
             latin:keyWidth="10.0%p" />
         <include
             latin:keyboardLayout="@xml/rowkeys_thai4" />
-        <include
-            latin:keyboardLayout="@xml/keys_exclamation_question" />
+        <switch>
+            <case
+                latin:keyboardLayoutSetElement="alphabetManualShifted|alphabetShiftLocked|alphabetShiftLockShifted"
+            >
+                <Spacer />
+            </case>
+            <default>
+                <include
+                    latin:keyboardLayout="@xml/keys_exclamation_question" />
+            </default>
+        </switch>
     </Row>
     <include
         latin:keyboardLayout="@xml/row_qwerty4" />
diff --git a/java/res/xml/kbd_khmer.xml b/java/res/xml/kbd_khmer.xml
new file mode 100644
index 0000000..7a2337a
--- /dev/null
+++ b/java/res/xml/kbd_khmer.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2013, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<Keyboard
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+    latin:rowHeight="20%p"
+    latin:verticalGap="@fraction/key_bottom_gap_5row"
+    latin:keyLetterSize="@fraction/key_letter_ratio_5row"
+    latin:keyShiftedLetterHintRatio="@fraction/key_uppercase_letter_ratio_5row"
+    latin:touchPositionCorrectionData="@array/touch_position_correction_data_default"
+>
+    <include
+        latin:keyboardLayout="@xml/rows_khmer" />
+</Keyboard>
diff --git a/java/res/xml/key_styles_common.xml b/java/res/xml/key_styles_common.xml
index 6b3dc9a..67ed962 100644
--- a/java/res/xml/key_styles_common.xml
+++ b/java/res/xml/key_styles_common.xml
@@ -184,4 +184,11 @@
         latin:keyLabelFlags="hasPopupHint"
         latin:moreKeys="!text/more_keys_for_punctuation"
         latin:backgroundType="functional" />
+    <key-style
+        latin:styleName="comKeyStyle"
+        latin:keyLabel="!text/keylabel_for_popular_domain"
+        latin:keyLabelFlags="autoXScale|fontNormal|hasPopupHint|preserveCase"
+        latin:keyOutputText="!text/keylabel_for_popular_domain"
+        latin:moreKeys="!text/more_keys_for_popular_domain"
+        latin:backgroundType="functional" />
 </merge>
diff --git a/java/res/xml/key_styles_currency.xml b/java/res/xml/key_styles_currency.xml
index b7677a2..84c2abc 100644
--- a/java/res/xml/key_styles_currency.xml
+++ b/java/res/xml/key_styles_currency.xml
@@ -103,6 +103,8 @@
              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|lo|mn|ne|th|uk|vi"
         >
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/keys_comma_period.xml b/java/res/xml/keys_comma_period.xml
index 7e7c728..02b46c2 100644
--- a/java/res/xml/keys_comma_period.xml
+++ b/java/res/xml/keys_comma_period.xml
@@ -73,6 +73,20 @@
                 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="."
diff --git a/java/res/xml/method.xml b/java/res/xml/method.xml
index 6014646..f0e04c2 100644
--- a/java/res/xml/method.xml
+++ b/java/res/xml/method.xml
@@ -54,6 +54,7 @@
     iw: Hebrew/hebrew        # "he" is official language code of Hebrew.
     ka: Georgian/georgian
     (kk: Kazakh/east_slavic) # disabled temporarily. waiting for strnig resources.
+    km: Khmer/khmer
     ky: Kyrgyz/east_slavic
     lo: Lao/lao
     lt: Lithuanian/qwerty
@@ -62,8 +63,8 @@
     mn: Mongolian/mongolian
     ms: Malay/qwerty
     nb: Norwegian Bokmål/nordic
-    ne: Nepali Romanized/nepali_romanized
-    ne: Nepali Traditional/nepali_traditional
+    ne: Nepali Romanized/nepali_romanized  # disabled temporarily
+    ne: Nepali Traditional/nepali_traditional  # disabled temporarily
     nl: Dutch/qwerty
     nl_BE: Dutch Belgium/azerty
     pl: Polish/qwerty
@@ -94,464 +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_subtype_keyboard"
+    <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"
+            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"
+            android:imeSubtypeExtraValue="KeyboardLayoutSet=lao,EmojiCapable"
     />
-    <subtype android:icon="@drawable/ic_subtype_keyboard"
+    <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"
+            android:imeSubtypeExtraValue="KeyboardLayoutSet=nepali_romanized,EmojiCapable"
     />
-    <subtype android:icon="@drawable/ic_subtype_keyboard"
+    <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"
+            android:imeSubtypeExtraValue="KeyboardLayoutSet=nepali_traditional,EmojiCapable"
     />
-    <subtype android:icon="@drawable/ic_subtype_keyboard"
+    -->
+    <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_subtype_keyboard"
+    <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"
+            android:imeSubtypeExtraValue="KeyboardLayoutSet=emoji,EmojiCapable"
     />
     -->
 </input-method>
diff --git a/java/res/xml/row_pcqwerty5.xml b/java/res/xml/row_pcqwerty5.xml
index 0e61805..4ec908b 100644
--- a/java/res/xml/row_pcqwerty5.xml
+++ b/java/res/xml/row_pcqwerty5.xml
@@ -51,13 +51,13 @@
                     latin:keyWidth="11.538%p" />
                 <Key
                     latin:keyStyle="spaceKeyStyle"
-                    latin:keyWidth="42.310%p" />
+                    latin:keyWidth="38.464%p" />
                 </case>
             <!-- languageSwitchKeyEnabled="false" -->
             <default>
                 <Key
                     latin:keyStyle="spaceKeyStyle"
-                    latin:keyWidth="53.848%p" />
+                    latin:keyWidth="50.002%p" />
             </default>
         </switch>
         <Key
@@ -71,9 +71,9 @@
             </case>
             <!-- keyboardLayoutSetElement="alphabetManualShifted|alphabetShiftLocked|alphabetShiftLockShifted" -->
             <default>
-                <include
-                    latin:keyboardLayout="@xml/key_symbols_period"
-                    latin:backgroundType="functional" />
+                <Key
+                    latin:keyStyle="emojiKeyStyle"
+                    latin:keyWidth="fillRight" />
             </default>
         </switch>
     </Row>
diff --git a/java/res/xml/row_qwerty4.xml b/java/res/xml/row_qwerty4.xml
index 340beb9..578bc12 100644
--- a/java/res/xml/row_qwerty4.xml
+++ b/java/res/xml/row_qwerty4.xml
@@ -49,6 +49,14 @@
                 <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/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/rows_khmer.xml b/java/res/xml/rows_khmer.xml
new file mode 100644
index 0000000..e399387
--- /dev/null
+++ b/java/res/xml/rows_khmer.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2013, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <include
+        latin:keyboardLayout="@xml/key_styles_common" />
+    <Row
+        latin:keyWidth="8.3333%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_khmer1" />
+    </Row>
+    <Row
+        latin:keyWidth="8.3333%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_khmer2" />
+    </Row>
+    <Row
+        latin:keyWidth="8.3333%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_khmer3" />
+    </Row>
+    <Row
+        latin:keyWidth="8.3333%p"
+    >
+        <Key
+            latin:keyStyle="shiftKeyStyle" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_khmer4" />
+        <Key
+            latin:keyStyle="deleteKeyStyle" />
+    </Row>
+    <include
+        latin:keyboardLayout="@xml/row_qwerty4" />
+</merge>
diff --git a/java/src/com/android/inputmethod/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
index 702ed20..4e61eda 100644
--- a/java/src/com/android/inputmethod/keyboard/EmojiKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/EmojiKeyboardView.java
@@ -28,11 +28,13 @@
 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;
@@ -76,10 +78,12 @@
     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;
 
@@ -197,6 +201,21 @@
             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;
         }
@@ -205,6 +224,10 @@
             mCurrentCategoryPageId = id;
         }
 
+        public int getCurrentCategoryPageId() {
+            return mCurrentCategoryPageId;
+        }
+
         public void saveLastTypedCategoryPage() {
             Settings.writeEmojiCategoryLastTypedId(
                     mPrefs, mCurrentCategoryId, mCurrentCategoryPageId);
@@ -382,6 +405,7 @@
         mLayoutSet = builder.build();
         mEmojiCategory = new EmojiCategory(PreferenceManager.getDefaultSharedPreferences(context),
                 context.getResources(), builder.build());
+        mDeleteKeyOnTouchListener = new DeleteKeyOnTouchListener(context);
     }
 
     @Override
@@ -435,18 +459,20 @@
         mEmojiPager.setOffscreenPageLimit(0);
         final Resources res = getResources();
         final EmojiLayoutParams emojiLp = new EmojiLayoutParams(res);
-        emojiLp.setPagerProps(mEmojiPager);
+        emojiLp.setPagerProperties(mEmojiPager);
+
+        mEmojiCategoryPageIndicatorView =
+                (EmojiCategoryPageIndicatorView)findViewById(R.id.emoji_category_page_id_view);
+        emojiLp.setCategoryPageIdViewProperties(mEmojiCategoryPageIndicatorView);
 
         setCurrentCategoryId(mEmojiCategory.getCurrentCategoryId(), true /* force */);
 
         final LinearLayout actionBar = (LinearLayout)findViewById(R.id.emoji_action_bar);
-        emojiLp.setActionBarProps(actionBar);
+        emojiLp.setActionBarProperties(actionBar);
 
-        // TODO: Implement auto repeat, using View.OnTouchListener?
         final ImageView deleteKey = (ImageView)findViewById(R.id.emoji_keyboard_delete);
-        deleteKey.setBackgroundResource(mEmojiFunctionalKeyBackgroundId);
         deleteKey.setTag(Constants.CODE_DELETE);
-        deleteKey.setOnClickListener(this);
+        deleteKey.setOnTouchListener(mDeleteKeyOnTouchListener);
         final ImageView alphabetKey = (ImageView)findViewById(R.id.emoji_keyboard_alphabet);
         alphabetKey.setBackgroundResource(mEmojiFunctionalKeyBackgroundId);
         alphabetKey.setTag(Constants.CODE_SWITCH_ALPHA_SYMBOL);
@@ -455,7 +481,7 @@
         spaceKey.setBackgroundResource(mKeyBackgroundId);
         spaceKey.setTag(Constants.CODE_SPACE);
         spaceKey.setOnClickListener(this);
-        emojiLp.setKeyProps(spaceKey);
+        emojiLp.setKeyProperties(spaceKey);
         final ImageView sendKey = (ImageView)findViewById(R.id.emoji_keyboard_send);
         sendKey.setBackgroundResource(mEmojiFunctionalKeyBackgroundId);
         sendKey.setTag(Constants.CODE_ENTER);
@@ -466,6 +492,7 @@
     public void onTabChanged(final String tabId) {
         final int categoryId = mEmojiCategory.getCategoryId(tabId);
         setCurrentCategoryId(categoryId, false /* force */);
+        updateEmojiCategoryPageIdView();
     }
 
 
@@ -475,6 +502,7 @@
                 mEmojiCategory.getCategoryIdAndPageIdFromPagePosition(position);
         setCurrentCategoryId(newPos.first /* categoryId */, false /* force */);
         mEmojiCategory.setCurrentCategoryPageId(newPos.second /* categoryPageId */);
+        updateEmojiCategoryPageIdView();
     }
 
     @Override
@@ -485,7 +513,23 @@
     @Override
     public void onPageScrolled(final int position, final float positionOffset,
             final int positionOffsetPixels) {
-        // Ignore this message. Only want the actual page selected.
+        final Pair<Integer, Integer> newPos =
+                mEmojiCategory.getCategoryIdAndPageIdFromPagePosition(position);
+        final int newCategoryId = newPos.first;
+        final int newCategorySize = mEmojiCategory.getCategoryPageSize(newCategoryId);
+        final int currentCategoryId = mEmojiCategory.getCurrentCategoryId();
+        final int currentCategoryPageId = mEmojiCategory.getCurrentCategoryPageId();
+        final int currentCategorySize = mEmojiCategory.getCurrentCategoryPageSize();
+        if (newCategoryId == currentCategoryId) {
+            mEmojiCategoryPageIndicatorView.setCategoryPageId(
+                    newCategorySize, newPos.second, positionOffset);
+        } else if (newCategoryId > currentCategoryId) {
+            mEmojiCategoryPageIndicatorView.setCategoryPageId(
+                    currentCategorySize, currentCategoryPageId, positionOffset);
+        } else if (newCategoryId < currentCategoryId) {
+            mEmojiCategoryPageIndicatorView.setCategoryPageId(
+                    currentCategorySize, currentCategoryPageId, positionOffset - 1);
+        }
     }
 
     @Override
@@ -505,10 +549,7 @@
 
     @Override
     public void onKeyClick(final Key key) {
-        // TODO: Save emoticons to recents
-        if (mEmojiCategory.getCurrentCategoryId() != CATEGORY_ID_EMOTICONS) {
-            mEmojiKeyboardAdapter.addRecentKey(key);
-        }
+        mEmojiKeyboardAdapter.addRecentKey(key);
         mEmojiCategory.saveLastTypedCategoryPage();
         final int code = key.getCode();
         if (code == Constants.CODE_OUTPUT_TEXT) {
@@ -524,6 +565,16 @@
 
     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) {
@@ -624,4 +675,92 @@
             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
index 5570d59..267fad5 100644
--- a/java/src/com/android/inputmethod/keyboard/EmojiLayoutParams.java
+++ b/java/src/com/android/inputmethod/keyboard/EmojiLayoutParams.java
@@ -30,6 +30,7 @@
     public final int mEmojiPagerHeight;
     private final int mEmojiPagerBottomMargin;
     public final int mEmojiKeyboardHeight;
+    private final int mEmojiCategoryPageIdViewHeight;
     public final int mEmojiActionBarHeight;
     public final int mKeyVerticalGap;
     private final int mKeyHorizontalGap;
@@ -47,23 +48,32 @@
                 (int) defaultKeyboardHeight, (int) defaultKeyboardHeight);
         mKeyHorizontalGap = (int) (res.getFraction(R.fraction.key_horizontal_gap_ics,
                 defaultKeyboardWidth, defaultKeyboardWidth));
+        mEmojiCategoryPageIdViewHeight =
+                (int) (res.getDimension(R.dimen.emoji_category_page_id_height));
         final int baseheight = defaultKeyboardHeight - mBottomPadding - mTopPadding
                 + mKeyVerticalGap;
         mEmojiActionBarHeight = ((int) baseheight) / DEFAULT_KEYBOARD_ROWS
                 - (mKeyVerticalGap - mBottomPadding) / 2;
-        mEmojiPagerHeight = defaultKeyboardHeight - mEmojiActionBarHeight;
+        mEmojiPagerHeight = defaultKeyboardHeight - mEmojiActionBarHeight
+                - mEmojiCategoryPageIdViewHeight;
         mEmojiPagerBottomMargin = mKeyVerticalGap / 2;
         mEmojiKeyboardHeight = mEmojiPagerHeight - mEmojiPagerBottomMargin - 1;
     }
 
-    public void setPagerProps(ViewPager vp) {
+    public void setPagerProperties(ViewPager vp) {
         final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) vp.getLayoutParams();
-        lp.height = mEmojiPagerHeight - mEmojiPagerBottomMargin;
+        lp.height = mEmojiKeyboardHeight;
         lp.bottomMargin = mEmojiPagerBottomMargin;
         vp.setLayoutParams(lp);
     }
 
-    public void setActionBarProps(LinearLayout ll) {
+    public void setCategoryPageIdViewProperties(LinearLayout ll) {
+        final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) ll.getLayoutParams();
+        lp.height = mEmojiCategoryPageIdViewHeight;
+        ll.setLayoutParams(lp);
+    }
+
+    public void setActionBarProperties(LinearLayout ll) {
         final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) ll.getLayoutParams();
         lp.height = mEmojiActionBarHeight;
         lp.topMargin = 0;
@@ -71,7 +81,7 @@
         ll.setLayoutParams(lp);
     }
 
-    public void setKeyProps(ImageView ib) {
+    public void setKeyProperties(ImageView ib) {
         final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) ib.getLayoutParams();
         lp.leftMargin = mKeyHorizontalGap / 2;
         lp.rightMargin = mKeyHorizontalGap / 2;
diff --git a/java/src/com/android/inputmethod/keyboard/Keyboard.java b/java/src/com/android/inputmethod/keyboard/Keyboard.java
index 23f037f..bc1383a 100644
--- a/java/src/com/android/inputmethod/keyboard/Keyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/Keyboard.java
@@ -155,6 +155,15 @@
         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;
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
index 0ef6802..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;
@@ -445,6 +446,8 @@
         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()) {
@@ -465,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);
             }
diff --git a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
index f8ad43e..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;
@@ -816,7 +815,6 @@
         if (background != null) {
             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);
diff --git a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
index cd127c7..a031669 100644
--- a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
+++ b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
@@ -245,8 +245,8 @@
         final int threshold = (int) (defaultWidth * SEARCH_DISTANCE);
         final int thresholdSquared = threshold * threshold;
         // Round-up so we don't have any pixels outside the grid
-        final int fullGridWidth = mGridWidth * mCellWidth;
-        final int fullGridHeight = mGridHeight * mCellHeight;
+        final int lastPixelXCoordinate = mGridWidth * mCellWidth - 1;
+        final int lastPixelYCoordinate = mGridHeight * mCellHeight - 1;
 
         // For large layouts, 'neighborsFlatBuffer' is about 80k of memory: gridSize is usually 512,
         // keycount is about 40 and a pointer to a Key is 4 bytes. This contains, for each cell,
@@ -329,22 +329,20 @@
             final int yMiddleOfTopCell = topPixelWithinThreshold - yDeltaToGrid + halfCellHeight;
             final int yStart = Math.max(halfCellHeight,
                     yMiddleOfTopCell + (yDeltaToGrid <= halfCellHeight ? 0 : mCellHeight));
-            final int yEnd = Math.min(fullGridHeight, keyY + key.getHeight() + threshold);
+            final int yEnd = Math.min(lastPixelYCoordinate, keyY + key.getHeight() + threshold);
 
             final int leftPixelWithinThreshold = keyX - threshold;
             final int xDeltaToGrid = leftPixelWithinThreshold % mCellWidth;
             final int xMiddleOfLeftCell = leftPixelWithinThreshold - xDeltaToGrid + halfCellWidth;
             final int xStart = Math.max(halfCellWidth,
                     xMiddleOfLeftCell + (xDeltaToGrid <= halfCellWidth ? 0 : mCellWidth));
-            final int xEnd = Math.min(fullGridWidth, keyX + key.getWidth() + threshold);
+            final int xEnd = Math.min(lastPixelXCoordinate, keyX + key.getWidth() + threshold);
 
             int baseIndexOfCurrentRow = (yStart / mCellHeight) * mGridWidth + (xStart / mCellWidth);
             for (int centerY = yStart; centerY <= yEnd; centerY += mCellHeight) {
                 int index = baseIndexOfCurrentRow;
                 for (int centerX = xStart; centerX <= xEnd; centerX += mCellWidth) {
-                    // TODO: Remove "index < neighborCountPerCell.length" below.
-                    if (index < neighborCountPerCell.length
-                            && key.squaredDistanceToEdge(centerX, centerY) < thresholdSquared) {
+                    if (key.squaredDistanceToEdge(centerX, centerY) < thresholdSquared) {
                         neighborsFlatBuffer[index * keyCount + neighborCountPerCell[index]] = key;
                         ++neighborCountPerCell[index];
                     }
diff --git a/java/src/com/android/inputmethod/keyboard/internal/DynamicGridKeyboard.java b/java/src/com/android/inputmethod/keyboard/internal/DynamicGridKeyboard.java
index f203eb7..0dd71e2 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/DynamicGridKeyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/DynamicGridKeyboard.java
@@ -18,25 +18,27 @@
 
 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.Constants;
 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;
-    // Recent codes are saved as an integer array, so we use comma as a separater.
-    private static final String RECENT_KEY_SEPARATOR = Constants.STRING_COMMA;
 
     private final SharedPreferences mPrefs;
     private final int mLeftPadding;
@@ -84,6 +86,9 @@
     }
 
     private void addKey(final Key usedKey, final boolean addFirst) {
+        if (usedKey == null) {
+            return;
+        }
         synchronized (mGridKeys) {
             mCachedGridKeys = null;
             final GridKey key = new GridKey(usedKey);
@@ -109,27 +114,44 @@
     }
 
     private void saveRecentKeys() {
-        final StringBuilder sb = new StringBuilder();
+        final ArrayList<Object> keys = CollectionUtils.newArrayList();
         for (final Key key : mGridKeys) {
-            sb.append(key.getCode()).append(RECENT_KEY_SEPARATOR);
+            if (key.getOutputText() != null) {
+                keys.add(key.getOutputText());
+            } else {
+                keys.add(key.getCode());
+            }
         }
-        Settings.writeEmojiRecentKeys(mPrefs, sb.toString());
+        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);
-        for (String s : str.split(RECENT_KEY_SEPARATOR)) {
-            if (TextUtils.isEmpty(s)) {
-                continue;
-            }
-            final int code = Integer.valueOf(s);
-            for (DynamicGridKeyboard kbd : keyboards) {
-                final Key key = kbd.getKey(code);
-                if (key != null) {
-                    addKeyLast(key);
-                    break;
-                }
-            }
+        final List<Object> keys = StringUtils.jsonStrToList(str);
+        for (final Object o : keys) {
+            addKeyLast(getKey(keyboards, o));
         }
     }
 
@@ -171,7 +193,7 @@
         public void updateCorrdinates(final int x, final int y) {
             mCurrentX = x;
             mCurrentY = y;
-            getHitBox().offsetTo(x, y);
+            getHitBox().set(x, y, x + getWidth(), y + getHeight());
         }
 
         @Override
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java
index a72595f..67553fb 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java
@@ -1769,15 +1769,27 @@
         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
-        /* 53 */ "!fixedColumnOrder!4,\u055E,!,\\,,?,:,;,@",
+        // 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, null, null, null, null, null, null, null, null,
-        /* ~107 */
-        /* 108 */ "\u055E,?",
+        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 */
@@ -2015,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~ */
@@ -3407,6 +3438,7 @@
         "iw", LANGUAGE_iw, /* Hebrew */
         "ka", LANGUAGE_ka, /* Georgian */
         "kk", LANGUAGE_kk, /* Kazakh */
+        "km", LANGUAGE_km, /* Khmer */
         "ky", LANGUAGE_ky, /* Kirghiz */
         "lo", LANGUAGE_lo, /* Lao */
         "lt", LANGUAGE_lt, /* Lithuanian */
diff --git a/java/src/com/android/inputmethod/latin/AbstractDictionaryWriter.java b/java/src/com/android/inputmethod/latin/AbstractDictionaryWriter.java
index 55df263..845a9b9 100644
--- a/java/src/com/android/inputmethod/latin/AbstractDictionaryWriter.java
+++ b/java/src/com/android/inputmethod/latin/AbstractDictionaryWriter.java
@@ -58,7 +58,7 @@
         final File file = new File(mContext.getFilesDir(), fileName);
         final File tempFile = new File(mContext.getFilesDir(), tempFileName);
         try {
-            final DictEncoder dictEncoder = new Ver3DictEncoder(file);
+            final DictEncoder dictEncoder = new Ver3DictEncoder(tempFile);
             writeDictionary(dictEncoder);
             tempFile.renameTo(file);
         } catch (IOException e) {
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
index b49cd80..61ccfcf 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
@@ -27,6 +27,7 @@
 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;
@@ -109,7 +110,7 @@
     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,
@@ -122,6 +123,8 @@
     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,
@@ -219,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);
     }
@@ -232,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.
@@ -243,6 +256,7 @@
         if (TextUtils.isEmpty(word)) {
             return;
         }
+        runGCIfRequired();
         final int[] codePoints = StringUtils.toCodePointArray(word);
         addUnigramWordNative(mNativeDict, codePoints, probability);
     }
@@ -252,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);
@@ -262,29 +277,41 @@
         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);
     }
 
-    @UsedForTesting
     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 */);
     }
 
-    @UsedForTesting
     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 */);
     }
 
-    @UsedForTesting
     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
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
index 5661842..181ad17 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
@@ -286,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 029ba02..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 = '}';
@@ -221,7 +228,6 @@
     }
 
     public static final int MAX_INT_BIT_COUNT = 32;
-    public static final String STRING_COMMA = ",";
 
     private Constants() {
         // This utility class is not publicly instantiable.
diff --git a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
index 67eb7f3..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;
@@ -145,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);
         }
     }
 
@@ -173,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;
     }
diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
index 2a90764..0774ce2 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
@@ -22,14 +22,22 @@
 
 import com.android.inputmethod.annotations.UsedForTesting;
 import com.android.inputmethod.keyboard.ProximityInfo;
-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.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.ConcurrentHashMap;
 import java.util.concurrent.atomic.AtomicReference;
 
@@ -49,9 +57,9 @@
     /** Whether to print debug output to log */
     private static boolean DEBUG = false;
 
-    // TODO: Remove and enable dynamic update in native code.
+    // TODO: Remove.
     /** Whether to call binary dictionary dynamically updating methods. */
-    private static boolean ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE = false;
+    public static boolean ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE = true;
 
     private static final int TIMEOUT_FOR_READ_OPS_IN_MILLISECONDS = 100;
 
@@ -60,6 +68,9 @@
      */
     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 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
@@ -154,7 +165,11 @@
     private static AbstractDictionaryWriter getDictionaryWriter(final Context context,
             final String dictType, final boolean isDynamicPersonalizationDictionary) {
         if (isDynamicPersonalizationDictionary) {
-            return new DynamicPersonalizationDictionaryWriter(context, dictType);
+            if (ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE) {
+                return null;
+            } else {
+                return new DynamicPersonalizationDictionaryWriter(context, dictType);
+            }
         } else {
             return new DictionaryWriter(context, dictType);
         }
@@ -198,7 +213,9 @@
                     mBinaryDictionary.close();
                     mBinaryDictionary = null;
                 }
-                mDictionaryWriter.close();
+                if (mDictionaryWriter != null) {
+                    mDictionaryWriter.close();
+                }
             }
         });
     }
@@ -220,7 +237,23 @@
         getExecutor(mFilename).execute(new Runnable() {
             @Override
             public void run() {
-                mDictionaryWriter.clear();
+                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();
+                }
             }
         });
     }
@@ -257,9 +290,10 @@
             public void run() {
                 if (ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE) {
                     mBinaryDictionary.addUnigramWord(word, frequency);
+                } else {
+                    // TODO: Remove.
+                    mDictionaryWriter.addUnigramWord(word, shortcutTarget, frequency, isNotAWord);
                 }
-                // TODO: Remove.
-                mDictionaryWriter.addUnigramWord(word, shortcutTarget, frequency, isNotAWord);
             }
         });
     }
@@ -280,10 +314,11 @@
             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 */);
                 }
-                // TODO: Remove.
-                mDictionaryWriter.addBigramWords(word0, word1, frequency, isValid,
-                        0 /* lastTouchedTime */);
             }
         });
     }
@@ -303,17 +338,19 @@
             public void run() {
                 if (ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE) {
                     mBinaryDictionary.removeBigramWords(word0, word1);
+                } else {
+                    // TODO: Remove.
+                    mDictionaryWriter.removeBigramWords(word0, word1);
                 }
-                // TODO: Remove.
-                mDictionaryWriter.removeBigramWords(word0, word1);
             }
         });
     }
 
     @Override
-    public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
+    public ArrayList<SuggestedWordInfo> getSuggestionsWithSessionId(final WordComposer composer,
             final String prevWord, final ProximityInfo proximityInfo,
-            final boolean blockOffensiveWords, final int[] additionalFeaturesOptions) {
+            final boolean blockOffensiveWords, final int[] additionalFeaturesOptions,
+            final int sessionId) {
         reloadDictionaryIfRequired();
         final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList();
         final AsyncResultHolder<ArrayList<SuggestedWordInfo>> holder =
@@ -321,32 +358,54 @@
         getExecutor(mFilename).executePrioritized(new Runnable() {
             @Override
             public void run() {
-                final ArrayList<SuggestedWordInfo> inMemDictSuggestion =
-                        mDictionaryWriter.getSuggestions(composer, prevWord, proximityInfo,
-                                blockOffensiveWords, additionalFeaturesOptions);
-                // TODO: Remove checking mIsUpdatable and use native suggestion.
-                if (mBinaryDictionary != null && !mIsUpdatable) {
-                    final ArrayList<SuggestedWordInfo> binarySuggestion =
-                            mBinaryDictionary.getSuggestions(composer, prevWord, proximityInfo,
-                                    blockOffensiveWords, additionalFeaturesOptions);
-                    if (inMemDictSuggestion == null) {
-                        holder.set(binarySuggestion);
-                    } else if (binarySuggestion == null) {
-                        holder.set(inMemDictSuggestion);
-                    } else {
-                        binarySuggestion.addAll(inMemDictSuggestion);
-                        holder.set(binarySuggestion);
+                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 {
-                    holder.set(inMemDictSuggestion);
+                    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, final int[] additionalFeaturesOptions) {
+        return getSuggestionsWithSessionId(composer, prevWord, proximityInfo, blockOffensiveWords,
+                additionalFeaturesOptions, 0 /* sessionId */);
+    }
+
+    @Override
     public boolean isValidWord(final String word) {
         reloadDictionaryIfRequired();
         return isValidWordInner(word);
@@ -401,8 +460,9 @@
         final BinaryDictionary newBinaryDictionary = new BinaryDictionary(filename, 0, length,
                 true /* useFullEditDistance */, null, mDictType, mIsUpdatable);
 
-        // Ensure all threads accessing the current dictionary have finished before swapping in
-        // the new one.
+        // 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
@@ -433,8 +493,33 @@
         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);
     }
 
     /**
@@ -529,7 +614,9 @@
         getExecutor(mFilename).executePrioritized(new Runnable() {
             @Override
             public void run() {
-                loadDictionaryAsync();
+                if (!ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE) {
+                    loadDictionaryAsync();
+                }
             }
         });
     }
@@ -537,7 +624,7 @@
     /**
      * Generate binary dictionary using DictionaryWriter.
      */
-    protected void asyncWriteBinaryDictionary() {
+    protected void asyncFlashAllBinaryDictionary() {
         final Runnable newTask = new Runnable() {
             @Override
             public void run() {
@@ -610,8 +697,12 @@
             @Override
             public void run() {
                 if (mDictType == Dictionary.TYPE_USER_HISTORY) {
-                    holder.set(((DynamicPersonalizationDictionaryWriter) mDictionaryWriter)
-                            .isInDictionaryForTests(word));
+                    if (ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE) {
+                        holder.set(mBinaryDictionary.isValidWord(word));
+                    } else {
+                        holder.set(((DynamicPersonalizationDictionaryWriter) mDictionaryWriter)
+                                .isInDictionaryForTests(word));
+                    }
                 }
             }
         });
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index d3a18d4..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;
@@ -232,10 +233,13 @@
         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;
@@ -269,7 +273,13 @@
                 break;
             case MSG_SHOW_GESTURE_PREVIEW_AND_SUGGESTION_STRIP:
                 if (msg.arg1 == ARG1_NOT_GESTURE_INPUT) {
-                    latinIme.showSuggestionStrip((SuggestedWords) msg.obj);
+                    if (msg.arg2 == ARG2_WITH_TYPED_WORD) {
+                        final Pair<SuggestedWords, String> p =
+                                (Pair<SuggestedWords, String>) msg.obj;
+                        latinIme.showSuggestionStripWithTypedWord(p.first, p.second);
+                    } else {
+                        latinIme.showSuggestionStrip((SuggestedWords) msg.obj);
+                    }
                 } else {
                     latinIme.showGesturePreviewAndSuggestionStrip((SuggestedWords) msg.obj,
                             msg.arg1 == ARG1_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT);
@@ -288,6 +298,10 @@
             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;
             }
         }
 
@@ -304,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);
         }
@@ -331,14 +351,23 @@
             final int arg1 = dismissGestureFloatingPreviewText
                     ? ARG1_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT
                     : ARG1_SHOW_GESTURE_FLOATING_PREVIEW_TEXT;
-            obtainMessage(MSG_SHOW_GESTURE_PREVIEW_AND_SUGGESTION_STRIP, arg1, 0, suggestedWords)
-                    .sendToTarget();
+            obtainMessage(MSG_SHOW_GESTURE_PREVIEW_AND_SUGGESTION_STRIP, arg1,
+                    ARG2_WITHOUT_TYPED_WORD, suggestedWords).sendToTarget();
         }
 
         public void showSuggestionStrip(final SuggestedWords suggestedWords) {
             removeMessages(MSG_SHOW_GESTURE_PREVIEW_AND_SUGGESTION_STRIP);
             obtainMessage(MSG_SHOW_GESTURE_PREVIEW_AND_SUGGESTION_STRIP,
-                    ARG1_NOT_GESTURE_INPUT, 0, suggestedWords).sendToTarget();
+                    ARG1_NOT_GESTURE_INPUT, ARG2_WITHOUT_TYPED_WORD, suggestedWords).sendToTarget();
+        }
+
+        // TODO: Remove this method.
+        public void showSuggestionStripWithTypedWord(final SuggestedWords suggestedWords,
+                final String typedWord) {
+            removeMessages(MSG_SHOW_GESTURE_PREVIEW_AND_SUGGESTION_STRIP);
+            obtainMessage(MSG_SHOW_GESTURE_PREVIEW_AND_SUGGESTION_STRIP, ARG1_NOT_GESTURE_INPUT,
+                    ARG2_WITH_TYPED_WORD,
+                    new Pair<SuggestedWords, String>(suggestedWords, typedWord)).sendToTarget();
         }
 
         public void onEndBatchInput(final SuggestedWords suggestedWords) {
@@ -834,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();
@@ -853,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();
@@ -881,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();
@@ -900,6 +939,35 @@
         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) {
@@ -1054,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 */);
             }
 
@@ -1290,7 +1358,8 @@
         } else {
             setSuggestedWords(settingsValues.mSuggestPuncList, false);
         }
-        mConnection.resetCachesUponCursorMove(newCursorPosition, shouldFinishComposition);
+        mConnection.resetCachesUponCursorMoveAndReturnSuccess(newCursorPosition,
+                shouldFinishComposition);
     }
 
     private void resetComposingState(final boolean alsoResetLastComposedWord) {
@@ -1394,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
@@ -2467,27 +2537,39 @@
                 false /* isPrediction */);
     }
 
-    private void setAutoCorrection(final SuggestedWords suggestedWords) {
+    private void setAutoCorrection(final SuggestedWords suggestedWords, final String typedWord) {
         if (suggestedWords.isEmpty()) return;
         final String autoCorrection;
         if (suggestedWords.mWillAutoCorrect) {
             autoCorrection = suggestedWords.getWord(SuggestedWords.INDEX_OF_AUTO_CORRECTION);
         } else {
-            autoCorrection = suggestedWords.getWord(SuggestedWords.INDEX_OF_TYPED_WORD);
+            // We can't use suggestedWords.getWord(SuggestedWords.INDEX_OF_TYPED_WORD)
+            // because it may differ from mWordComposer.mTypedWord.
+            autoCorrection = typedWord;
         }
         mWordComposer.setAutoCorrection(autoCorrection);
     }
 
+    private void showSuggestionStripWithTypedWord(final SuggestedWords suggestedWords,
+            final String typedWord) {
+      if (suggestedWords.isEmpty()) {
+          clearSuggestionStrip();
+          return;
+      }
+      setAutoCorrection(suggestedWords, typedWord);
+      final boolean isAutoCorrection = suggestedWords.willAutoCorrect();
+      setSuggestedWords(suggestedWords, isAutoCorrection);
+      setAutoCorrectionIndicator(isAutoCorrection);
+      setSuggestionStripShown(isSuggestionsStripVisible());
+    }
+
     private void showSuggestionStrip(final SuggestedWords suggestedWords) {
         if (suggestedWords.isEmpty()) {
             clearSuggestionStrip();
             return;
         }
-        setAutoCorrection(suggestedWords);
-        final boolean isAutoCorrection = suggestedWords.willAutoCorrect();
-        setSuggestedWords(suggestedWords, isAutoCorrection);
-        setAutoCorrectionIndicator(isAutoCorrection);
-        setSuggestionStripShown(isSuggestionsStripVisible());
+        showSuggestionStripWithTypedWord(suggestedWords,
+            suggestedWords.getWord(SuggestedWords.INDEX_OF_TYPED_WORD));
     }
 
     private void commitCurrentAutoCorrection(final String separator) {
@@ -2685,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.
@@ -2708,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()) {
@@ -2757,7 +2848,10 @@
                         // Since there is only one word, willAutoCorrect is false.
                         suggestedWords = suggestedWordsIncludingTypedWord;
                     }
-                    unsetIsAutoCorrectionIndicatorOnAndCallShowSuggestionStrip(suggestedWords);
+                    // We need to pass typedWord because mWordComposer.mTypedWord may differ from
+                    // typedWord.
+                    unsetIsAutoCorrectionIndicatorOnAndCallShowSuggestionStrip(suggestedWords,
+                        typedWord);
                 }});
         } else {
             // We found suggestion spans in the word. We'll create the SuggestedWords out of
@@ -2766,12 +2860,13 @@
                     true /* typedWordValid */, false /* willAutoCorrect */,
                     false /* isPunctuationSuggestions */, false /* isObsoleteSuggestions */,
                     false /* isPrediction */);
-            unsetIsAutoCorrectionIndicatorOnAndCallShowSuggestionStrip(suggestedWords);
+            // We need to pass typedWord because mWordComposer.mTypedWord may differ from typedWord.
+            unsetIsAutoCorrectionIndicatorOnAndCallShowSuggestionStrip(suggestedWords, typedWord);
         }
     }
 
     public void unsetIsAutoCorrectionIndicatorOnAndCallShowSuggestionStrip(
-            final SuggestedWords suggestedWords) {
+            final SuggestedWords suggestedWords, final String typedWord) {
         // Note that it's very important here that suggestedWords.mWillAutoCorrect is false.
         // We never want to auto-correct on a resumed suggestion. Please refer to the three places
         // above in restartSuggestionsOnWordTouchedByCursor() where suggestedWords is affected.
@@ -2779,7 +2874,7 @@
         // the text to adapt it.
         // TODO: remove mIsAutoCorrectionIndicatorOn (see comment on definition)
         mIsAutoCorrectionIndicatorOn = false;
-        mHandler.showSuggestionStrip(suggestedWords);
+        mHandler.showSuggestionStripWithTypedWord(suggestedWords, typedWord);
     }
 
     /**
@@ -2809,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;
diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java
index a031bb3..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() {
@@ -233,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.
@@ -362,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 =
@@ -404,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) {
@@ -523,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;
@@ -568,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 0889f22..cd9c89f 100644
--- a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
+++ b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
@@ -58,17 +58,20 @@
 
     // 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,
-            SubtypeLocaleUtils.NO_LANGUAGE, "keyboard",
-            "KeyboardLayoutSet=" + SubtypeLocaleUtils.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_subtype_keyboard,
-            SubtypeLocaleUtils.NO_LANGUAGE, "keyboard",
-            "KeyboardLayoutSet=" + SubtypeLocaleUtils.EMOJI,
+            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 {
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index 7815f4d..6c18c94 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -361,12 +361,6 @@
 
         // At second character typed, search the unigrams (scores being affected by bigrams)
         for (final String key : mDictionaries.keySet()) {
-            // Skip User history dictionary for lookup
-            // TODO: The user history dictionary should just override getSuggestionsWithSessionId
-            // to make sure it doesn't return anything and we should remove this test
-            if (key.equals(Dictionary.TYPE_USER_HISTORY)) {
-                continue;
-            }
             final Dictionary dictionary = mDictionaries.get(key);
             suggestionsSet.addAll(dictionary.getSuggestionsWithSessionId(wordComposer,
                     prevWordForBigram, proximityInfo, blockOffensiveWords,
@@ -460,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());
@@ -471,7 +465,12 @@
         } 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,
diff --git a/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java b/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
index a241b55..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 = "";
@@ -168,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/makedict/BinaryDictDecoderUtils.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderUtils.java
index 5b319ad..665c7a2 100644
--- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderUtils.java
+++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderUtils.java
@@ -498,7 +498,7 @@
 
             // reach the end of the array.
             if (options.mSupportsDynamicUpdate) {
-                final boolean hasValidForwardLink = dictDecoder.readForwardLinkAndAdvancePosition();
+                final boolean hasValidForwardLink = dictDecoder.readAndFollowForwardLink();
                 if (!hasValidForwardLink) break;
             }
         } while (options.mSupportsDynamicUpdate && dictDecoder.hasNextPtNodeArray());
@@ -550,7 +550,7 @@
      * @return the created (or merged) dictionary.
      */
     @UsedForTesting
-    /* package */ static FusionDictionary readDictionaryBinary(final Ver3DictDecoder dictDecoder,
+    /* package */ static FusionDictionary readDictionaryBinary(final DictDecoder dictDecoder,
             final FusionDictionary dict) throws IOException, UnsupportedFormatException {
         // Read header
         final FileHeader fileHeader = dictDecoder.readHeader();
diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictEncoderUtils.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictEncoderUtils.java
index f333b0d..4dba8e5 100644
--- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictEncoderUtils.java
+++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictEncoderUtils.java
@@ -368,9 +368,9 @@
             if (null != ptNode.mBigrams) {
                 for (WeightedString bigram : ptNode.mBigrams) {
                     final int offset = getOffsetToTargetPtNodeDuringUpdate(ptNodeArray,
-                            nodeSize + size + FormatSpec.PTNODE_FLAGS_SIZE,
+                            nodeSize + size + FormatSpec.PTNODE_ATTRIBUTE_FLAGS_SIZE,
                             FusionDictionary.findWordInTree(dict.mRootNodeArray, bigram.mWord));
-                    nodeSize += getByteSize(offset) + FormatSpec.PTNODE_FLAGS_SIZE;
+                    nodeSize += getByteSize(offset) + FormatSpec.PTNODE_ATTRIBUTE_FLAGS_SIZE;
                 }
             }
             ptNode.mCachedSize = nodeSize;
@@ -758,8 +758,15 @@
             final FormatOptions formatOptions) {
         int positionOfChildrenPosField = ptNode.mCachedAddressAfterUpdate
                 + getNodeHeaderSize(ptNode, formatOptions);
-        if (ptNode.mFrequency >= 0) {
-            positionOfChildrenPosField += FormatSpec.PTNODE_FREQUENCY_SIZE;
+        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;
diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java
index 2c5e93e..a282f59 100644
--- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java
+++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java
@@ -114,7 +114,7 @@
             if (p.mPosition == p.mNumOfPtNode) {
                 if (formatOptions.mSupportsDynamicUpdate) {
                     final boolean hasValidForwardLinkAddress =
-                            dictDecoder.readForwardLinkAndAdvancePosition();
+                            dictDecoder.readAndFollowForwardLink();
                     if (hasValidForwardLinkAddress && dictDecoder.hasNextPtNodeArray()) {
                         // The node array has a forward link.
                         p.mNumOfPtNode = Position.NOT_READ_PTNODE_COUNT;
@@ -233,7 +233,7 @@
                 }
 
                 final boolean hasValidForwardLinkAddress =
-                        dictDecoder.readForwardLinkAndAdvancePosition();
+                        dictDecoder.readAndFollowForwardLink();
                 if (!hasValidForwardLinkAddress || !dictDecoder.hasNextPtNodeArray()) {
                     return FormatSpec.NOT_VALID_WORD;
                 }
diff --git a/java/src/com/android/inputmethod/latin/makedict/DictDecoder.java b/java/src/com/android/inputmethod/latin/makedict/DictDecoder.java
index 40e8524..3796a46 100644
--- a/java/src/com/android/inputmethod/latin/makedict/DictDecoder.java
+++ b/java/src/com/android/inputmethod/latin/makedict/DictDecoder.java
@@ -17,9 +17,11 @@
 package com.android.inputmethod.latin.makedict;
 
 import com.android.inputmethod.annotations.UsedForTesting;
+import com.android.inputmethod.latin.makedict.BinaryDictDecoderUtils.CharEncoding;
 import com.android.inputmethod.latin.makedict.BinaryDictDecoderUtils.DictBuffer;
 import com.android.inputmethod.latin.makedict.FormatSpec.FileHeader;
 import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions;
+import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
 import com.android.inputmethod.latin.utils.ByteArrayDictBuffer;
 
 import java.io.File;
@@ -30,13 +32,50 @@
 import java.nio.ByteBuffer;
 import java.nio.channels.FileChannel;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.TreeMap;
 
 /**
- * An interface of binary dictionary decoder.
+ * The base class of binary dictionary decoders.
  */
-public interface DictDecoder {
-    public FileHeader readHeader() throws IOException, UnsupportedFormatException;
+public abstract class DictDecoder {
+
+    protected FileHeader readHeader(final DictBuffer dictBuffer)
+            throws IOException, UnsupportedFormatException {
+        if (dictBuffer == null) {
+            openDictBuffer();
+        }
+
+        final int version = HeaderReader.readVersion(dictBuffer);
+        if (version < FormatSpec.MINIMUM_SUPPORTED_VERSION
+                || version > FormatSpec.MAXIMUM_SUPPORTED_VERSION) {
+            throw new UnsupportedFormatException("Unsupported version : " + version);
+        }
+        // TODO: Remove this field.
+        final int optionsFlags = HeaderReader.readOptionFlags(dictBuffer);
+
+        final int headerSize = HeaderReader.readHeaderSize(dictBuffer);
+
+        if (headerSize < 0) {
+            throw new UnsupportedFormatException("header size can't be negative.");
+        }
+
+        final HashMap<String, String> attributes = HeaderReader.readAttributes(dictBuffer,
+                headerSize);
+
+        final FileHeader header = new FileHeader(headerSize,
+                new FusionDictionary.DictionaryOptions(attributes,
+                        0 != (optionsFlags & FormatSpec.GERMAN_UMLAUT_PROCESSING_FLAG),
+                        0 != (optionsFlags & FormatSpec.FRENCH_LIGATURE_PROCESSING_FLAG)),
+                        new FormatOptions(version,
+                                0 != (optionsFlags & FormatSpec.SUPPORTS_DYNAMIC_UPDATE)));
+        return header;
+    }
+
+    /**
+     * Reads and returns the file header.
+     */
+    public abstract FileHeader readHeader() throws IOException, UnsupportedFormatException;
 
     /**
      * Reads PtNode from nodeAddress.
@@ -44,7 +83,7 @@
      * @param formatOptions the format options.
      * @return PtNodeInfo.
      */
-    public PtNodeInfo readPtNode(final int ptNodePos, final FormatOptions formatOptions);
+    public abstract PtNodeInfo readPtNode(final int ptNodePos, final FormatOptions formatOptions);
 
     /**
      * Reads a buffer and returns the memory representation of the dictionary.
@@ -59,9 +98,9 @@
      * @return the created (or merged) dictionary.
      */
     @UsedForTesting
-    public FusionDictionary readDictionaryBinary(final FusionDictionary dict,
+    public abstract FusionDictionary readDictionaryBinary(final FusionDictionary dict,
             final boolean deleteDictIfBroken)
-            throws FileNotFoundException, IOException, UnsupportedFormatException;
+                    throws FileNotFoundException, IOException, UnsupportedFormatException;
 
     /**
      * Gets the address of the last PtNode of the exact matching word in the dictionary.
@@ -74,7 +113,12 @@
      */
     @UsedForTesting
     public int getTerminalPosition(final String word)
-            throws IOException, UnsupportedFormatException;
+            throws IOException, UnsupportedFormatException {
+        if (!isDictBufferOpen()) {
+            openDictBuffer();
+        }
+        return BinaryDictIOUtils.getTerminalPosition(this, word);
+    }
 
     /**
      * Reads unigrams and bigrams from the binary file.
@@ -86,50 +130,56 @@
      * @throws IOException if the file can't be read.
      * @throws UnsupportedFormatException if the format of the file is not recognized.
      */
+    @UsedForTesting
     public void readUnigramsAndBigramsBinary(final TreeMap<Integer, String> words,
             final TreeMap<Integer, Integer> frequencies,
             final TreeMap<Integer, ArrayList<PendingAttribute>> bigrams)
-            throws IOException, UnsupportedFormatException;
+            throws IOException, UnsupportedFormatException {
+        if (!isDictBufferOpen()) {
+            openDictBuffer();
+        }
+        BinaryDictIOUtils.readUnigramsAndBigramsBinary(this, words, frequencies, bigrams);
+    }
 
     /**
      * Sets the position of the buffer to the given value.
      *
      * @param newPos the new position
      */
-    public void setPosition(final int newPos);
+    public abstract void setPosition(final int newPos);
 
     /**
      * Gets the position of the buffer.
      *
      * @return the position
      */
-    public int getPosition();
+    public abstract int getPosition();
 
     /**
      * Reads and returns the PtNode count out of a buffer and forwards the pointer.
      */
-    public int readPtNodeCount();
+    public abstract int readPtNodeCount();
 
     /**
      * Reads the forward link and advances the position.
      *
-     * @return if this method advances the position then true else false.
+     * @return true if this method moves the file pointer, false otherwise.
      */
-    public boolean readForwardLinkAndAdvancePosition();
-    public boolean hasNextPtNodeArray();
+    public abstract boolean readAndFollowForwardLink();
+    public abstract boolean hasNextPtNodeArray();
 
     /**
      * Opens the dictionary file and makes DictBuffer.
      */
     @UsedForTesting
-    public void openDictBuffer() throws FileNotFoundException, IOException;
+    public abstract void openDictBuffer() throws FileNotFoundException, IOException;
     @UsedForTesting
-    public boolean isOpenedDictBuffer();
+    public abstract boolean isDictBufferOpen();
 
-    // Flags for DictionaryBufferFactory.
+    // Constants for DictionaryBufferFactory.
     public static final int USE_READONLY_BYTEBUFFER = 0x01000000;
     public static final int USE_BYTEARRAY = 0x02000000;
-    public static final int USE_WRITABLE_BYTEBUFFER = 0x04000000;
+    public static final int USE_WRITABLE_BYTEBUFFER = 0x03000000;
     public static final int MASK_DICTBUFFER = 0x0F000000;
 
     public interface DictionaryBufferFactory {
@@ -221,4 +271,124 @@
             return null;
         }
     }
+
+    /**
+     * A utility class for reading a file header.
+     */
+    protected static class HeaderReader {
+        protected static int readVersion(final DictBuffer dictBuffer)
+                throws IOException, UnsupportedFormatException {
+            return BinaryDictDecoderUtils.checkFormatVersion(dictBuffer);
+        }
+
+        protected static int readOptionFlags(final DictBuffer dictBuffer) {
+            return dictBuffer.readUnsignedShort();
+        }
+
+        protected static int readHeaderSize(final DictBuffer dictBuffer) {
+            return dictBuffer.readInt();
+        }
+
+        protected static HashMap<String, String> readAttributes(final DictBuffer dictBuffer,
+                final int headerSize) {
+            final HashMap<String, String> attributes = new HashMap<String, String>();
+            while (dictBuffer.position() < headerSize) {
+                // We can avoid an infinite loop here since dictBuffer.position() is always
+                // increased by calling CharEncoding.readString.
+                final String key = CharEncoding.readString(dictBuffer);
+                final String value = CharEncoding.readString(dictBuffer);
+                attributes.put(key, value);
+            }
+            dictBuffer.position(headerSize);
+            return attributes;
+        }
+    }
+
+    /**
+     * A utility class for reading a PtNode.
+     */
+    protected static class PtNodeReader {
+        protected static int readPtNodeOptionFlags(final DictBuffer dictBuffer) {
+            return dictBuffer.readUnsignedByte();
+        }
+
+        protected static int readParentAddress(final DictBuffer dictBuffer,
+                final FormatOptions formatOptions) {
+            if (BinaryDictIOUtils.supportsDynamicUpdate(formatOptions)) {
+                return BinaryDictDecoderUtils.readSInt24(dictBuffer);
+            } else {
+                return FormatSpec.NO_PARENT_ADDRESS;
+            }
+        }
+
+        protected static int readChildrenAddress(final DictBuffer dictBuffer, final int optionFlags,
+                final FormatOptions formatOptions) {
+            if (BinaryDictIOUtils.supportsDynamicUpdate(formatOptions)) {
+                final int address = BinaryDictDecoderUtils.readSInt24(dictBuffer);
+                if (address == 0) return FormatSpec.NO_CHILDREN_ADDRESS;
+                return address;
+            } else {
+                switch (optionFlags & FormatSpec.MASK_CHILDREN_ADDRESS_TYPE) {
+                    case FormatSpec.FLAG_CHILDREN_ADDRESS_TYPE_ONEBYTE:
+                        return dictBuffer.readUnsignedByte();
+                    case FormatSpec.FLAG_CHILDREN_ADDRESS_TYPE_TWOBYTES:
+                        return dictBuffer.readUnsignedShort();
+                    case FormatSpec.FLAG_CHILDREN_ADDRESS_TYPE_THREEBYTES:
+                        return dictBuffer.readUnsignedInt24();
+                    case FormatSpec.FLAG_CHILDREN_ADDRESS_TYPE_NOADDRESS:
+                    default:
+                        return FormatSpec.NO_CHILDREN_ADDRESS;
+                }
+            }
+        }
+
+        // Reads shortcuts and returns the read length.
+        protected static int readShortcut(final DictBuffer dictBuffer,
+                final ArrayList<WeightedString> shortcutTargets) {
+            final int pointerBefore = dictBuffer.position();
+            dictBuffer.readUnsignedShort(); // skip the size
+            while (true) {
+                final int targetFlags = dictBuffer.readUnsignedByte();
+                final String word = CharEncoding.readString(dictBuffer);
+                shortcutTargets.add(new WeightedString(word,
+                        targetFlags & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_FREQUENCY));
+                if (0 == (targetFlags & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_HAS_NEXT)) break;
+            }
+            return dictBuffer.position() - pointerBefore;
+        }
+
+        protected static int readBigramAddresses(final DictBuffer dictBuffer,
+                final ArrayList<PendingAttribute> bigrams, final int baseAddress) {
+            int readLength = 0;
+            int bigramCount = 0;
+            while (bigramCount++ < FormatSpec.MAX_BIGRAMS_IN_A_PTNODE) {
+                final int bigramFlags = dictBuffer.readUnsignedByte();
+                ++readLength;
+                final int sign = 0 == (bigramFlags & FormatSpec.FLAG_BIGRAM_ATTR_OFFSET_NEGATIVE)
+                        ? 1 : -1;
+                int bigramAddress = baseAddress + readLength;
+                switch (bigramFlags & FormatSpec.MASK_BIGRAM_ATTR_ADDRESS_TYPE) {
+                    case FormatSpec.FLAG_BIGRAM_ATTR_ADDRESS_TYPE_ONEBYTE:
+                        bigramAddress += sign * dictBuffer.readUnsignedByte();
+                        readLength += 1;
+                        break;
+                    case FormatSpec.FLAG_BIGRAM_ATTR_ADDRESS_TYPE_TWOBYTES:
+                        bigramAddress += sign * dictBuffer.readUnsignedShort();
+                        readLength += 2;
+                        break;
+                    case FormatSpec.FLAG_BIGRAM_ATTR_ADDRESS_TYPE_THREEBYTES:
+                        bigramAddress += sign * dictBuffer.readUnsignedInt24();
+                        readLength += 3;
+                        break;
+                    default:
+                        throw new RuntimeException("Has bigrams with no address");
+                }
+                bigrams.add(new PendingAttribute(
+                        bigramFlags & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_FREQUENCY,
+                        bigramAddress));
+                if (0 == (bigramFlags & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_HAS_NEXT)) break;
+            }
+            return readLength;
+        }
+    }
 }
diff --git a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
index 96ccd8e..51b89a0 100644
--- a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
+++ b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
@@ -360,18 +360,26 @@
      * Returns new dictionary decoder.
      *
      * @param dictFile the dictionary file.
-     * @param bufferType the flag indicating buffer type which is used by the dictionary decoder.
+     * @param bufferType The type of buffer, as one of USE_* in DictDecoder.
      * @return new dictionary decoder if the dictionary file exists, otherwise null.
      */
     public static DictDecoder getDictDecoder(final File dictFile, final int bufferType) {
-        if (!dictFile.isFile()) return null;
-        return new Ver3DictDecoder(dictFile, bufferType);
+        if (dictFile.isDirectory()) {
+            return new Ver4DictDecoder(dictFile, bufferType);
+        } else if (dictFile.isFile()) {
+            return new Ver3DictDecoder(dictFile, bufferType);
+        }
+        return null;
     }
 
     public static DictDecoder getDictDecoder(final File dictFile,
             final DictionaryBufferFactory factory) {
-        if (!dictFile.isFile()) return null;
-        return new Ver3DictDecoder(dictFile, factory);
+        if (dictFile.isDirectory()) {
+            return new Ver4DictDecoder(dictFile, factory);
+        } else if (dictFile.isFile()) {
+            return new Ver3DictDecoder(dictFile, factory);
+        }
+        return null;
     }
 
     public static DictDecoder getDictDecoder(final File dictFile) {
diff --git a/java/src/com/android/inputmethod/latin/makedict/Ver3DictDecoder.java b/java/src/com/android/inputmethod/latin/makedict/Ver3DictDecoder.java
index 1a90a4b..848277c 100644
--- a/java/src/com/android/inputmethod/latin/makedict/Ver3DictDecoder.java
+++ b/java/src/com/android/inputmethod/latin/makedict/Ver3DictDecoder.java
@@ -32,14 +32,12 @@
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.HashMap;
-import java.util.TreeMap;
 
 /**
  * An implementation of DictDecoder for version 3 binary dictionary.
  */
 @UsedForTesting
-public class Ver3DictDecoder implements DictDecoder {
+public class Ver3DictDecoder extends DictDecoder {
     private static final String TAG = Ver3DictDecoder.class.getSimpleName();
 
     static {
@@ -49,124 +47,10 @@
     // TODO: implement something sensical instead of just a phony method
     private static native int doNothing();
 
-    private final static class HeaderReader {
-        protected static int readVersion(final DictBuffer dictBuffer)
-                throws IOException, UnsupportedFormatException {
-            return BinaryDictDecoderUtils.checkFormatVersion(dictBuffer);
-        }
-
-        protected static int readOptionFlags(final DictBuffer dictBuffer) {
-            return dictBuffer.readUnsignedShort();
-        }
-
-        protected static int readHeaderSize(final DictBuffer dictBuffer) {
-            return dictBuffer.readInt();
-        }
-
-        protected static HashMap<String, String> readAttributes(final DictBuffer dictBuffer,
-                final int headerSize) {
-            final HashMap<String, String> attributes = new HashMap<String, String>();
-            while (dictBuffer.position() < headerSize) {
-                // We can avoid an infinite loop here since dictBuffer.position() is always
-                // increased by calling CharEncoding.readString.
-                final String key = CharEncoding.readString(dictBuffer);
-                final String value = CharEncoding.readString(dictBuffer);
-                attributes.put(key, value);
-            }
-            dictBuffer.position(headerSize);
-            return attributes;
-        }
-    }
-
-    private final static class PtNodeReader {
-        protected static int readPtNodeOptionFlags(final DictBuffer dictBuffer) {
+    protected static class PtNodeReader extends DictDecoder.PtNodeReader {
+        private static int readFrequency(final DictBuffer dictBuffer) {
             return dictBuffer.readUnsignedByte();
         }
-
-        protected static int readParentAddress(final DictBuffer dictBuffer,
-                final FormatOptions formatOptions) {
-            if (BinaryDictIOUtils.supportsDynamicUpdate(formatOptions)) {
-                return BinaryDictDecoderUtils.readSInt24(dictBuffer);
-            } else {
-                return FormatSpec.NO_PARENT_ADDRESS;
-            }
-        }
-
-        protected static int readFrequency(final DictBuffer dictBuffer) {
-            return dictBuffer.readUnsignedByte();
-        }
-
-        protected static int readChildrenAddress(final DictBuffer dictBuffer, final int optionFlags,
-                final FormatOptions formatOptions) {
-            if (BinaryDictIOUtils.supportsDynamicUpdate(formatOptions)) {
-                final int address = BinaryDictDecoderUtils.readSInt24(dictBuffer);
-                if (address == 0) return FormatSpec.NO_CHILDREN_ADDRESS;
-                return address;
-            } else {
-                switch (optionFlags & FormatSpec.MASK_CHILDREN_ADDRESS_TYPE) {
-                    case FormatSpec.FLAG_CHILDREN_ADDRESS_TYPE_ONEBYTE:
-                        return dictBuffer.readUnsignedByte();
-                    case FormatSpec.FLAG_CHILDREN_ADDRESS_TYPE_TWOBYTES:
-                        return dictBuffer.readUnsignedShort();
-                    case FormatSpec.FLAG_CHILDREN_ADDRESS_TYPE_THREEBYTES:
-                        return dictBuffer.readUnsignedInt24();
-                    case FormatSpec.FLAG_CHILDREN_ADDRESS_TYPE_NOADDRESS:
-                    default:
-                        return FormatSpec.NO_CHILDREN_ADDRESS;
-                }
-            }
-        }
-
-        // Reads shortcuts and returns the read length.
-        protected static int readShortcut(final DictBuffer dictBuffer,
-                final ArrayList<WeightedString> shortcutTargets) {
-            final int pointerBefore = dictBuffer.position();
-            dictBuffer.readUnsignedShort(); // skip the size
-            while (true) {
-                final int targetFlags = dictBuffer.readUnsignedByte();
-                final String word = CharEncoding.readString(dictBuffer);
-                shortcutTargets.add(new WeightedString(word,
-                        targetFlags & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_FREQUENCY));
-                if (0 == (targetFlags & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_HAS_NEXT)) break;
-            }
-            return dictBuffer.position() - pointerBefore;
-        }
-
-        protected static int readBigrams(final DictBuffer dictBuffer,
-                final ArrayList<PendingAttribute> bigrams, final int baseAddress) {
-            int readLength = 0;
-            int bigramCount = 0;
-            while (bigramCount++ < FormatSpec.MAX_BIGRAMS_IN_A_PTNODE) {
-                final int bigramFlags = dictBuffer.readUnsignedByte();
-                ++readLength;
-                final int sign = 0 == (bigramFlags & FormatSpec.FLAG_BIGRAM_ATTR_OFFSET_NEGATIVE)
-                        ? 1 : -1;
-                int bigramAddress = baseAddress + readLength;
-                switch (bigramFlags & FormatSpec.MASK_BIGRAM_ATTR_ADDRESS_TYPE) {
-                    case FormatSpec.FLAG_BIGRAM_ATTR_ADDRESS_TYPE_ONEBYTE:
-                        bigramAddress += sign * dictBuffer.readUnsignedByte();
-                        readLength += 1;
-                        break;
-                    case FormatSpec.FLAG_BIGRAM_ATTR_ADDRESS_TYPE_TWOBYTES:
-                        bigramAddress += sign * dictBuffer.readUnsignedShort();
-                        readLength += 2;
-                        break;
-                    case FormatSpec.FLAG_BIGRAM_ATTR_ADDRESS_TYPE_THREEBYTES:
-                        final int offset = (dictBuffer.readUnsignedByte() << 16)
-                                + dictBuffer.readUnsignedShort();
-                        bigramAddress += sign * offset;
-                        readLength += 3;
-                        break;
-                    default:
-                        throw new RuntimeException("Has bigrams with no address");
-                }
-                bigrams.add(new PendingAttribute(
-                        bigramFlags & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_FREQUENCY,
-                        bigramAddress));
-                if (0 == (bigramFlags & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_HAS_NEXT)) break;
-            }
-            return readLength;
-        }
     }
 
     private final File mDictionaryBinaryFile;
@@ -199,7 +83,7 @@
     }
 
     @Override
-    public boolean isOpenedDictBuffer() {
+    public boolean isDictBufferOpen() {
         return mDictBuffer != null;
     }
 
@@ -218,25 +102,11 @@
         if (mDictBuffer == null) {
             openDictBuffer();
         }
-
-        final int version = HeaderReader.readVersion(mDictBuffer);
-        final int optionsFlags = HeaderReader.readOptionFlags(mDictBuffer);
-
-        final int headerSize = HeaderReader.readHeaderSize(mDictBuffer);
-
-        if (headerSize < 0) {
-            throw new UnsupportedFormatException("header size can't be negative.");
+        final FileHeader header = super.readHeader(mDictBuffer);
+        final int version = header.mFormatOptions.mVersion;
+        if (!(version >= 2 && version <= 3)) {
+          throw new UnsupportedFormatException("File header has a wrong version : " + version);
         }
-
-        final HashMap<String, String> attributes = HeaderReader.readAttributes(mDictBuffer,
-                headerSize);
-
-        final FileHeader header = new FileHeader(headerSize,
-                new FusionDictionary.DictionaryOptions(attributes,
-                        0 != (optionsFlags & FormatSpec.GERMAN_UMLAUT_PROCESSING_FLAG),
-                        0 != (optionsFlags & FormatSpec.FRENCH_LIGATURE_PROCESSING_FLAG)),
-                new FormatOptions(version,
-                        0 != (optionsFlags & FormatSpec.SUPPORTS_DYNAMIC_UPDATE)));
         return header;
     }
 
@@ -246,11 +116,11 @@
     public PtNodeInfo readPtNode(final int ptNodePos, final FormatOptions options) {
         int addressPointer = ptNodePos;
         final int flags = PtNodeReader.readPtNodeOptionFlags(mDictBuffer);
-        ++addressPointer;
+        addressPointer += FormatSpec.PTNODE_FLAGS_SIZE;
 
         final int parentAddress = PtNodeReader.readParentAddress(mDictBuffer, options);
         if (BinaryDictIOUtils.supportsDynamicUpdate(options)) {
-            addressPointer += 3;
+            addressPointer += FormatSpec.PARENT_ADDRESS_SIZE;
         }
 
         final int characters[];
@@ -258,7 +128,7 @@
             int index = 0;
             int character = CharEncoding.readChar(mDictBuffer);
             addressPointer += CharEncoding.getCharSize(character);
-            while (-1 != character) {
+            while (FormatSpec.INVALID_CHARACTER != character) {
                 // FusionDictionary is making sure that the length of the word is smaller than
                 // MAX_WORD_LENGTH.
                 // So we'll never write past the end of mCharacterBuffer.
@@ -274,8 +144,8 @@
         }
         final int frequency;
         if (0 != (FormatSpec.FLAG_IS_TERMINAL & flags)) {
-            ++addressPointer;
             frequency = PtNodeReader.readFrequency(mDictBuffer);
+            addressPointer += FormatSpec.PTNODE_FREQUENCY_SIZE;
         } else {
             frequency = PtNode.NOT_A_TERMINAL;
         }
@@ -296,7 +166,8 @@
         final ArrayList<PendingAttribute> bigrams;
         if (0 != (flags & FormatSpec.FLAG_HAS_BIGRAMS)) {
             bigrams = new ArrayList<PendingAttribute>();
-            addressPointer += PtNodeReader.readBigrams(mDictBuffer, bigrams, addressPointer);
+            addressPointer += PtNodeReader.readBigramAddresses(mDictBuffer, bigrams, 
+                    addressPointer);
             if (bigrams.size() >= FormatSpec.MAX_BIGRAMS_IN_A_PTNODE) {
                 MakedictLog.d("too many bigrams in a PtNode.");
             }
@@ -332,25 +203,6 @@
     }
 
     @Override
-    public int getTerminalPosition(String word) throws IOException, UnsupportedFormatException {
-        if (mDictBuffer == null) {
-            openDictBuffer();
-        }
-        return BinaryDictIOUtils.getTerminalPosition(this, word);
-    }
-
-    @Override
-    public void readUnigramsAndBigramsBinary(final TreeMap<Integer, String> words,
-            final TreeMap<Integer, Integer> frequencies,
-            final TreeMap<Integer, ArrayList<PendingAttribute>> bigrams)
-            throws IOException, UnsupportedFormatException {
-        if (mDictBuffer == null) {
-            openDictBuffer();
-        }
-        BinaryDictIOUtils.readUnigramsAndBigramsBinary(this, words, frequencies, bigrams);
-    }
-
-    @Override
     public void setPosition(int newPos) {
         mDictBuffer.position(newPos);
     }
@@ -366,7 +218,7 @@
     }
 
     @Override
-    public boolean readForwardLinkAndAdvancePosition() {
+    public boolean readAndFollowForwardLink() {
         final int nextAddress = mDictBuffer.readUnsignedInt24();
         if (nextAddress >= 0 && nextAddress < mDictBuffer.limit()) {
             mDictBuffer.position(nextAddress);
diff --git a/java/src/com/android/inputmethod/latin/makedict/Ver3DictEncoder.java b/java/src/com/android/inputmethod/latin/makedict/Ver3DictEncoder.java
index 222a0f4..76f0f40 100644
--- a/java/src/com/android/inputmethod/latin/makedict/Ver3DictEncoder.java
+++ b/java/src/com/android/inputmethod/latin/makedict/Ver3DictEncoder.java
@@ -167,7 +167,7 @@
         }
     }
 
-    public void writeChildrenPosition(final PtNode ptNode, final FormatOptions formatOptions) {
+    private void writeChildrenPosition(final PtNode ptNode, final FormatOptions formatOptions) {
         final int childrenPos = BinaryDictEncoderUtils.getChildrenPosition(ptNode, formatOptions);
         if (formatOptions.mSupportsDynamicUpdate) {
             mPosition += BinaryDictEncoderUtils.writeSignedChildrenPosition(mBuffer, mPosition,
diff --git a/java/src/com/android/inputmethod/latin/makedict/Ver4DictDecoder.java b/java/src/com/android/inputmethod/latin/makedict/Ver4DictDecoder.java
new file mode 100644
index 0000000..36c5a27
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/makedict/Ver4DictDecoder.java
@@ -0,0 +1,259 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.makedict;
+
+import com.android.inputmethod.annotations.UsedForTesting;
+import com.android.inputmethod.latin.makedict.BinaryDictDecoderUtils.CharEncoding;
+import com.android.inputmethod.latin.makedict.BinaryDictDecoderUtils.DictBuffer;
+import com.android.inputmethod.latin.makedict.FormatSpec.FileHeader;
+import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions;
+import com.android.inputmethod.latin.makedict.FusionDictionary.PtNode;
+import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
+
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * An implementation of binary dictionary decoder for version 4 binary dictionary.
+ */
+@UsedForTesting
+public class Ver4DictDecoder extends DictDecoder {
+    private static final String TAG = Ver4DictDecoder.class.getSimpleName();
+
+    private static final int FILETYPE_TRIE = 1;
+    private static final int FILETYPE_FREQUENCY = 2;
+
+    private final File mDictDirectory;
+    private final DictionaryBufferFactory mBufferFactory;
+    private DictBuffer mDictBuffer;
+    private DictBuffer mFrequencyBuffer;
+
+    @UsedForTesting
+    /* package */ Ver4DictDecoder(final File dictDirectory, final int factoryFlag) {
+        mDictDirectory = dictDirectory;
+        mDictBuffer = mFrequencyBuffer = null;
+
+        if ((factoryFlag & MASK_DICTBUFFER) == USE_READONLY_BYTEBUFFER) {
+            mBufferFactory = new DictionaryBufferFromReadOnlyByteBufferFactory();
+        } else if ((factoryFlag  & MASK_DICTBUFFER) == USE_BYTEARRAY) {
+            mBufferFactory = new DictionaryBufferFromByteArrayFactory();
+        } else if ((factoryFlag & MASK_DICTBUFFER) == USE_WRITABLE_BYTEBUFFER) {
+            mBufferFactory = new DictionaryBufferFromWritableByteBufferFactory();
+        } else {
+            mBufferFactory = new DictionaryBufferFromReadOnlyByteBufferFactory();
+        }
+    }
+
+    @UsedForTesting
+    /* package */ Ver4DictDecoder(final File dictDirectory, final DictionaryBufferFactory factory) {
+        mDictDirectory = dictDirectory;
+        mBufferFactory = factory;
+        mDictBuffer = mFrequencyBuffer = null;
+    }
+
+    private File getFile(final int fileType) {
+        if (fileType == FILETYPE_TRIE) {
+            return new File(mDictDirectory,
+                    mDictDirectory.getName() + FormatSpec.TRIE_FILE_EXTENSION);
+        } else if (fileType == FILETYPE_FREQUENCY) {
+            return new File(mDictDirectory,
+                    mDictDirectory.getName() + FormatSpec.FREQ_FILE_EXTENSION);
+        } else {
+            throw new RuntimeException("Unsupported kind of file : " + fileType);
+        }
+    }
+
+    @Override
+    public void openDictBuffer() throws FileNotFoundException, IOException {
+        final String filename = mDictDirectory.getName();
+        mDictBuffer = mBufferFactory.getDictionaryBuffer(getFile(FILETYPE_TRIE));
+        mFrequencyBuffer = mBufferFactory.getDictionaryBuffer(getFile(FILETYPE_FREQUENCY));
+    }
+
+    @Override
+    public boolean isDictBufferOpen() {
+        return mDictBuffer != null;
+    }
+
+    /* package */ DictBuffer getDictBuffer() {
+        return mDictBuffer;
+    }
+
+    @Override
+    public FileHeader readHeader() throws IOException, UnsupportedFormatException {
+        if (mDictBuffer == null) {
+            openDictBuffer();
+        }
+        final FileHeader header = super.readHeader(mDictBuffer);
+        final int version = header.mFormatOptions.mVersion;
+        if (version != 4) {
+            throw new UnsupportedFormatException("File header has a wrong version : " + version);
+        }
+        return header;
+    }
+
+    protected static class PtNodeReader extends DictDecoder.PtNodeReader {
+        protected static int readFrequency(final DictBuffer frequencyBuffer, final int terminalId) {
+            frequencyBuffer.position(terminalId * FormatSpec.FREQUENCY_AND_FLAGS_SIZE + 1);
+            return frequencyBuffer.readUnsignedByte();
+        }
+
+        protected static int readTerminalId(final DictBuffer dictBuffer) {
+            return dictBuffer.readInt();
+        }
+    }
+
+    // TODO: Make this buffer thread safe.
+    // TODO: Support words longer than FormatSpec.MAX_WORD_LENGTH.
+    private final int[] mCharacterBuffer = new int[FormatSpec.MAX_WORD_LENGTH];
+    @Override
+    public PtNodeInfo readPtNode(int ptNodePos, FormatOptions options) {
+        int addressPointer = ptNodePos;
+        final int flags = PtNodeReader.readPtNodeOptionFlags(mDictBuffer);
+        addressPointer += FormatSpec.PTNODE_FLAGS_SIZE;
+
+        final int parentAddress = PtNodeReader.readParentAddress(mDictBuffer, options);
+        if (BinaryDictIOUtils.supportsDynamicUpdate(options)) {
+            addressPointer += FormatSpec.PARENT_ADDRESS_SIZE;
+        }
+
+        final int characters[];
+        if (0 != (flags & FormatSpec.FLAG_HAS_MULTIPLE_CHARS)) {
+            int index = 0;
+            int character = CharEncoding.readChar(mDictBuffer);
+            addressPointer += CharEncoding.getCharSize(character);
+            while (FormatSpec.INVALID_CHARACTER != character
+                    && index < FormatSpec.MAX_WORD_LENGTH) {
+                mCharacterBuffer[index++] = character;
+                character = CharEncoding.readChar(mDictBuffer);
+                addressPointer += CharEncoding.getCharSize(character);
+            }
+            characters = Arrays.copyOfRange(mCharacterBuffer, 0, index);
+        } else {
+            final int character = CharEncoding.readChar(mDictBuffer);
+            addressPointer += CharEncoding.getCharSize(character);
+            characters = new int[] { character };
+        }
+        final int terminalId;
+        if (0 != (FormatSpec.FLAG_IS_TERMINAL & flags)) {
+            terminalId = PtNodeReader.readTerminalId(mDictBuffer);
+            addressPointer += FormatSpec.PTNODE_TERMINAL_ID_SIZE;
+        } else {
+            terminalId = PtNode.NOT_A_TERMINAL;
+        }
+
+        final int frequency;
+        if (0 != (FormatSpec.FLAG_IS_TERMINAL & flags)) {
+            frequency = PtNodeReader.readFrequency(mFrequencyBuffer, terminalId);
+        } else {
+            frequency = PtNode.NOT_A_TERMINAL;
+        }
+        int childrenAddress = PtNodeReader.readChildrenAddress(mDictBuffer, flags, options);
+        if (childrenAddress != FormatSpec.NO_CHILDREN_ADDRESS) {
+            childrenAddress += addressPointer;
+        }
+        addressPointer += BinaryDictIOUtils.getChildrenAddressSize(flags, options);
+        final ArrayList<WeightedString> shortcutTargets;
+        if (0 != (flags & FormatSpec.FLAG_HAS_SHORTCUT_TARGETS)) {
+            // readShortcut will add shortcuts to shortcutTargets.
+            shortcutTargets = new ArrayList<WeightedString>();
+            addressPointer += PtNodeReader.readShortcut(mDictBuffer, shortcutTargets);
+        } else {
+            shortcutTargets = null;
+        }
+
+        final ArrayList<PendingAttribute> bigrams;
+        if (0 != (flags & FormatSpec.FLAG_HAS_BIGRAMS)) {
+            bigrams = new ArrayList<PendingAttribute>();
+            addressPointer += PtNodeReader.readBigramAddresses(mDictBuffer, bigrams,
+                    addressPointer);
+            if (bigrams.size() >= FormatSpec.MAX_BIGRAMS_IN_A_PTNODE) {
+                MakedictLog.d("too many bigrams in a node.");
+            }
+        } else {
+            bigrams = null;
+        }
+        return new PtNodeInfo(ptNodePos, addressPointer, flags, characters, frequency,
+                parentAddress, childrenAddress, shortcutTargets, bigrams);
+    }
+
+    private void deleteDictFiles() {
+        final File[] files = mDictDirectory.listFiles();
+        for (int i = 0; i < files.length; ++i) {
+            files[i].delete();
+        }
+    }
+
+    @Override
+    public FusionDictionary readDictionaryBinary(final FusionDictionary dict,
+            final boolean deleteDictIfBroken)
+            throws FileNotFoundException, IOException, UnsupportedFormatException {
+        if (mDictBuffer == null) {
+            openDictBuffer();
+        }
+        try {
+            return BinaryDictDecoderUtils.readDictionaryBinary(this, dict);
+        } catch (IOException e) {
+            Log.e(TAG, "The dictionary " + mDictDirectory.getName() + " is broken.", e);
+            if (deleteDictIfBroken) {
+                deleteDictFiles();
+            }
+            throw e;
+        } catch (UnsupportedFormatException e) {
+            Log.e(TAG, "The dictionary " + mDictDirectory.getName() + " is broken.", e);
+            if (deleteDictIfBroken) {
+                deleteDictFiles();
+            }
+            throw e;
+        }
+    }
+
+    @Override
+    public void setPosition(int newPos) {
+        mDictBuffer.position(newPos);
+    }
+
+    @Override
+    public int getPosition() {
+        return mDictBuffer.position();
+    }
+
+    @Override
+    public int readPtNodeCount() {
+        return BinaryDictDecoderUtils.readPtNodeCount(mDictBuffer);
+    }
+
+    @Override
+    public boolean readAndFollowForwardLink() {
+        final int nextAddress = mDictBuffer.readUnsignedInt24();
+        if (nextAddress >= 0 && nextAddress < mDictBuffer.limit()) {
+            mDictBuffer.position(nextAddress);
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public boolean hasNextPtNodeArray() {
+        return mDictBuffer.position() != FormatSpec.NO_FORWARD_LINK_ADDRESS;
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/personalization/DynamicPersonalizationDictionaryWriter.java b/java/src/com/android/inputmethod/latin/personalization/DynamicPersonalizationDictionaryWriter.java
index e43e74d..0af028a 100644
--- a/java/src/com/android/inputmethod/latin/personalization/DynamicPersonalizationDictionaryWriter.java
+++ b/java/src/com/android/inputmethod/latin/personalization/DynamicPersonalizationDictionaryWriter.java
@@ -19,6 +19,7 @@
 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;
@@ -41,7 +42,8 @@
 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 MAX_HISTORY_BIGRAMS = 10000;
+    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;
@@ -53,10 +55,14 @@
     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
@@ -72,6 +78,10 @@
     @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);
     }
@@ -79,6 +89,10 @@
     @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(),
@@ -102,19 +116,22 @@
     protected void writeDictionary(final DictEncoder dictEncoder)
             throws IOException, UnsupportedFormatException {
         UserHistoryDictIOUtils.writeDictionary(dictEncoder,
-                new FrequencyProvider(mBigramList, mExpandableDictionary), mBigramList,
-                        FORMAT_OPTIONS);
+                new FrequencyProvider(mBigramList, mExpandableDictionary, mMaxHistoryBigrams),
+                mBigramList, FORMAT_OPTIONS);
     }
 
     private static class FrequencyProvider implements BigramDictionaryInterface {
-        final private UserHistoryDictionaryBigramList mBigramList;
-        final private ExpandableDictionary mExpandableDictionary;
+        private final UserHistoryDictionaryBigramList mBigramList;
+        private final ExpandableDictionary mExpandableDictionary;
+        private final int mMaxHistoryBigrams;
 
         public FrequencyProvider(final UserHistoryDictionaryBigramList bigramList,
-                final ExpandableDictionary expandableDictionary) {
+                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;
@@ -130,7 +147,7 @@
                     if (prevFc > 0 && prevFc == fc) {
                         freq = fc & 0xFF;
                     } else if (UserHistoryForgettingCurveUtils.
-                            needsToSave(fc, isValid, mBigramList.size() <= MAX_HISTORY_BIGRAMS)) {
+                            needsToSave(fc, isValid, mBigramList.size() <= mMaxHistoryBigrams)) {
                         freq = fc & 0xFF;
                     } else {
                         // Delete this entry
diff --git a/java/src/com/android/inputmethod/latin/personalization/DynamicPredictionDictionaryBase.java b/java/src/com/android/inputmethod/latin/personalization/DynamicPredictionDictionaryBase.java
index 9364fb0..075d7e3 100644
--- a/java/src/com/android/inputmethod/latin/personalization/DynamicPredictionDictionaryBase.java
+++ b/java/src/com/android/inputmethod/latin/personalization/DynamicPredictionDictionaryBase.java
@@ -74,12 +74,12 @@
 
     @Override
     public void close() {
-        // Close only binary dictionary to reuse this dictionary.
-        // super.close();
-        closeBinaryDictionary();
+        if (!ExpandableBinaryDictionary.ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE) {
+            closeBinaryDictionary();
+        }
         // Flush pending writes.
         // TODO: Remove after this class become to use a dynamic binary dictionary.
-        asyncWriteBinaryDictionary();
+        asyncFlashAllBinaryDictionary();
         Settings.writeLastUserHistoryWriteTime(mPrefs, mLocale);
     }
 
@@ -212,6 +212,6 @@
         // Clear the node structure on memory
         clear();
         // Then flush the cleared state of the dictionary on disk.
-        asyncWriteBinaryDictionary();
+        asyncFlashAllBinaryDictionary();
     }
 }
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/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/StringUtils.java b/java/src/com/android/inputmethod/latin/utils/StringUtils.java
index be41840..327780a 100644
--- a/java/src/com/android/inputmethod/latin/utils/StringUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/StringUtils.java
@@ -16,16 +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
@@ -390,4 +404,151 @@
         }
         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/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/native/jni/Android.mk b/native/jni/Android.mk
index d83bdad..c207032 100644
--- a/native/jni/Android.mk
+++ b/native/jni/Android.mk
@@ -70,9 +70,10 @@
         bigram/bigram_list_read_write_utils.cpp \
         bigram/dynamic_bigram_list_policy.cpp \
         header/header_policy.cpp \
-        header/header_reading_utils.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 \
diff --git a/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
index a63fab6..7f47493 100644
--- a/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
+++ b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
@@ -188,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);
@@ -198,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,
@@ -269,6 +270,16 @@
             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"),
@@ -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/src/suggest/core/dictionary/bigram_dictionary.cpp b/native/jni/src/suggest/core/dictionary/bigram_dictionary.cpp
index 425b076..71f4ef6 100644
--- a/native/jni/src/suggest/core/dictionary/bigram_dictionary.cpp
+++ b/native/jni/src/suggest/core/dictionary/bigram_dictionary.cpp
@@ -147,27 +147,29 @@
     int pos = mDictionaryStructurePolicy->getTerminalNodePositionOfWord(prevWord, prevWordLength,
             forceLowerCaseSearch);
     if (NOT_A_DICT_POS == pos) return NOT_A_DICT_POS;
-    return mDictionaryStructurePolicy->getBigramsPositionOfNode(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;
+    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 false;
+    if (NOT_A_DICT_POS == nextWordPos) return NOT_A_PROBABILITY;
 
     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 99b964c..8af7ee7 100644
--- a/native/jni/src/suggest/core/dictionary/bigram_dictionary.h
+++ b/native/jni/src/suggest/core/dictionary/bigram_dictionary.h
@@ -29,7 +29,7 @@
 
     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:
diff --git a/native/jni/src/suggest/core/dictionary/dictionary.cpp b/native/jni/src/suggest/core/dictionary/dictionary.cpp
index 0335722..ec1b63a 100644
--- a/native/jni/src/suggest/core/dictionary/dictionary.cpp
+++ b/native/jni/src/suggest/core/dictionary/dictionary.cpp
@@ -93,8 +93,9 @@
     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) {
diff --git a/native/jni/src/suggest/core/dictionary/dictionary.h b/native/jni/src/suggest/core/dictionary/dictionary.h
index 06e84bb..9744474 100644
--- a/native/jni/src/suggest/core/dictionary/dictionary.h
+++ b/native/jni/src/suggest/core/dictionary/dictionary.h
@@ -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);
 
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 aecf413..4633c07 100644
--- a/native/jni/src/suggest/core/dictionary/multi_bigram_map.h
+++ b/native/jni/src/suggest/core/dictionary/multi_bigram_map.h
@@ -68,7 +68,7 @@
 
         void init(const DictionaryStructureWithBufferPolicy *const structurePolicy,
                 const int nodePos) {
-            const int bigramsListPos = structurePolicy->getBigramsPositionOfNode(nodePos);
+            const int bigramsListPos = structurePolicy->getBigramsPositionOfPtNode(nodePos);
             BinaryDictionaryBigramsIterator bigramsIt(structurePolicy->getBigramsStructurePolicy(),
                     bigramsListPos);
             while (bigramsIt.hasNext()) {
@@ -112,7 +112,7 @@
             const DictionaryStructureWithBufferPolicy *const structurePolicy, const int nodePos,
             const int nextWordPosition, const int unigramProbability) {
         int bigramProbability = NOT_A_PROBABILITY;
-        const int bigramsListPos = structurePolicy->getBigramsPositionOfNode(nodePos);
+        const int bigramsListPos = structurePolicy->getBigramsPositionOfPtNode(nodePos);
         BinaryDictionaryBigramsIterator bigramsIt(structurePolicy->getBigramsStructurePolicy(),
                 bigramsListPos);
         while (bigramsIt.hasNext()) {
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
index 24d1b8b..b95488e 100644
--- 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
@@ -52,9 +52,9 @@
 
     virtual int getUnigramProbabilityOfPtNode(const int nodePos) const = 0;
 
-    virtual int getShortcutPositionOfNode(const int nodePos) const = 0;
+    virtual int getShortcutPositionOfPtNode(const int nodePos) const = 0;
 
-    virtual int getBigramsPositionOfNode(const int nodePos) const = 0;
+    virtual int getBigramsPositionOfPtNode(const int nodePos) const = 0;
 
     virtual const DictionaryHeaderStructurePolicy *getHeaderStructurePolicy() const = 0;
 
diff --git a/native/jni/src/suggest/core/suggest.cpp b/native/jni/src/suggest/core/suggest.cpp
index 0c925be..b1340e1 100644
--- a/native/jni/src/suggest/core/suggest.cpp
+++ b/native/jni/src/suggest/core/suggest.cpp
@@ -223,7 +223,7 @@
             BinaryDictionaryShortcutIterator shortcutIt(
                     traverseSession->getDictionaryStructurePolicy()->getShortcutsStructurePolicy(),
                     traverseSession->getDictionaryStructurePolicy()
-                            ->getShortcutPositionOfNode(terminalDicNode->getPos()));
+                            ->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);
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
index 26015d5..6ff95ca 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/bigram/bigram_list_policy.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/bigram/bigram_list_policy.h
@@ -33,10 +33,9 @@
 
     void getNextBigram(int *const outBigramPos, int *const outProbability, bool *const outHasNext,
             int *const pos) const {
-        const BigramListReadWriteUtils::BigramFlags flags =
-                BigramListReadWriteUtils::getFlagsAndForwardPointer(mBigramsBuf, pos);
-        *outBigramPos = BigramListReadWriteUtils::getBigramAddressAndForwardPointer(
-                        mBigramsBuf, flags, pos);
+        BigramListReadWriteUtils::BigramFlags flags;
+        BigramListReadWriteUtils::getBigramEntryPropertiesAndAdvancePosition(mBigramsBuf, &flags,
+                outBigramPos, pos);
         *outProbability = BigramListReadWriteUtils::getProbabilityFromFlags(flags);
         *outHasNext = BigramListReadWriteUtils::hasNext(flags);
     }
diff --git a/native/jni/src/suggest/policyimpl/dictionary/bigram/bigram_list_read_write_utils.cpp b/native/jni/src/suggest/policyimpl/dictionary/bigram/bigram_list_read_write_utils.cpp
index 09e832f..1926b98 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/bigram/bigram_list_read_write_utils.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/bigram/bigram_list_read_write_utils.cpp
@@ -16,7 +16,9 @@
 
 #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 {
 
@@ -38,23 +40,31 @@
         BigramListReadWriteUtils::MASK_ATTRIBUTE_PROBABILITY = 0x0F;
 const int BigramListReadWriteUtils::ATTRIBUTE_ADDRESS_SHIFT = 4;
 
-/* static */ BigramListReadWriteUtils::BigramFlags
-        BigramListReadWriteUtils::getFlagsAndForwardPointer(const uint8_t *const bigramsBuf,
-                int *const pos) {
-    return ByteArrayUtils::readUint8AndAdvancePosition(bigramsBuf, pos);
+/* static */ void BigramListReadWriteUtils::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 pos) {
-    BigramFlags flags = getFlagsAndForwardPointer(bigramsBuf, pos);
-    while (hasNext(flags)) {
-        *pos += attributeAddressSize(flags);
-        flags = getFlagsAndForwardPointer(bigramsBuf, pos);
-    }
-    *pos += attributeAddressSize(flags);
+        int *const bigramListPos) {
+    BigramFlags flags;
+    do {
+        getBigramEntryPropertiesAndAdvancePosition(bigramsBuf, &flags, 0 /* outTargetPtNodePos */,
+                bigramListPos);
+    } while(hasNext(flags));
 }
 
-/* static */ int BigramListReadWriteUtils::getBigramAddressAndForwardPointer(
+/* static */ int BigramListReadWriteUtils::getBigramAddressAndAdvancePosition(
         const uint8_t *const bigramsBuf, const BigramFlags flags, int *const pos) {
     int offset = 0;
     const int origin = *pos;
@@ -69,8 +79,10 @@
             offset = ByteArrayUtils::readUint24AndAdvancePosition(bigramsBuf, pos);
             break;
     }
-    if (offset == 0) {
+    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;
@@ -79,4 +91,92 @@
     }
 }
 
+/* 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
index 884bcd7..eabe4e0 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/bigram/bigram_list_read_write_utils.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/bigram/bigram_list_read_write_utils.h
@@ -24,11 +24,15 @@
 
 namespace latinime {
 
+class BufferWithExtendableBuffer;
+
 class BigramListReadWriteUtils {
 public:
    typedef uint8_t BigramFlags;
 
-   static BigramFlags getFlagsAndForwardPointer(const uint8_t *const bigramsBuf, int *const pos);
+   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;
@@ -39,10 +43,7 @@
    }
 
    // Bigrams reading methods
-   static void skipExistingBigrams(const uint8_t *const bigramsBuf, int *const pos);
-
-   static int getBigramAddressAndForwardPointer(const uint8_t *const bigramsBuf,
-           const BigramFlags flags, int *const pos);
+   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) {
@@ -58,50 +59,19 @@
        */
    }
 
-   static AK_FORCE_INLINE BigramFlags setHasNextFlag(const BigramFlags flags) {
-       return flags | FLAG_ATTRIBUTE_HAS_NEXT;
-   }
+   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);
    }
 
-   // Returns true if the bigram entry is valid and put entry values into out*.
-   static AK_FORCE_INLINE bool createBigramEntryAndGetFlagsAndOffsetAndOffsetFieldSize(
-           const int entryPos, const int targetPos, const int probability, const bool hasNext,
-           BigramFlags *const outBigramFlags, uint32_t *const outOffset,
-           int *const outOffsetFieldSize) {
-       if (targetPos == NOT_A_DICT_POS) {
-           return false;
-       }
-       BigramFlags flags = probability & MASK_ATTRIBUTE_PROBABILITY;
-       if (hasNext) {
-           flags |= FLAG_ATTRIBUTE_HAS_NEXT;
-       }
-       const int targetFieldPos = entryPos + 1;
-       const int offset = targetPos - targetFieldPos;
-       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;
-           *outOffsetFieldSize = 3;
-       } else if ((absOffest >> 8) != 0) {
-           flags |= FLAG_ATTRIBUTE_ADDRESS_TYPE_TWOBYTES;
-           *outOffsetFieldSize = 2;
-       } else {
-           flags |= FLAG_ATTRIBUTE_ADDRESS_TYPE_ONEBYTE;
-           *outOffsetFieldSize = 1;
-       }
-       *outBigramFlags = flags;
-       *outOffset = absOffest;
-       return true;
-   }
+   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);
@@ -115,9 +85,18 @@
    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
index 4c44d22..bc2f5ee 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/bigram/dynamic_bigram_list_policy.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/bigram/dynamic_bigram_list_policy.cpp
@@ -16,58 +16,73 @@
 
 #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::BIGRAM_LINK_COUNT_LIMIT = 10000;
+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 pos) const {
-    const bool usesAdditionalBuffer = mBuffer->isInAdditionalBuffer(*pos);
+        bool *const outHasNext, int *const bigramEntryPos) const {
+    const bool usesAdditionalBuffer = mBuffer->isInAdditionalBuffer(*bigramEntryPos);
     const uint8_t *const buffer = mBuffer->getBuffer(usesAdditionalBuffer);
     if (usesAdditionalBuffer) {
-        *pos -= mBuffer->getOriginalBufferSize();
+        *bigramEntryPos -= mBuffer->getOriginalBufferSize();
     }
-    const BigramListReadWriteUtils::BigramFlags flags =
-            BigramListReadWriteUtils::getFlagsAndForwardPointer(buffer, pos);
-    int originalBigramPos = BigramListReadWriteUtils::getBigramAddressAndForwardPointer(
-            buffer, flags, pos);
+    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(flags);
-    *outHasNext = BigramListReadWriteUtils::hasNext(flags);
+    *outProbability = BigramListReadWriteUtils::getProbabilityFromFlags(bigramFlags);
+    *outHasNext = BigramListReadWriteUtils::hasNext(bigramFlags);
     if (usesAdditionalBuffer) {
-        *pos += mBuffer->getOriginalBufferSize();
+        *bigramEntryPos += mBuffer->getOriginalBufferSize();
     }
 }
 
-void DynamicBigramListPolicy::skipAllBigrams(int *const pos) const {
-    const bool usesAdditionalBuffer = mBuffer->isInAdditionalBuffer(*pos);
+void DynamicBigramListPolicy::skipAllBigrams(int *const bigramListPos) const {
+    const bool usesAdditionalBuffer = mBuffer->isInAdditionalBuffer(*bigramListPos);
     const uint8_t *const buffer = mBuffer->getBuffer(usesAdditionalBuffer);
     if (usesAdditionalBuffer) {
-        *pos -= mBuffer->getOriginalBufferSize();
+        *bigramListPos -= mBuffer->getOriginalBufferSize();
     }
-    BigramListReadWriteUtils::skipExistingBigrams(buffer, pos);
+    BigramListReadWriteUtils::skipExistingBigrams(buffer, bigramListPos);
     if (usesAdditionalBuffer) {
-        *pos += mBuffer->getOriginalBufferSize();
+        *bigramListPos += mBuffer->getOriginalBufferSize();
     }
 }
 
-bool DynamicBigramListPolicy::copyAllBigrams(int *const fromPos, int *const toPos,
-        int *outBigramsCount) {
+bool DynamicBigramListPolicy::copyAllBigrams(BufferWithExtendableBuffer *const bufferToWrite,
+        int *const fromPos, int *const toPos, int *const outBigramsCount) const {
     const bool usesAdditionalBuffer = mBuffer->isInAdditionalBuffer(*fromPos);
     if (usesAdditionalBuffer) {
         *fromPos -= mBuffer->getOriginalBufferSize();
     }
     *outBigramsCount = 0;
-    BigramListReadWriteUtils::BigramFlags flags;
+    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.
-        const uint8_t *const buffer = mBuffer->getBuffer(usesAdditionalBuffer);
-        flags = BigramListReadWriteUtils::getFlagsAndForwardPointer(buffer, fromPos);
-        int originalBigramPos = BigramListReadWriteUtils::getBigramAddressAndForwardPointer(
-                buffer, flags, fromPos);
+        int originalBigramPos;
+        BigramListReadWriteUtils::getBigramEntryPropertiesAndAdvancePosition(
+                mBuffer->getBuffer(usesAdditionalBuffer), &bigramFlags, &originalBigramPos,
+                fromPos);
         if (originalBigramPos == NOT_A_DICT_POS) {
             // skip invalid bigram entry.
             continue;
@@ -76,132 +91,223 @@
             originalBigramPos += mBuffer->getOriginalBufferSize();
         }
         const int bigramPos = followBigramLinkAndGetCurrentBigramPtNodePos(originalBigramPos);
-        BigramListReadWriteUtils::BigramFlags newBigramFlags;
-        uint32_t newBigramOffset;
-        int newBigramOffsetFieldSize;
-        if(!BigramListReadWriteUtils::createBigramEntryAndGetFlagsAndOffsetAndOffsetFieldSize(
-                *toPos, bigramPos, BigramListReadWriteUtils::getProbabilityFromFlags(flags),
-                BigramListReadWriteUtils::hasNext(flags), &newBigramFlags, &newBigramOffset,
-                &newBigramOffsetFieldSize)) {
+        if (bigramPos == NOT_A_DICT_POS) {
+            // Target PtNode has been invalidated.
             continue;
         }
-        // Write bigram entry. Target buffer is always the additional buffer.
-        if (!mBuffer->writeUintAndAdvancePosition(newBigramFlags, 1 /* size */,toPos)) {
-            return false;
-        }
-        if (!mBuffer->writeUintAndAdvancePosition(newBigramOffset, newBigramOffsetFieldSize,
-                toPos)) {
+        lastWrittenEntryPos = *toPos;
+        if (!BigramListReadWriteUtils::createAndWriteBigramEntry(bufferToWrite, bigramPos,
+                BigramListReadWriteUtils::getProbabilityFromFlags(bigramFlags),
+                BigramListReadWriteUtils::hasNext(bigramFlags), toPos)) {
             return false;
         }
         (*outBigramsCount)++;
-    } while(BigramListReadWriteUtils::hasNext(flags));
+    } 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;
 }
 
-bool DynamicBigramListPolicy::addNewBigramEntryToBigramList(const int bigramPos,
-        const int probability, int *const pos) {
-    const bool usesAdditionalBuffer = mBuffer->isInAdditionalBuffer(*pos);
+// 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) {
-        *pos -= mBuffer->getOriginalBufferSize();
+        *bigramListPos -= mBuffer->getOriginalBufferSize();
     }
-    BigramListReadWriteUtils::BigramFlags flags;
+    DynamicPatriciaTrieNodeReader nodeReader(mBuffer, this /* bigramsPolicy */, mShortcutPolicy);
+    BigramListReadWriteUtils::BigramFlags bigramFlags;
+    int bigramEntryCount = 0;
     do {
-        int entryPos = *pos;
+        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.
-        const uint8_t *const buffer = mBuffer->getBuffer(usesAdditionalBuffer);
-        flags = BigramListReadWriteUtils::getFlagsAndForwardPointer(buffer, pos);
-        int originalBigramPos = BigramListReadWriteUtils::getBigramAddressAndForwardPointer(
-                buffer, flags, pos);
+        BigramListReadWriteUtils::getBigramEntryPropertiesAndAdvancePosition(
+                mBuffer->getBuffer(usesAdditionalBuffer), &bigramFlags, &originalBigramPos,
+                bigramListPos);
         if (usesAdditionalBuffer && originalBigramPos != NOT_A_DICT_POS) {
             originalBigramPos += mBuffer->getOriginalBufferSize();
         }
-        if (followBigramLinkAndGetCurrentBigramPtNodePos(originalBigramPos) == bigramPos) {
+        if (followBigramLinkAndGetCurrentBigramPtNodePos(originalBigramPos) == bigramTargetPos) {
             // Update this bigram entry.
             const BigramListReadWriteUtils::BigramFlags updatedFlags =
-                    BigramListReadWriteUtils::setProbabilityInFlags(flags, probability);
-            return mBuffer->writeUintAndAdvancePosition(updatedFlags, 1 /* size */, &entryPos);
+                    BigramListReadWriteUtils::setProbabilityInFlags(bigramFlags, probability);
+            return BigramListReadWriteUtils::writeBigramEntry(mBuffer, updatedFlags,
+                    originalBigramPos, &entryPos);
         }
-        if (BigramListReadWriteUtils::hasNext(flags)) {
+        if (BigramListReadWriteUtils::hasNext(bigramFlags)) {
             continue;
         }
         // The current last entry is found.
         // First, update the flags of the last entry.
-        const BigramListReadWriteUtils::BigramFlags updatedFlags =
-                BigramListReadWriteUtils::setHasNextFlag(flags);
-        if (!mBuffer->writeUintAndAdvancePosition(updatedFlags, 1 /* size */, &entryPos)) {
+        if (!BigramListReadWriteUtils::setHasNextFlag(mBuffer, true /* hasNext */, entryPos)) {
             return false;
         }
         if (usesAdditionalBuffer) {
-            *pos += mBuffer->getOriginalBufferSize();
+            *bigramListPos += mBuffer->getOriginalBufferSize();
         }
         // Then, add a new entry after the last entry.
-        return writeNewBigramEntry(bigramPos, probability, pos);
-    } while(BigramListReadWriteUtils::hasNext(flags));
+        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 bigramPos, const int probability,
+bool DynamicBigramListPolicy::writeNewBigramEntry(const int bigramTargetPos, const int probability,
         int *const writingPos) {
-    BigramListReadWriteUtils::BigramFlags newBigramFlags;
-    uint32_t newBigramOffset;
-    int newBigramOffsetFieldSize;
-    if(!BigramListReadWriteUtils::createBigramEntryAndGetFlagsAndOffsetAndOffsetFieldSize(
-            *writingPos, bigramPos, probability, false /* hasNext */, &newBigramFlags,
-            &newBigramOffset, &newBigramOffsetFieldSize)) {
-        return false;
-    }
-    // Write bigram flags.
-    if (!mBuffer->writeUintAndAdvancePosition(newBigramFlags, 1 /* size */, writingPos)) {
-        return false;
-    }
-    // Write bigram positon offset.
-    if (!mBuffer->writeUintAndAdvancePosition(newBigramOffset, newBigramOffsetFieldSize,
-            writingPos)) {
-        return false;
-    }
-    return true;
+    // 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 targetBigramPos) {
+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 flags;
+    BigramListReadWriteUtils::BigramFlags bigramFlags;
+    int bigramEntryCount = 0;
     do {
-        // The buffer address can be changed after calling buffer writing methods.
-        const uint8_t *const buffer = mBuffer->getBuffer(usesAdditionalBuffer);
-        flags = BigramListReadWriteUtils::getFlagsAndForwardPointer(buffer, &pos);
-        int bigramOffsetFieldPos = pos;
-        if (usesAdditionalBuffer) {
-            bigramOffsetFieldPos += mBuffer->getOriginalBufferSize();
+        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 originalBigramPos = BigramListReadWriteUtils::getBigramAddressAndForwardPointer(
-                buffer, flags, &pos);
+        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 != targetBigramPos) {
+        if (bigramPos != bigramTargetPos) {
             continue;
         }
-        // Target entry is found. Write 0 into the bigram pos field to mark the bigram invalid.
-        const int bigramOffsetFieldSize = BigramListReadWriteUtils::attributeAddressSize(flags);
-        if (!mBuffer->writeUintAndAdvancePosition(0 /* data */, bigramOffsetFieldSize,
-                &bigramOffsetFieldPos)) {
-            return false;
-        }
-        return true;
-    } while(BigramListReadWriteUtils::hasNext(flags));
+        // 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;
 }
 
@@ -212,14 +318,14 @@
     }
     int currentPos = originalBigramPos;
     DynamicPatriciaTrieNodeReader nodeReader(mBuffer, this /* bigramsPolicy */, mShortcutPolicy);
-    nodeReader.fetchNodeInfoFromBuffer(currentPos);
+    nodeReader.fetchNodeInfoInBufferFromPtNodePos(currentPos);
     int bigramLinkCount = 0;
     while (nodeReader.getBigramLinkedNodePos() != NOT_A_DICT_POS) {
         currentPos = nodeReader.getBigramLinkedNodePos();
-        nodeReader.fetchNodeInfoFromBuffer(currentPos);
+        nodeReader.fetchNodeInfoInBufferFromPtNodePos(currentPos);
         bigramLinkCount++;
-        if (bigramLinkCount > BIGRAM_LINK_COUNT_LIMIT) {
-            AKLOGI("Bigram link is invalid. start position: %d", bigramPos);
+        if (bigramLinkCount > CONTINUING_BIGRAM_LINK_COUNT_LIMIT) {
+            AKLOGE("Bigram link is invalid. start position: %d", bigramPos);
             ASSERT(false);
             return NOT_A_DICT_POS;
         }
diff --git a/native/jni/src/suggest/policyimpl/dictionary/bigram/dynamic_bigram_list_policy.h b/native/jni/src/suggest/policyimpl/dictionary/bigram/dynamic_bigram_list_policy.h
index dafb62d..8ea318a 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/bigram/dynamic_bigram_list_policy.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/bigram/dynamic_bigram_list_policy.h
@@ -21,13 +21,13 @@
 
 #include "defines.h"
 #include "suggest/core/policy/dictionary_bigrams_structure_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_reading_helper.h"
-#include "suggest/policyimpl/dictionary/utils/buffer_with_extendable_buffer.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.
  */
@@ -40,27 +40,36 @@
     ~DynamicBigramListPolicy() {}
 
     void getNextBigram(int *const outBigramPos, int *const outProbability, bool *const outHasNext,
-            int *const pos) const;
+            int *const bigramEntryPos) const;
 
-    void skipAllBigrams(int *const pos) const;
+    void skipAllBigrams(int *const bigramListPos) const;
 
-    // Copy bigrams from the bigram list that starts at fromPos to toPos and advance these
-    // positions after bigram lists. This method skips invalid bigram entries and write the valid
-    // bigram entry count to outBigramsCount.
-    bool copyAllBigrams(int *const fromPos, int *const toPos, int *outBigramsCount);
+    // Copy bigrams from the bigram list that starts at fromPos in mBuffer to toPos in
+    // bufferToWrite and advance these positions after bigram lists. This method skips invalid
+    // bigram entries and write the valid bigram entry count to outBigramsCount.
+    bool copyAllBigrams(BufferWithExtendableBuffer *const bufferToWrite, int *const fromPos,
+            int *const toPos, int *const outBigramsCount) const;
 
-    bool addNewBigramEntryToBigramList(const int bigramPos, const int probability, int *const pos);
+    bool updateAllBigramEntriesAndDeleteUselessEntries(int *const bigramListPos);
 
-    bool writeNewBigramEntry(const int bigramPos, const int probability,
+    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 targetBigramPos);
+    bool removeBigram(const int bigramListPos, const int bigramTargetPos);
 
  private:
     DISALLOW_IMPLICIT_CONSTRUCTORS(DynamicBigramListPolicy);
 
-    static const int BIGRAM_LINK_COUNT_LIMIT;
+    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;
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 7370984..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
@@ -23,26 +23,27 @@
 
 namespace latinime {
 
-void DynamicPatriciaTrieNodeReader::fetchNodeInfoFromBufferAndProcessMovedNode(const int nodePos,
-        const int maxCodePointCount, int *const outCodePoints) {
-    if (nodePos < 0 || nodePos >= mBuffer->getTailPosition()) {
+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",
-                nodePos, mBuffer->getTailPosition());
+                ptNodePos, mBuffer->getTailPosition());
         ASSERT(false);
         invalidatePtNodeInfo();
         return;
     }
-    const bool usesAdditionalBuffer = mBuffer->isInAdditionalBuffer(nodePos);
+    const bool usesAdditionalBuffer = mBuffer->isInAdditionalBuffer(ptNodePos);
     const uint8_t *const dictBuf = mBuffer->getBuffer(usesAdditionalBuffer);
-    int pos = nodePos;
-    mHeadPos = nodePos;
+    int pos = ptNodePos;
+    mHeadPos = ptNodePos;
     if (usesAdditionalBuffer) {
         pos -= mBuffer->getOriginalBufferSize();
     }
     mFlags = PatriciaTrieReadingUtils::getFlagsAndAdvancePosition(dictBuf, &pos);
-    const int parentPos =
-            DynamicPatriciaTrieReadingUtils::getParentPosAndAdvancePosition(dictBuf, &pos);
-    mParentPos = (parentPos != 0) ? nodePos + parentPos : NOT_A_DICT_POS;
+    const int parentPosOffset =
+            DynamicPatriciaTrieReadingUtils::getParentPtNodePosOffsetAndAdvancePosition(dictBuf,
+                    &pos);
+    mParentPos = DynamicPatriciaTrieReadingUtils::getParentPtNodePos(parentPosOffset, mHeadPos);
     if (outCodePoints != 0) {
         mCodePointCount = PatriciaTrieReadingUtils::getCharsAndAdvancePosition(
                 dictBuf, mFlags, maxCodePointCount, outCodePoints, &pos);
@@ -99,7 +100,8 @@
     // 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);
     }
 }
 
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 6ef5f58..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
@@ -48,17 +48,17 @@
 
     ~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) {
+    AK_FORCE_INLINE void fetchNodeInfoInBufferFromPtNodePosAndGetNodeCodePoints(
+            const int ptNodePos, const int maxCodePointCount, int *const outCodePoints) {
         mSiblingPos = NOT_A_DICT_POS;
         mBigramLinkedNodePos = NOT_A_DICT_POS;
-        fetchNodeInfoFromBufferAndProcessMovedNode(nodePos, maxCodePointCount, outCodePoints);
+        fetchPtNodeInfoFromBufferAndProcessMovedPtNode(ptNodePos, maxCodePointCount, outCodePoints);
     }
 
     // HeadPos is different from NodePos when the current PtNode is a moved PtNode.
@@ -154,8 +154,8 @@
     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();
 };
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 3cfbfd8..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
@@ -35,7 +35,7 @@
     }
     DynamicPatriciaTrieReadingHelper readingHelper(&mBufferWithExtendableBuffer,
             getBigramsStructurePolicy(), getShortcutsStructurePolicy());
-    readingHelper.initWithNodeArrayPos(dicNode->getChildrenPos());
+    readingHelper.initWithPtNodeArrayPos(dicNode->getChildrenPos());
     const DynamicPatriciaTrieNodeReader *const nodeReader = readingHelper.getNodeReader();
     while (!readingHelper.isEnd()) {
         childDicNodes->pushLeavingChild(dicNode, nodeReader->getHeadPos(),
@@ -48,7 +48,7 @@
 }
 
 int DynamicPatriciaTriePolicy::getCodePointsAndProbabilityAndReturnCodePointCount(
-        const int nodePos, const int maxCodePointCount, int *const outCodePoints,
+        const int ptNodePos, const int maxCodePointCount, int *const outCodePoints,
         int *const outUnigramProbability) const {
     // 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.
@@ -56,9 +56,9 @@
     DynamicPatriciaTrieReadingHelper readingHelper(&mBufferWithExtendableBuffer,
             getBigramsStructurePolicy(), getShortcutsStructurePolicy());
     // First, read the terminal node and get its probability.
-    readingHelper.initWithNodePos(nodePos);
+    readingHelper.initWithPtNodePos(ptNodePos);
     if (!readingHelper.isValidTerminalNode()) {
-        // Node at the nodePos is not a valid terminal node.
+        // Node at the ptNodePos is not a valid terminal node.
         *outUnigramProbability = NOT_A_PROBABILITY;
         return 0;
     }
@@ -67,7 +67,7 @@
     // Then, following parent node link to the dictionary root and fetch node code points.
     while (!readingHelper.isEnd()) {
         if (readingHelper.getTotalCodePointCount() > maxCodePointCount) {
-            // The nodePos is not a valid terminal node position in the dictionary.
+            // The ptNodePos is not a valid terminal node position in the dictionary.
             *outUnigramProbability = NOT_A_PROBABILITY;
             return 0;
         }
@@ -98,7 +98,7 @@
     }
     DynamicPatriciaTrieReadingHelper readingHelper(&mBufferWithExtendableBuffer,
             getBigramsStructurePolicy(), getShortcutsStructurePolicy());
-    readingHelper.initWithNodeArrayPos(getRootPosition());
+    readingHelper.initWithPtNodeArrayPos(getRootPosition());
     const DynamicPatriciaTrieNodeReader *const nodeReader = readingHelper.getNodeReader();
     while (!readingHelper.isEnd()) {
         const int matchedCodePointCount = readingHelper.getPrevTotalCodePointCount();
@@ -148,39 +148,39 @@
     }
 }
 
-int DynamicPatriciaTriePolicy::getUnigramProbabilityOfPtNode(const int nodePos) const {
-    if (nodePos == NOT_A_DICT_POS) {
+int DynamicPatriciaTriePolicy::getUnigramProbabilityOfPtNode(const int ptNodePos) const {
+    if (ptNodePos == NOT_A_DICT_POS) {
         return NOT_A_PROBABILITY;
     }
     DynamicPatriciaTrieNodeReader nodeReader(&mBufferWithExtendableBuffer,
             getBigramsStructurePolicy(), getShortcutsStructurePolicy());
-    nodeReader.fetchNodeInfoFromBuffer(nodePos);
+    nodeReader.fetchNodeInfoInBufferFromPtNodePos(ptNodePos);
     if (nodeReader.isDeleted() || nodeReader.isBlacklisted() || nodeReader.isNotAWord()) {
         return NOT_A_PROBABILITY;
     }
     return getProbability(nodeReader.getProbability(), NOT_A_PROBABILITY);
 }
 
-int DynamicPatriciaTriePolicy::getShortcutPositionOfNode(const int nodePos) const {
-    if (nodePos == NOT_A_DICT_POS) {
+int DynamicPatriciaTriePolicy::getShortcutPositionOfPtNode(const int ptNodePos) const {
+    if (ptNodePos == NOT_A_DICT_POS) {
         return NOT_A_DICT_POS;
     }
     DynamicPatriciaTrieNodeReader nodeReader(&mBufferWithExtendableBuffer,
             getBigramsStructurePolicy(), getShortcutsStructurePolicy());
-    nodeReader.fetchNodeInfoFromBuffer(nodePos);
+    nodeReader.fetchNodeInfoInBufferFromPtNodePos(ptNodePos);
     if (nodeReader.isDeleted()) {
         return NOT_A_DICT_POS;
     }
     return nodeReader.getShortcutPos();
 }
 
-int DynamicPatriciaTriePolicy::getBigramsPositionOfNode(const int nodePos) const {
-    if (nodePos == NOT_A_DICT_POS) {
+int DynamicPatriciaTriePolicy::getBigramsPositionOfPtNode(const int ptNodePos) const {
+    if (ptNodePos == NOT_A_DICT_POS) {
         return NOT_A_DICT_POS;
     }
     DynamicPatriciaTrieNodeReader nodeReader(&mBufferWithExtendableBuffer,
             getBigramsStructurePolicy(), getShortcutsStructurePolicy());
-    nodeReader.fetchNodeInfoFromBuffer(nodePos);
+    nodeReader.fetchNodeInfoInBufferFromPtNodePos(ptNodePos);
     if (nodeReader.isDeleted()) {
         return NOT_A_DICT_POS;
     }
@@ -195,7 +195,7 @@
     }
     DynamicPatriciaTrieReadingHelper readingHelper(&mBufferWithExtendableBuffer,
             getBigramsStructurePolicy(), getShortcutsStructurePolicy());
-    readingHelper.initWithNodeArrayPos(getRootPosition());
+    readingHelper.initWithPtNodeArrayPos(getRootPosition());
     DynamicPatriciaTrieWritingHelper writingHelper(&mBufferWithExtendableBuffer,
             &mBigramListPolicy, &mShortcutListPolicy);
     return writingHelper.addUnigramWord(&readingHelper, word, length, probability);
@@ -248,7 +248,9 @@
         AKLOGI("Warning: flush() is called for non-updatable dictionary.");
         return;
     }
-    // TODO: Implement.
+    DynamicPatriciaTrieWritingHelper writingHelper(&mBufferWithExtendableBuffer,
+            &mBigramListPolicy, &mShortcutListPolicy);
+    writingHelper.writeToDictFile(filePath, &mHeaderPolicy);
 }
 
 void DynamicPatriciaTriePolicy::flushWithGC(const char *const filePath) {
@@ -256,7 +258,9 @@
         AKLOGI("Warning: flushWithGC() is called for non-updatable dictionary.");
         return;
     }
-    // TODO: Implement.
+    DynamicPatriciaTrieWritingHelper writingHelper(&mBufferWithExtendableBuffer,
+            &mBigramListPolicy, &mShortcutListPolicy);
+    writingHelper.writeToDictFileWithGC(getRootPosition(), filePath, &mHeaderPolicy);
 }
 
 bool DynamicPatriciaTriePolicy::needsToRunGC() const {
@@ -264,8 +268,8 @@
         AKLOGI("Warning: needsToRunGC() is called for non-updatable dictionary.");
         return false;
     }
-    // TODO: Implement.
-    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 2cbb0ff..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
@@ -33,7 +33,7 @@
 class DynamicPatriciaTriePolicy : public DictionaryStructureWithBufferPolicy {
  public:
     DynamicPatriciaTriePolicy(const MmappedBuffer *const buffer)
-            : mBuffer(buffer), mHeaderPolicy(mBuffer->getBuffer()),
+            : mBuffer(buffer), mHeaderPolicy(mBuffer->getBuffer(), buffer->getBufferSize()),
               mBufferWithExtendableBuffer(mBuffer->getBuffer() + mHeaderPolicy.getSize(),
                       mBuffer->getBufferSize() - mHeaderPolicy.getSize()),
               mShortcutListPolicy(&mBufferWithExtendableBuffer),
@@ -51,7 +51,7 @@
             DicNodeVector *const childDicNodes) const;
 
     int getCodePointsAndProbabilityAndReturnCodePointCount(
-            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 int *const inWord,
@@ -59,11 +59,11 @@
 
     int getProbability(const int unigramProbability, const int bigramProbability) const;
 
-    int getUnigramProbabilityOfPtNode(const int nodePos) const;
+    int getUnigramProbabilityOfPtNode(const int ptNodePos) const;
 
-    int getShortcutPositionOfNode(const int nodePos) const;
+    int getShortcutPositionOfPtNode(const int ptNodePos) const;
 
-    int getBigramsPositionOfNode(const int nodePos) const;
+    int getBigramsPositionOfPtNode(const int ptNodePos) const;
 
     const DictionaryHeaderStructurePolicy *getHeaderStructurePolicy() const {
         return &mHeaderPolicy;
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
index a0b5be6..f4a2ef3 100644
--- 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
@@ -23,36 +23,167 @@
 // 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::nextNodeArray() {
-    const bool usesAdditionalBuffer = mBuffer->isInAdditionalBuffer(mPos);
+void DynamicPatriciaTrieReadingHelper::nextPtNodeArray() {
+    mReadingState.mPosOfLastPtNodeArrayHead = mReadingState.mPos;
+    const bool usesAdditionalBuffer = mBuffer->isInAdditionalBuffer(mReadingState.mPos);
     const uint8_t *const dictBuf = mBuffer->getBuffer(usesAdditionalBuffer);
     if (usesAdditionalBuffer) {
-        mPos -= mBuffer->getOriginalBufferSize();
+        mReadingState.mPos -= mBuffer->getOriginalBufferSize();
     }
-    mNodeCount = PatriciaTrieReadingUtils::getPtNodeArraySizeAndAdvancePosition(dictBuf,
-            &mPos);
+    mReadingState.mNodeCount = PatriciaTrieReadingUtils::getPtNodeArraySizeAndAdvancePosition(
+            dictBuf, &mReadingState.mPos);
     if (usesAdditionalBuffer) {
-        mPos += mBuffer->getOriginalBufferSize();
+        mReadingState.mPos += mBuffer->getOriginalBufferSize();
     }
     // Count up nodes and node arrays to avoid infinite loop.
-    mTotalNodeCount += mNodeCount;
-    mNodeArrayCount++;
-    if (mNodeCount < 0 || mTotalNodeCount > MAX_CHILD_COUNT_TO_AVOID_INFINITE_LOOP
-            || mNodeArrayCount > MAX_NODE_ARRAY_COUNT_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",
-                mNodeCount, mTotalNodeCount, MAX_CHILD_COUNT_TO_AVOID_INFINITE_LOOP,
-                mNodeArrayCount, MAX_NODE_ARRAY_COUNT_TO_AVOID_INFINITE_LOOP);
+                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;
-        mPos = NOT_A_DICT_POS;
+        mReadingState.mPos = NOT_A_DICT_POS;
         return;
     }
-    if (mNodeCount == 0) {
+    if (mReadingState.mNodeCount == 0) {
         // Empty node array. Try following forward link.
         followForwardLink();
     }
@@ -60,24 +191,24 @@
 
 // Follow the forward link and read the next node array if exists.
 void DynamicPatriciaTrieReadingHelper::followForwardLink() {
-    const bool usesAdditionalBuffer = mBuffer->isInAdditionalBuffer(mPos);
+    const bool usesAdditionalBuffer = mBuffer->isInAdditionalBuffer(mReadingState.mPos);
     const uint8_t *const dictBuf = mBuffer->getBuffer(usesAdditionalBuffer);
     if (usesAdditionalBuffer) {
-        mPos -= mBuffer->getOriginalBufferSize();
+        mReadingState.mPos -= mBuffer->getOriginalBufferSize();
     }
     const int forwardLinkPosition =
-            DynamicPatriciaTrieReadingUtils::getForwardLinkPosition(dictBuf, mPos);
+            DynamicPatriciaTrieReadingUtils::getForwardLinkPosition(dictBuf, mReadingState.mPos);
     if (usesAdditionalBuffer) {
-        mPos += mBuffer->getOriginalBufferSize();
+        mReadingState.mPos += mBuffer->getOriginalBufferSize();
     }
-    mPosOfLastForwardLinkField = mPos;
+    mReadingState.mPosOfLastForwardLinkField = mReadingState.mPos;
     if (DynamicPatriciaTrieReadingUtils::isValidForwardLinkPosition(forwardLinkPosition)) {
         // Follow the forward link.
-        mPos += forwardLinkPosition;
-        nextNodeArray();
+        mReadingState.mPos += forwardLinkPosition;
+        nextPtNodeArray();
     } else {
         // All node arrays have been read.
-        mPos = NOT_A_DICT_POS;
+        mReadingState.mPos = NOT_A_DICT_POS;
     }
 }
 
diff --git a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_helper.h b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_helper.h
index 120fd76..b033eee 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_helper.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_helper.h
@@ -17,6 +17,9 @@
 #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"
@@ -34,12 +37,35 @@
  */
 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), mPos(NOT_A_DICT_POS), mNodeCount(0), mPrevTotalCodePointCount(0),
-              mTotalNodeCount(0), mNodeArrayCount(0), mPosOfLastForwardLinkField(NOT_A_DICT_POS),
-              mBuffer(buffer), mNodeReader(mBuffer, bigramsPolicy, shortcutsPolicy) {}
+            : mIsError(false), mReadingState(), mBuffer(buffer),
+              mNodeReader(mBuffer, bigramsPolicy, shortcutsPolicy), mReadingStateStack() {}
 
     ~DynamicPatriciaTrieReadingHelper() {}
 
@@ -48,41 +74,43 @@
     }
 
     AK_FORCE_INLINE bool isEnd() const {
-        return mPos == NOT_A_DICT_POS;
+        return mReadingState.mPos == NOT_A_DICT_POS;
     }
 
-    // Initialize reading state with the head position of a node array.
-    AK_FORCE_INLINE void initWithNodeArrayPos(const int nodeArrayPos) {
-        if (nodeArrayPos == NOT_A_DICT_POS) {
-            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;
-            mPos = nodeArrayPos;
-            mNodeCount = 0;
-            mPrevTotalCodePointCount = 0;
-            mTotalNodeCount = 0;
-            mNodeArrayCount = 0;
-            mPosOfLastForwardLinkField = NOT_A_DICT_POS;
-            nextNodeArray();
+            mReadingState.mPos = ptNodeArrayPos;
+            mReadingState.mPrevTotalCodePointCount = 0;
+            mReadingState.mTotalNodeCount = 0;
+            mReadingState.mNodeArrayCount = 0;
+            mReadingState.mPosOfLastForwardLinkField = NOT_A_DICT_POS;
+            mReadingStateStack.clear();
+            nextPtNodeArray();
             if (!isEnd()) {
-                fetchNodeInfo();
+                fetchPtNodeInfo();
             }
         }
     }
 
     // Initialize reading state with the head position of a node.
-    AK_FORCE_INLINE void initWithNodePos(const int nodePos) {
-        if (nodePos == NOT_A_DICT_POS) {
-            mPos = NOT_A_DICT_POS;
+    AK_FORCE_INLINE void initWithPtNodePos(const int ptNodePos) {
+        if (ptNodePos == NOT_A_DICT_POS) {
+            mReadingState.mPos = NOT_A_DICT_POS;
         } else {
             mIsError = false;
-            mPos = nodePos;
-            mNodeCount = 1;
-            mPrevTotalCodePointCount = 0;
-            mTotalNodeCount = 1;
-            mNodeArrayCount = 1;
-            mPosOfLastForwardLinkField = NOT_A_DICT_POS;
-            fetchNodeInfo();
+            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();
         }
     }
 
@@ -100,12 +128,12 @@
 
     // Return code point count exclude the last read node's code points.
     AK_FORCE_INLINE int getPrevTotalCodePointCount() const {
-        return mPrevTotalCodePointCount;
+        return mReadingState.mPrevTotalCodePointCount;
     }
 
     // Return code point count include the last read node's code points.
     AK_FORCE_INLINE int getTotalCodePointCount() const {
-        return mPrevTotalCodePointCount + mNodeReader.getCodePointCount();
+        return mReadingState.mPrevTotalCodePointCount + mNodeReader.getCodePointCount();
     }
 
     AK_FORCE_INLINE void fetchMergedNodeCodePointsInReverseOrder(
@@ -121,85 +149,136 @@
     }
 
     AK_FORCE_INLINE void readNextSiblingNode() {
-        mNodeCount -= 1;
-        mPos = mNodeReader.getSiblingNodePos();
-        if (mNodeCount <= 0) {
+        mReadingState.mNodeCount -= 1;
+        mReadingState.mPos = mNodeReader.getSiblingNodePos();
+        if (mReadingState.mNodeCount <= 0) {
             // All nodes in the current node array have been read.
             followForwardLink();
             if (!isEnd()) {
-                fetchNodeInfo();
+                fetchPtNodeInfo();
             }
         } else {
-            fetchNodeInfo();
+            fetchPtNodeInfo();
         }
     }
 
     // Read the first child node of the current node.
     AK_FORCE_INLINE void readChildNode() {
         if (mNodeReader.hasChildren()) {
-            mPrevTotalCodePointCount += mNodeReader.getCodePointCount();
-            mTotalNodeCount = 0;
-            mNodeArrayCount = 0;
-            mPos = mNodeReader.getChildrenPos();
-            mPosOfLastForwardLinkField = NOT_A_DICT_POS;
+            mReadingState.mPrevTotalCodePointCount += mNodeReader.getCodePointCount();
+            mReadingState.mTotalNodeCount = 0;
+            mReadingState.mNodeArrayCount = 0;
+            mReadingState.mPos = mNodeReader.getChildrenPos();
+            mReadingState.mPosOfLastForwardLinkField = NOT_A_DICT_POS;
             // Read children node array.
-            nextNodeArray();
+            nextPtNodeArray();
             if (!isEnd()) {
-                fetchNodeInfo();
+                fetchPtNodeInfo();
             }
         } else {
-            mPos = NOT_A_DICT_POS;
+            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) {
-            mPrevTotalCodePointCount += mNodeReader.getCodePointCount();
-            mTotalNodeCount = 1;
-            mNodeArrayCount = 1;
-            mNodeCount = 1;
-            mPos = mNodeReader.getParentPos();
-            mPosOfLastForwardLinkField = NOT_A_DICT_POS;
-            fetchNodeInfo();
+            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 {
-            mPos = NOT_A_DICT_POS;
+            mReadingState.mPos = NOT_A_DICT_POS;
         }
     }
 
     AK_FORCE_INLINE int getPosOfLastForwardLinkField() const {
-        return mPosOfLastForwardLinkField;
+        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;
-    int mPos;
-    // Node count of a node array.
-    int mNodeCount;
-    int mPrevTotalCodePointCount;
-    int mTotalNodeCount;
-    int mNodeArrayCount;
-    int mPosOfLastForwardLinkField;
+    ReadingState mReadingState;
     const BufferWithExtendableBuffer *const mBuffer;
     DynamicPatriciaTrieNodeReader mNodeReader;
     int mMergedNodeCodePoints[MAX_WORD_LENGTH];
+    std::vector<ReadingState> mReadingStateStack;
 
-    void nextNodeArray();
+    void nextPtNodeArray();
 
     void followForwardLink();
 
-    AK_FORCE_INLINE void fetchNodeInfo() {
-        mNodeReader.fetchNodeInfoFromBufferAndGetNodeCodePoints(mPos, MAX_WORD_LENGTH,
-                mMergedNodeCodePoints);
+    AK_FORCE_INLINE void fetchPtNodeInfo() {
+        mNodeReader.fetchNodeInfoInBufferFromPtNodePosAndGetNodeCodePoints(mReadingState.mPos,
+                MAX_WORD_LENGTH, mMergedNodeCodePoints);
         if (mNodeReader.getCodePointCount() <= 0) {
             // Empty node is not allowed.
             mIsError = true;
-            mPos = NOT_A_DICT_POS;
+            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();
         }
     }
 };
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 8428c0b..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
@@ -28,24 +28,42 @@
 const DptReadingUtils::NodeFlags DptReadingUtils::FLAG_IS_MOVED = 0x40;
 const DptReadingUtils::NodeFlags DptReadingUtils::FLAG_IS_DELETED = 0x80;
 
+// 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::getParentPosAndAdvancePosition(const uint8_t *const buffer,
-        int *const pos) {
+/* 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 == 0) {
-        // 0 offset means that the node does not have children.
+    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 db5f9b1..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
@@ -27,13 +27,19 @@
  public:
     typedef uint8_t NodeFlags;
 
+    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 int getParentPosAndAdvancePosition(const uint8_t *const buffer, int *const pos);
+    static int getParentPtNodePosOffsetAndAdvancePosition(const uint8_t *const buffer,
+            int *const pos);
+
+    static int getParentPtNodePos(const int parentOffset, const int ptNodePos);
 
     static int readChildrenPositionAndAdvancePosition(const uint8_t *const buffer, int *const pos);
 
diff --git a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_helper.cpp b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_helper.cpp
index 311d31e..a51ae5e 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_helper.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_helper.cpp
@@ -16,17 +16,27 @@
 
 #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,
@@ -83,7 +93,7 @@
         const int probability) {
     int mMergedNodeCodePoints[MAX_WORD_LENGTH];
     DynamicPatriciaTrieNodeReader nodeReader(mBuffer, mBigramPolicy, mShortcutPolicy);
-    nodeReader.fetchNodeInfoFromBufferAndGetNodeCodePoints(word0Pos, MAX_WORD_LENGTH,
+    nodeReader.fetchNodeInfoInBufferFromPtNodePosAndGetNodeCodePoints(word0Pos, MAX_WORD_LENGTH,
             mMergedNodeCodePoints);
     // Move node to add bigram entry.
     const int newNodePos = mBuffer->getTailPosition();
@@ -91,13 +101,13 @@
         return false;
     }
     int writingPos = newNodePos;
-    // Write a new PtNode using original PtNode's info to the tail of the dictionary.
-    if (!writePtNodeToBufferByCopyingPtNodeInfo(&nodeReader, nodeReader.getParentPos(),
+    // Write a new PtNode using original PtNode's info to the tail of the dictionary in mBuffer.
+    if (!writePtNodeToBufferByCopyingPtNodeInfo(mBuffer, &nodeReader, nodeReader.getParentPos(),
             mMergedNodeCodePoints, nodeReader.getCodePointCount(), nodeReader.getProbability(),
             &writingPos)) {
         return false;
     }
-    nodeReader.fetchNodeInfoFromBuffer(newNodePos);
+    nodeReader.fetchNodeInfoInBufferFromPtNodePos(newNodePos);
     if (nodeReader.getBigramsPos() != NOT_A_DICT_POS) {
         // Insert a new bigram entry into the existing bigram list.
         int bigramListPos = nodeReader.getBigramsPos();
@@ -124,13 +134,56 @@
 // Remove a bigram relation from word0Pos to word1Pos.
 bool DynamicPatriciaTrieWritingHelper::removeBigramWords(const int word0Pos, const int word1Pos) {
     DynamicPatriciaTrieNodeReader nodeReader(mBuffer, mBigramPolicy, mShortcutPolicy);
-    nodeReader.fetchNodeInfoFromBuffer(word0Pos);
+    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) {
@@ -153,9 +206,8 @@
         return false;
     }
     // Update moved position, which is stored in the parent offset field.
-    const int movedPosOffset = movedPos - originalNode->getHeadPos();
-    if (!DynamicPatriciaTrieWritingUtils::writeParentOffsetAndAdvancePosition(
-            mBuffer, movedPosOffset, &writingPos)) {
+    if (!DynamicPatriciaTrieWritingUtils::writeParentPosOffsetAndAdvancePosition(
+            mBuffer, movedPos, originalNode->getHeadPos(), &writingPos)) {
         return false;
     }
     // Update bigram linked node position, which is stored in the children position field.
@@ -168,13 +220,12 @@
         // Update children's parent position.
         DynamicPatriciaTrieReadingHelper readingHelper(mBuffer, mBigramPolicy, mShortcutPolicy);
         const DynamicPatriciaTrieNodeReader *const nodeReader = readingHelper.getNodeReader();
-        readingHelper.initWithNodeArrayPos(originalNode->getChildrenPos());
+        readingHelper.initWithPtNodeArrayPos(originalNode->getChildrenPos());
         while (!readingHelper.isEnd()) {
-            const int childPtNodeWrittenPos = nodeReader->getHeadPos();
-            const int parentOffset = movedPos - childPtNodeWrittenPos;
-            int parentOffsetFieldPos = childPtNodeWrittenPos + 1 /* Flags */;
-            if (!DynamicPatriciaTrieWritingUtils::writeParentOffsetAndAdvancePosition(
-                    mBuffer, parentOffset, &parentOffsetFieldPos)) {
+            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;
@@ -186,7 +237,8 @@
 }
 
 // Write new PtNode at writingPos.
-bool DynamicPatriciaTrieWritingHelper::writePtNodeWithFullInfoToBuffer(const bool isBlacklisted,
+bool DynamicPatriciaTrieWritingHelper::writePtNodeWithFullInfoToBuffer(
+        BufferWithExtendableBuffer *const bufferToWrite, const bool isBlacklisted,
         const bool isNotAWord, const int parentPos, const int *const codePoints,
         const int codePointCount, const int probability, const int childrenPos,
         const int originalBigramListPos, const int originalShortcutListPos,
@@ -194,38 +246,38 @@
     const int nodePos = *writingPos;
     // Write dummy flags. The Node flags are updated with appropriate flags at the last step of the
     // PtNode writing.
-    if (!DynamicPatriciaTrieWritingUtils::writeFlagsAndAdvancePosition(mBuffer, 0 /* nodeFlags */,
-            writingPos)) {
+    if (!DynamicPatriciaTrieWritingUtils::writeFlagsAndAdvancePosition(bufferToWrite,
+            0 /* nodeFlags */, writingPos)) {
         return false;
     }
     // Calculate a parent offset and write the offset.
-    const int parentOffset = (parentPos != NOT_A_DICT_POS) ? parentPos - nodePos : NOT_A_DICT_POS;
-    if (!DynamicPatriciaTrieWritingUtils::writeParentOffsetAndAdvancePosition(mBuffer,
-            parentOffset, writingPos)) {
+    if (!DynamicPatriciaTrieWritingUtils::writeParentPosOffsetAndAdvancePosition(bufferToWrite,
+            parentPos, nodePos, writingPos)) {
         return false;
     }
     // Write code points
-    if (!DynamicPatriciaTrieWritingUtils::writeCodePointsAndAdvancePosition(mBuffer, codePoints,
-            codePointCount, writingPos)) {
+    if (!DynamicPatriciaTrieWritingUtils::writeCodePointsAndAdvancePosition(bufferToWrite,
+            codePoints, codePointCount, writingPos)) {
         return false;
     }
     // Write probability when the probability is a valid probability, which means this node is
     // terminal.
     if (probability != NOT_A_PROBABILITY) {
-        if (!DynamicPatriciaTrieWritingUtils::writeProbabilityAndAdvancePosition(mBuffer,
+        if (!DynamicPatriciaTrieWritingUtils::writeProbabilityAndAdvancePosition(bufferToWrite,
                 probability, writingPos)) {
             return false;
         }
     }
     // Write children position
-    if (!DynamicPatriciaTrieWritingUtils::writeChildrenPositionAndAdvancePosition(mBuffer,
+    if (!DynamicPatriciaTrieWritingUtils::writeChildrenPositionAndAdvancePosition(bufferToWrite,
             childrenPos, writingPos)) {
         return false;
     }
     // Copy shortcut list when the originalShortcutListPos is valid dictionary position.
     if (originalShortcutListPos != NOT_A_DICT_POS) {
         int fromPos = originalShortcutListPos;
-        if (!mShortcutPolicy->copyAllShortcutsAndReturnIfSucceededOrNot(&fromPos, writingPos)) {
+        if (!mShortcutPolicy->copyAllShortcutsAndReturnIfSucceededOrNot(bufferToWrite, &fromPos,
+                writingPos)) {
             return false;
         }
     }
@@ -233,7 +285,7 @@
     int bigramCount = 0;
     if (originalBigramListPos != NOT_A_DICT_POS) {
         int fromPos = originalBigramListPos;
-        if (!mBigramPolicy->copyAllBigrams(&fromPos, writingPos, &bigramCount)) {
+        if (!mBigramPolicy->copyAllBigrams(bufferToWrite, &fromPos, writingPos, &bigramCount)) {
             return false;
         }
     }
@@ -245,27 +297,29 @@
                     bigramCount > 0 /* hasBigrams */, codePointCount > 1 /* hasMultipleChars */,
                     CHILDREN_POSITION_FIELD_SIZE);
     int flagsFieldPos = nodePos;
-    if (!DynamicPatriciaTrieWritingUtils::writeFlagsAndAdvancePosition(mBuffer, nodeFlags,
+    if (!DynamicPatriciaTrieWritingUtils::writeFlagsAndAdvancePosition(bufferToWrite, nodeFlags,
             &flagsFieldPos)) {
         return false;
     }
     return true;
 }
 
-bool DynamicPatriciaTrieWritingHelper::writePtNodeToBuffer(const int parentPos,
+bool DynamicPatriciaTrieWritingHelper::writePtNodeToBuffer(
+        BufferWithExtendableBuffer *const bufferToWrite, const int parentPos,
         const int *const codePoints, const int codePointCount, const int probability,
         int *const writingPos) {
-    return writePtNodeWithFullInfoToBuffer(false /* isBlacklisted */, false /* isNotAWord */,
-            parentPos, codePoints, codePointCount, probability,
+    return writePtNodeWithFullInfoToBuffer(bufferToWrite, false /* isBlacklisted */,
+            false /* isNotAWord */, parentPos, codePoints, codePointCount, probability,
             NOT_A_DICT_POS /* childrenPos */, NOT_A_DICT_POS /* originalBigramsPos */,
             NOT_A_DICT_POS /* originalShortcutPos */, writingPos);
 }
 
 bool DynamicPatriciaTrieWritingHelper::writePtNodeToBufferByCopyingPtNodeInfo(
+        BufferWithExtendableBuffer *const bufferToWrite,
         const DynamicPatriciaTrieNodeReader *const originalNode, const int parentPos,
         const int *const codePoints, const int codePointCount, const int probability,
         int *const writingPos) {
-    return writePtNodeWithFullInfoToBuffer(originalNode->isBlacklisted(),
+    return writePtNodeWithFullInfoToBuffer(bufferToWrite, originalNode->isBlacklisted(),
             originalNode->isNotAWord(), parentPos, codePoints, codePointCount, probability,
             originalNode->getChildrenPos(), originalNode->getBigramsPos(),
             originalNode->getShortcutPos(), writingPos);
@@ -299,8 +353,9 @@
         if (!markNodeAsMovedAndSetPosition(originalPtNode, movedPos, movedPos)) {
             return false;
         }
-        if (!writePtNodeToBufferByCopyingPtNodeInfo(originalPtNode, originalPtNode->getParentPos(),
-                codePoints, originalPtNode->getCodePointCount(), probability, &movedPos)) {
+        if (!writePtNodeToBufferByCopyingPtNodeInfo(mBuffer, originalPtNode,
+                originalPtNode->getParentPos(), codePoints, originalPtNode->getCodePointCount(),
+                probability, &movedPos)) {
             return false;
         }
     }
@@ -328,8 +383,8 @@
             1 /* arraySize */, &writingPos)) {
         return false;
     }
-    if (!writePtNodeToBuffer(parentPtNodePos, nodeCodePoints, nodeCodePointCount, probability,
-            &writingPos)) {
+    if (!writePtNodeToBuffer(mBuffer, parentPtNodePos, nodeCodePoints, nodeCodePointCount,
+            probability, &writingPos)) {
         return false;
     }
     if (!DynamicPatriciaTrieWritingUtils::writeForwardLinkPositionAndAdvancePosition(mBuffer,
@@ -358,8 +413,9 @@
     // Write the 1st part of the reallocating node. The children position will be updated later
     // with actual children position.
     const int newProbability = addsExtraChild ? NOT_A_PROBABILITY : probabilityOfNewPtNode;
-    if (!writePtNodeToBuffer(reallocatingPtNode->getParentPos(), reallocatingPtNodeCodePoints,
-            overlappingCodePointCount, newProbability, &writingPos)) {
+    if (!writePtNodeToBuffer(mBuffer, reallocatingPtNode->getParentPos(),
+            reallocatingPtNodeCodePoints, overlappingCodePointCount, newProbability,
+            &writingPos)) {
         return false;
     }
     const int actualChildrenPos = writingPos;
@@ -371,14 +427,15 @@
     }
     // Write the 2nd part of the reallocating node.
     const int secondPartOfReallocatedPtNodePos = writingPos;
-    if (!writePtNodeToBufferByCopyingPtNodeInfo(reallocatingPtNode, firstPartOfReallocatedPtNodePos,
+    if (!writePtNodeToBufferByCopyingPtNodeInfo(mBuffer, reallocatingPtNode,
+            firstPartOfReallocatedPtNodePos,
             reallocatingPtNodeCodePoints + overlappingCodePointCount,
             reallocatingPtNode->getCodePointCount() - overlappingCodePointCount,
             reallocatingPtNode->getProbability(), &writingPos)) {
         return false;
     }
     if (addsExtraChild) {
-        if (!writePtNodeToBuffer(firstPartOfReallocatedPtNodePos,
+        if (!writePtNodeToBuffer(mBuffer, firstPartOfReallocatedPtNodePos,
                 newNodeCodePoints + overlappingCodePointCount,
                 newNodeCodePointCount - overlappingCodePointCount, probabilityOfNewPtNode,
                 &writingPos)) {
@@ -396,7 +453,7 @@
     }
     // Load node info. Information of the 1st part will be fetched.
     DynamicPatriciaTrieNodeReader nodeReader(mBuffer, mBigramPolicy, mShortcutPolicy);
-    nodeReader.fetchNodeInfoFromBuffer(firstPartOfReallocatedPtNodePos);
+    nodeReader.fetchNodeInfoInBufferFromPtNodePos(firstPartOfReallocatedPtNodePos);
     // Update children position.
     int childrenPosFieldPos = nodeReader.getChildrenPosFieldPos();
     if (!DynamicPatriciaTrieWritingUtils::writeChildrenPositionAndAdvancePosition(mBuffer,
@@ -406,4 +463,107 @@
     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
index 20e35ab..028fa60 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_helper.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_helper.h
@@ -17,7 +17,11 @@
 #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 {
 
@@ -26,9 +30,24 @@
 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)
@@ -46,10 +65,28 @@
     // 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;
@@ -58,18 +95,15 @@
     bool markNodeAsMovedAndSetPosition(const DynamicPatriciaTrieNodeReader *const nodeToUpdate,
             const int movedPos, const int bigramLinkedNodePos);
 
-    bool writePtNodeWithFullInfoToBuffer(const bool isBlacklisted, const bool isNotAWord,
+    bool writePtNodeWithFullInfoToBuffer(BufferWithExtendableBuffer *const bufferToWrite,
+            const bool isBlacklisted, const bool isNotAWord,
             const int parentPos,  const int *const codePoints, const int codePointCount,
             const int probability, const int childrenPos, const int originalBigramListPos,
             const int originalShortcutListPos, int *const writingPos);
 
-    bool writePtNodeToBuffer(const int parentPos, const int *const codePoints,
-            const int codePointCount, const int probability, int *const writingPos);
-
-    bool writePtNodeToBufferByCopyingPtNodeInfo(
-            const DynamicPatriciaTrieNodeReader *const originalNode, const int parentPos,
-            const int *const codePoints, const int codePointCount, const int probability,
-            int *const writingPos);
+    bool writePtNodeToBuffer(BufferWithExtendableBuffer *const bufferToWrite,
+            const int parentPos, const int *const codePoints, const int codePointCount,
+            const int probability, int *const writingPos);
 
     bool createAndInsertNodeIntoPtNodeArray(const int parentPos, const int *const nodeCodePoints,
             const int nodeCodePointCount, const int probability, int *const forwardLinkFieldPos);
@@ -89,6 +123,15 @@
             const int *const reallocatingPtNodeCodePoints, const int overlappingCodePointCount,
             const int probabilityOfNewPtNode, const int *const newNodeCodePoints,
             const int newNodeCodePointCount);
+
+    void flushAllToFile(const char *const fileName,
+            BufferWithExtendableBuffer *const dictHeader,
+            BufferWithExtendableBuffer *const dictBody) const;
+
+    bool writeBufferToFilePointer(FILE *const file,
+            const BufferWithExtendableBuffer *const buffer) const;
+
+    bool runGC(const int rootPtNodeArrayPos, BufferWithExtendableBuffer *const bufferToWrite);
 };
 } // namespace latinime
 #endif /* LATINIME_DYNAMIC_PATRICIA_TRIE_WRITING_HELPER_H */
diff --git a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_utils.cpp b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_utils.cpp
index b261e59..5a39837 100644
--- 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
@@ -39,18 +39,20 @@
 /* static */ bool DynamicPatriciaTrieWritingUtils::writeForwardLinkPositionAndAdvancePosition(
         BufferWithExtendableBuffer *const buffer, const int forwardLinkPos,
         int *const forwardLinkFieldPos) {
-    const int offset = (forwardLinkPos != NOT_A_DICT_POS) ?
-            forwardLinkPos - (*forwardLinkFieldPos) : 0;
-    return writeDictOffset(buffer, offset, forwardLinkFieldPos);
+    return writeDictOffset(buffer, forwardLinkPos, (*forwardLinkFieldPos), forwardLinkFieldPos);
 }
 
 /* static */ bool DynamicPatriciaTrieWritingUtils::writePtNodeArraySizeAndAdvancePosition(
         BufferWithExtendableBuffer *const buffer, const size_t arraySize,
         int *const arraySizeFieldPos) {
-    if (arraySize <= MAX_PTNODE_ARRAY_SIZE_TO_USE_SMALL_SIZE_FIELD) {
+    // 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) {
+    } 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);
@@ -69,11 +71,10 @@
 }
 
 // Note that parentOffset is offset from node's head position.
-/* static */ bool DynamicPatriciaTrieWritingUtils::writeParentOffsetAndAdvancePosition(
-        BufferWithExtendableBuffer *const buffer, const int parentOffset,
+/* static */ bool DynamicPatriciaTrieWritingUtils::writeParentPosOffsetAndAdvancePosition(
+        BufferWithExtendableBuffer *const buffer, const int parentPos, const int basePos,
         int *const parentPosFieldPos) {
-    int offset = (parentOffset != NOT_A_DICT_POS) ? parentOffset : 0;
-    return writeDictOffset(buffer, offset, parentPosFieldPos);
+    return writeDictOffset(buffer, parentPos, basePos, parentPosFieldPos);
 }
 
 /* static */ bool DynamicPatriciaTrieWritingUtils::writeCodePointsAndAdvancePosition(
@@ -106,13 +107,19 @@
 /* static */ bool DynamicPatriciaTrieWritingUtils::writeChildrenPositionAndAdvancePosition(
         BufferWithExtendableBuffer *const buffer, const int childrenPosition,
         int *const childrenPositionFieldPos) {
-    int offset = (childrenPosition != NOT_A_DICT_POS) ?
-            childrenPosition - (*childrenPositionFieldPos) : 0;
-    return writeDictOffset(buffer, offset, childrenPositionFieldPos);
+    return writeDictOffset(buffer, childrenPosition, (*childrenPositionFieldPos),
+            childrenPositionFieldPos);
 }
 
 /* static */ bool DynamicPatriciaTrieWritingUtils::writeDictOffset(
-        BufferWithExtendableBuffer *const buffer, const int offset, int *const offsetFieldPos) {
+        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);
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
index 183ede4..a37e9fb 100644
--- 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
@@ -28,6 +28,8 @@
 
 class DynamicPatriciaTrieWritingUtils {
  public:
+    static const int NODE_FLAG_FIELD_SIZE;
+
     static bool writeForwardLinkPositionAndAdvancePosition(
             BufferWithExtendableBuffer *const buffer, const int forwardLinkPos,
             int *const forwardLinkFieldPos);
@@ -39,8 +41,8 @@
             const DynamicPatriciaTrieReadingUtils::NodeFlags nodeFlags,
             int *const nodeFlagsFieldPos);
 
-    static bool writeParentOffsetAndAdvancePosition(BufferWithExtendableBuffer *const buffer,
-            const int parentPosition, int *const parentPosFieldPos);
+    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);
@@ -63,11 +65,10 @@
     static const int MAX_DICT_OFFSET_VALUE;
     static const int MIN_DICT_OFFSET_VALUE;
     static const int DICT_OFFSET_NEGATIVE_FLAG;
-    static const int NODE_FLAG_FIELD_SIZE;
     static const int PROBABILITY_FIELD_SIZE;
 
-    static bool writeDictOffset(BufferWithExtendableBuffer *const buffer, const int offset,
-            int *const offsetFieldPos);
+    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
index 196da5c..47ace23 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/header/header_policy.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/header/header_policy.cpp
@@ -17,6 +17,8 @@
 #include "suggest/policyimpl/dictionary/header/header_policy.h"
 
 #include <cstddef>
+#include <cstdio>
+#include <ctime>
 
 namespace latinime {
 
@@ -36,7 +38,7 @@
     }
     std::vector<int> keyCodePointVector;
     insertCharactersIntoVector(key, &keyCodePointVector);
-    HeaderReadingUtils::AttributeMap::const_iterator it = mAttributeMap.find(keyCodePointVector);
+    HeaderReadWriteUtils::AttributeMap::const_iterator it = mAttributeMap.find(keyCodePointVector);
     if (it == mAttributeMap.end()) {
         // The key was not found.
         outValue[0] = '?';
@@ -85,7 +87,7 @@
 bool HeaderPolicy::getAttributeValueAsInt(const char *const key, int *const outValue) const {
     std::vector<int> keyVector;
     insertCharactersIntoVector(key, &keyVector);
-    HeaderReadingUtils::AttributeMap::const_iterator it = mAttributeMap.find(keyVector);
+    HeaderReadWriteUtils::AttributeMap::const_iterator it = mAttributeMap.find(keyVector);
     if (it == mAttributeMap.end()) {
         // The key was not found.
         return false;
@@ -94,10 +96,56 @@
     return true;
 }
 
-/* static */ HeaderReadingUtils::AttributeMap HeaderPolicy::createAttributeMapAndReadAllAttributes(
-        const uint8_t *const dictBuf) {
-    HeaderReadingUtils::AttributeMap attributeMap;
-    HeaderReadingUtils::fetchAllHeaderAttributes(dictBuf, &attributeMap);
+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;
 }
 
diff --git a/native/jni/src/suggest/policyimpl/dictionary/header/header_policy.h b/native/jni/src/suggest/policyimpl/dictionary/header/header_policy.h
index 930b475..6b396f3 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/header/header_policy.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/header/header_policy.h
@@ -22,15 +22,18 @@
 
 #include "defines.h"
 #include "suggest/core/policy/dictionary_header_structure_policy.h"
-#include "suggest/policyimpl/dictionary/header/header_reading_utils.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)
-            : mDictBuf(dictBuf), mDictionaryFlags(HeaderReadingUtils::getFlags(dictBuf)),
-              mSize(HeaderReadingUtils::getHeaderSize(dictBuf)),
+    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()),
@@ -43,16 +46,15 @@
     }
 
     AK_FORCE_INLINE bool supportsDynamicUpdate() const {
-        return HeaderReadingUtils::supportsDynamicUpdate(mDictionaryFlags);
+        return HeaderReadWriteUtils::supportsDynamicUpdate(mDictionaryFlags);
     }
 
     AK_FORCE_INLINE bool requiresGermanUmlautProcessing() const {
-        return HeaderReadingUtils::requiresGermanUmlautProcessing(mDictionaryFlags);
+        return HeaderReadWriteUtils::requiresGermanUmlautProcessing(mDictionaryFlags);
     }
 
     AK_FORCE_INLINE bool requiresFrenchLigatureProcessing() const {
-        return HeaderReadingUtils::requiresFrenchLigatureProcessing(
-                mDictionaryFlags);
+        return HeaderReadWriteUtils::requiresFrenchLigatureProcessing(mDictionaryFlags);
     }
 
     AK_FORCE_INLINE float getMultiWordCostMultiplier() const {
@@ -70,6 +72,9 @@
     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);
 
@@ -80,9 +85,10 @@
     static const float MULTIPLE_WORD_COST_MULTIPLIER_SCALE;
 
     const uint8_t *const mDictBuf;
-    const HeaderReadingUtils::DictionaryFlags mDictionaryFlags;
+    const FormatUtils::FORMAT_VERSION mDictFormatVersion;
+    const HeaderReadWriteUtils::DictionaryFlags mDictionaryFlags;
     const int mSize;
-    HeaderReadingUtils::AttributeMap mAttributeMap;
+    HeaderReadWriteUtils::AttributeMap mAttributeMap;
     const float mMultiWordCostMultiplier;
     const bool mUsesForgettingCurve;
     const int mLastUpdatedTime;
@@ -95,7 +101,7 @@
 
     bool getAttributeValueAsInt(const char *const key, int *const outValue) const;
 
-    static HeaderReadingUtils::AttributeMap createAttributeMapAndReadAllAttributes(
+    static HeaderReadWriteUtils::AttributeMap createAttributeMapAndReadAllAttributes(
             const uint8_t *const dictBuf);
 
     static int parseIntAttributeValue(const std::vector<int> *const attributeValue);
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_reading_utils.h b/native/jni/src/suggest/policyimpl/dictionary/header/header_read_write_utils.h
similarity index 73%
rename from native/jni/src/suggest/policyimpl/dictionary/header/header_reading_utils.h
rename to native/jni/src/suggest/policyimpl/dictionary/header/header_read_write_utils.h
index 5716198..6cce733 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/header/header_reading_utils.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/header/header_read_write_utils.h
@@ -14,18 +14,21 @@
  * limitations under the License.
  */
 
-#ifndef LATINIME_HEADER_READING_UTILS_H
-#define LATINIME_HEADER_READING_UTILS_H
+#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 HeaderReadingUtils {
+class BufferWithExtendableBuffer;
+
+class HeaderReadWriteUtils {
  public:
     typedef uint16_t DictionaryFlags;
     typedef std::map<std::vector<int>, std::vector<int> > AttributeMap;
@@ -54,8 +57,20 @@
     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(HeaderReadingUtils);
+    DISALLOW_IMPLICIT_CONSTRUCTORS(HeaderReadWriteUtils);
 
     static const int MAX_ATTRIBUTE_KEY_LENGTH;
     static const int MAX_ATTRIBUTE_VALUE_LENGTH;
@@ -75,4 +90,4 @@
     static const DictionaryFlags CONTAINS_BIGRAMS_FLAG;
 };
 }
-#endif /* LATINIME_HEADER_READING_UTILS_H */
+#endif /* LATINIME_HEADER_READ_WRITE_UTILS_H */
diff --git a/native/jni/src/suggest/policyimpl/dictionary/header/header_reading_utils.cpp b/native/jni/src/suggest/policyimpl/dictionary/header/header_reading_utils.cpp
deleted file mode 100644
index 186c043..0000000
--- a/native/jni/src/suggest/policyimpl/dictionary/header/header_reading_utils.cpp
+++ /dev/null
@@ -1,81 +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/policyimpl/dictionary/header/header_reading_utils.h"
-
-#include <vector>
-
-#include "defines.h"
-#include "suggest/policyimpl/dictionary/utils/byte_array_utils.h"
-
-namespace latinime {
-
-const int HeaderReadingUtils::MAX_ATTRIBUTE_KEY_LENGTH = 256;
-const int HeaderReadingUtils::MAX_ATTRIBUTE_VALUE_LENGTH = 256;
-
-const int HeaderReadingUtils::HEADER_MAGIC_NUMBER_SIZE = 4;
-const int HeaderReadingUtils::HEADER_DICTIONARY_VERSION_SIZE = 2;
-const int HeaderReadingUtils::HEADER_FLAG_SIZE = 2;
-const int HeaderReadingUtils::HEADER_SIZE_FIELD_SIZE = 4;
-
-const HeaderReadingUtils::DictionaryFlags HeaderReadingUtils::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 HeaderReadingUtils::DictionaryFlags
-        HeaderReadingUtils::GERMAN_UMLAUT_PROCESSING_FLAG = 0x1;
-const HeaderReadingUtils::DictionaryFlags
-        HeaderReadingUtils::SUPPORTS_DYNAMIC_UPDATE_FLAG = 0x2;
-const HeaderReadingUtils::DictionaryFlags
-        HeaderReadingUtils::FRENCH_LIGATURE_PROCESSING_FLAG = 0x4;
-
-/* static */ int HeaderReadingUtils::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 */ HeaderReadingUtils::DictionaryFlags
-        HeaderReadingUtils::getFlags(const uint8_t *const dictBuf) {
-    return ByteArrayUtils::readUint16(dictBuf,
-            HEADER_MAGIC_NUMBER_SIZE + HEADER_DICTIONARY_VERSION_SIZE);
-}
-
-/* static */ void HeaderReadingUtils::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));
-    }
-}
-
-} // namespace latinime
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 e6cff43..5269795 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/patricia_trie_policy.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/patricia_trie_policy.cpp
@@ -49,7 +49,7 @@
 // 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 :
- * nodePos: the byte position of the terminal PtNode of the word we are searching for (this is
+ * 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.
@@ -57,7 +57,7 @@
  */
 // TODO: Split this function to be more readable
 int PatriciaTriePolicy::getCodePointsAndProbabilityAndReturnCodePointCount(
-        const int nodePos, const int maxCodePointCount, int *const outCodePoints,
+        const int ptNodePos, const int maxCodePointCount, int *const outCodePoints,
         int *const outUnigramProbability) const {
     int pos = getRootPosition();
     int wordPos = 0;
@@ -78,7 +78,7 @@
                     PatriciaTrieReadingUtils::getFlagsAndAdvancePosition(mDictRoot, &pos);
             const int character = PatriciaTrieReadingUtils::getCodePointAndAdvancePosition(
                     mDictRoot, &pos);
-            if (nodePos == startPos) {
+            if (ptNodePos == startPos) {
                 // We found the position. Copy the rest of the code points in the buffer and return
                 // the length.
                 outCodePoints[wordPos] = character;
@@ -121,7 +121,7 @@
                 // Here comes the tricky part. First, read the children position.
                 const int childrenPos = PatriciaTrieReadingUtils
                         ::readChildrenPositionAndAdvancePosition(mDictRoot, flags, &currentPos);
-                if (childrenPos > nodePos) {
+                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.
@@ -213,7 +213,7 @@
 
         }
     }
-    // If we have looked through all the PtNodes and found no match, the nodePos is
+    // 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;
 }
@@ -319,11 +319,11 @@
     }
 }
 
-int PatriciaTriePolicy::getUnigramProbabilityOfPtNode(const int nodePos) const {
-    if (nodePos == NOT_A_DICT_POS) {
+int PatriciaTriePolicy::getUnigramProbabilityOfPtNode(const int ptNodePos) const {
+    if (ptNodePos == NOT_A_DICT_POS) {
         return NOT_A_PROBABILITY;
     }
-    int pos = nodePos;
+    int pos = ptNodePos;
     const PatriciaTrieReadingUtils::NodeFlags flags =
             PatriciaTrieReadingUtils::getFlagsAndAdvancePosition(mDictRoot, &pos);
     if (!PatriciaTrieReadingUtils::isTerminal(flags)) {
@@ -341,11 +341,11 @@
             mDictRoot, &pos), NOT_A_PROBABILITY);
 }
 
-int PatriciaTriePolicy::getShortcutPositionOfNode(const int nodePos) const {
-    if (nodePos == NOT_A_DICT_POS) {
+int PatriciaTriePolicy::getShortcutPositionOfPtNode(const int ptNodePos) const {
+    if (ptNodePos == NOT_A_DICT_POS) {
         return NOT_A_DICT_POS;
     }
-    int pos = nodePos;
+    int pos = ptNodePos;
     const PatriciaTrieReadingUtils::NodeFlags flags =
             PatriciaTrieReadingUtils::getFlagsAndAdvancePosition(mDictRoot, &pos);
     if (!PatriciaTrieReadingUtils::hasShortcutTargets(flags)) {
@@ -361,11 +361,11 @@
     return pos;
 }
 
-int PatriciaTriePolicy::getBigramsPositionOfNode(const int nodePos) const {
-    if (nodePos == NOT_A_DICT_POS) {
+int PatriciaTriePolicy::getBigramsPositionOfPtNode(const int ptNodePos) const {
+    if (ptNodePos == NOT_A_DICT_POS) {
         return NOT_A_DICT_POS;
     }
-    int pos = nodePos;
+    int pos = ptNodePos;
     const PatriciaTrieReadingUtils::NodeFlags flags =
             PatriciaTrieReadingUtils::getFlagsAndAdvancePosition(mDictRoot, &pos);
     if (!PatriciaTrieReadingUtils::hasBigrams(flags)) {
@@ -385,8 +385,8 @@
 }
 
 int PatriciaTriePolicy::createAndGetLeavingChildNode(const DicNode *const dicNode,
-        const int nodePos, DicNodeVector *childDicNodes) const {
-    int pos = nodePos;
+        const int ptNodePos, DicNodeVector *childDicNodes) const {
+    int pos = ptNodePos;
     const PatriciaTrieReadingUtils::NodeFlags flags =
             PatriciaTrieReadingUtils::getFlagsAndAdvancePosition(mDictRoot, &pos);
     int mergedNodeCodePoints[MAX_WORD_LENGTH];
@@ -404,7 +404,7 @@
     if (PatriciaTrieReadingUtils::hasBigrams(flags)) {
         getBigramsStructurePolicy()->skipAllBigrams(&pos);
     }
-    childDicNodes->pushLeavingChild(dicNode, nodePos, childrenPos, probability,
+    childDicNodes->pushLeavingChild(dicNode, ptNodePos, childrenPos, probability,
             PatriciaTrieReadingUtils::isTerminal(flags),
             PatriciaTrieReadingUtils::hasChildrenInFlags(flags),
             PatriciaTrieReadingUtils::isBlacklisted(flags) ||
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 cee3e4a..19155f9 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/patricia_trie_policy.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/patricia_trie_policy.h
@@ -34,7 +34,7 @@
 class PatriciaTriePolicy : public DictionaryStructureWithBufferPolicy {
  public:
     PatriciaTriePolicy(const MmappedBuffer *const buffer)
-            : mBuffer(buffer), mHeaderPolicy(mBuffer->getBuffer()),
+            : mBuffer(buffer), mHeaderPolicy(mBuffer->getBuffer(), buffer->getBufferSize()),
               mDictRoot(mBuffer->getBuffer() + mHeaderPolicy.getSize()),
               mBigramListPolicy(mDictRoot), mShortcutListPolicy(mDictRoot) {}
 
@@ -58,11 +58,11 @@
 
     int getProbability(const int unigramProbability, const int bigramProbability) const;
 
-    int getUnigramProbabilityOfPtNode(const int nodePos) const;
+    int getUnigramProbabilityOfPtNode(const int ptNodePos) const;
 
-    int getShortcutPositionOfNode(const int nodePos) const;
+    int getShortcutPositionOfPtNode(const int ptNodePos) const;
 
-    int getBigramsPositionOfNode(const int nodePos) const;
+    int getBigramsPositionOfPtNode(const int ptNodePos) const;
 
     const DictionaryHeaderStructurePolicy *getHeaderStructurePolicy() const {
         return &mHeaderPolicy;
@@ -121,7 +121,7 @@
     const BigramListPolicy mBigramListPolicy;
     const ShortcutListPolicy mShortcutListPolicy;
 
-    int createAndGetLeavingChildNode(const DicNode *const dicNode, const int nodePos,
+    int createAndGetLeavingChildNode(const DicNode *const dicNode, const int ptNodePos,
             DicNodeVector *const childDicNodes) const;
 };
 } // namespace latinime
diff --git a/native/jni/src/suggest/policyimpl/dictionary/shortcut/dynamic_shortcut_list_policy.h b/native/jni/src/suggest/policyimpl/dictionary/shortcut/dynamic_shortcut_list_policy.h
index 1803c09..bd3211f 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/shortcut/dynamic_shortcut_list_policy.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/shortcut/dynamic_shortcut_list_policy.h
@@ -31,7 +31,7 @@
  */
 class DynamicShortcutListPolicy : public DictionaryShortcutsStructurePolicy {
  public:
-    explicit DynamicShortcutListPolicy(BufferWithExtendableBuffer *const buffer)
+    explicit DynamicShortcutListPolicy(const BufferWithExtendableBuffer *const buffer)
             : mBuffer(buffer) {}
 
     ~DynamicShortcutListPolicy() {}
@@ -82,18 +82,20 @@
         }
     }
 
-    // Copy shortcuts from the shortcut list that starts at fromPos to toPos and advance these
-    // positions after the shortcut lists. This returns whether the copy was succeeded or not.
-    bool copyAllShortcutsAndReturnIfSucceededOrNot(int *const fromPos, int *const toPos) {
+    // Copy shortcuts from the shortcut list that starts at fromPos in mBuffer to toPos in
+    // bufferToWrite and advance these positions after the shortcut lists. This returns whether
+    // the copy was succeeded or not.
+    bool copyAllShortcutsAndReturnIfSucceededOrNot(BufferWithExtendableBuffer *const bufferToWrite,
+            int *const fromPos, int *const toPos) const {
         const bool usesAdditionalBuffer = mBuffer->isInAdditionalBuffer(*fromPos);
-        const uint8_t *const buffer = mBuffer->getBuffer(usesAdditionalBuffer);
         if (usesAdditionalBuffer) {
             *fromPos -= mBuffer->getOriginalBufferSize();
         }
         const int shortcutListSize = ShortcutListReadingUtils
-                ::getShortcutListSizeAndForwardPointer(buffer, fromPos);
+                ::getShortcutListSizeAndForwardPointer(mBuffer->getBuffer(usesAdditionalBuffer),
+                        fromPos);
         // Copy shortcut list size.
-        if (!mBuffer->writeUintAndAdvancePosition(
+        if (!bufferToWrite->writeUintAndAdvancePosition(
                 shortcutListSize + ShortcutListReadingUtils::getShortcutListSizeFieldSize(),
                 ShortcutListReadingUtils::getShortcutListSizeFieldSize(), toPos)) {
             return false;
@@ -102,7 +104,7 @@
         for (int i = 0; i < shortcutListSize; ++i) {
             const uint8_t data = ByteArrayUtils::readUint8AndAdvancePosition(
                     mBuffer->getBuffer(usesAdditionalBuffer), fromPos);
-            if (!mBuffer->writeUintAndAdvancePosition(data, 1 /* size */, toPos)) {
+            if (!bufferToWrite->writeUintAndAdvancePosition(data, 1 /* size */, toPos)) {
                 return false;
             }
         }
@@ -115,7 +117,7 @@
  private:
     DISALLOW_IMPLICIT_CONSTRUCTORS(DynamicShortcutListPolicy);
 
-    BufferWithExtendableBuffer *const mBuffer;
+    const BufferWithExtendableBuffer *const mBuffer;
 };
 } // namespace latinime
 #endif // LATINIME_DYNAMIC_SHORTCUT_LIST_POLICY_H
diff --git a/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
index 0fed275..f692882 100644
--- 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
@@ -18,9 +18,10 @@
 
 namespace latinime {
 
-const size_t BufferWithExtendableBuffer::INITIAL_ADDITIONAL_BUFFER_SIZE = 16 * 1024;
 const size_t BufferWithExtendableBuffer::MAX_ADDITIONAL_BUFFER_SIZE = 1024 * 1024;
-const size_t BufferWithExtendableBuffer::EXTEND_ADDITIONAL_BUFFER_SIZE_STEP = 16 * 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) {
@@ -64,6 +65,16 @@
     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();
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
index c6a4841..17d2e39 100644
--- 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
@@ -32,9 +32,11 @@
 // raw pointer but provides several methods that handle boundary checking for writing data.
 class BufferWithExtendableBuffer {
  public:
-    BufferWithExtendableBuffer(uint8_t *const originalBuffer, const int originalBufferSize)
+    BufferWithExtendableBuffer(uint8_t *const originalBuffer, const int originalBufferSize,
+            const int maxAdditionalBufferSize = MAX_ADDITIONAL_BUFFER_SIZE)
             : mOriginalBuffer(originalBuffer), mOriginalBufferSize(originalBufferSize),
-              mAdditionalBuffer(INITIAL_ADDITIONAL_BUFFER_SIZE), mUsedAdditionalBufferSize(0) {}
+              mAdditionalBuffer(EXTEND_ADDITIONAL_BUFFER_SIZE_STEP), mUsedAdditionalBufferSize(0),
+              mMaxAdditionalBufferSize(maxAdditionalBufferSize) {}
 
     AK_FORCE_INLINE int getTailPosition() const {
         return mOriginalBufferSize + mUsedAdditionalBufferSize;
@@ -61,6 +63,11 @@
         return mOriginalBufferSize;
     }
 
+    AK_FORCE_INLINE bool isNearSizeLimit() const {
+        return mAdditionalBuffer.size() >= ((mMaxAdditionalBufferSize
+                * NEAR_BUFFER_LIMIT_THRESHOLD_PERCENTILE) / 100);
+    }
+
     /**
      * For writing.
      *
@@ -75,28 +82,22 @@
  private:
     DISALLOW_COPY_AND_ASSIGN(BufferWithExtendableBuffer);
 
-    static const size_t INITIAL_ADDITIONAL_BUFFER_SIZE;
     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.
-    AK_FORCE_INLINE bool extendBuffer() {
-        if (mAdditionalBuffer.size() + EXTEND_ADDITIONAL_BUFFER_SIZE_STEP
-                > MAX_ADDITIONAL_BUFFER_SIZE) {
-            return false;
-        }
-        mAdditionalBuffer.resize(mAdditionalBuffer.size() + EXTEND_ADDITIONAL_BUFFER_SIZE_STEP);
-        return true;
-    }
+    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.
-    AK_FORCE_INLINE bool checkAndPrepareWriting(const int pos, const int size);
+    bool checkAndPrepareWriting(const int pos, const int size);
 };
 }
 #endif /* LATINIME_BUFFER_WITH_EXTENDABLE_BUFFER_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
index 3796c7b..1d77d5c 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/utils/format_utils.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/utils/format_utils.cpp
@@ -20,20 +20,10 @@
 
 namespace latinime {
 
-/**
- * Dictionary size
- */
-// Any file smaller than this is not a dictionary.
-const int FormatUtils::DICTIONARY_MINIMUM_SIZE = 4;
+const uint32_t FormatUtils::MAGIC_NUMBER = 0x9BC13AFE;
 
-/**
- * Format versions
- */
-// 32 bit magic number is stored at the beginning of the dictionary header to reject unsupported
-// or obsolete dictionary formats.
-const uint32_t FormatUtils::HEADER_VERSION_2_MAGIC_NUMBER = 0x9BC13AFE;
-// Magic number (4 bytes), version (2 bytes), options (2 bytes), header size (4 bytes) = 12
-const int FormatUtils::HEADER_VERSION_2_MINIMUM_SIZE = 12;
+// 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) {
@@ -45,16 +35,10 @@
     }
     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;
-            }
+        case MAGIC_NUMBER:
             // Version 2 header is as follows:
             // Magic number (4 bytes) 0x9B 0xC1 0x3A 0xFE
-            // Version number (2 bytes)
+            // Dictionary format version number (2 bytes)
             // Options (2 bytes)
             // Header size (4 bytes) : integer, big endian
             if (ByteArrayUtils::readUint16(dict, 4) == 2) {
diff --git a/native/jni/src/suggest/policyimpl/dictionary/utils/format_utils.h b/native/jni/src/suggest/policyimpl/dictionary/utils/format_utils.h
index f843215..79ed0de 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/utils/format_utils.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/utils/format_utils.h
@@ -34,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(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_FORMAT_UTILS_H */
diff --git a/tests/src/com/android/inputmethod/latin/BinaryDictionaryTests.java b/tests/src/com/android/inputmethod/latin/BinaryDictionaryTests.java
index 4d231cd..96a2217 100644
--- a/tests/src/com/android/inputmethod/latin/BinaryDictionaryTests.java
+++ b/tests/src/com/android/inputmethod/latin/BinaryDictionaryTests.java
@@ -18,6 +18,7 @@
 
 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;
@@ -151,7 +152,7 @@
         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);
+            probabilityMap.put(word, random.nextInt(0xFF));
         }
         for (String word : probabilityMap.keySet()) {
             binaryDictionary.addUnigramWord(word, probabilityMap.get(word));
@@ -163,8 +164,6 @@
     }
 
     public void testAddBigramWords() {
-        // TODO: Add a test to check the frequency of the bigram score which uses current value
-        // calculated in the native code
         File dictFile = null;
         try {
             dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary");
@@ -179,6 +178,7 @@
 
         final int unigramProbability = 100;
         final int bigramProbability = 10;
+        final int updatedBigramProbability = 15;
         binaryDictionary.addUnigramWord("aaa", unigramProbability);
         binaryDictionary.addUnigramWord("abb", unigramProbability);
         binaryDictionary.addUnigramWord("bcc", unigramProbability);
@@ -187,21 +187,49 @@
         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() {
-        // TODO: Add a test to check the frequency of the bigram score which uses current value
-        // calculated in the native code
         final int wordCount = 100;
         final int bigramCount = 1000;
         final int codePointSetSize = 50;
@@ -222,29 +250,38 @@
         // 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 unigramProbability = 100;
-        final int bigramProbability = 10;
+        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 boolean[][] bigramRelations = new boolean[wordCount][wordCount];
+        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);
-
-            bigramRelations[word0Index][word1Index] = true;
+            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(bigramRelations[i][j],
-                        binaryDictionary.isValidBigram(words.get(i), words.get(j)));
+                assertEquals(probabilities[i][j],
+                        binaryDictionary.getBigramProbability(words.get(i), words.get(j)));
             }
         }
 
@@ -263,7 +300,6 @@
         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);
@@ -299,4 +335,319 @@
 
         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/InputLogicTests.java b/tests/src/com/android/inputmethod/latin/InputLogicTests.java
index fe92be6..cc2569f 100644
--- a/tests/src/com/android/inputmethod/latin/InputLogicTests.java
+++ b/tests/src/com/android/inputmethod/latin/InputLogicTests.java
@@ -134,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.";
diff --git a/tests/src/com/android/inputmethod/latin/SuggestedWordsTests.java b/tests/src/com/android/inputmethod/latin/SuggestedWordsTests.java
index 4cf8333..a594baf 100644
--- a/tests/src/com/android/inputmethod/latin/SuggestedWordsTests.java
+++ b/tests/src/com/android/inputmethod/latin/SuggestedWordsTests.java
@@ -64,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
index 8bc0095..a4d9426 100644
--- a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderEncoderTests.java
+++ b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderEncoderTests.java
@@ -25,6 +25,7 @@
 import com.android.inputmethod.latin.makedict.BinaryDictDecoderUtils.CharEncoding;
 import com.android.inputmethod.latin.makedict.BinaryDictDecoderUtils.DictBuffer;
 import com.android.inputmethod.latin.makedict.FormatSpec.FileHeader;
+import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions;
 import com.android.inputmethod.latin.makedict.FusionDictionary.DictionaryOptions;
 import com.android.inputmethod.latin.makedict.FusionDictionary.PtNode;
 import com.android.inputmethod.latin.makedict.FusionDictionary.PtNodeArray;
@@ -75,6 +76,10 @@
             new FormatSpec.FormatOptions(3, false /* supportsDynamicUpdate */);
     private static final FormatSpec.FormatOptions VERSION3_WITH_DYNAMIC_UPDATE =
             new FormatSpec.FormatOptions(3, true /* supportsDynamicUpdate */);
+    private static final FormatSpec.FormatOptions VERSION4_WITHOUT_DYNAMIC_UPDATE =
+            new FormatSpec.FormatOptions(4, false /* supportsDynamicUpdate */);
+    private static final FormatSpec.FormatOptions VERSION4_WITH_DYNAMIC_UPDATE =
+            new FormatSpec.FormatOptions(4, true /* supportsDynamicUpdate */);
 
     private static final String TEST_DICT_FILE_EXTENSION = ".testDict";
 
@@ -114,6 +119,17 @@
         }
     }
 
+    private DictEncoder getDictEncoder(final File file, final FormatOptions formatOptions) {
+        if (formatOptions.mVersion == FormatSpec.VERSION4) {
+            return new Ver4DictEncoder(getContext().getCacheDir());
+        } else if (formatOptions.mVersion == 3 || formatOptions.mVersion == 2) {
+            return new Ver3DictEncoder(file);
+        } else {
+            throw new RuntimeException("The format option has a wrong version : "
+                    + formatOptions.mVersion);
+        }
+    }
+
     private void generateWords(final int number, final Random random, final int[] codePointSet) {
         final Set<String> wordSet = CollectionUtils.newHashSet();
         while (wordSet.size() < number) {
@@ -165,7 +181,7 @@
         long now = -1, diff = -1;
 
         try {
-            final DictEncoder dictEncoder = new Ver3DictEncoder(file);
+            final DictEncoder dictEncoder = getDictEncoder(file, formatOptions);
 
             now = System.currentTimeMillis();
             // If you need to dump the dict to a textual file, uncomment the line below and the
@@ -246,16 +262,28 @@
         return file;
     }
 
+    private DictDecoder getDictDecoder(final File file, final int bufferType,
+            final FormatOptions formatOptions, final DictionaryOptions dictOptions) {
+        if (formatOptions.mVersion == FormatSpec.VERSION4) {
+            final FileHeader header = new FileHeader(0, dictOptions, formatOptions);
+            return FormatSpec.getDictDecoder(new File(getContext().getCacheDir(),
+                    header.getId() + "." + header.getVersion()), bufferType);
+        } else {
+            return FormatSpec.getDictDecoder(file, bufferType);
+        }
+    }
     // Tests for readDictionaryBinary and writeDictionaryBinary
 
     private long timeReadingAndCheckDict(final File file, final List<String> words,
             final SparseArray<List<Integer>> bigrams,
-            final HashMap<String, List<String>> shortcutMap, final int bufferType) {
+            final HashMap<String, List<String>> shortcutMap, final int bufferType,
+            final FormatOptions formatOptions, final DictionaryOptions dictOptions) {
         long now, diff = -1;
 
         FusionDictionary dict = null;
         try {
-            final DictDecoder dictDecoder = FormatSpec.getDictDecoder(file, bufferType);
+            final DictDecoder dictDecoder = getDictDecoder(file, bufferType, formatOptions,
+                    dictOptions);
             now = System.currentTimeMillis();
             dict = dictDecoder.readDictionaryBinary(null, false /* deleteDictIfBroken */);
             diff  = System.currentTimeMillis() - now;
@@ -286,7 +314,8 @@
         checkDictionary(dict, words, bigrams, shortcuts);
 
         final long write = timeWritingDictToFile(file, dict, formatOptions);
-        final long read = timeReadingAndCheckDict(file, words, bigrams, shortcuts, bufferType);
+        final long read = timeReadingAndCheckDict(file, words, bigrams, shortcuts, bufferType,
+                formatOptions, dict.mOptions);
 
         return "PROF: read=" + read + "ms, write=" + write + "ms :" + message
                 + " : " + outputOptions(bufferType, formatOptions);
@@ -330,6 +359,8 @@
         runReadAndWriteTests(results, USE_BYTE_BUFFER, VERSION2);
         runReadAndWriteTests(results, USE_BYTE_BUFFER, VERSION3_WITHOUT_DYNAMIC_UPDATE);
         runReadAndWriteTests(results, USE_BYTE_BUFFER, VERSION3_WITH_DYNAMIC_UPDATE);
+        runReadAndWriteTests(results, USE_BYTE_BUFFER, VERSION4_WITHOUT_DYNAMIC_UPDATE);
+        runReadAndWriteTests(results, USE_BYTE_BUFFER, VERSION4_WITH_DYNAMIC_UPDATE);
 
         for (final String result : results) {
             Log.d(TAG, result);
@@ -342,6 +373,8 @@
         runReadAndWriteTests(results, USE_BYTE_ARRAY, VERSION2);
         runReadAndWriteTests(results, USE_BYTE_ARRAY, VERSION3_WITHOUT_DYNAMIC_UPDATE);
         runReadAndWriteTests(results, USE_BYTE_ARRAY, VERSION3_WITH_DYNAMIC_UPDATE);
+        runReadAndWriteTests(results, USE_BYTE_ARRAY, VERSION4_WITHOUT_DYNAMIC_UPDATE);
+        runReadAndWriteTests(results, USE_BYTE_ARRAY, VERSION4_WITH_DYNAMIC_UPDATE);
 
         for (final String result : results) {
             Log.d(TAG, result);
@@ -397,7 +430,8 @@
     }
 
     private long timeAndCheckReadUnigramsAndBigramsBinary(final File file, final List<String> words,
-            final SparseArray<List<Integer>> bigrams, final int bufferType) {
+            final SparseArray<List<Integer>> bigrams, final int bufferType,
+            final FormatOptions formatOptions, final DictionaryOptions dictOptions) {
         FileInputStream inStream = null;
 
         final TreeMap<Integer, String> resultWords = CollectionUtils.newTreeMap();
@@ -407,7 +441,8 @@
 
         long now = -1, diff = -1;
         try {
-            final DictDecoder dictDecoder = FormatSpec.getDictDecoder(file, bufferType);
+            final DictDecoder dictDecoder = getDictDecoder(file, bufferType, formatOptions,
+                    dictOptions);
             now = System.currentTimeMillis();
             dictDecoder.readUnigramsAndBigramsBinary(resultWords, resultFreqs, resultBigrams);
             diff = System.currentTimeMillis() - now;
@@ -444,9 +479,10 @@
 
         timeWritingDictToFile(file, dict, formatOptions);
 
-        long wordMap = timeAndCheckReadUnigramsAndBigramsBinary(file, words, bigrams, bufferType);
+        long wordMap = timeAndCheckReadUnigramsAndBigramsBinary(file, words, bigrams, bufferType,
+                formatOptions, dict.mOptions);
         long fullReading = timeReadingAndCheckDict(file, words, bigrams, null /* shortcutMap */,
-                bufferType);
+                bufferType, formatOptions, dict.mOptions);
 
         return "readDictionaryBinary=" + fullReading + ", readUnigramsAndBigramsBinary=" + wordMap
                 + " : " + message + " : " + outputOptions(bufferType, formatOptions);
@@ -458,7 +494,7 @@
                 formatOptions, "unigram"));
         results.add(runReadUnigramsAndBigramsBinary(sWords, sChainBigrams, bufferType,
                 formatOptions, "chain"));
-        results.add(runReadUnigramsAndBigramsBinary(sWords, sChainBigrams, bufferType,
+        results.add(runReadUnigramsAndBigramsBinary(sWords, sStarBigrams, bufferType,
                 formatOptions, "star"));
     }
 
@@ -468,6 +504,8 @@
         runReadUnigramsAndBigramsTests(results, USE_BYTE_BUFFER, VERSION2);
         runReadUnigramsAndBigramsTests(results, USE_BYTE_BUFFER, VERSION3_WITHOUT_DYNAMIC_UPDATE);
         runReadUnigramsAndBigramsTests(results, USE_BYTE_BUFFER, VERSION3_WITH_DYNAMIC_UPDATE);
+        runReadUnigramsAndBigramsTests(results, USE_BYTE_BUFFER, VERSION4_WITHOUT_DYNAMIC_UPDATE);
+        runReadUnigramsAndBigramsTests(results, USE_BYTE_BUFFER, VERSION4_WITH_DYNAMIC_UPDATE);
 
         for (final String result : results) {
             Log.d(TAG, result);
@@ -480,6 +518,8 @@
         runReadUnigramsAndBigramsTests(results, USE_BYTE_ARRAY, VERSION2);
         runReadUnigramsAndBigramsTests(results, USE_BYTE_ARRAY, VERSION3_WITHOUT_DYNAMIC_UPDATE);
         runReadUnigramsAndBigramsTests(results, USE_BYTE_ARRAY, VERSION3_WITH_DYNAMIC_UPDATE);
+        runReadUnigramsAndBigramsTests(results, USE_BYTE_ARRAY, VERSION4_WITHOUT_DYNAMIC_UPDATE);
+        runReadUnigramsAndBigramsTests(results, USE_BYTE_ARRAY, VERSION4_WITH_DYNAMIC_UPDATE);
 
         for (final String result : results) {
             Log.d(TAG, result);
@@ -503,7 +543,7 @@
                 address, fileHeader.mFormatOptions).mWord;
     }
 
-    private long runGetTerminalPosition(final DictDecoder dictDecoder, final String word,
+    private long checkGetTerminalPosition(final DictDecoder dictDecoder, final String word,
             int index, boolean contained) {
         final int expectedFrequency = (UNIGRAM_FREQ + index) % 255;
         long diff = -1;
@@ -523,7 +563,9 @@
         return diff;
     }
 
-    public void testGetTerminalPosition() {
+    private void runGetTerminalPosition(final ArrayList<String> words,
+            final SparseArray<List<Integer>> bigrams, final int bufferType,
+            final FormatOptions formatOptions, final String message) {
         final String dictName = "testGetTerminalPosition";
         final String dictVersion = Long.toString(System.currentTimeMillis());
         final File file = setUpDictionaryFile(dictName, dictVersion);
@@ -531,16 +573,18 @@
         final FusionDictionary dict = new FusionDictionary(new PtNodeArray(),
                 getDictionaryOptions(dictName, dictVersion));
         addUnigrams(sWords.size(), dict, sWords, null /* shortcutMap */);
-        timeWritingDictToFile(file, dict, VERSION3_WITH_DYNAMIC_UPDATE);
+        addBigrams(dict, words, bigrams);
+        timeWritingDictToFile(file, dict, formatOptions);
 
-        final DictDecoder dictDecoder = FormatSpec.getDictDecoder(file, DictDecoder.USE_BYTEARRAY);
+        final DictDecoder dictDecoder = getDictDecoder(file, DictDecoder.USE_BYTEARRAY,
+                formatOptions, dict.mOptions);
         try {
             dictDecoder.openDictBuffer();
         } catch (IOException e) {
             // ignore
             Log.e(TAG, "IOException while opening the buffer", e);
         }
-        assertTrue("Can't get the buffer", dictDecoder.isOpenedDictBuffer());
+        assertTrue("Can't get the buffer", dictDecoder.isDictBufferOpen());
 
         try {
             // too long word
@@ -559,10 +603,11 @@
         // Test a word that is contained within the dictionary.
         long sum = 0;
         for (int i = 0; i < sWords.size(); ++i) {
-            final long time = runGetTerminalPosition(dictDecoder, sWords.get(i), i, true);
+            final long time = checkGetTerminalPosition(dictDecoder, sWords.get(i), i, true);
             sum += time == -1 ? 0 : time;
         }
-        Log.d(TAG, "per a search : " + (((double)sum) / sWords.size() / 1000000));
+        Log.d(TAG, "per search : " + (((double)sum) / sWords.size() / 1000000) + " : " + message
+                + " : " + outputOptions(bufferType, formatOptions));
 
         // Test a word that isn't contained within the dictionary.
         final Random random = new Random((int)System.currentTimeMillis());
@@ -571,7 +616,32 @@
         for (int i = 0; i < 1000; ++i) {
             final String word = CodePointUtils.generateWord(random, codePointSet);
             if (sWords.indexOf(word) != -1) continue;
-            runGetTerminalPosition(dictDecoder, word, i, false);
+            checkGetTerminalPosition(dictDecoder, word, i, false);
+        }
+    }
+
+    private void runGetTerminalPositionTests(final ArrayList<String> results, final int bufferType,
+            final FormatOptions formatOptions) {
+        runGetTerminalPosition(sWords, sEmptyBigrams, bufferType, formatOptions, "unigram");
+    }
+
+    public void testGetTerminalPosition() {
+        final ArrayList<String> results = CollectionUtils.newArrayList();
+
+        runGetTerminalPositionTests(results, USE_BYTE_ARRAY, VERSION2);
+        runGetTerminalPositionTests(results, USE_BYTE_ARRAY, VERSION3_WITHOUT_DYNAMIC_UPDATE);
+        runGetTerminalPositionTests(results, USE_BYTE_ARRAY, VERSION3_WITH_DYNAMIC_UPDATE);
+        runGetTerminalPositionTests(results, USE_BYTE_ARRAY, VERSION4_WITHOUT_DYNAMIC_UPDATE);
+        runGetTerminalPositionTests(results, USE_BYTE_ARRAY, VERSION4_WITH_DYNAMIC_UPDATE);
+
+        runGetTerminalPositionTests(results, USE_BYTE_BUFFER, VERSION2);
+        runGetTerminalPositionTests(results, USE_BYTE_BUFFER, VERSION3_WITHOUT_DYNAMIC_UPDATE);
+        runGetTerminalPositionTests(results, USE_BYTE_BUFFER, VERSION3_WITH_DYNAMIC_UPDATE);
+        runGetTerminalPositionTests(results, USE_BYTE_BUFFER, VERSION4_WITHOUT_DYNAMIC_UPDATE);
+        runGetTerminalPositionTests(results, USE_BYTE_BUFFER, VERSION4_WITH_DYNAMIC_UPDATE);
+
+        for (final String result : results) {
+            Log.d(TAG, result);
         }
     }
 
@@ -593,7 +663,7 @@
             // ignore
             Log.e(TAG, "IOException while opening the buffer", e);
         }
-        assertTrue("Can't get the buffer", dictDecoder.isOpenedDictBuffer());
+        assertTrue("Can't get the buffer", dictDecoder.isDictBufferOpen());
 
         try {
             MoreAsserts.assertNotEqual(FormatSpec.NOT_VALID_WORD,
diff --git a/tests/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryTests.java b/tests/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryTests.java
index bf44a14..d605cdb 100644
--- a/tests/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryTests.java
+++ b/tests/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryTests.java
@@ -100,7 +100,11 @@
                 Thread.sleep(TimeUnit.MILLISECONDS.convert(5L, TimeUnit.SECONDS));
             } catch (InterruptedException e) {
             }
-            for (int i = 0; i < 10 && i < numberOfWords; ++i) {
+            // 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));
@@ -202,4 +206,41 @@
             }
         }
     }
+
+    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/StringUtilsTests.java b/tests/src/com/android/inputmethod/latin/utils/StringUtilsTests.java
index c6fa943..eb9fb98 100644
--- a/tests/src/com/android/inputmethod/latin/utils/StringUtilsTests.java
+++ b/tests/src/com/android/inputmethod/latin/utils/StringUtilsTests.java
@@ -20,7 +20,14 @@
 
 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
@@ -268,4 +275,44 @@
         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/tools/dicttool/src/com/android/inputmethod/latin/dicttool/DictionaryMaker.java b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/DictionaryMaker.java
index 5302e97..5c7e8b4 100644
--- a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/DictionaryMaker.java
+++ b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/DictionaryMaker.java
@@ -23,8 +23,8 @@
 import com.android.inputmethod.latin.makedict.FusionDictionary;
 import com.android.inputmethod.latin.makedict.MakedictLog;
 import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
-import com.android.inputmethod.latin.makedict.Ver3DictDecoder;
 import com.android.inputmethod.latin.makedict.Ver3DictEncoder;
+import com.android.inputmethod.latin.makedict.Ver4DictEncoder;
 
 import java.io.BufferedWriter;
 import java.io.File;
@@ -45,9 +45,9 @@
 public class DictionaryMaker {
 
     static class Arguments {
-        private static final String OPTION_VERSION_1 = "-1";
         private static final String OPTION_VERSION_2 = "-2";
         private static final String OPTION_VERSION_3 = "-3";
+        private static final String OPTION_VERSION_4 = "-4";
         private static final String OPTION_INPUT_SOURCE = "-s";
         private static final String OPTION_INPUT_BIGRAM_XML = "-b";
         private static final String OPTION_INPUT_SHORTCUT_XML = "-c";
@@ -128,12 +128,12 @@
                     + "| [-s <combined format input]"
                     + "| [-s <binary input>] [-d <binary output>] [-x <xml output>] "
                     + " [-o <combined output>]"
-                    + "[-1] [-2] [-3]\n"
+                    + "[-2] [-3] [-4]\n"
                     + "\n"
                     + "  Converts a source dictionary file to one or several outputs.\n"
                     + "  Source can be an XML file, with an optional XML bigrams file, or a\n"
                     + "  binary dictionary file.\n"
-                    + "  Binary version 1 (Ice Cream Sandwich), 2 (Jelly Bean), 3, XML and\n"
+                    + "  Binary version 2 (Jelly Bean), 3, 4, XML and\n"
                     + "  combined format outputs are supported.";
         }
 
@@ -160,8 +160,8 @@
                         // Do nothing, this is the default
                     } else if (OPTION_VERSION_3.equals(arg)) {
                         outputBinaryFormatVersion = 3;
-                    } else if (OPTION_VERSION_1.equals(arg)) {
-                        outputBinaryFormatVersion = 1;
+                    } else if (OPTION_VERSION_4.equals(arg)) {
+                        outputBinaryFormatVersion = 4;
                     } else if (OPTION_HELP.equals(arg)) {
                         displayHelp();
                     } else {
@@ -357,7 +357,12 @@
             throws FileNotFoundException, IOException, UnsupportedFormatException {
         final File outputFile = new File(outputFilename);
         final FormatSpec.FormatOptions formatOptions = new FormatSpec.FormatOptions(version);
-        final DictEncoder dictEncoder = new Ver3DictEncoder(outputFile);
+        final DictEncoder dictEncoder;
+        if (version == 4) {
+            dictEncoder = new Ver4DictEncoder(outputFile);
+        } else {
+            dictEncoder = new Ver3DictEncoder(outputFile);
+        }
         dictEncoder.writeDictionary(dict, formatOptions);
     }
 
diff --git a/tools/make-keyboard-text/res/values-hy/donottranslate-more-keys.xml b/tools/make-keyboard-text/res/values-hy/donottranslate-more-keys.xml
index f6c6428..2f34128 100644
--- a/tools/make-keyboard-text/res/values-hy/donottranslate-more-keys.xml
+++ b/tools/make-keyboard-text/res/values-hy/donottranslate-more-keys.xml
@@ -18,7 +18,23 @@
 */
 -->
 <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 -->
-    <string name="more_keys_for_punctuation">"!fixedColumnOrder!4,&#x055E;,!,\\,,\?,:,;,\@"</string>
-    <string name="more_keys_for_tablet_period">&#x055E;,\?</string>
+    <!-- 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/make-keyboard-text/res/values-km/donottranslate-more-keys.xml b/tools/make-keyboard-text/res/values-km/donottranslate-more-keys.xml
new file mode 100644
index 0000000..c33831c
--- /dev/null
+++ b/tools/make-keyboard-text/res/values-km/donottranslate-more-keys.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2013, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Label for "switch to alphabetic" key.
+         U+1780: "ក" KHMER LETTER KA
+         U+1781: "ខ" KHMER LETTER KHA
+         U+1782: "គ" KHMER LETTER KO -->
+    <string name="label_to_alpha_key">&#x1780;&#x1781;&#x1782;</string>
+    <!-- U+17DB: "៛" KHMER CURRENCY SYMBOL RIEL -->
+    <string name="more_keys_for_currency_dollar">&#x17DB;,&#x00A2;,&#x00A3;,&#x20AC;,&#x00A5;,&#x20B1;</string>
+
+</resources>