merge in master-release history after reset to 305778b53a5e7c865cae4010e657d00bb9bf5075
diff --git a/java/res/color/emoji_tab_label_color_holo.xml b/java/res/color/emoji_tab_label_color_holo.xml
deleted file mode 100644
index 373e931..0000000
--- a/java/res/color/emoji_tab_label_color_holo.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2013, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item
-        android:state_focused="true"
-        android:color="@color/key_text_color_holo" />
-    <item
-        android:state_pressed="true"
-        android:color="@color/key_text_color_holo" />
-    <item
-        android:state_selected="true"
-        android:color="@color/key_text_color_holo" />
-    <item
-        android:color="@color/key_text_inactivated_color_holo" />
-</selector>
diff --git a/java/res/drawable-hdpi/emoji_category_tab_selected_holo_dark.9.png b/java/res/drawable-hdpi/emoji_category_tab_selected_holo_dark.9.png
deleted file mode 100644
index 84e63df..0000000
--- a/java/res/drawable-hdpi/emoji_category_tab_selected_holo_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-hdpi/emoji_category_tab_selected_ics.9.png b/java/res/drawable-hdpi/emoji_category_tab_selected_ics.9.png
new file mode 100644
index 0000000..9138cef
--- /dev/null
+++ b/java/res/drawable-hdpi/emoji_category_tab_selected_ics.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/emoji_category_tab_selected_klp.9.png b/java/res/drawable-hdpi/emoji_category_tab_selected_klp.9.png
new file mode 100644
index 0000000..345d05e
--- /dev/null
+++ b/java/res/drawable-hdpi/emoji_category_tab_selected_klp.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/emoji_category_tab_selected_lxx_dark.9.png b/java/res/drawable-hdpi/emoji_category_tab_selected_lxx_dark.9.png
new file mode 100644
index 0000000..ca981f4
--- /dev/null
+++ b/java/res/drawable-hdpi/emoji_category_tab_selected_lxx_dark.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/emoji_category_tab_unselected_lxx_dark.9.png b/java/res/drawable-hdpi/emoji_category_tab_unselected_lxx_dark.9.png
new file mode 100644
index 0000000..10b1dd2
--- /dev/null
+++ b/java/res/drawable-hdpi/emoji_category_tab_unselected_lxx_dark.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/ic_emoji_emoticons_activated_holo_dark.png b/java/res/drawable-hdpi/ic_emoji_emoticons_activated_holo_dark.png
new file mode 100644
index 0000000..1c937c9
--- /dev/null
+++ b/java/res/drawable-hdpi/ic_emoji_emoticons_activated_holo_dark.png
Binary files differ
diff --git a/java/res/drawable-hdpi/ic_emoji_emoticons_normal_holo_dark.png b/java/res/drawable-hdpi/ic_emoji_emoticons_normal_holo_dark.png
new file mode 100644
index 0000000..3508d24
--- /dev/null
+++ b/java/res/drawable-hdpi/ic_emoji_emoticons_normal_holo_dark.png
Binary files differ
diff --git a/java/res/drawable-hdpi/ic_emoji_recent_activated_holo_dark.png b/java/res/drawable-hdpi/ic_emoji_recents_activated_holo_dark.png
similarity index 100%
rename from java/res/drawable-hdpi/ic_emoji_recent_activated_holo_dark.png
rename to java/res/drawable-hdpi/ic_emoji_recents_activated_holo_dark.png
Binary files differ
diff --git a/java/res/drawable-hdpi/ic_emoji_recent_normal_holo_dark.png b/java/res/drawable-hdpi/ic_emoji_recents_normal_holo_dark.png
similarity index 100%
rename from java/res/drawable-hdpi/ic_emoji_recent_normal_holo_dark.png
rename to java/res/drawable-hdpi/ic_emoji_recents_normal_holo_dark.png
Binary files differ
diff --git a/java/res/drawable-hdpi/keyboard_key_feedback_background_lxx_dark.9.png b/java/res/drawable-hdpi/keyboard_key_feedback_background_lxx_dark.9.png
index 6a76ce6..9d6514b 100644
--- a/java/res/drawable-hdpi/keyboard_key_feedback_background_lxx_dark.9.png
+++ b/java/res/drawable-hdpi/keyboard_key_feedback_background_lxx_dark.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/keyboard_key_feedback_left_background_lxx_dark.9.png b/java/res/drawable-hdpi/keyboard_key_feedback_left_background_lxx_dark.9.png
deleted file mode 100644
index 752c758..0000000
--- a/java/res/drawable-hdpi/keyboard_key_feedback_left_background_lxx_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-hdpi/keyboard_key_feedback_left_more_background_lxx_dark.9.png b/java/res/drawable-hdpi/keyboard_key_feedback_left_more_background_lxx_dark.9.png
deleted file mode 100644
index a24519f..0000000
--- a/java/res/drawable-hdpi/keyboard_key_feedback_left_more_background_lxx_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-hdpi/keyboard_key_feedback_more_background_lxx_dark.9.png b/java/res/drawable-hdpi/keyboard_key_feedback_more_background_lxx_dark.9.png
index f3c2f6b..8ad54f6 100644
--- a/java/res/drawable-hdpi/keyboard_key_feedback_more_background_lxx_dark.9.png
+++ b/java/res/drawable-hdpi/keyboard_key_feedback_more_background_lxx_dark.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/keyboard_key_feedback_right_background_lxx_dark.9.png b/java/res/drawable-hdpi/keyboard_key_feedback_right_background_lxx_dark.9.png
deleted file mode 100644
index 0a9e467..0000000
--- a/java/res/drawable-hdpi/keyboard_key_feedback_right_background_lxx_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-hdpi/keyboard_key_feedback_right_more_background_lxx_dark.9.png b/java/res/drawable-hdpi/keyboard_key_feedback_right_more_background_lxx_dark.9.png
deleted file mode 100644
index 6a58ed3..0000000
--- a/java/res/drawable-hdpi/keyboard_key_feedback_right_more_background_lxx_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_shift_locked_lxx_dark.png b/java/res/drawable-hdpi/sym_keyboard_shift_locked_lxx_dark.png
new file mode 100644
index 0000000..108eb74
--- /dev/null
+++ b/java/res/drawable-hdpi/sym_keyboard_shift_locked_lxx_dark.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_shift_lxx_dark.png b/java/res/drawable-hdpi/sym_keyboard_shift_lxx_dark.png
new file mode 100644
index 0000000..141f5ad
--- /dev/null
+++ b/java/res/drawable-hdpi/sym_keyboard_shift_lxx_dark.png
Binary files differ
diff --git a/java/res/drawable-mdpi/emoji_category_tab_selected_holo_dark.9.png b/java/res/drawable-mdpi/emoji_category_tab_selected_holo_dark.9.png
deleted file mode 100644
index 4b00f35..0000000
--- a/java/res/drawable-mdpi/emoji_category_tab_selected_holo_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-mdpi/emoji_category_tab_selected_ics.9.png b/java/res/drawable-mdpi/emoji_category_tab_selected_ics.9.png
new file mode 100644
index 0000000..1731b46
--- /dev/null
+++ b/java/res/drawable-mdpi/emoji_category_tab_selected_ics.9.png
Binary files differ
diff --git a/java/res/drawable-mdpi/emoji_category_tab_selected_klp.9.png b/java/res/drawable-mdpi/emoji_category_tab_selected_klp.9.png
new file mode 100644
index 0000000..6354b99
--- /dev/null
+++ b/java/res/drawable-mdpi/emoji_category_tab_selected_klp.9.png
Binary files differ
diff --git a/java/res/drawable-mdpi/emoji_category_tab_selected_lxx_dark.9.png b/java/res/drawable-mdpi/emoji_category_tab_selected_lxx_dark.9.png
new file mode 100644
index 0000000..a08735c
--- /dev/null
+++ b/java/res/drawable-mdpi/emoji_category_tab_selected_lxx_dark.9.png
Binary files differ
diff --git a/java/res/drawable-mdpi/emoji_category_tab_unselected_lxx_dark.9.png b/java/res/drawable-mdpi/emoji_category_tab_unselected_lxx_dark.9.png
new file mode 100644
index 0000000..4cf8f5c
--- /dev/null
+++ b/java/res/drawable-mdpi/emoji_category_tab_unselected_lxx_dark.9.png
Binary files differ
diff --git a/java/res/drawable-mdpi/ic_emoji_emoticons_activated_holo_dark.png b/java/res/drawable-mdpi/ic_emoji_emoticons_activated_holo_dark.png
new file mode 100644
index 0000000..c7394e1
--- /dev/null
+++ b/java/res/drawable-mdpi/ic_emoji_emoticons_activated_holo_dark.png
Binary files differ
diff --git a/java/res/drawable-mdpi/ic_emoji_emoticons_normal_holo_dark.png b/java/res/drawable-mdpi/ic_emoji_emoticons_normal_holo_dark.png
new file mode 100644
index 0000000..eb4dab4
--- /dev/null
+++ b/java/res/drawable-mdpi/ic_emoji_emoticons_normal_holo_dark.png
Binary files differ
diff --git a/java/res/drawable-mdpi/ic_emoji_recent_activated_holo_dark.png b/java/res/drawable-mdpi/ic_emoji_recents_activated_holo_dark.png
similarity index 100%
rename from java/res/drawable-mdpi/ic_emoji_recent_activated_holo_dark.png
rename to java/res/drawable-mdpi/ic_emoji_recents_activated_holo_dark.png
Binary files differ
diff --git a/java/res/drawable-mdpi/ic_emoji_recent_normal_holo_dark.png b/java/res/drawable-mdpi/ic_emoji_recents_normal_holo_dark.png
similarity index 100%
rename from java/res/drawable-mdpi/ic_emoji_recent_normal_holo_dark.png
rename to java/res/drawable-mdpi/ic_emoji_recents_normal_holo_dark.png
Binary files differ
diff --git a/java/res/drawable-mdpi/keyboard_key_feedback_background_lxx_dark.9.png b/java/res/drawable-mdpi/keyboard_key_feedback_background_lxx_dark.9.png
index 0173d09..d320092 100644
--- a/java/res/drawable-mdpi/keyboard_key_feedback_background_lxx_dark.9.png
+++ b/java/res/drawable-mdpi/keyboard_key_feedback_background_lxx_dark.9.png
Binary files differ
diff --git a/java/res/drawable-mdpi/keyboard_key_feedback_left_background_lxx_dark.9.png b/java/res/drawable-mdpi/keyboard_key_feedback_left_background_lxx_dark.9.png
deleted file mode 100644
index 6d89549..0000000
--- a/java/res/drawable-mdpi/keyboard_key_feedback_left_background_lxx_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-mdpi/keyboard_key_feedback_left_more_background_lxx_dark.9.png b/java/res/drawable-mdpi/keyboard_key_feedback_left_more_background_lxx_dark.9.png
deleted file mode 100644
index a459d42..0000000
--- a/java/res/drawable-mdpi/keyboard_key_feedback_left_more_background_lxx_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-mdpi/keyboard_key_feedback_more_background_lxx_dark.9.png b/java/res/drawable-mdpi/keyboard_key_feedback_more_background_lxx_dark.9.png
index 5c00e7e..60a3633 100644
--- a/java/res/drawable-mdpi/keyboard_key_feedback_more_background_lxx_dark.9.png
+++ b/java/res/drawable-mdpi/keyboard_key_feedback_more_background_lxx_dark.9.png
Binary files differ
diff --git a/java/res/drawable-mdpi/keyboard_key_feedback_right_background_lxx_dark.9.png b/java/res/drawable-mdpi/keyboard_key_feedback_right_background_lxx_dark.9.png
deleted file mode 100644
index f85dd03..0000000
--- a/java/res/drawable-mdpi/keyboard_key_feedback_right_background_lxx_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-mdpi/keyboard_key_feedback_right_more_background_lxx_dark.9.png b/java/res/drawable-mdpi/keyboard_key_feedback_right_more_background_lxx_dark.9.png
deleted file mode 100644
index 83d72a2..0000000
--- a/java/res/drawable-mdpi/keyboard_key_feedback_right_more_background_lxx_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_keyboard_shift_locked_lxx_dark.png b/java/res/drawable-mdpi/sym_keyboard_shift_locked_lxx_dark.png
new file mode 100644
index 0000000..9c89031
--- /dev/null
+++ b/java/res/drawable-mdpi/sym_keyboard_shift_locked_lxx_dark.png
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_keyboard_shift_lxx_dark.png b/java/res/drawable-mdpi/sym_keyboard_shift_lxx_dark.png
new file mode 100644
index 0000000..926c363
--- /dev/null
+++ b/java/res/drawable-mdpi/sym_keyboard_shift_lxx_dark.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/emoji_category_tab_selected_holo_dark.9.png b/java/res/drawable-xhdpi/emoji_category_tab_selected_holo_dark.9.png
deleted file mode 100644
index 95e5f43..0000000
--- a/java/res/drawable-xhdpi/emoji_category_tab_selected_holo_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xhdpi/emoji_category_tab_selected_ics.9.png b/java/res/drawable-xhdpi/emoji_category_tab_selected_ics.9.png
new file mode 100644
index 0000000..11bc966
--- /dev/null
+++ b/java/res/drawable-xhdpi/emoji_category_tab_selected_ics.9.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/emoji_category_tab_selected_klp.9.png b/java/res/drawable-xhdpi/emoji_category_tab_selected_klp.9.png
new file mode 100644
index 0000000..5e8549b
--- /dev/null
+++ b/java/res/drawable-xhdpi/emoji_category_tab_selected_klp.9.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/emoji_category_tab_selected_lxx_dark.png b/java/res/drawable-xhdpi/emoji_category_tab_selected_lxx_dark.png
new file mode 100644
index 0000000..65a54b8
--- /dev/null
+++ b/java/res/drawable-xhdpi/emoji_category_tab_selected_lxx_dark.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/emoji_category_tab_unselected_lxx_dark.9.png b/java/res/drawable-xhdpi/emoji_category_tab_unselected_lxx_dark.9.png
new file mode 100644
index 0000000..666b354
--- /dev/null
+++ b/java/res/drawable-xhdpi/emoji_category_tab_unselected_lxx_dark.9.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/ic_emoji_emoticons_activated_holo_dark.png b/java/res/drawable-xhdpi/ic_emoji_emoticons_activated_holo_dark.png
new file mode 100644
index 0000000..997c9b7
--- /dev/null
+++ b/java/res/drawable-xhdpi/ic_emoji_emoticons_activated_holo_dark.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/ic_emoji_emoticons_normal_holo_dark.png b/java/res/drawable-xhdpi/ic_emoji_emoticons_normal_holo_dark.png
new file mode 100644
index 0000000..23a519c
--- /dev/null
+++ b/java/res/drawable-xhdpi/ic_emoji_emoticons_normal_holo_dark.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/ic_emoji_recent_activated_holo_dark.png b/java/res/drawable-xhdpi/ic_emoji_recents_activated_holo_dark.png
similarity index 100%
rename from java/res/drawable-xhdpi/ic_emoji_recent_activated_holo_dark.png
rename to java/res/drawable-xhdpi/ic_emoji_recents_activated_holo_dark.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/ic_emoji_recent_normal_holo_dark.png b/java/res/drawable-xhdpi/ic_emoji_recents_normal_holo_dark.png
similarity index 100%
rename from java/res/drawable-xhdpi/ic_emoji_recent_normal_holo_dark.png
rename to java/res/drawable-xhdpi/ic_emoji_recents_normal_holo_dark.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/keyboard_key_feedback_background_lxx_dark.9.png b/java/res/drawable-xhdpi/keyboard_key_feedback_background_lxx_dark.9.png
index fed1da7..d40afeb 100644
--- a/java/res/drawable-xhdpi/keyboard_key_feedback_background_lxx_dark.9.png
+++ b/java/res/drawable-xhdpi/keyboard_key_feedback_background_lxx_dark.9.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/keyboard_key_feedback_left_background_lxx_dark.9.png b/java/res/drawable-xhdpi/keyboard_key_feedback_left_background_lxx_dark.9.png
deleted file mode 100644
index ca90d52..0000000
--- a/java/res/drawable-xhdpi/keyboard_key_feedback_left_background_lxx_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xhdpi/keyboard_key_feedback_left_more_background_lxx_dark.9.png b/java/res/drawable-xhdpi/keyboard_key_feedback_left_more_background_lxx_dark.9.png
deleted file mode 100644
index ad277d6..0000000
--- a/java/res/drawable-xhdpi/keyboard_key_feedback_left_more_background_lxx_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xhdpi/keyboard_key_feedback_more_background_lxx_dark.9.png b/java/res/drawable-xhdpi/keyboard_key_feedback_more_background_lxx_dark.9.png
index 3c5f89f..d1a2f75 100644
--- a/java/res/drawable-xhdpi/keyboard_key_feedback_more_background_lxx_dark.9.png
+++ b/java/res/drawable-xhdpi/keyboard_key_feedback_more_background_lxx_dark.9.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/keyboard_key_feedback_right_background_lxx_dark.9.png b/java/res/drawable-xhdpi/keyboard_key_feedback_right_background_lxx_dark.9.png
deleted file mode 100644
index 6c49824..0000000
--- a/java/res/drawable-xhdpi/keyboard_key_feedback_right_background_lxx_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xhdpi/keyboard_key_feedback_right_more_background_lxx_dark.9.png b/java/res/drawable-xhdpi/keyboard_key_feedback_right_more_background_lxx_dark.9.png
deleted file mode 100644
index 2cd7b02..0000000
--- a/java/res/drawable-xhdpi/keyboard_key_feedback_right_more_background_lxx_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xhdpi/sym_keyboard_shift_locked_lxx_dark.png b/java/res/drawable-xhdpi/sym_keyboard_shift_locked_lxx_dark.png
new file mode 100644
index 0000000..6ee55e8
--- /dev/null
+++ b/java/res/drawable-xhdpi/sym_keyboard_shift_locked_lxx_dark.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/sym_keyboard_shift_lxx_dark.png b/java/res/drawable-xhdpi/sym_keyboard_shift_lxx_dark.png
new file mode 100644
index 0000000..a5c5093
--- /dev/null
+++ b/java/res/drawable-xhdpi/sym_keyboard_shift_lxx_dark.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/emoji_category_tab_selected_holo_dark.9.png b/java/res/drawable-xxhdpi/emoji_category_tab_selected_holo_dark.9.png
deleted file mode 100644
index e5efc58..0000000
--- a/java/res/drawable-xxhdpi/emoji_category_tab_selected_holo_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xxhdpi/emoji_category_tab_selected_ics.9.png b/java/res/drawable-xxhdpi/emoji_category_tab_selected_ics.9.png
new file mode 100644
index 0000000..b13ee2b
--- /dev/null
+++ b/java/res/drawable-xxhdpi/emoji_category_tab_selected_ics.9.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/emoji_category_tab_selected_klp.9.png b/java/res/drawable-xxhdpi/emoji_category_tab_selected_klp.9.png
new file mode 100644
index 0000000..c81e651
--- /dev/null
+++ b/java/res/drawable-xxhdpi/emoji_category_tab_selected_klp.9.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/emoji_category_tab_selected_lxx_dark.9.png b/java/res/drawable-xxhdpi/emoji_category_tab_selected_lxx_dark.9.png
new file mode 100644
index 0000000..084554f
--- /dev/null
+++ b/java/res/drawable-xxhdpi/emoji_category_tab_selected_lxx_dark.9.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/emoji_category_tab_unselected_lxx_dark.9.png b/java/res/drawable-xxhdpi/emoji_category_tab_unselected_lxx_dark.9.png
new file mode 100644
index 0000000..62547a9
--- /dev/null
+++ b/java/res/drawable-xxhdpi/emoji_category_tab_unselected_lxx_dark.9.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/ic_emoji_emoticons_activated_holo_dark.png b/java/res/drawable-xxhdpi/ic_emoji_emoticons_activated_holo_dark.png
new file mode 100644
index 0000000..a217269
--- /dev/null
+++ b/java/res/drawable-xxhdpi/ic_emoji_emoticons_activated_holo_dark.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/ic_emoji_emoticons_normal_holo_dark.png b/java/res/drawable-xxhdpi/ic_emoji_emoticons_normal_holo_dark.png
new file mode 100644
index 0000000..dfa43cd
--- /dev/null
+++ b/java/res/drawable-xxhdpi/ic_emoji_emoticons_normal_holo_dark.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/ic_emoji_recent_activated_holo_dark.png b/java/res/drawable-xxhdpi/ic_emoji_recents_activated_holo_dark.png
similarity index 100%
rename from java/res/drawable-xxhdpi/ic_emoji_recent_activated_holo_dark.png
rename to java/res/drawable-xxhdpi/ic_emoji_recents_activated_holo_dark.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/ic_emoji_recent_normal_holo_dark.png b/java/res/drawable-xxhdpi/ic_emoji_recents_normal_holo_dark.png
similarity index 100%
rename from java/res/drawable-xxhdpi/ic_emoji_recent_normal_holo_dark.png
rename to java/res/drawable-xxhdpi/ic_emoji_recents_normal_holo_dark.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/keyboard_key_feedback_background_lxx_dark.9.png b/java/res/drawable-xxhdpi/keyboard_key_feedback_background_lxx_dark.9.png
index 1f10e92..f781491 100644
--- a/java/res/drawable-xxhdpi/keyboard_key_feedback_background_lxx_dark.9.png
+++ b/java/res/drawable-xxhdpi/keyboard_key_feedback_background_lxx_dark.9.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/keyboard_key_feedback_left_background_lxx_dark.9.png b/java/res/drawable-xxhdpi/keyboard_key_feedback_left_background_lxx_dark.9.png
deleted file mode 100644
index 25e362c..0000000
--- a/java/res/drawable-xxhdpi/keyboard_key_feedback_left_background_lxx_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xxhdpi/keyboard_key_feedback_left_more_background_lxx_dark.9.png b/java/res/drawable-xxhdpi/keyboard_key_feedback_left_more_background_lxx_dark.9.png
deleted file mode 100644
index c6d9f3c..0000000
--- a/java/res/drawable-xxhdpi/keyboard_key_feedback_left_more_background_lxx_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xxhdpi/keyboard_key_feedback_more_background_lxx_dark.9.png b/java/res/drawable-xxhdpi/keyboard_key_feedback_more_background_lxx_dark.9.png
index a197c29..a79499f 100644
--- a/java/res/drawable-xxhdpi/keyboard_key_feedback_more_background_lxx_dark.9.png
+++ b/java/res/drawable-xxhdpi/keyboard_key_feedback_more_background_lxx_dark.9.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/keyboard_key_feedback_right_background_lxx_dark.9.png b/java/res/drawable-xxhdpi/keyboard_key_feedback_right_background_lxx_dark.9.png
deleted file mode 100644
index 686f757..0000000
--- a/java/res/drawable-xxhdpi/keyboard_key_feedback_right_background_lxx_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xxhdpi/keyboard_key_feedback_right_more_background_lxx_dark.9.png b/java/res/drawable-xxhdpi/keyboard_key_feedback_right_more_background_lxx_dark.9.png
deleted file mode 100644
index 41fad2d..0000000
--- a/java/res/drawable-xxhdpi/keyboard_key_feedback_right_more_background_lxx_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xxhdpi/sym_keyboard_shift_locked_lxx_dark.png b/java/res/drawable-xxhdpi/sym_keyboard_shift_locked_lxx_dark.png
new file mode 100644
index 0000000..2595ec9
--- /dev/null
+++ b/java/res/drawable-xxhdpi/sym_keyboard_shift_locked_lxx_dark.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/sym_keyboard_shift_lxx_dark.png b/java/res/drawable-xxhdpi/sym_keyboard_shift_lxx_dark.png
new file mode 100644
index 0000000..a58b525
--- /dev/null
+++ b/java/res/drawable-xxhdpi/sym_keyboard_shift_lxx_dark.png
Binary files differ
diff --git a/java/res/color/emoji_tab_label_color_lxx_dark.xml b/java/res/drawable/ic_emoji_emoticons_holo_dark.xml
similarity index 73%
rename from java/res/color/emoji_tab_label_color_lxx_dark.xml
rename to java/res/drawable/ic_emoji_emoticons_holo_dark.xml
index 090f83e..59e2349 100644
--- a/java/res/color/emoji_tab_label_color_lxx_dark.xml
+++ b/java/res/drawable/ic_emoji_emoticons_holo_dark.xml
@@ -21,13 +21,12 @@
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
     <item
         android:state_focused="true"
-        android:color="@color/key_text_color_lxx_dark" />
+        android:drawable="@drawable/ic_emoji_emoticons_activated_holo_dark" />
     <item
         android:state_pressed="true"
-        android:color="@color/key_text_color_lxx_dark" />
+        android:drawable="@drawable/ic_emoji_emoticons_activated_holo_dark" />
     <item
         android:state_selected="true"
-        android:color="@color/key_text_color_lxx_dark" />
-    <item
-        android:color="@color/key_text_inactive_color_lxx_dark" />
+        android:drawable="@drawable/ic_emoji_emoticons_activated_holo_dark" />
+    <item android:drawable="@drawable/ic_emoji_emoticons_normal_holo_dark" />
 </selector>
diff --git a/java/res/drawable/ic_emoji_recent_holo_dark.xml b/java/res/drawable/ic_emoji_recents_holo_dark.xml
similarity index 75%
rename from java/res/drawable/ic_emoji_recent_holo_dark.xml
rename to java/res/drawable/ic_emoji_recents_holo_dark.xml
index 225caeb..f14349f 100644
--- a/java/res/drawable/ic_emoji_recent_holo_dark.xml
+++ b/java/res/drawable/ic_emoji_recents_holo_dark.xml
@@ -21,12 +21,12 @@
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
     <item
         android:state_focused="true"
-        android:drawable="@drawable/ic_emoji_recent_activated_holo_dark" />
+        android:drawable="@drawable/ic_emoji_recents_activated_holo_dark" />
     <item
         android:state_pressed="true"
-        android:drawable="@drawable/ic_emoji_recent_activated_holo_dark" />
+        android:drawable="@drawable/ic_emoji_recents_activated_holo_dark" />
     <item
         android:state_selected="true"
-        android:drawable="@drawable/ic_emoji_recent_activated_holo_dark" />
-    <item android:drawable="@drawable/ic_emoji_recent_normal_holo_dark" />
+        android:drawable="@drawable/ic_emoji_recents_activated_holo_dark" />
+    <item android:drawable="@drawable/ic_emoji_recents_normal_holo_dark" />
 </selector>
diff --git a/java/res/drawable/keyboard_key_feedback_lxx_dark.xml b/java/res/drawable/keyboard_key_feedback_lxx_dark.xml
index e624a8b..ab1109b 100644
--- a/java/res/drawable/keyboard_key_feedback_lxx_dark.xml
+++ b/java/res/drawable/keyboard_key_feedback_lxx_dark.xml
@@ -20,15 +20,15 @@
 >
     <!-- Left edge -->
     <item latin:state_left_edge="true" latin:state_has_morekeys="true"
-          android:drawable="@drawable/keyboard_key_feedback_left_more_background_lxx_dark" />
+          android:drawable="@drawable/keyboard_key_feedback_more_background_lxx_dark" />
     <item latin:state_left_edge="true"
-          android:drawable="@drawable/keyboard_key_feedback_left_background_lxx_dark" />
+          android:drawable="@drawable/keyboard_key_feedback_background_lxx_dark" />
 
     <!-- Right edge -->
     <item latin:state_right_edge="true" latin:state_has_morekeys="true"
-          android:drawable="@drawable/keyboard_key_feedback_right_more_background_lxx_dark" />
+          android:drawable="@drawable/keyboard_key_feedback_more_background_lxx_dark" />
     <item latin:state_right_edge="true"
-          android:drawable="@drawable/keyboard_key_feedback_right_background_lxx_dark" />
+          android:drawable="@drawable/keyboard_key_feedback_background_lxx_dark" />
 
     <item latin:state_has_morekeys="true"
           android:drawable="@drawable/keyboard_key_feedback_more_background_lxx_dark" />
diff --git a/java/res/layout/emoji_keyboard_tab_label.xml b/java/res/layout/emoji_keyboard_tab_label.xml
deleted file mode 100644
index 62c552d..0000000
--- a/java/res/layout/emoji_keyboard_tab_label.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2013, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<TextView xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="0dip"
-    android:layout_weight="1.0"
-    android:layout_height="wrap_content"
-    android:gravity="center"
-/>
diff --git a/java/res/layout/emoji_palettes_view.xml b/java/res/layout/emoji_palettes_view.xml
index c0c346e..e2933f9 100644
--- a/java/res/layout/emoji_palettes_view.xml
+++ b/java/res/layout/emoji_palettes_view.xml
@@ -41,11 +41,9 @@
                 android:id="@android:id/tabs"
                 android:layout_width="match_parent"
                 android:layout_height="match_parent"
-                android:background="@drawable/emoji_category_tab_selected_holo_dark"
                 android:divider="@null"
                 android:tabStripEnabled="true"
-                android:tabStripLeft="@drawable/emoji_category_tab_unselected_holo_dark"
-                android:tabStripRight="@drawable/emoji_category_tab_unselected_holo_dark" />
+                style="?attr/emojiTabWidgetStyle" />
             <FrameLayout
                 android:id="@android:id/tabcontent"
                 android:layout_width="0dip"
@@ -70,7 +68,6 @@
             android:layout_width="0dip"
             android:layout_weight="12.5"
             android:layout_height="match_parent"
-            android:src="@drawable/sym_keyboard_delete_holo_dark"
             android:contentDescription="@string/spoken_description_delete" />
     </LinearLayout>
     <android.support.v4.view.ViewPager
@@ -80,8 +77,8 @@
     <com.android.inputmethod.keyboard.emoji.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_background_holo" />
+        android:layout_height="2dip"
+        style="?attr/emojiCategoryPageIndicatorViewStyle" />
     <LinearLayout
         android:id="@+id/emoji_action_bar"
         android:orientation="horizontal"
diff --git a/java/res/values/attrs.xml b/java/res/values/attrs.xml
index b90e82b..307ef13 100644
--- a/java/res/values/attrs.xml
+++ b/java/res/values/attrs.xml
@@ -30,6 +30,10 @@
         <attr name="keyPreviewTextViewStyle" format="reference"/>
         <!-- EmojiPalettesView style -->
         <attr name="emojiPalettesViewStyle" format="reference" />
+        <!-- TabWidget of EmojiPlettesView style -->
+        <attr name="emojiTabWidgetStyle" format="reference" />
+        <!-- EmojiCategoryPageIndicatorView style -->
+        <attr name="emojiCategoryPageIndicatorViewStyle" format="reference" />
         <!-- MoreKeysKeyboard style -->
         <attr name="moreKeysKeyboardStyle" format="reference" />
         <!-- MoreKeysKeyboardView style -->
@@ -172,7 +176,17 @@
     </declare-styleable>
 
     <declare-styleable name="EmojiPalettesView">
-        <attr name="emojiTabLabelColor" format="reference" />
+        <attr name="iconEmojiRecentsTab" format="reference" />
+        <attr name="iconEmojiCategory1Tab" format="reference" />
+        <attr name="iconEmojiCategory2Tab" format="reference" />
+        <attr name="iconEmojiCategory3Tab" format="reference" />
+        <attr name="iconEmojiCategory4Tab" format="reference" />
+        <attr name="iconEmojiCategory5Tab" format="reference" />
+        <attr name="iconEmojiCategory6Tab" format="reference" />
+    </declare-styleable>
+
+    <declare-styleable name="EmojiCategoryPageIndicatorView">
+        <attr name="emojiCategoryPageIndicatorColor" format="color" />
     </declare-styleable>
 
     <declare-styleable name="SuggestionStripView">
diff --git a/java/res/values/colors.xml b/java/res/values/colors.xml
index 12cb71f..0eee08d 100644
--- a/java/res/values/colors.xml
+++ b/java/res/values/colors.xml
@@ -34,6 +34,7 @@
     <color name="spacebar_text_color_holo">#FFC0C0C0</color>
     <color name="spacebar_text_shadow_color_holo">#80000000</color>
     <color name="gesture_floating_preview_color_holo">#C0000000</color>
+    <color name="emoji_tab_page_indicator_background_holo">#111111</color>
     <!-- Color resources for KLP theme. Base color = F0F0F0 -->
     <color name="highlight_color_klp">#FFF0F0F0</color>
     <color name="typed_word_color_klp">#D8F0F0F0</color>
@@ -53,12 +54,11 @@
     <color name="suggestions_strip_background_lxx_dark">#263238</color>
     <color name="suggested_word_background_selected_lxx_dark">#384248</color>
     <color name="gesture_floating_preview_color_lxx_dark">#C0000000</color>
+    <color name="emoji_tab_page_indicator_background_lxx_dark">#263238</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_background_holo">#FF000000</color>
-    <color name="emoji_category_page_id_foreground_holo">#80FFFFFF</color>
 </resources>
diff --git a/java/res/values/themes-common.xml b/java/res/values/themes-common.xml
index df26fb3..c5d3f55 100644
--- a/java/res/values/themes-common.xml
+++ b/java/res/values/themes-common.xml
@@ -110,6 +110,8 @@
     <!-- Though {@link EmojiPalettesView} doesn't extend {@link KeyboardView}, some views inside it,
          for instance delete button, need themed {@link KeyboardView} attributes. -->
     <style name="EmojiPalettesView" />
+    <style name="EmojiTabWidget" />
+    <style name="EmojiCategoryPageIndicatorView" />
     <style name="MoreKeysKeyboard" />
     <style
         name="MoreKeysKeyboardView"
diff --git a/java/res/values/themes-ics.xml b/java/res/values/themes-ics.xml
index 22fb2c8..f7ca5a3 100644
--- a/java/res/values/themes-ics.xml
+++ b/java/res/values/themes-ics.xml
@@ -25,6 +25,8 @@
         <item name="mainKeyboardViewStyle">@style/MainKeyboardView.ICS</item>
         <item name="keyPreviewTextViewStyle">@style/KeyPreviewTextView.ICS</item>
         <item name="emojiPalettesViewStyle">@style/EmojiPalettesView.ICS</item>
+        <item name="emojiTabWidgetStyle">@style/EmojiTabWidget.ICS</item>
+        <item name="emojiCategoryPageIndicatorViewStyle">@style/EmojiCategoryPageIndicatorView.ICS</item>
         <item name="moreKeysKeyboardStyle">@style/MoreKeysKeyboard.ICS</item>
         <item name="moreKeysKeyboardViewStyle">@style/MoreKeysKeyboardView.ICS</item>
         <item name="suggestionStripViewStyle">@style/SuggestionStripView.ICS</item>
@@ -87,7 +89,22 @@
         name="EmojiPalettesView.ICS"
         parent="MainKeyboardView.ICS"
     >
-        <item name="emojiTabLabelColor">@color/emoji_tab_label_color_holo</item>
+        <item name="iconEmojiRecentsTab">@drawable/ic_emoji_recents_holo_dark</item>
+        <item name="iconEmojiCategory1Tab">@drawable/ic_emoji_people_holo_dark</item>
+        <item name="iconEmojiCategory2Tab">@drawable/ic_emoji_objects_holo_dark</item>
+        <item name="iconEmojiCategory3Tab">@drawable/ic_emoji_nature_holo_dark</item>
+        <item name="iconEmojiCategory4Tab">@drawable/ic_emoji_places_holo_dark</item>
+        <item name="iconEmojiCategory5Tab">@drawable/ic_emoji_symbols_holo_dark</item>
+        <item name="iconEmojiCategory6Tab">@drawable/ic_emoji_emoticons_holo_dark</item>
+    </style>
+    <style name="EmojiCategoryPageIndicatorView.ICS">
+        <item name="android:background">@color/emoji_tab_page_indicator_background_holo</item>
+        <item name="emojiCategoryPageIndicatorColor">@color/highlight_color_ics</item>
+    </style>
+    <style name="EmojiTabWidget.ICS">
+        <item name="android:background">@drawable/emoji_category_tab_selected_ics</item>
+        <item name="android:tabStripLeft">@drawable/emoji_category_tab_unselected_holo_dark</item>
+        <item name="android:tabStripRight">@drawable/emoji_category_tab_unselected_holo_dark</item>
     </style>
     <style
         name="MoreKeysKeyboard.ICS"
diff --git a/java/res/values/themes-klp.xml b/java/res/values/themes-klp.xml
index 212f5db..96d0478 100644
--- a/java/res/values/themes-klp.xml
+++ b/java/res/values/themes-klp.xml
@@ -25,6 +25,8 @@
         <item name="mainKeyboardViewStyle">@style/MainKeyboardView.KLP</item>
         <item name="keyPreviewTextViewStyle">@style/KeyPreviewTextView.KLP</item>
         <item name="emojiPalettesViewStyle">@style/EmojiPalettesView.KLP</item>
+        <item name="emojiTabWidgetStyle">@style/EmojiTabWidget.KLP</item>
+        <item name="emojiCategoryPageIndicatorViewStyle">@style/EmojiCategoryPageIndicatorView.KLP</item>
         <item name="moreKeysKeyboardStyle">@style/MoreKeysKeyboard.KLP</item>
         <item name="moreKeysKeyboardViewStyle">@style/MoreKeysKeyboardView.KLP</item>
         <item name="suggestionStripViewStyle">@style/SuggestionStripView.KLP</item>
@@ -87,7 +89,22 @@
         name="EmojiPalettesView.KLP"
         parent="MainKeyboardView.KLP"
     >
-        <item name="emojiTabLabelColor">@color/emoji_tab_label_color_holo</item>
+        <item name="iconEmojiRecentsTab">@drawable/ic_emoji_recents_holo_dark</item>
+        <item name="iconEmojiCategory1Tab">@drawable/ic_emoji_people_holo_dark</item>
+        <item name="iconEmojiCategory2Tab">@drawable/ic_emoji_objects_holo_dark</item>
+        <item name="iconEmojiCategory3Tab">@drawable/ic_emoji_nature_holo_dark</item>
+        <item name="iconEmojiCategory4Tab">@drawable/ic_emoji_places_holo_dark</item>
+        <item name="iconEmojiCategory5Tab">@drawable/ic_emoji_symbols_holo_dark</item>
+        <item name="iconEmojiCategory6Tab">@drawable/ic_emoji_emoticons_holo_dark</item>
+    </style>
+    <style name="EmojiCategoryPageIndicatorView.KLP">
+        <item name="android:background">@color/emoji_tab_page_indicator_background_holo</item>
+        <item name="emojiCategoryPageIndicatorColor">@color/highlight_color_klp</item>
+    </style>
+    <style name="EmojiTabWidget.KLP">
+        <item name="android:background">@drawable/emoji_category_tab_selected_klp</item>
+        <item name="android:tabStripLeft">@drawable/emoji_category_tab_unselected_holo_dark</item>
+        <item name="android:tabStripRight">@drawable/emoji_category_tab_unselected_holo_dark</item>
     </style>
     <style
         name="MoreKeysKeyboard.KLP"
diff --git a/java/res/values/themes-lxx-dark.xml b/java/res/values/themes-lxx-dark.xml
index ace5fe9..7fb122a 100644
--- a/java/res/values/themes-lxx-dark.xml
+++ b/java/res/values/themes-lxx-dark.xml
@@ -25,6 +25,8 @@
         <item name="mainKeyboardViewStyle">@style/MainKeyboardView.LXX_Dark</item>
         <item name="keyPreviewTextViewStyle">@style/KeyPreviewTextView.LXX_Dark</item>
         <item name="emojiPalettesViewStyle">@style/EmojiPalettesView.LXX_Dark</item>
+        <item name="emojiTabWidgetStyle">@style/EmojiTabWidget.LXX_Dark</item>
+        <item name="emojiCategoryPageIndicatorViewStyle">@style/EmojiCategoryPageIndicatorView.LXX_Dark</item>
         <item name="moreKeysKeyboardStyle">@style/MoreKeysKeyboard.LXX_Dark</item>
         <item name="moreKeysKeyboardViewStyle">@style/MoreKeysKeyboardView.LXX_Dark</item>
         <item name="suggestionStripViewStyle">@style/SuggestionStripView.LXX_Dark</item>
@@ -88,7 +90,23 @@
         name="EmojiPalettesView.LXX_Dark"
         parent="MainKeyboardView.LXX_Dark"
     >
-        <item name="emojiTabLabelColor">@color/emoji_tab_label_color_lxx_dark</item>
+        <!-- TODO: Update those icons to LXX_Dark theme. -->
+        <item name="iconEmojiRecentsTab">@drawable/ic_emoji_recents_holo_dark</item>
+        <item name="iconEmojiCategory1Tab">@drawable/ic_emoji_people_holo_dark</item>
+        <item name="iconEmojiCategory2Tab">@drawable/ic_emoji_objects_holo_dark</item>
+        <item name="iconEmojiCategory3Tab">@drawable/ic_emoji_nature_holo_dark</item>
+        <item name="iconEmojiCategory4Tab">@drawable/ic_emoji_places_holo_dark</item>
+        <item name="iconEmojiCategory5Tab">@drawable/ic_emoji_symbols_holo_dark</item>
+        <item name="iconEmojiCategory6Tab">@drawable/ic_emoji_emoticons_holo_dark</item>
+    </style>
+    <style name="EmojiCategoryPageIndicatorView.LXX_Dark">
+        <item name="android:background">@color/emoji_tab_page_indicator_background_lxx_dark</item>
+        <item name="emojiCategoryPageIndicatorColor">@color/highlight_color_lxx_dark</item>
+    </style>
+    <style name="EmojiTabWidget.LXX_Dark">
+        <item name="android:background">@drawable/emoji_category_tab_selected_lxx_dark</item>
+        <item name="android:tabStripLeft">@drawable/emoji_category_tab_unselected_lxx_dark</item>
+        <item name="android:tabStripRight">@drawable/emoji_category_tab_unselected_lxx_dark</item>
     </style>
     <style
         name="MoreKeysKeyboard.LXX_Dark"
diff --git a/java/res/xml/method.xml b/java/res/xml/method.xml
index 2adc957..777a13d 100644
--- a/java/res/xml/method.xml
+++ b/java/res/xml/method.xml
@@ -332,6 +332,7 @@
     />
     <!-- TODO: This hindi_compact keyboard is a preliminary layout.
                This isn't based on the final specification. -->
+    <!--
     <subtype android:icon="@drawable/ic_ime_switcher_dark"
             android:label="@string/subtype_generic_compact"
             android:subtypeId="0xe49c89a1"
@@ -340,6 +341,7 @@
             android:imeSubtypeExtraValue="KeyboardLayoutSet=hindi_compact,EmojiCapable"
             android:isAsciiCapable="false"
     />
+    -->
     <subtype android:icon="@drawable/ic_ime_switcher_dark"
             android:label="@string/subtype_generic"
             android:subtypeId="0x35b7526a"
@@ -480,6 +482,7 @@
     />
     <!-- TODO: This marathi keyboard is a preliminary layout.
                This isn't based on the final specification. -->
+    <!--
     <subtype android:icon="@drawable/ic_ime_switcher_dark"
             android:label="@string/subtype_generic"
             android:subtypeId="0x747b9f03"
@@ -488,6 +491,7 @@
             android:imeSubtypeExtraValue="KeyboardLayoutSet=marathi,EmojiCapable"
             android:isAsciiCapable="false"
     />
+    -->
     <subtype android:icon="@drawable/ic_ime_switcher_dark"
             android:label="@string/subtype_generic"
             android:subtypeId="0x84c87c61"
@@ -498,6 +502,7 @@
     />
     <!-- TODO: This Myanmar keyboard is a preliminary layout.
                This isn't based on the final specification. -->
+    <!--
     <subtype android:icon="@drawable/ic_ime_switcher_dark"
             android:label="@string/subtype_generic"
             android:subtypeId="0xea266ea4"
@@ -506,6 +511,7 @@
             android:imeSubtypeExtraValue="KeyboardLayoutSet=myanmar,EmojiCapable,CombiningRules=MyanmarReordering"
             android:isAsciiCapable="false"
     />
+    -->
     <subtype android:icon="@drawable/ic_ime_switcher_dark"
             android:label="@string/subtype_generic"
             android:subtypeId="0x3f12ee14"
diff --git a/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java b/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
index 0499a34..46caef6 100644
--- a/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
+++ b/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
@@ -17,6 +17,7 @@
 package com.android.inputmethod.accessibility;
 
 import android.content.Context;
+import android.content.res.Resources;
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.SparseIntArray;
@@ -27,21 +28,18 @@
 import com.android.inputmethod.keyboard.KeyboardId;
 import com.android.inputmethod.latin.Constants;
 import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.utils.CollectionUtils;
 
-import java.util.HashMap;
+import java.util.Locale;
 
 public final class KeyCodeDescriptionMapper {
     private static final String TAG = KeyCodeDescriptionMapper.class.getSimpleName();
+    private static final String SPOKEN_EMOJI_RESOURCE_NAME_FORMAT = "spoken_emoji_%04X";
 
     // The resource ID of the string spoken for obscured keys
     private static final int OBSCURED_KEY_RES_ID = R.string.spoken_description_dot;
 
     private static KeyCodeDescriptionMapper sInstance = new KeyCodeDescriptionMapper();
 
-    // Map of key labels to spoken description resource IDs
-    private final HashMap<CharSequence, Integer> mKeyLabelMap = CollectionUtils.newHashMap();
-
     // Sparse array of spoken description resource IDs indexed by key codes
     private final SparseIntArray mKeyCodeMap;
 
@@ -114,17 +112,12 @@
             return getDescriptionForActionKey(context, keyboard, key);
         }
 
-        if (!TextUtils.isEmpty(key.getLabel())) {
-            final String label = key.getLabel().trim();
-
-            // First, attempt to map the label to a pre-defined description.
-            if (mKeyLabelMap.containsKey(label)) {
-                return context.getString(mKeyLabelMap.get(label));
-            }
+        if (code == Constants.CODE_OUTPUT_TEXT) {
+            return key.getOutputText();
         }
 
         // Just attempt to speak the description.
-        if (key.getCode() != Constants.CODE_UNSPECIFIED) {
+        if (code != Constants.CODE_UNSPECIFIED) {
             return getDescriptionForKeyCode(context, keyboard, key, shouldObscure);
         }
         return null;
@@ -139,7 +132,7 @@
      * @param keyboard The keyboard on which the key resides.
      * @return a character sequence describing the action performed by pressing the key
      */
-    private String getDescriptionForSwitchAlphaSymbol(final Context context,
+    private static String getDescriptionForSwitchAlphaSymbol(final Context context,
             final Keyboard keyboard) {
         final KeyboardId keyboardId = keyboard.mId;
         final int elementId = keyboardId.mElementId;
@@ -177,7 +170,8 @@
      * @param keyboard The keyboard on which the key resides.
      * @return A context-sensitive description of the "Shift" key.
      */
-    private String getDescriptionForShiftKey(final Context context, final Keyboard keyboard) {
+    private static String getDescriptionForShiftKey(final Context context,
+            final Keyboard keyboard) {
         final KeyboardId keyboardId = keyboard.mId;
         final int elementId = keyboardId.mElementId;
         final int resId;
@@ -211,7 +205,7 @@
      * @param key The key to describe.
      * @return Returns a context-sensitive description of the "Enter" action key.
      */
-    private String getDescriptionForActionKey(final Context context, final Keyboard keyboard,
+    private static String getDescriptionForActionKey(final Context context, final Keyboard keyboard,
             final Key key) {
         final KeyboardId keyboardId = keyboard.mId;
         final int actionId = keyboardId.imeAction();
@@ -280,6 +274,13 @@
         if (mKeyCodeMap.indexOfKey(code) >= 0) {
             return context.getString(mKeyCodeMap.get(code));
         }
+        final int spokenEmojiId = getSpokenDescriptionId(
+                context, code, SPOKEN_EMOJI_RESOURCE_NAME_FORMAT);
+        if (spokenEmojiId != 0) {
+            final String spokenEmoji = context.getString(spokenEmojiId);
+            mKeyCodeMap.append(code, spokenEmojiId);
+            return spokenEmoji;
+        }
         if (isDefinedNonCtrl) {
             return Character.toString((char) code);
         }
@@ -288,4 +289,13 @@
         }
         return context.getString(R.string.spoken_description_unknown, code);
     }
+
+    private static int getSpokenDescriptionId(final Context context, final int code,
+            final String resourceNameFormat) {
+        final String resourceName = String.format(Locale.ROOT, resourceNameFormat, code);
+        final Resources resources = context.getResources();
+        final String packageName = resources.getResourcePackageName(
+                R.string.spoken_description_unknown);
+        return resources.getIdentifier(resourceName, "string", packageName);
+    }
 }
diff --git a/java/src/com/android/inputmethod/accessibility/KeyboardAccessibilityDelegate.java b/java/src/com/android/inputmethod/accessibility/KeyboardAccessibilityDelegate.java
new file mode 100644
index 0000000..eed40f4
--- /dev/null
+++ b/java/src/com/android/inputmethod/accessibility/KeyboardAccessibilityDelegate.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.accessibility;
+
+import android.os.SystemClock;
+import android.support.v4.view.AccessibilityDelegateCompat;
+import android.support.v4.view.ViewCompat;
+import android.support.v4.view.accessibility.AccessibilityEventCompat;
+import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewParent;
+import android.view.accessibility.AccessibilityEvent;
+
+import com.android.inputmethod.keyboard.Key;
+import com.android.inputmethod.keyboard.KeyDetector;
+import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.keyboard.KeyboardView;
+
+public class KeyboardAccessibilityDelegate<KV extends KeyboardView>
+        extends AccessibilityDelegateCompat {
+    protected final KV mKeyboardView;
+    protected final KeyDetector mKeyDetector;
+    private Keyboard mKeyboard;
+    private KeyboardAccessibilityNodeProvider mAccessibilityNodeProvider;
+    private Key mLastHoverKey;
+
+    public KeyboardAccessibilityDelegate(final KV keyboardView, final KeyDetector keyDetector) {
+        super();
+        mKeyboardView = keyboardView;
+        mKeyDetector = keyDetector;
+
+        // Ensure that the view has an accessibility delegate.
+        ViewCompat.setAccessibilityDelegate(keyboardView, this);
+    }
+
+    /**
+     * Called when the keyboard layout changes.
+     * <p>
+     * <b>Note:</b> This method will be called even if accessibility is not
+     * enabled.
+     * @param keyboard The keyboard that is being set to the wrapping view.
+     */
+    public void setKeyboard(final Keyboard keyboard) {
+        if (keyboard == null) {
+            return;
+        }
+        if (mAccessibilityNodeProvider != null) {
+            mAccessibilityNodeProvider.setKeyboard(keyboard);
+        }
+        mKeyboard = keyboard;
+    }
+
+    protected Keyboard getKeyboard() {
+        return mKeyboard;
+    }
+
+    /**
+     * Sends a window state change event with the specified text.
+     *
+     * @param text The text to send with the event.
+     */
+    protected void sendWindowStateChanged(final String text) {
+        final AccessibilityEvent stateChange = AccessibilityEvent.obtain(
+                AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+        mKeyboardView.onInitializeAccessibilityEvent(stateChange);
+        stateChange.getText().add(text);
+        stateChange.setContentDescription(null);
+
+        final ViewParent parent = mKeyboardView.getParent();
+        if (parent != null) {
+            parent.requestSendAccessibilityEvent(mKeyboardView, stateChange);
+        }
+    }
+
+    /**
+     * Delegate method for View.getAccessibilityNodeProvider(). This method is called in SDK
+     * version 15 (Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) and higher to obtain the virtual
+     * node hierarchy provider.
+     *
+     * @param host The host view for the provider.
+     * @return The accessibility node provider for the current keyboard.
+     */
+    @Override
+    public KeyboardAccessibilityNodeProvider getAccessibilityNodeProvider(final View host) {
+        return getAccessibilityNodeProvider();
+    }
+
+    /**
+     * @return A lazily-instantiated node provider for this view delegate.
+     */
+    protected KeyboardAccessibilityNodeProvider getAccessibilityNodeProvider() {
+        // Instantiate the provide only when requested. Since the system
+        // will call this method multiple times it is a good practice to
+        // cache the provider instance.
+        if (mAccessibilityNodeProvider == null) {
+            mAccessibilityNodeProvider = new KeyboardAccessibilityNodeProvider(mKeyboardView);
+        }
+        return mAccessibilityNodeProvider;
+    }
+
+    /**
+     * Receives hover events when touch exploration is turned on in SDK versions ICS and higher.
+     *
+     * @param event The hover event.
+     * @return {@code true} if the event is handled
+     */
+    public boolean dispatchHoverEvent(final MotionEvent event) {
+        final int x = (int) event.getX();
+        final int y = (int) event.getY();
+        final Key previousKey = mLastHoverKey;
+        final Key key = mKeyDetector.detectHitKey(x, y);
+        mLastHoverKey = key;
+
+        switch (event.getAction()) {
+        case MotionEvent.ACTION_HOVER_EXIT:
+            // Make sure we're not getting an EXIT event because the user slid
+            // off the keyboard area, then force a key press.
+            if (key != null) {
+                final long downTime = simulateKeyPress(key);
+                simulateKeyRelease(key, downTime);
+            }
+            //$FALL-THROUGH$
+        case MotionEvent.ACTION_HOVER_ENTER:
+            return onHoverKey(key, event);
+        case MotionEvent.ACTION_HOVER_MOVE:
+            if (key != previousKey) {
+                return onTransitionKey(key, previousKey, event);
+            }
+            return onHoverKey(key, event);
+        }
+        return false;
+    }
+
+    /**
+     * Simulates a key press by injecting touch an event into the keyboard view.
+     * This avoids the complexity of trackers and listeners within the keyboard.
+     *
+     * @param key The key to press.
+     */
+    private long simulateKeyPress(final Key key) {
+        final int x = key.getHitBox().centerX();
+        final int y = key.getHitBox().centerY();
+        final long downTime = SystemClock.uptimeMillis();
+        final MotionEvent downEvent = MotionEvent.obtain(
+                downTime, downTime, MotionEvent.ACTION_DOWN, x, y, 0);
+        mKeyboardView.onTouchEvent(downEvent);
+        downEvent.recycle();
+        return downTime;
+    }
+
+    /**
+     * Simulates a key release by injecting touch an event into the keyboard view.
+     * This avoids the complexity of trackers and listeners within the keyboard.
+     *
+     * @param key The key to release.
+     */
+    private void simulateKeyRelease(final Key key, final long downTime) {
+        final int x = key.getHitBox().centerX();
+        final int y = key.getHitBox().centerY();
+        final MotionEvent upEvent = MotionEvent.obtain(
+                downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, x, y, 0);
+        mKeyboardView.onTouchEvent(upEvent);
+        upEvent.recycle();
+    }
+
+    /**
+     * Simulates a transition between two {@link Key}s by sending a HOVER_EXIT on the previous key,
+     * a HOVER_ENTER on the current key, and a HOVER_MOVE on the current key.
+     *
+     * @param currentKey The currently hovered key.
+     * @param previousKey The previously hovered key.
+     * @param event The event that triggered the transition.
+     * @return {@code true} if the event was handled.
+     */
+    private boolean onTransitionKey(final Key currentKey, final Key previousKey,
+            final MotionEvent event) {
+        final int savedAction = event.getAction();
+        event.setAction(MotionEvent.ACTION_HOVER_EXIT);
+        onHoverKey(previousKey, event);
+        event.setAction(MotionEvent.ACTION_HOVER_ENTER);
+        onHoverKey(currentKey, event);
+        event.setAction(MotionEvent.ACTION_HOVER_MOVE);
+        final boolean handled = onHoverKey(currentKey, event);
+        event.setAction(savedAction);
+        return handled;
+    }
+
+    /**
+     * Handles a hover event on a key. If {@link Key} extended View, this would be analogous to
+     * calling View.onHoverEvent(MotionEvent).
+     *
+     * @param key The currently hovered key.
+     * @param event The hover event.
+     * @return {@code true} if the event was handled.
+     */
+    private boolean onHoverKey(final Key key, final MotionEvent event) {
+        // Null keys can't receive events.
+        if (key == null) {
+            return false;
+        }
+        final KeyboardAccessibilityNodeProvider provider = getAccessibilityNodeProvider();
+
+        switch (event.getAction()) {
+        case MotionEvent.ACTION_HOVER_ENTER:
+            provider.sendAccessibilityEventForKey(
+                    key, AccessibilityEventCompat.TYPE_VIEW_HOVER_ENTER);
+            provider.performActionForKey(
+                    key, AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS, null);
+            break;
+        case MotionEvent.ACTION_HOVER_EXIT:
+            provider.sendAccessibilityEventForKey(
+                    key, AccessibilityEventCompat.TYPE_VIEW_HOVER_EXIT);
+            break;
+        }
+        return true;
+    }
+}
diff --git a/java/src/com/android/inputmethod/accessibility/MainKeyboardAccessibilityNodeProvider.java b/java/src/com/android/inputmethod/accessibility/KeyboardAccessibilityNodeProvider.java
similarity index 97%
rename from java/src/com/android/inputmethod/accessibility/MainKeyboardAccessibilityNodeProvider.java
rename to java/src/com/android/inputmethod/accessibility/KeyboardAccessibilityNodeProvider.java
index f69d316..cddd1c7 100644
--- a/java/src/com/android/inputmethod/accessibility/MainKeyboardAccessibilityNodeProvider.java
+++ b/java/src/com/android/inputmethod/accessibility/KeyboardAccessibilityNodeProvider.java
@@ -47,8 +47,8 @@
  * virtual views, thus conveying their logical structure.
  * </p>
  */
-public final class MainKeyboardAccessibilityNodeProvider extends AccessibilityNodeProviderCompat {
-    private static final String TAG = MainKeyboardAccessibilityNodeProvider.class.getSimpleName();
+public final class KeyboardAccessibilityNodeProvider extends AccessibilityNodeProviderCompat {
+    private static final String TAG = KeyboardAccessibilityNodeProvider.class.getSimpleName();
     private static final int UNDEFINED = Integer.MIN_VALUE;
 
     private final KeyCodeDescriptionMapper mKeyCodeDescriptionMapper;
@@ -69,7 +69,8 @@
     /** The current keyboard. */
     private Keyboard mKeyboard;
 
-    public MainKeyboardAccessibilityNodeProvider(final KeyboardView keyboardView) {
+    public KeyboardAccessibilityNodeProvider(final KeyboardView keyboardView) {
+        super();
         mKeyCodeDescriptionMapper = KeyCodeDescriptionMapper.getInstance();
         mAccessibilityUtils = AccessibilityUtils.getInstance();
         mKeyboardView = keyboardView;
diff --git a/java/src/com/android/inputmethod/accessibility/MainKeyboardAccessibilityDelegate.java b/java/src/com/android/inputmethod/accessibility/MainKeyboardAccessibilityDelegate.java
index 1092942..c114551 100644
--- a/java/src/com/android/inputmethod/accessibility/MainKeyboardAccessibilityDelegate.java
+++ b/java/src/com/android/inputmethod/accessibility/MainKeyboardAccessibilityDelegate.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2011 The Android Open Source Project
+ * Copyright (C) 2014 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -18,15 +18,10 @@
 
 import android.content.Context;
 import android.os.SystemClock;
-import android.support.v4.view.AccessibilityDelegateCompat;
-import android.support.v4.view.ViewCompat;
 import android.support.v4.view.accessibility.AccessibilityEventCompat;
 import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
 import android.util.SparseIntArray;
 import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewParent;
-import android.view.accessibility.AccessibilityEvent;
 
 import com.android.inputmethod.keyboard.Key;
 import com.android.inputmethod.keyboard.KeyDetector;
@@ -36,7 +31,8 @@
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
 
-public final class MainKeyboardAccessibilityDelegate extends AccessibilityDelegateCompat {
+public final class MainKeyboardAccessibilityDelegate
+        extends KeyboardAccessibilityDelegate<MainKeyboardView> {
     /** Map of keyboard modes to resource IDs. */
     private static final SparseIntArray KEYBOARD_MODE_RES_IDS = new SparseIntArray();
 
@@ -52,48 +48,26 @@
         KEYBOARD_MODE_RES_IDS.put(KeyboardId.MODE_URL, R.string.keyboard_mode_url);
     }
 
-    private final MainKeyboardView mView;
-    private Keyboard mKeyboard;
-    private MainKeyboardAccessibilityNodeProvider mAccessibilityNodeProvider;
-
-    private Key mLastHoverKey = null;
-
-    /**
-     * Inset in pixels to look for keys when the user's finger exits the keyboard area.
-     */
-    private int mEdgeSlop;
-
     /** The most recently set keyboard mode. */
     private int mLastKeyboardMode = KEYBOARD_IS_HIDDEN;
     private static final int KEYBOARD_IS_HIDDEN = -1;
 
-    public MainKeyboardAccessibilityDelegate(final MainKeyboardView view) {
-        final Context context = view.getContext();
-        mEdgeSlop = context.getResources().getDimensionPixelSize(
-                R.dimen.config_accessibility_edge_slop);
-        mView = view;
-
-        // Ensure that the view has an accessibility delegate.
-        ViewCompat.setAccessibilityDelegate(view, this);
+    public MainKeyboardAccessibilityDelegate(final MainKeyboardView mainKeyboardView,
+            final KeyDetector keyDetector) {
+        super(mainKeyboardView, keyDetector);
     }
 
     /**
-     * Called when the keyboard layout changes.
-     * <p>
-     * <b>Note:</b> This method will be called even if accessibility is not
-     * enabled.
-     * @param keyboard The keyboard that is being set to the wrapping view.
+     * {@inheritDoc}
      */
+    @Override
     public void setKeyboard(final Keyboard keyboard) {
         if (keyboard == null) {
             return;
         }
-        if (mAccessibilityNodeProvider != null) {
-            mAccessibilityNodeProvider.setKeyboard(keyboard);
-        }
-        final Keyboard lastKeyboard = mKeyboard;
+        final Keyboard lastKeyboard = getKeyboard();
+        super.setKeyboard(keyboard);
         final int lastKeyboardMode = mLastKeyboardMode;
-        mKeyboard = keyboard;
         mLastKeyboardMode = keyboard.mId.mMode;
 
         // Since this method is called even when accessibility is off, make sure
@@ -144,7 +118,7 @@
      * @param keyboard The new keyboard.
      */
     private void announceKeyboardMode(final Keyboard keyboard) {
-        final Context context = mView.getContext();
+        final Context context = mKeyboardView.getContext();
         final int modeTextResId = KEYBOARD_MODE_RES_IDS.get(keyboard.mId.mMode);
         if (modeTextResId == 0) {
             return;
@@ -194,7 +168,7 @@
         default:
             return;
         }
-        final String text = mView.getContext().getString(resId);
+        final String text = mKeyboardView.getContext().getString(resId);
         sendWindowStateChanged(text);
     }
 
@@ -202,191 +176,9 @@
      * Announces that the keyboard has been hidden.
      */
     private void announceKeyboardHidden() {
-        final Context context = mView.getContext();
+        final Context context = mKeyboardView.getContext();
         final String text = context.getString(R.string.announce_keyboard_hidden);
 
         sendWindowStateChanged(text);
     }
-
-    /**
-     * Sends a window state change event with the specified text.
-     *
-     * @param text The text to send with the event.
-     */
-    private void sendWindowStateChanged(final String text) {
-        final AccessibilityEvent stateChange = AccessibilityEvent.obtain(
-                AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
-        mView.onInitializeAccessibilityEvent(stateChange);
-        stateChange.getText().add(text);
-        stateChange.setContentDescription(null);
-
-        final ViewParent parent = mView.getParent();
-        if (parent != null) {
-            parent.requestSendAccessibilityEvent(mView, stateChange);
-        }
-    }
-
-    /**
-     * Delegate method for View.getAccessibilityNodeProvider(). This method is called in SDK
-     * version 15 (Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) and higher to obtain the virtual
-     * node hierarchy provider.
-     *
-     * @param host The host view for the provider.
-     * @return The accessibility node provider for the current keyboard.
-     */
-    @Override
-    public MainKeyboardAccessibilityNodeProvider getAccessibilityNodeProvider(final View host) {
-        return getAccessibilityNodeProvider();
-    }
-
-    /**
-     * Receives hover events when touch exploration is turned on in SDK versions ICS and higher.
-     *
-     * @param event The hover event.
-     * @param keyDetector The {@link KeyDetector} to determine on which key the <code>event</code>
-     *     is hovering.
-     * @return {@code true} if the event is handled
-     */
-    public boolean dispatchHoverEvent(final MotionEvent event, final KeyDetector keyDetector) {
-        final int x = (int) event.getX();
-        final int y = (int) event.getY();
-        final Key previousKey = mLastHoverKey;
-        final Key key;
-
-        if (pointInView(x, y)) {
-            key = keyDetector.detectHitKey(x, y);
-        } else {
-            key = null;
-        }
-        mLastHoverKey = key;
-
-        switch (event.getAction()) {
-        case MotionEvent.ACTION_HOVER_EXIT:
-            // Make sure we're not getting an EXIT event because the user slid
-            // off the keyboard area, then force a key press.
-            if (key != null) {
-                final long downTime = simulateKeyPress(key);
-                simulateKeyRelease(key, downTime);
-            }
-            //$FALL-THROUGH$
-        case MotionEvent.ACTION_HOVER_ENTER:
-            return onHoverKey(key, event);
-        case MotionEvent.ACTION_HOVER_MOVE:
-            if (key != previousKey) {
-                return onTransitionKey(key, previousKey, event);
-            }
-            return onHoverKey(key, event);
-        }
-        return false;
-    }
-
-    /**
-     * @return A lazily-instantiated node provider for this view delegate.
-     */
-    private MainKeyboardAccessibilityNodeProvider getAccessibilityNodeProvider() {
-        // Instantiate the provide only when requested. Since the system
-        // will call this method multiple times it is a good practice to
-        // cache the provider instance.
-        if (mAccessibilityNodeProvider == null) {
-            mAccessibilityNodeProvider = new MainKeyboardAccessibilityNodeProvider(mView);
-        }
-        return mAccessibilityNodeProvider;
-    }
-
-    /**
-     * Utility method to determine whether the given point, in local coordinates, is inside the
-     * view, where the area of the view is contracted by the edge slop factor.
-     *
-     * @param localX The local x-coordinate.
-     * @param localY The local y-coordinate.
-     */
-    private boolean pointInView(final int localX, final int localY) {
-        return (localX >= mEdgeSlop) && (localY >= mEdgeSlop)
-                && (localX < (mView.getWidth() - mEdgeSlop))
-                && (localY < (mView.getHeight() - mEdgeSlop));
-    }
-
-    /**
-     * Simulates a key press by injecting touch an event into the keyboard view.
-     * This avoids the complexity of trackers and listeners within the keyboard.
-     *
-     * @param key The key to press.
-     */
-    private long simulateKeyPress(final Key key) {
-        final int x = key.getHitBox().centerX();
-        final int y = key.getHitBox().centerY();
-        final long downTime = SystemClock.uptimeMillis();
-        final MotionEvent downEvent = MotionEvent.obtain(
-                downTime, downTime, MotionEvent.ACTION_DOWN, x, y, 0);
-        mView.onTouchEvent(downEvent);
-        downEvent.recycle();
-        return downTime;
-    }
-
-    /**
-     * Simulates a key release by injecting touch an event into the keyboard view.
-     * This avoids the complexity of trackers and listeners within the keyboard.
-     *
-     * @param key The key to release.
-     */
-    private void simulateKeyRelease(final Key key, final long downTime) {
-        final int x = key.getHitBox().centerX();
-        final int y = key.getHitBox().centerY();
-        final MotionEvent upEvent = MotionEvent.obtain(
-                downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, x, y, 0);
-        mView.onTouchEvent(upEvent);
-        upEvent.recycle();
-    }
-
-    /**
-     * Simulates a transition between two {@link Key}s by sending a HOVER_EXIT on the previous key,
-     * a HOVER_ENTER on the current key, and a HOVER_MOVE on the current key.
-     *
-     * @param currentKey The currently hovered key.
-     * @param previousKey The previously hovered key.
-     * @param event The event that triggered the transition.
-     * @return {@code true} if the event was handled.
-     */
-    private boolean onTransitionKey(final Key currentKey, final Key previousKey,
-            final MotionEvent event) {
-        final int savedAction = event.getAction();
-        event.setAction(MotionEvent.ACTION_HOVER_EXIT);
-        onHoverKey(previousKey, event);
-        event.setAction(MotionEvent.ACTION_HOVER_ENTER);
-        onHoverKey(currentKey, event);
-        event.setAction(MotionEvent.ACTION_HOVER_MOVE);
-        final boolean handled = onHoverKey(currentKey, event);
-        event.setAction(savedAction);
-        return handled;
-    }
-
-    /**
-     * Handles a hover event on a key. If {@link Key} extended View, this would be analogous to
-     * calling View.onHoverEvent(MotionEvent).
-     *
-     * @param key The currently hovered key.
-     * @param event The hover event.
-     * @return {@code true} if the event was handled.
-     */
-    private boolean onHoverKey(final Key key, final MotionEvent event) {
-        // Null keys can't receive events.
-        if (key == null) {
-            return false;
-        }
-        final MainKeyboardAccessibilityNodeProvider provider = getAccessibilityNodeProvider();
-
-        switch (event.getAction()) {
-        case MotionEvent.ACTION_HOVER_ENTER:
-            provider.sendAccessibilityEventForKey(
-                    key, AccessibilityEventCompat.TYPE_VIEW_HOVER_ENTER);
-            provider.performActionForKey(
-                    key, AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS, null);
-            break;
-        case MotionEvent.ACTION_HOVER_EXIT:
-            provider.sendAccessibilityEventForKey(
-                    key, AccessibilityEventCompat.TYPE_VIEW_HOVER_EXIT);
-            break;
-        }
-        return true;
-    }
 }
diff --git a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
index 26b6dd0..86036cc 100644
--- a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
@@ -269,7 +269,7 @@
         mLanguageOnSpacebarHorizontalMargin = (int)getResources().getDimension(
                 R.dimen.config_language_on_spacebar_horizontal_margin);
 
-        mAccessibilityDelegate = new MainKeyboardAccessibilityDelegate(this);
+        mAccessibilityDelegate = new MainKeyboardAccessibilityDelegate(this, mKeyDetector);
     }
 
     @Override
@@ -773,12 +773,11 @@
      */
     @Override
     public boolean dispatchHoverEvent(final MotionEvent event) {
-        if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()) {
-            return mAccessibilityDelegate.dispatchHoverEvent(event, mKeyDetector);
+        if (!AccessibilityUtils.getInstance().isTouchExplorationEnabled()) {
+            // Reflection doesn't support calling superclass methods.
+            return false;
         }
-
-        // Reflection doesn't support calling superclass methods.
-        return false;
+        return mAccessibilityDelegate.dispatchHoverEvent(event);
     }
 
     public void updateShortcutKey(final boolean available) {
diff --git a/java/src/com/android/inputmethod/keyboard/emoji/EmojiCategory.java b/java/src/com/android/inputmethod/keyboard/emoji/EmojiCategory.java
index 495fa55..8590991 100644
--- a/java/src/com/android/inputmethod/keyboard/emoji/EmojiCategory.java
+++ b/java/src/com/android/inputmethod/keyboard/emoji/EmojiCategory.java
@@ -18,6 +18,7 @@
 
 import android.content.SharedPreferences;
 import android.content.res.Resources;
+import android.content.res.TypedArray;
 import android.graphics.Rect;
 import android.os.Build;
 import android.util.Log;
@@ -69,17 +70,14 @@
             "symbols",
             "emoticons" };
 
-    private static final int[] sCategoryIcon = {
-            R.drawable.ic_emoji_recent_holo_dark,
-            R.drawable.ic_emoji_people_holo_dark,
-            R.drawable.ic_emoji_objects_holo_dark,
-            R.drawable.ic_emoji_nature_holo_dark,
-            R.drawable.ic_emoji_places_holo_dark,
-            R.drawable.ic_emoji_symbols_holo_dark,
-            0 };
-
-    private static final String[] sCategoryLabel =
-            { null, null, null, null, null, null, ":-)" };
+    private static final int[] sCategoryTabIconAttr = {
+            R.styleable.EmojiPalettesView_iconEmojiRecentsTab,
+            R.styleable.EmojiPalettesView_iconEmojiCategory1Tab,
+            R.styleable.EmojiPalettesView_iconEmojiCategory2Tab,
+            R.styleable.EmojiPalettesView_iconEmojiCategory3Tab,
+            R.styleable.EmojiPalettesView_iconEmojiCategory4Tab,
+            R.styleable.EmojiPalettesView_iconEmojiCategory5Tab,
+            R.styleable.EmojiPalettesView_iconEmojiCategory6Tab };
 
     private static final int[] sAccessibilityDescriptionResourceIdsForCategories = {
             R.string.spoken_descrption_emoji_category_recents,
@@ -104,6 +102,7 @@
     private final int mMaxPageKeyCount;
     private final KeyboardLayoutSet mLayoutSet;
     private final HashMap<String, Integer> mCategoryNameToIdMap = CollectionUtils.newHashMap();
+    private final int[] mCategoryTabIconId = new int[sCategoryName.length];
     private final ArrayList<CategoryProperties> mShownCategories =
             CollectionUtils.newArrayList();
     private final ConcurrentHashMap<Long, DynamicGridKeyboard>
@@ -113,13 +112,15 @@
     private int mCurrentCategoryPageId = 0;
 
     public EmojiCategory(final SharedPreferences prefs, final Resources res,
-            final KeyboardLayoutSet layoutSet) {
+            final KeyboardLayoutSet layoutSet, final TypedArray emojiPaletteViewAttr) {
         mPrefs = prefs;
         mRes = res;
         mMaxPageKeyCount = res.getInteger(R.integer.config_emoji_keyboard_max_page_key_count);
         mLayoutSet = layoutSet;
         for (int i = 0; i < sCategoryName.length; ++i) {
             mCategoryNameToIdMap.put(sCategoryName[i], i);
+            mCategoryTabIconId[i] = emojiPaletteViewAttr.getResourceId(
+                    sCategoryTabIconAttr[i], 0);
         }
         addShownCategoryId(EmojiCategory.ID_RECENTS);
         if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR2
@@ -158,12 +159,8 @@
         return mCategoryNameToIdMap.get(strings[0]);
     }
 
-    public int getCategoryIcon(final int categoryId) {
-        return sCategoryIcon[categoryId];
-    }
-
-    public String getCategoryLabel(final int categoryId) {
-        return sCategoryLabel[categoryId];
+    public int getCategoryTabIcon(final int categoryId) {
+        return mCategoryTabIconId[categoryId];
     }
 
     public String getAccessibilityDescription(final int categoryId) {
diff --git a/java/src/com/android/inputmethod/keyboard/emoji/EmojiCategoryPageIndicatorView.java b/java/src/com/android/inputmethod/keyboard/emoji/EmojiCategoryPageIndicatorView.java
index f5aa107..a6b0891 100644
--- a/java/src/com/android/inputmethod/keyboard/emoji/EmojiCategoryPageIndicatorView.java
+++ b/java/src/com/android/inputmethod/keyboard/emoji/EmojiCategoryPageIndicatorView.java
@@ -16,29 +16,36 @@
 
 package com.android.inputmethod.keyboard.emoji;
 
-import com.android.inputmethod.latin.R;
-
 import android.content.Context;
+import android.content.res.TypedArray;
 import android.graphics.Canvas;
 import android.graphics.Paint;
 import android.util.AttributeSet;
-import android.widget.LinearLayout;
+import android.view.View;
 
-public final class EmojiCategoryPageIndicatorView extends LinearLayout {
+import com.android.inputmethod.latin.R;
+
+public final class EmojiCategoryPageIndicatorView extends View {
     private static final float BOTTOM_MARGIN_RATIO = 1.0f;
     private final Paint mPaint = new Paint();
     private int mCategoryPageSize = 0;
     private int mCurrentCategoryPageId = 0;
     private float mOffset = 0.0f;
 
-    public EmojiCategoryPageIndicatorView(final Context context) {
-        this(context, null /* attrs */);
+    public EmojiCategoryPageIndicatorView(final Context context, final AttributeSet attrs) {
+        this(context, attrs, R.attr.emojiCategoryPageIndicatorViewStyle);
     }
 
-    public EmojiCategoryPageIndicatorView(final Context context, final AttributeSet attrs) {
-        super(context, attrs);
-        mPaint.setColor(context.getResources().getColor(
-                R.color.emoji_category_page_id_foreground_holo));
+    public EmojiCategoryPageIndicatorView(final Context context, final AttributeSet attrs,
+            final int defStyle) {
+        super(context, attrs, defStyle);
+        final TypedArray indicatorViewAttr = context.obtainStyledAttributes(attrs,
+                R.styleable.EmojiCategoryPageIndicatorView, defStyle,
+                R.style.EmojiCategoryPageIndicatorView);
+        final int indicatorColor = indicatorViewAttr.getColor(
+                R.styleable.EmojiCategoryPageIndicatorView_emojiCategoryPageIndicatorColor, 0);
+        indicatorViewAttr.recycle();
+        mPaint.setColor(indicatorColor);
     }
 
     public void setCategoryPageId(final int size, final int id, final float offset) {
diff --git a/java/src/com/android/inputmethod/keyboard/emoji/EmojiLayoutParams.java b/java/src/com/android/inputmethod/keyboard/emoji/EmojiLayoutParams.java
index 77c183a..fbc1282 100644
--- a/java/src/com/android/inputmethod/keyboard/emoji/EmojiLayoutParams.java
+++ b/java/src/com/android/inputmethod/keyboard/emoji/EmojiLayoutParams.java
@@ -16,14 +16,15 @@
 
 package com.android.inputmethod.keyboard.emoji;
 
-import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.utils.ResourceUtils;
-
 import android.content.res.Resources;
 import android.support.v4.view.ViewPager;
+import android.view.View;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 
+import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.utils.ResourceUtils;
+
 final class EmojiLayoutParams {
     private static final int DEFAULT_KEYBOARD_ROWS = 4;
 
@@ -67,10 +68,10 @@
         vp.setLayoutParams(lp);
     }
 
-    public void setCategoryPageIdViewProperties(final LinearLayout ll) {
-        final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) ll.getLayoutParams();
+    public void setCategoryPageIdViewProperties(final View v) {
+        final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) v.getLayoutParams();
         lp.height = mEmojiCategoryPageIdViewHeight;
-        ll.setLayoutParams(lp);
+        v.setLayoutParams(lp);
     }
 
     public int getActionBarHeight() {
diff --git a/java/src/com/android/inputmethod/keyboard/emoji/EmojiPageKeyboardView.java b/java/src/com/android/inputmethod/keyboard/emoji/EmojiPageKeyboardView.java
index d14ffee..48efa17 100644
--- a/java/src/com/android/inputmethod/keyboard/emoji/EmojiPageKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/emoji/EmojiPageKeyboardView.java
@@ -22,6 +22,8 @@
 import android.view.GestureDetector;
 import android.view.MotionEvent;
 
+import com.android.inputmethod.accessibility.AccessibilityUtils;
+import com.android.inputmethod.accessibility.KeyboardAccessibilityDelegate;
 import com.android.inputmethod.keyboard.Key;
 import com.android.inputmethod.keyboard.KeyDetector;
 import com.android.inputmethod.keyboard.Keyboard;
@@ -53,6 +55,7 @@
     private OnKeyEventListener mListener = EMPTY_LISTENER;
     private final KeyDetector mKeyDetector = new KeyDetector();
     private final GestureDetector mGestureDetector;
+    private final KeyboardAccessibilityDelegate<EmojiPageKeyboardView> mAccessibilityDelegate;
 
     public EmojiPageKeyboardView(final Context context, final AttributeSet attrs) {
         this(context, attrs, R.attr.keyboardViewStyle);
@@ -64,6 +67,7 @@
         mGestureDetector = new GestureDetector(context, this);
         mGestureDetector.setIsLongpressEnabled(false /* isLongpressEnabled */);
         mHandler = new Handler();
+        mAccessibilityDelegate = new KeyboardAccessibilityDelegate<>(this, mKeyDetector);
     }
 
     public void setOnKeyEventListener(final OnKeyEventListener listener) {
@@ -79,6 +83,15 @@
         mKeyDetector.setKeyboard(keyboard, 0 /* correctionX */, 0 /* correctionY */);
     }
 
+    @Override
+    public boolean dispatchHoverEvent(final MotionEvent event) {
+        if (!AccessibilityUtils.getInstance().isTouchExplorationEnabled()) {
+            // Reflection doesn't support calling superclass methods.
+            return false;
+        }
+        return mAccessibilityDelegate.dispatchHoverEvent(event);
+    }
+
     /**
      * {@inheritDoc}
      */
diff --git a/java/src/com/android/inputmethod/keyboard/emoji/EmojiPalettesView.java b/java/src/com/android/inputmethod/keyboard/emoji/EmojiPalettesView.java
index 8e45957..6b9e880 100644
--- a/java/src/com/android/inputmethod/keyboard/emoji/EmojiPalettesView.java
+++ b/java/src/com/android/inputmethod/keyboard/emoji/EmojiPalettesView.java
@@ -19,11 +19,9 @@
 import static com.android.inputmethod.latin.Constants.NOT_A_COORDINATE;
 
 import android.content.Context;
-import android.content.res.ColorStateList;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.graphics.Color;
-import android.graphics.Typeface;
 import android.os.CountDownTimer;
 import android.preference.PreferenceManager;
 import android.support.v4.view.ViewPager;
@@ -56,7 +54,7 @@
 
 /**
  * View class to implement Emoji palettes.
- * The Emoji keyboard consists of group of views {@link R.layout#emoji_palettes_view}.
+ * The Emoji keyboard consists of group of views layout/emoji_palettes_view.
  * <ol>
  * <li> Emoji category tabs.
  * <li> Delete button.
@@ -70,7 +68,6 @@
         EmojiPageKeyboardView.OnKeyEventListener {
     private final int mFunctionalKeyBackgroundId;
     private final int mSpacebarBackgroundId;
-    private final ColorStateList mTabLabelColor;
     private final DeleteKeyOnTouchListener mDeleteKeyOnTouchListener;
     private EmojiPalettesAdapter mEmojiPalettesAdapter;
     private final EmojiLayoutParams mEmojiLayoutParams;
@@ -103,11 +100,6 @@
         mSpacebarBackgroundId = keyboardViewAttr.getResourceId(
                 R.styleable.KeyboardView_spacebarBackground, keyBackgroundId);
         keyboardViewAttr.recycle();
-        final TypedArray emojiPalettesViewAttr = context.obtainStyledAttributes(attrs,
-                R.styleable.EmojiPalettesView, defStyle, R.style.EmojiPalettesView);
-        mTabLabelColor = emojiPalettesViewAttr.getColorStateList(
-                R.styleable.EmojiPalettesView_emojiTabLabelColor);
-        emojiPalettesViewAttr.recycle();
         final KeyboardLayoutSet.Builder builder = new KeyboardLayoutSet.Builder(
                 context, null /* editorInfo */);
         final Resources res = context.getResources();
@@ -117,8 +109,12 @@
                 mEmojiLayoutParams.mEmojiKeyboardHeight);
         builder.setOptions(false /* shortcutImeEnabled */, false /* showsVoiceInputKey */,
                 false /* languageSwitchKeyEnabled */);
+        final KeyboardLayoutSet layoutSet = builder.build();
+        final TypedArray emojiPalettesViewAttr = context.obtainStyledAttributes(attrs,
+                R.styleable.EmojiPalettesView, defStyle, R.style.EmojiPalettesView);
         mEmojiCategory = new EmojiCategory(PreferenceManager.getDefaultSharedPreferences(context),
-                context.getResources(), builder.build());
+                res, layoutSet, emojiPalettesViewAttr);
+        emojiPalettesViewAttr.recycle();
         mDeleteKeyOnTouchListener = new DeleteKeyOnTouchListener(context);
     }
 
@@ -139,24 +135,11 @@
         final String tabId = mEmojiCategory.getCategoryName(categoryId, 0 /* categoryPageId */);
         final TabHost.TabSpec tspec = host.newTabSpec(tabId);
         tspec.setContent(R.id.emoji_keyboard_dummy);
-        if (mEmojiCategory.getCategoryIcon(categoryId) != 0) {
-            final ImageView iconView = (ImageView)LayoutInflater.from(getContext()).inflate(
-                    R.layout.emoji_keyboard_tab_icon, null);
-            iconView.setImageResource(mEmojiCategory.getCategoryIcon(categoryId));
-            iconView.setContentDescription(mEmojiCategory.getAccessibilityDescription(categoryId));
-            tspec.setIndicator(iconView);
-        }
-        if (mEmojiCategory.getCategoryLabel(categoryId) != null) {
-            final TextView textView = (TextView)LayoutInflater.from(getContext()).inflate(
-                    R.layout.emoji_keyboard_tab_label, null);
-            textView.setText(mEmojiCategory.getCategoryLabel(categoryId));
-            textView.setTypeface(Typeface.DEFAULT_BOLD);
-            textView.setContentDescription(mEmojiCategory.getAccessibilityDescription(categoryId));
-            if (mTabLabelColor != null) {
-                textView.setTextColor(mTabLabelColor);
-            }
-            tspec.setIndicator(textView);
-        }
+        final ImageView iconView = (ImageView)LayoutInflater.from(getContext()).inflate(
+                R.layout.emoji_keyboard_tab_icon, null);
+        iconView.setImageResource(mEmojiCategory.getCategoryTabIcon(categoryId));
+        iconView.setContentDescription(mEmojiCategory.getAccessibilityDescription(categoryId));
+        tspec.setIndicator(iconView);
         host.addTab(tspec);
     }
 
@@ -353,7 +336,7 @@
     private static void setupAlphabetKey(final TextView alphabetKey, final String label,
             final KeyDrawParams params) {
         alphabetKey.setText(label);
-        alphabetKey.setTextColor(params.mTextColor);
+        alphabetKey.setTextColor(params.mFunctionalTextColor);
         alphabetKey.setTextSize(TypedValue.COMPLEX_UNIT_PX, params.mLabelSize);
         alphabetKey.setTypeface(params.mTypeface);
     }
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
index b8cf3f8..e7ab02a 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
@@ -359,14 +359,16 @@
     }
 
     @UsedForTesting
-    public boolean isValidBigram(final String word0, final String word1) {
-        return getBigramProbability(word0, word1) != NOT_A_PROBABILITY;
+    public boolean isValidNgram(final PrevWordsInfo prevWordsInfo, final String word) {
+        return getNgramProbability(prevWordsInfo, word) != 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);
+    public int getNgramProbability(final PrevWordsInfo prevWordsInfo, final String word) {
+        if (TextUtils.isEmpty(prevWordsInfo.mPrevWord) || TextUtils.isEmpty(word)) {
+            return NOT_A_PROBABILITY;
+        }
+        final int[] codePoints0 = StringUtils.toCodePointArray(prevWordsInfo.mPrevWord);
+        final int[] codePoints1 = StringUtils.toCodePointArray(word);
         return getBigramProbabilityNative(mNativeDict, codePoints0, codePoints1);
     }
 
@@ -417,7 +419,7 @@
     }
 
     // Add a unigram entry to binary dictionary with unigram attributes in native code.
-    public void addUnigramWord(final String word, final int probability,
+    public void addUnigramEntry(final String word, final int probability,
             final String shortcutTarget, final int shortcutProbability, final boolean isNotAWord,
             final boolean isBlacklisted, final int timestamp) {
         if (TextUtils.isEmpty(word)) {
@@ -431,25 +433,26 @@
         mHasUpdated = true;
     }
 
-    // Add a bigram entry to binary dictionary with timestamp in native code.
-    public void addBigramWords(final String word0, final String word1, final int probability,
+    // Add an n-gram entry to the binary dictionary with timestamp in native code.
+    public void addNgramEntry(final PrevWordsInfo prevWordsInfo, final String word,
+            final int probability,
             final int timestamp) {
-        if (TextUtils.isEmpty(word0) || TextUtils.isEmpty(word1)) {
+        if (TextUtils.isEmpty(prevWordsInfo.mPrevWord) || TextUtils.isEmpty(word)) {
             return;
         }
-        final int[] codePoints0 = StringUtils.toCodePointArray(word0);
-        final int[] codePoints1 = StringUtils.toCodePointArray(word1);
+        final int[] codePoints0 = StringUtils.toCodePointArray(prevWordsInfo.mPrevWord);
+        final int[] codePoints1 = StringUtils.toCodePointArray(word);
         addBigramWordsNative(mNativeDict, codePoints0, codePoints1, probability, timestamp);
         mHasUpdated = true;
     }
 
-    // Remove a bigram entry form binary dictionary in native code.
-    public void removeBigramWords(final String word0, final String word1) {
-        if (TextUtils.isEmpty(word0) || TextUtils.isEmpty(word1)) {
+    // Remove an n-gram entry from the binary dictionary in native code.
+    public void removeNgramEntry(final PrevWordsInfo prevWordsInfo, final String word) {
+        if (TextUtils.isEmpty(prevWordsInfo.mPrevWord) || TextUtils.isEmpty(word)) {
             return;
         }
-        final int[] codePoints0 = StringUtils.toCodePointArray(word0);
-        final int[] codePoints1 = StringUtils.toCodePointArray(word1);
+        final int[] codePoints0 = StringUtils.toCodePointArray(prevWordsInfo.mPrevWord);
+        final int[] codePoints1 = StringUtils.toCodePointArray(word);
         removeBigramWordsNative(mNativeDict, codePoints0, codePoints1);
         mHasUpdated = true;
     }
diff --git a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
index e04fcda..3fb76b1 100644
--- a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
@@ -142,7 +142,7 @@
                 Log.d(TAG, "loadAccountVocabulary: " + word);
             }
             runGCIfRequiredLocked(true /* mindsBlockByGC */);
-            addWordDynamicallyLocked(word, FREQUENCY_FOR_CONTACTS, null /* shortcut */,
+            addUnigramLocked(word, FREQUENCY_FOR_CONTACTS, null /* shortcut */,
                     0 /* shortcutFreq */, false /* isNotAWord */, false /* isBlacklisted */,
                     BinaryDictionary.NOT_A_VALID_TIMESTAMP);
         }
@@ -224,7 +224,7 @@
      */
     private void addNameLocked(final String name) {
         int len = StringUtils.codePointCount(name);
-        String prevWord = null;
+        PrevWordsInfo prevWordsInfo = new PrevWordsInfo(null);
         // TODO: Better tokenization for non-Latin writing systems
         for (int i = 0; i < len; i++) {
             if (Character.isLetter(name.codePointAt(i))) {
@@ -239,19 +239,19 @@
                 final int wordLen = StringUtils.codePointCount(word);
                 if (wordLen < MAX_WORD_LENGTH && wordLen > 1) {
                     if (DEBUG) {
-                        Log.d(TAG, "addName " + name + ", " + word + ", " + prevWord);
+                        Log.d(TAG, "addName " + name + ", " + word + ", "
+                                + prevWordsInfo.mPrevWord);
                     }
                     runGCIfRequiredLocked(true /* mindsBlockByGC */);
-                    addWordDynamicallyLocked(word, FREQUENCY_FOR_CONTACTS,
+                    addUnigramLocked(word, FREQUENCY_FOR_CONTACTS,
                             null /* shortcut */, 0 /* shortcutFreq */, false /* isNotAWord */,
                             false /* isBlacklisted */, BinaryDictionary.NOT_A_VALID_TIMESTAMP);
-                    if (!TextUtils.isEmpty(prevWord) && mUseFirstLastBigrams) {
+                    if (!TextUtils.isEmpty(prevWordsInfo.mPrevWord) && mUseFirstLastBigrams) {
                         runGCIfRequiredLocked(true /* mindsBlockByGC */);
-                        addBigramDynamicallyLocked(prevWord, word,
-                                FREQUENCY_FOR_CONTACTS_BIGRAM,
+                        addNgramEntryLocked(prevWordsInfo, word, FREQUENCY_FOR_CONTACTS_BIGRAM,
                                 BinaryDictionary.NOT_A_VALID_TIMESTAMP);
                     }
-                    prevWord = word;
+                    prevWordsInfo = new PrevWordsInfo(word);
                 }
             }
         }
diff --git a/java/src/com/android/inputmethod/latin/DictionaryFacilitatorForSuggest.java b/java/src/com/android/inputmethod/latin/DictionaryFacilitatorForSuggest.java
index 14c8bb6..301b832 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryFacilitatorForSuggest.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryFacilitatorForSuggest.java
@@ -370,22 +370,23 @@
     }
 
     public void addToUserHistory(final String suggestion, final boolean wasAutoCapitalized,
-            final String previousWord, final int timeStampInSeconds,
+            final PrevWordsInfo prevWordsInfo, final int timeStampInSeconds,
             final boolean blockPotentiallyOffensive) {
         final Dictionaries dictionaries = mDictionaries;
         final String[] words = suggestion.split(Constants.WORD_SEPARATOR);
         for (int i = 0; i < words.length; i++) {
             final String currentWord = words[i];
-            final String prevWord = (i == 0) ? previousWord : words[i - 1];
+            final PrevWordsInfo prevWordsInfoForCurrentWord =
+                    (i == 0) ? prevWordsInfo : new PrevWordsInfo(words[i - 1]);
             final boolean wasCurrentWordAutoCapitalized = (i == 0) ? wasAutoCapitalized : false;
-            addWordToUserHistory(dictionaries, prevWord, currentWord,
+            addWordToUserHistory(dictionaries, prevWordsInfoForCurrentWord, currentWord,
                     wasCurrentWordAutoCapitalized, timeStampInSeconds, blockPotentiallyOffensive);
         }
     }
 
-    private void addWordToUserHistory(final Dictionaries dictionaries, final String prevWord,
-            final String word, final boolean wasAutoCapitalized, final int timeStampInSeconds,
-            final boolean blockPotentiallyOffensive) {
+    private void addWordToUserHistory(final Dictionaries dictionaries,
+            final PrevWordsInfo prevWordsInfo, final String word, final boolean wasAutoCapitalized,
+            final int timeStampInSeconds, final boolean blockPotentiallyOffensive) {
         final ExpandableBinaryDictionary userHistoryDictionary =
                 dictionaries.getSubDict(Dictionary.TYPE_USER_HISTORY);
         if (userHistoryDictionary == null) {
@@ -430,15 +431,16 @@
         // We demote unrecognized words (frequency < 0, below) by specifying them as "invalid".
         // We don't add words with 0-frequency (assuming they would be profanity etc.).
         final boolean isValid = maxFreq > 0;
-        UserHistoryDictionary.addToDictionary(userHistoryDictionary, prevWord, secondWord,
+        UserHistoryDictionary.addToDictionary(userHistoryDictionary, prevWordsInfo, secondWord,
                 isValid, timeStampInSeconds);
     }
 
-    public void cancelAddingUserHistory(final String previousWord, final String committedWord) {
+    public void cancelAddingUserHistory(final PrevWordsInfo prevWordsInfo,
+            final String committedWord) {
         final ExpandableBinaryDictionary userHistoryDictionary =
                 mDictionaries.getSubDict(Dictionary.TYPE_USER_HISTORY);
         if (userHistoryDictionary != null) {
-            userHistoryDictionary.removeBigramDynamically(previousWord, committedWord);
+            userHistoryDictionary.removeNgramDynamically(prevWordsInfo, committedWord);
         }
     }
 
diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
index 629f3fd..d67253c 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
@@ -269,9 +269,9 @@
     }
 
     /**
-     * Dynamically adds a word unigram to the dictionary. May overwrite an existing entry.
+     * Adds unigram information of a word to the dictionary. May overwrite an existing entry.
      */
-    public void addWordDynamically(final String word, final int frequency,
+    public void addUnigramEntry(final String word, final int frequency,
             final String shortcutTarget, final int shortcutFreq, final boolean isNotAWord,
             final boolean isBlacklisted, final int timestamp) {
         reloadDictionaryIfRequired();
@@ -282,23 +282,23 @@
                     return;
                 }
                 runGCIfRequiredLocked(true /* mindsBlockByGC */);
-                addWordDynamicallyLocked(word, frequency, shortcutTarget, shortcutFreq,
+                addUnigramLocked(word, frequency, shortcutTarget, shortcutFreq,
                         isNotAWord, isBlacklisted, timestamp);
             }
         });
     }
 
-    protected void addWordDynamicallyLocked(final String word, final int frequency,
+    protected void addUnigramLocked(final String word, final int frequency,
             final String shortcutTarget, final int shortcutFreq, final boolean isNotAWord,
             final boolean isBlacklisted, final int timestamp) {
-        mBinaryDictionary.addUnigramWord(word, frequency, shortcutTarget, shortcutFreq,
+        mBinaryDictionary.addUnigramEntry(word, frequency, shortcutTarget, shortcutFreq,
                 isNotAWord, isBlacklisted, timestamp);
     }
 
     /**
-     * Dynamically adds a word bigram in the dictionary. May overwrite an existing entry.
+     * Adds n-gram information of a word to the dictionary. May overwrite an existing entry.
      */
-    public void addBigramDynamically(final String word0, final String word1,
+    public void addNgramEntry(final PrevWordsInfo prevWordsInfo, final String word,
             final int frequency, final int timestamp) {
         reloadDictionaryIfRequired();
         asyncExecuteTaskWithWriteLock(new Runnable() {
@@ -308,20 +308,20 @@
                     return;
                 }
                 runGCIfRequiredLocked(true /* mindsBlockByGC */);
-                addBigramDynamicallyLocked(word0, word1, frequency, timestamp);
+                addNgramEntryLocked(prevWordsInfo, word, frequency, timestamp);
             }
         });
     }
 
-    protected void addBigramDynamicallyLocked(final String word0, final String word1,
+    protected void addNgramEntryLocked(final PrevWordsInfo prevWordsInfo, final String word,
             final int frequency, final int timestamp) {
-        mBinaryDictionary.addBigramWords(word0, word1, frequency, timestamp);
+        mBinaryDictionary.addNgramEntry(prevWordsInfo, word, frequency, timestamp);
     }
 
     /**
-     * Dynamically remove a word bigram in the dictionary.
+     * Dynamically remove the n-gram entry in the dictionary.
      */
-    public void removeBigramDynamically(final String word0, final String word1) {
+    public void removeNgramDynamically(final PrevWordsInfo prevWordsInfo, final String word1) {
         reloadDictionaryIfRequired();
         asyncExecuteTaskWithWriteLock(new Runnable() {
             @Override
@@ -330,7 +330,7 @@
                     return;
                 }
                 runGCIfRequiredLocked(true /* mindsBlockByGC */);
-                mBinaryDictionary.removeBigramWords(word0, word1);
+                mBinaryDictionary.removeNgramEntry(prevWordsInfo, word1);
             }
         });
     }
@@ -428,9 +428,9 @@
         return mBinaryDictionary.isValidWord(word);
     }
 
-    protected boolean isValidBigramLocked(final String word1, final String word2) {
+    protected boolean isValidNgramLocked(final PrevWordsInfo prevWordsInfo, final String word) {
         if (mBinaryDictionary == null) return false;
-        return mBinaryDictionary.isValidBigram(word1, word2);
+        return mBinaryDictionary.isValidNgram(prevWordsInfo, word);
     }
 
     /**
diff --git a/java/src/com/android/inputmethod/latin/InputAttributes.java b/java/src/com/android/inputmethod/latin/InputAttributes.java
index 726b3d1..df49483 100644
--- a/java/src/com/android/inputmethod/latin/InputAttributes.java
+++ b/java/src/com/android/inputmethod/latin/InputAttributes.java
@@ -46,6 +46,8 @@
         final int inputType = null != editorInfo ? editorInfo.inputType : 0;
         final int inputClass = inputType & InputType.TYPE_MASK_CLASS;
         mInputType = inputType;
+        mIsPasswordField = InputTypeUtils.isPasswordInputType(inputType)
+                || InputTypeUtils.isVisiblePasswordInputType(inputType);
         if (inputClass != InputType.TYPE_CLASS_TEXT) {
             // If we are not looking at a TYPE_CLASS_TEXT field, the following strange
             // cases may arise, so we do a couple sanity checks for them. If it's a
@@ -61,7 +63,6 @@
                 Log.w(TAG, String.format("Unexpected input class: inputType=0x%08x"
                         + " imeOptions=0x%08x", inputType, editorInfo.imeOptions));
             }
-            mIsPasswordField = false;
             mIsSettingsSuggestionStripOn = false;
             mInputTypeNoAutoCorrect = false;
             mApplicationSpecifiedCompletionOn = false;
@@ -79,8 +80,6 @@
         final boolean flagAutoComplete =
                 0 != (inputType & InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE);
 
-        mIsPasswordField = InputTypeUtils.isPasswordInputType(inputType)
-                || InputTypeUtils.isVisiblePasswordInputType(inputType);
         // TODO: Have a helper method in InputTypeUtils
         // Make sure that passwords are not displayed in {@link SuggestionStripView}.
         final boolean noSuggestionStrip = mIsPasswordField
diff --git a/java/src/com/android/inputmethod/latin/LastComposedWord.java b/java/src/com/android/inputmethod/latin/LastComposedWord.java
index 232bf74..9caec3e 100644
--- a/java/src/com/android/inputmethod/latin/LastComposedWord.java
+++ b/java/src/com/android/inputmethod/latin/LastComposedWord.java
@@ -48,7 +48,7 @@
     public final String mTypedWord;
     public final CharSequence mCommittedWord;
     public final String mSeparatorString;
-    public final String mPrevWord;
+    public final PrevWordsInfo mPrevWordsInfo;
     public final int mCapitalizedMode;
     public final InputPointers mInputPointers =
             new InputPointers(Constants.DICTIONARY_MAX_WORD_LENGTH);
@@ -64,7 +64,7 @@
     public LastComposedWord(final ArrayList<Event> events,
             final InputPointers inputPointers, final String typedWord,
             final CharSequence committedWord, final String separatorString,
-            final String prevWord, final int capitalizedMode) {
+            final PrevWordsInfo prevWordsInfo, final int capitalizedMode) {
         if (inputPointers != null) {
             mInputPointers.copy(inputPointers);
         }
@@ -73,7 +73,7 @@
         mCommittedWord = committedWord;
         mSeparatorString = separatorString;
         mActive = true;
-        mPrevWord = prevWord;
+        mPrevWordsInfo = prevWordsInfo;
         mCapitalizedMode = capitalizedMode;
     }
 
diff --git a/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java b/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
index c8ffbe4..b89ab84 100644
--- a/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
@@ -258,12 +258,12 @@
                 // Safeguard against adding really long words.
                 if (word.length() < MAX_WORD_LENGTH) {
                     runGCIfRequiredLocked(true /* mindsBlockByGC */);
-                    addWordDynamicallyLocked(word, adjustedFrequency, null /* shortcutTarget */,
+                    addUnigramLocked(word, adjustedFrequency, null /* shortcutTarget */,
                             0 /* shortcutFreq */, false /* isNotAWord */,
                             false /* isBlacklisted */, BinaryDictionary.NOT_A_VALID_TIMESTAMP);
                     if (null != shortcut && shortcut.length() < MAX_WORD_LENGTH) {
                         runGCIfRequiredLocked(true /* mindsBlockByGC */);
-                        addWordDynamicallyLocked(shortcut, adjustedFrequency, word,
+                        addUnigramLocked(shortcut, adjustedFrequency, word,
                                 USER_DICT_SHORTCUT_FREQUENCY, true /* isNotAWord */,
                                 false /* isBlacklisted */, BinaryDictionary.NOT_A_VALID_TIMESTAMP);
                     }
diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java
index 227b42b..09e9054 100644
--- a/java/src/com/android/inputmethod/latin/WordComposer.java
+++ b/java/src/com/android/inputmethod/latin/WordComposer.java
@@ -294,11 +294,10 @@
      * This will register NOT_A_COORDINATE for X and Ys, and use the passed keyboard for proximity.
      * @param codePoints the code points to set as the composing word.
      * @param coordinates the x, y coordinates of the key in the CoordinateUtils format
-     * @param previousWord the previous word, to use as context for suggestions. Can be null if
-     *   the context is nil (typically, at start of text).
+     * @param prevWordsInfo the information of previous words, to use as context for suggestions
      */
     public void setComposingWord(final int[] codePoints, final int[] coordinates,
-            final CharSequence previousWord) {
+            final PrevWordsInfo prevWordsInfo) {
         reset();
         final int length = codePoints.length;
         for (int i = 0; i < length; ++i) {
@@ -307,7 +306,7 @@
                     CoordinateUtils.yFromArray(coordinates, i)));
         }
         mIsResumed = true;
-        mPrevWordsInfo = new PrevWordsInfo(null == previousWord ? null : previousWord.toString());
+        mPrevWordsInfo = prevWordsInfo;
     }
 
     /**
@@ -413,13 +412,13 @@
     // `type' should be one of the LastComposedWord.COMMIT_TYPE_* constants above.
     // committedWord should contain suggestion spans if applicable.
     public LastComposedWord commitWord(final int type, final CharSequence committedWord,
-            final String separatorString, final String prevWord) {
+            final String separatorString, final PrevWordsInfo prevWordsInfo) {
         // Note: currently, we come here whenever we commit a word. If it's a MANUAL_PICK
         // or a DECIDED_WORD we may cancel the commit later; otherwise, we should deactivate
         // the last composed word to ensure this does not happen.
         final LastComposedWord lastComposedWord = new LastComposedWord(mEvents,
                 mInputPointers, mTypedWordCache.toString(), committedWord, separatorString,
-                prevWord, mCapitalizedMode);
+                prevWordsInfo, mCapitalizedMode);
         mInputPointers.reset();
         if (type != LastComposedWord.COMMIT_TYPE_DECIDED_WORD
                 && type != LastComposedWord.COMMIT_TYPE_MANUAL_PICK) {
diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
index ea58abc..58b1012 100644
--- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
+++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
@@ -37,6 +37,7 @@
 import com.android.inputmethod.latin.LastComposedWord;
 import com.android.inputmethod.latin.LatinIME;
 import com.android.inputmethod.latin.LatinImeLogger;
+import com.android.inputmethod.latin.PrevWordsInfo;
 import com.android.inputmethod.latin.RichInputConnection;
 import com.android.inputmethod.latin.Suggest;
 import com.android.inputmethod.latin.Suggest.OnGetSuggestedWordsCallback;
@@ -1233,7 +1234,7 @@
     }
 
     private void performAdditionToUserHistoryDictionary(final SettingsValues settingsValues,
-            final String suggestion, final String prevWord) {
+            final String suggestion, final PrevWordsInfo prevWordsInfo) {
         // If correction is not enabled, we don't add words to the user history dictionary.
         // That's to avoid unintended additions in some sensitive fields, or fields that
         // expect to receive non-words.
@@ -1244,8 +1245,8 @@
                 mWordComposer.wasAutoCapitalized() && !mWordComposer.isMostlyCaps();
         final int timeStampInSeconds = (int)TimeUnit.MILLISECONDS.toSeconds(
                 System.currentTimeMillis());
-        mSuggest.mDictionaryFacilitator.addToUserHistory(suggestion, wasAutoCapitalized, prevWord,
-                timeStampInSeconds, settingsValues.mBlockPotentiallyOffensive);
+        mSuggest.mDictionaryFacilitator.addToUserHistory(suggestion, wasAutoCapitalized,
+                prevWordsInfo, timeStampInSeconds, settingsValues.mBlockPotentiallyOffensive);
     }
 
     public void performUpdateSuggestionStripSync(final SettingsValues settingsValues) {
@@ -1370,13 +1371,15 @@
             }
         }
         final int[] codePoints = StringUtils.toCodePointArray(typedWord);
+        // We want the previous word for suggestion. If we have chars in the word
+        // before the cursor, then we want the word before that, hence 2; otherwise,
+        // we want the word immediately before the cursor, hence 1.
+        final String prevWord = getNthPreviousWordForSuggestion(
+                settingsValues.mSpacingAndPunctuations,
+                0 == numberOfCharsInWordBeforeCursor ? 1 : 2).toString();
         mWordComposer.setComposingWord(codePoints,
                 mLatinIME.getCoordinatesForCurrentKeyboard(codePoints),
-                getNthPreviousWordForSuggestion(settingsValues.mSpacingAndPunctuations,
-                        // We want the previous word for suggestion. If we have chars in the word
-                        // before the cursor, then we want the word before that, hence 2; otherwise,
-                        // we want the word immediately before the cursor, hence 1.
-                        0 == numberOfCharsInWordBeforeCursor ? 1 : 2));
+                new PrevWordsInfo(prevWord));
         mWordComposer.setCursorPositionWithinWord(
                 typedWord.codePointCount(0, numberOfCharsInWordBeforeCursor));
         mConnection.setComposingRegion(expectedCursorPosition - numberOfCharsInWordBeforeCursor,
@@ -1431,7 +1434,7 @@
      * @param inputTransaction The transaction in progress.
      */
     private void revertCommit(final InputTransaction inputTransaction) {
-        final String previousWord = mLastComposedWord.mPrevWord;
+        final PrevWordsInfo prevWordsInfo = mLastComposedWord.mPrevWordsInfo;
         final CharSequence originallyTypedWord = mLastComposedWord.mTypedWord;
         final CharSequence committedWord = mLastComposedWord.mCommittedWord;
         final String committedWordString = committedWord.toString();
@@ -1453,9 +1456,9 @@
             }
         }
         mConnection.deleteSurroundingText(deleteLength, 0);
-        if (!TextUtils.isEmpty(previousWord) && !TextUtils.isEmpty(committedWord)) {
+        if (!TextUtils.isEmpty(prevWordsInfo.mPrevWord) && !TextUtils.isEmpty(committedWord)) {
             mSuggest.mDictionaryFacilitator.cancelAddingUserHistory(
-                    previousWord, committedWordString);
+                    prevWordsInfo, committedWordString);
         }
         final String stringToCommit = originallyTypedWord + mLastComposedWord.mSeparatorString;
         final SpannableString textToCommit = new SpannableString(stringToCommit);
@@ -1504,7 +1507,7 @@
             // with the typed word, so we need to resume suggestions right away.
             final int[] codePoints = StringUtils.toCodePointArray(stringToCommit);
             mWordComposer.setComposingWord(codePoints,
-                    mLatinIME.getCoordinatesForCurrentKeyboard(codePoints), previousWord);
+                    mLatinIME.getCoordinatesForCurrentKeyboard(codePoints), prevWordsInfo);
             mConnection.setComposingText(textToCommit, 1);
         }
         if (inputTransaction.mSettingsValues.mIsInternal) {
@@ -1968,17 +1971,17 @@
                         suggestedWords);
         // Use the 2nd previous word as the previous word because the 1st previous word is the word
         // to be committed.
-        final String prevWord = mConnection.getNthPreviousWord(
-                settingsValues.mSpacingAndPunctuations, 2);
+        final PrevWordsInfo prevWordsInfo = new PrevWordsInfo(mConnection.getNthPreviousWord(
+                settingsValues.mSpacingAndPunctuations, 2));
         mConnection.commitText(chosenWordWithSuggestions, 1);
         // Add the word to the user history dictionary
-        performAdditionToUserHistoryDictionary(settingsValues, chosenWord, prevWord);
+        performAdditionToUserHistoryDictionary(settingsValues, chosenWord, prevWordsInfo);
         // TODO: figure out here if this is an auto-correct or if the best word is actually
         // what user typed. Note: currently this is done much later in
         // LastComposedWord#didCommitTypedWord by string equality of the remembered
         // strings.
         mLastComposedWord = mWordComposer.commitWord(commitType,
-                chosenWordWithSuggestions, separatorString, prevWord);
+                chosenWordWithSuggestions, separatorString, prevWordsInfo);
         final boolean shouldDiscardPreviousWordForSuggestion;
         if (0 == StringUtils.codePointCount(separatorString)) {
             // Separator is 0-length, we can keep the previous word for suggestion. Either this
diff --git a/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java b/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java
index 818cd9a..f89caf9 100644
--- a/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java
@@ -22,6 +22,7 @@
 import com.android.inputmethod.latin.Constants;
 import com.android.inputmethod.latin.Dictionary;
 import com.android.inputmethod.latin.ExpandableBinaryDictionary;
+import com.android.inputmethod.latin.PrevWordsInfo;
 
 import java.io.File;
 import java.util.Locale;
@@ -52,29 +53,32 @@
     }
 
     /**
-     * Pair will be added to the user history dictionary.
+     * Add a word to the user history dictionary.
      *
-     * The first word may be null. That means we don't know the context, in other words,
-     * it's only a unigram. The first word may also be an empty string : this means start
-     * context, as in beginning of a sentence for example.
-     * The second word may not be null (a NullPointerException would be thrown).
+     * @param userHistoryDictionary the user history dictionary
+     * @param prevWordsInfo the information of previous words
+     * @param word the word the user inputted
+     * @param isValid whether the word is valid or not
+     * @param timestamp the timestamp when the word has been inputted
      */
     public static void addToDictionary(final ExpandableBinaryDictionary userHistoryDictionary,
-            final String word0, final String word1, final boolean isValid, final int timestamp) {
-        if (word1.length() >= Constants.DICTIONARY_MAX_WORD_LENGTH ||
-                (word0 != null && word0.length() >= Constants.DICTIONARY_MAX_WORD_LENGTH)) {
+            final PrevWordsInfo prevWordsInfo, final String word, final boolean isValid,
+            final int timestamp) {
+        final String prevWord = prevWordsInfo.mPrevWord;
+        if (word.length() >= Constants.DICTIONARY_MAX_WORD_LENGTH ||
+                (prevWord != null && prevWord.length() >= Constants.DICTIONARY_MAX_WORD_LENGTH)) {
             return;
         }
         final int frequency = isValid ?
                 FREQUENCY_FOR_WORDS_IN_DICTS : FREQUENCY_FOR_WORDS_NOT_IN_DICTS;
-        userHistoryDictionary.addWordDynamically(word1, frequency, null /* shortcutTarget */,
+        userHistoryDictionary.addUnigramEntry(word, frequency, null /* shortcutTarget */,
                 0 /* shortcutFreq */, false /* isNotAWord */, false /* isBlacklisted */, timestamp);
         // Do not insert a word as a bigram of itself
-        if (word1.equals(word0)) {
+        if (word.equals(prevWord)) {
             return;
         }
-        if (null != word0) {
-            userHistoryDictionary.addBigramDynamically(word0, word1, frequency, timestamp);
+        if (null != prevWord) {
+            userHistoryDictionary.addNgramEntry(prevWordsInfo, word, frequency, timestamp);
         }
     }
 }
diff --git a/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java b/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java
index a219532..9ea7e21 100644
--- a/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java
+++ b/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java
@@ -114,7 +114,7 @@
         final int[] codePoints = StringUtils.toCodePointArray(testedWord);
         final int[] coordinates;
         coordinates = mKeyboard.getCoordinates(codePoints);
-        composer.setComposingWord(codePoints, coordinates, prevWordsInfo.mPrevWord);
+        composer.setComposingWord(codePoints, coordinates, prevWordsInfo);
 
         final int trailingSingleQuotesCount = StringUtils.getTrailingSingleQuotesCount(testedWord);
         final String consideredWord = trailingSingleQuotesCount > 0 ?
diff --git a/tests/src/com/android/inputmethod/latin/BinaryDictionaryDecayingTests.java b/tests/src/com/android/inputmethod/latin/BinaryDictionaryDecayingTests.java
index 90b90ff..2c2fed3 100644
--- a/tests/src/com/android/inputmethod/latin/BinaryDictionaryDecayingTests.java
+++ b/tests/src/com/android/inputmethod/latin/BinaryDictionaryDecayingTests.java
@@ -65,7 +65,7 @@
 
     private void addUnigramWord(final BinaryDictionary binaryDictionary, final String word,
             final int probability) {
-        binaryDictionary.addUnigramWord(word, probability, "" /* shortcutTarget */,
+        binaryDictionary.addUnigramEntry(word, probability, "" /* shortcutTarget */,
                 BinaryDictionary.NOT_A_PROBABILITY /* shortcutProbability */,
                 false /* isNotAWord */, false /* isBlacklisted */,
                 mCurrentTime /* timestamp */);
@@ -73,10 +73,15 @@
 
     private void addBigramWords(final BinaryDictionary binaryDictionary, final String word0,
             final String word1, final int probability) {
-        binaryDictionary.addBigramWords(word0, word1, probability,
+        binaryDictionary.addNgramEntry(new PrevWordsInfo(word0), word1, probability,
                 mCurrentTime /* timestamp */);
     }
 
+    private static boolean isValidBigram(final BinaryDictionary binaryDictionary,
+            final String word0, final String word1) {
+        return binaryDictionary.isValidNgram(new PrevWordsInfo(word0), word1);
+    }
+
     private void forcePassingShortTime(final BinaryDictionary binaryDictionary) {
         // 30 days.
         final int timeToElapse = (int)TimeUnit.SECONDS.convert(30, TimeUnit.DAYS);
@@ -224,19 +229,19 @@
         assertTrue(binaryDictionary.isValidWord("b"));
 
         addBigramWords(binaryDictionary, "a", "b", Dictionary.NOT_A_PROBABILITY);
-        assertFalse(binaryDictionary.isValidBigram("a", "b"));
+        assertFalse(isValidBigram(binaryDictionary, "a", "b"));
         addBigramWords(binaryDictionary, "a", "b", Dictionary.NOT_A_PROBABILITY);
-        assertTrue(binaryDictionary.isValidBigram("a", "b"));
+        assertTrue(isValidBigram(binaryDictionary, "a", "b"));
 
         addUnigramWord(binaryDictionary, "c", DUMMY_PROBABILITY);
         addBigramWords(binaryDictionary, "a", "c", DUMMY_PROBABILITY);
-        assertTrue(binaryDictionary.isValidBigram("a", "c"));
+        assertTrue(isValidBigram(binaryDictionary, "a", "c"));
 
         // Add bigrams of not valid unigrams.
         addBigramWords(binaryDictionary, "x", "y", Dictionary.NOT_A_PROBABILITY);
-        assertFalse(binaryDictionary.isValidBigram("x", "y"));
+        assertFalse(isValidBigram(binaryDictionary, "x", "y"));
         addBigramWords(binaryDictionary, "x", "y", DUMMY_PROBABILITY);
-        assertFalse(binaryDictionary.isValidBigram("x", "y"));
+        assertFalse(isValidBigram(binaryDictionary, "x", "y"));
 
         binaryDictionary.close();
         dictFile.delete();
@@ -276,9 +281,9 @@
         addUnigramWord(binaryDictionary, "a", DUMMY_PROBABILITY);
         addUnigramWord(binaryDictionary, "b", DUMMY_PROBABILITY);
         addBigramWords(binaryDictionary, "a", "b", DUMMY_PROBABILITY);
-        assertTrue(binaryDictionary.isValidBigram("a", "b"));
+        assertTrue(isValidBigram(binaryDictionary, "a", "b"));
         forcePassingShortTime(binaryDictionary);
-        assertFalse(binaryDictionary.isValidBigram("a", "b"));
+        assertFalse(isValidBigram(binaryDictionary, "a", "b"));
 
         addUnigramWord(binaryDictionary, "a", DUMMY_PROBABILITY);
         addUnigramWord(binaryDictionary, "b", DUMMY_PROBABILITY);
@@ -289,11 +294,11 @@
         addUnigramWord(binaryDictionary, "a", DUMMY_PROBABILITY);
         addUnigramWord(binaryDictionary, "b", DUMMY_PROBABILITY);
         addBigramWords(binaryDictionary, "a", "b", DUMMY_PROBABILITY);
-        assertTrue(binaryDictionary.isValidBigram("a", "b"));
+        assertTrue(isValidBigram(binaryDictionary, "a", "b"));
         forcePassingShortTime(binaryDictionary);
-        assertTrue(binaryDictionary.isValidBigram("a", "b"));
+        assertTrue(isValidBigram(binaryDictionary, "a", "b"));
         forcePassingLongTime(binaryDictionary);
-        assertFalse(binaryDictionary.isValidBigram("a", "b"));
+        assertFalse(isValidBigram(binaryDictionary, "a", "b"));
 
         binaryDictionary.close();
         dictFile.delete();
@@ -549,8 +554,8 @@
         for (int j = 0; j < weakBigramTypedCount; j++) {
             addBigramWords(binaryDictionary, weak, target, DUMMY_PROBABILITY);
         }
-        assertTrue(binaryDictionary.isValidBigram(strong, target));
-        assertTrue(binaryDictionary.isValidBigram(weak, target));
+        assertTrue(isValidBigram(binaryDictionary, strong, target));
+        assertTrue(isValidBigram(binaryDictionary, weak, target));
 
         for (int i = 0; i < bigramCount; i++) {
             final int word0Index = random.nextInt(words.size());
@@ -571,8 +576,8 @@
                         Integer.parseInt(binaryDictionary.getPropertyForTest(
                                 BinaryDictionary.BIGRAM_COUNT_QUERY));
                 assertTrue(bigramCountBeforeGC > bigramCountAfterGC);
-                assertTrue(binaryDictionary.isValidBigram(strong, target));
-                assertFalse(binaryDictionary.isValidBigram(weak, target));
+                assertTrue(isValidBigram(binaryDictionary, strong, target));
+                assertFalse(isValidBigram(binaryDictionary, weak, target));
                 break;
             }
         }
@@ -606,9 +611,9 @@
         addUnigramWord(binaryDictionary, "ccc", DUMMY_PROBABILITY);
         addUnigramWord(binaryDictionary, "abc", DUMMY_PROBABILITY);
         addBigramWords(binaryDictionary, "aaa", "abc", DUMMY_PROBABILITY);
-        assertTrue(binaryDictionary.isValidBigram("aaa", "abc"));
+        assertTrue(isValidBigram(binaryDictionary, "aaa", "abc"));
         addBigramWords(binaryDictionary, "aaa", "bbb", Dictionary.NOT_A_PROBABILITY);
-        assertFalse(binaryDictionary.isValidBigram("aaa", "bbb"));
+        assertFalse(isValidBigram(binaryDictionary, "aaa", "bbb"));
 
         assertEquals(fromFormatVersion, binaryDictionary.getFormatVersion());
         assertTrue(binaryDictionary.migrateTo(toFormatVersion));
@@ -619,10 +624,10 @@
         assertTrue(binaryDictionary.getFrequency("aaa") < binaryDictionary.getFrequency("ccc"));
         addUnigramWord(binaryDictionary, "bbb", Dictionary.NOT_A_PROBABILITY);
         assertTrue(binaryDictionary.isValidWord("bbb"));
-        assertTrue(binaryDictionary.isValidBigram("aaa", "abc"));
-        assertFalse(binaryDictionary.isValidBigram("aaa", "bbb"));
+        assertTrue(isValidBigram(binaryDictionary, "aaa", "abc"));
+        assertFalse(isValidBigram(binaryDictionary, "aaa", "bbb"));
         addBigramWords(binaryDictionary, "aaa", "bbb", Dictionary.NOT_A_PROBABILITY);
-        assertTrue(binaryDictionary.isValidBigram("aaa", "bbb"));
+        assertTrue(isValidBigram(binaryDictionary, "aaa", "bbb"));
         binaryDictionary.close();
         dictFile.delete();
     }
diff --git a/tests/src/com/android/inputmethod/latin/BinaryDictionaryTests.java b/tests/src/com/android/inputmethod/latin/BinaryDictionaryTests.java
index c87c2a9..2b82e54 100644
--- a/tests/src/com/android/inputmethod/latin/BinaryDictionaryTests.java
+++ b/tests/src/com/android/inputmethod/latin/BinaryDictionaryTests.java
@@ -170,7 +170,7 @@
         addUnigramWord(binaryDictionary, validLongWord, probability);
         addUnigramWord(binaryDictionary, invalidLongWord, probability);
         // Too long short cut.
-        binaryDictionary.addUnigramWord("a", probability, invalidLongWord,
+        binaryDictionary.addUnigramEntry("a", probability, invalidLongWord,
                 10 /* shortcutProbability */, false /* isNotAWord */, false /* isBlacklisted */,
                 BinaryDictionary.NOT_A_VALID_TIMESTAMP);
         addUnigramWord(binaryDictionary, "abc", probability);
@@ -188,20 +188,35 @@
         dictFile.delete();
     }
 
-    private void addUnigramWord(final BinaryDictionary binaryDictionary, final String word,
+    private static void addUnigramWord(final BinaryDictionary binaryDictionary, final String word,
             final int probability) {
-        binaryDictionary.addUnigramWord(word, probability, "" /* shortcutTarget */,
+        binaryDictionary.addUnigramEntry(word, probability, "" /* shortcutTarget */,
                 BinaryDictionary.NOT_A_PROBABILITY /* shortcutProbability */,
                 false /* isNotAWord */, false /* isBlacklisted */,
                 BinaryDictionary.NOT_A_VALID_TIMESTAMP /* timestamp */);
     }
 
-    private void addBigramWords(final BinaryDictionary binaryDictionary, final String word0,
+    private static void addBigramWords(final BinaryDictionary binaryDictionary, final String word0,
             final String word1, final int probability) {
-        binaryDictionary.addBigramWords(word0, word1, probability,
+        binaryDictionary.addNgramEntry(new PrevWordsInfo(word0), word1, probability,
                 BinaryDictionary.NOT_A_VALID_TIMESTAMP /* timestamp */);
     }
 
+    private static boolean isValidBigram(final BinaryDictionary binaryDictionary,
+            final String word0, final String word1) {
+        return binaryDictionary.isValidNgram(new PrevWordsInfo(word0), word1);
+    }
+
+    private static void removeBigramEntry(final BinaryDictionary binaryDictionary,
+            final String word0, final String word1) {
+        binaryDictionary.removeNgramEntry(new PrevWordsInfo(word0), word1);
+    }
+
+    private static int getBigramProbability(final BinaryDictionary binaryDictionary,
+            final String word0,  final String word1) {
+        return binaryDictionary.getNgramProbability(new PrevWordsInfo(word0), word1);
+    }
+
     public void testAddUnigramWord() {
         for (final int formatVersion : DICT_FORMAT_VERSIONS) {
             testAddUnigramWord(formatVersion);
@@ -312,32 +327,32 @@
         addBigramWords(binaryDictionary, "abb", "aaa", bigramProbability);
         addBigramWords(binaryDictionary, "abb", "bcc", bigramProbability);
 
-        assertTrue(binaryDictionary.isValidBigram("aaa", "abb"));
-        assertTrue(binaryDictionary.isValidBigram("aaa", "bcc"));
-        assertTrue(binaryDictionary.isValidBigram("abb", "aaa"));
-        assertTrue(binaryDictionary.isValidBigram("abb", "bcc"));
+        assertTrue(isValidBigram(binaryDictionary, "aaa", "abb"));
+        assertTrue(isValidBigram(binaryDictionary, "aaa", "bcc"));
+        assertTrue(isValidBigram(binaryDictionary, "abb", "aaa"));
+        assertTrue(isValidBigram(binaryDictionary, "abb", "bcc"));
         if (canCheckBigramProbability(formatVersion)) {
-            assertEquals(bigramProbability, binaryDictionary.getBigramProbability("aaa", "abb"));
-            assertEquals(bigramProbability, binaryDictionary.getBigramProbability("aaa", "bcc"));
-            assertEquals(bigramProbability, binaryDictionary.getBigramProbability("abb", "aaa"));
-            assertEquals(bigramProbability, binaryDictionary.getBigramProbability("abb", "bcc"));
+            assertEquals(bigramProbability, getBigramProbability(binaryDictionary, "aaa", "abb"));
+            assertEquals(bigramProbability, getBigramProbability(binaryDictionary, "aaa", "bcc"));
+            assertEquals(bigramProbability, getBigramProbability(binaryDictionary, "abb", "aaa"));
+            assertEquals(bigramProbability, getBigramProbability(binaryDictionary, "abb", "bcc"));
         }
 
         addBigramWords(binaryDictionary, "aaa", "abb", updatedBigramProbability);
         if (canCheckBigramProbability(formatVersion)) {
             assertEquals(updatedBigramProbability,
-                    binaryDictionary.getBigramProbability("aaa", "abb"));
+                    getBigramProbability(binaryDictionary, "aaa", "abb"));
         }
 
-        assertFalse(binaryDictionary.isValidBigram("bcc", "aaa"));
-        assertFalse(binaryDictionary.isValidBigram("bcc", "bbc"));
-        assertFalse(binaryDictionary.isValidBigram("aaa", "aaa"));
+        assertFalse(isValidBigram(binaryDictionary, "bcc", "aaa"));
+        assertFalse(isValidBigram(binaryDictionary, "bcc", "bbc"));
+        assertFalse(isValidBigram(binaryDictionary, "aaa", "aaa"));
         assertEquals(Dictionary.NOT_A_PROBABILITY,
-                binaryDictionary.getBigramProbability("bcc", "aaa"));
+                getBigramProbability(binaryDictionary, "bcc", "aaa"));
         assertEquals(Dictionary.NOT_A_PROBABILITY,
-                binaryDictionary.getBigramProbability("bcc", "bbc"));
+                getBigramProbability(binaryDictionary, "bcc", "bbc"));
         assertEquals(Dictionary.NOT_A_PROBABILITY,
-                binaryDictionary.getBigramProbability("aaa", "aaa"));
+                getBigramProbability(binaryDictionary, "aaa", "aaa"));
 
         // Testing bigram link.
         addUnigramWord(binaryDictionary, "abcde", unigramProbability);
@@ -349,14 +364,14 @@
 
         if (canCheckBigramProbability(formatVersion)) {
             assertEquals(bigramProbability,
-                    binaryDictionary.getBigramProbability("abcde", "fghij"));
+                    getBigramProbability(binaryDictionary, "abcde", "fghij"));
         }
         assertEquals(Dictionary.NOT_A_PROBABILITY,
-                binaryDictionary.getBigramProbability("abcde", "fgh"));
+                getBigramProbability(binaryDictionary, "abcde", "fgh"));
         addBigramWords(binaryDictionary, "abcde", "fghij", updatedBigramProbability);
         if (canCheckBigramProbability(formatVersion)) {
             assertEquals(updatedBigramProbability,
-                    binaryDictionary.getBigramProbability("abcde", "fghij"));
+                    getBigramProbability(binaryDictionary, "abcde", "fghij"));
         }
 
         dictFile.delete();
@@ -418,10 +433,10 @@
         for (final Pair<String, String> bigram : bigramWords) {
             final int bigramProbability = bigramProbabilities.get(bigram);
             assertEquals(bigramProbability != Dictionary.NOT_A_PROBABILITY,
-                    binaryDictionary.isValidBigram(bigram.first, bigram.second));
+                    isValidBigram(binaryDictionary, bigram.first, bigram.second));
             if (canCheckBigramProbability(formatVersion)) {
                 assertEquals(bigramProbability,
-                        binaryDictionary.getBigramProbability(bigram.first, bigram.second));
+                        getBigramProbability(binaryDictionary, bigram.first, bigram.second));
             }
         }
 
@@ -454,28 +469,28 @@
         addBigramWords(binaryDictionary, "abb", "aaa", bigramProbability);
         addBigramWords(binaryDictionary, "abb", "bcc", bigramProbability);
 
-        assertTrue(binaryDictionary.isValidBigram("aaa", "abb"));
-        assertTrue(binaryDictionary.isValidBigram("aaa", "bcc"));
-        assertTrue(binaryDictionary.isValidBigram("abb", "aaa"));
-        assertTrue(binaryDictionary.isValidBigram("abb", "bcc"));
+        assertTrue(isValidBigram(binaryDictionary, "aaa", "abb"));
+        assertTrue(isValidBigram(binaryDictionary, "aaa", "bcc"));
+        assertTrue(isValidBigram(binaryDictionary, "abb", "aaa"));
+        assertTrue(isValidBigram(binaryDictionary, "abb", "bcc"));
 
-        binaryDictionary.removeBigramWords("aaa", "abb");
-        assertFalse(binaryDictionary.isValidBigram("aaa", "abb"));
+        removeBigramEntry(binaryDictionary, "aaa", "abb");
+        assertFalse(isValidBigram(binaryDictionary, "aaa", "abb"));
         addBigramWords(binaryDictionary, "aaa", "abb", bigramProbability);
-        assertTrue(binaryDictionary.isValidBigram("aaa", "abb"));
+        assertTrue(isValidBigram(binaryDictionary, "aaa", "abb"));
 
 
-        binaryDictionary.removeBigramWords("aaa", "bcc");
-        assertFalse(binaryDictionary.isValidBigram("aaa", "bcc"));
-        binaryDictionary.removeBigramWords("abb", "aaa");
-        assertFalse(binaryDictionary.isValidBigram("abb", "aaa"));
-        binaryDictionary.removeBigramWords("abb", "bcc");
-        assertFalse(binaryDictionary.isValidBigram("abb", "bcc"));
+        removeBigramEntry(binaryDictionary, "aaa", "bcc");
+        assertFalse(isValidBigram(binaryDictionary, "aaa", "bcc"));
+        removeBigramEntry(binaryDictionary, "abb", "aaa");
+        assertFalse(isValidBigram(binaryDictionary, "abb", "aaa"));
+        removeBigramEntry(binaryDictionary, "abb", "bcc");
+        assertFalse(isValidBigram(binaryDictionary, "abb", "bcc"));
 
-        binaryDictionary.removeBigramWords("aaa", "abb");
+        removeBigramEntry(binaryDictionary, "aaa", "abb");
         // Test remove non-existing bigram operation.
-        binaryDictionary.removeBigramWords("aaa", "abb");
-        binaryDictionary.removeBigramWords("bcc", "aaa");
+        removeBigramEntry(binaryDictionary, "aaa", "abb");
+        removeBigramEntry(binaryDictionary, "bcc", "aaa");
 
         dictFile.delete();
     }
@@ -570,14 +585,14 @@
         assertEquals(unigramProbability, binaryDictionary.getFrequency("abb"));
         assertEquals(unigramProbability, binaryDictionary.getFrequency("bcc"));
         if (canCheckBigramProbability(formatVersion)) {
-            assertEquals(bigramProbability, binaryDictionary.getBigramProbability("aaa", "abb"));
-            assertEquals(bigramProbability, binaryDictionary.getBigramProbability("aaa", "bcc"));
-            assertEquals(bigramProbability, binaryDictionary.getBigramProbability("abb", "aaa"));
-            assertEquals(bigramProbability, binaryDictionary.getBigramProbability("abb", "bcc"));
+            assertEquals(bigramProbability, getBigramProbability(binaryDictionary, "aaa", "abb"));
+            assertEquals(bigramProbability, getBigramProbability(binaryDictionary, "aaa", "bcc"));
+            assertEquals(bigramProbability, getBigramProbability(binaryDictionary, "abb", "aaa"));
+            assertEquals(bigramProbability, getBigramProbability(binaryDictionary, "abb", "bcc"));
         }
-        assertFalse(binaryDictionary.isValidBigram("bcc", "aaa"));
-        assertFalse(binaryDictionary.isValidBigram("bcc", "bbc"));
-        assertFalse(binaryDictionary.isValidBigram("aaa", "aaa"));
+        assertFalse(isValidBigram(binaryDictionary, "bcc", "aaa"));
+        assertFalse(isValidBigram(binaryDictionary, "bcc", "bbc"));
+        assertFalse(isValidBigram(binaryDictionary, "aaa", "aaa"));
         binaryDictionary.flushWithGC();
         binaryDictionary.close();
 
@@ -649,10 +664,10 @@
         for (final Pair<String, String> bigram : bigramWords) {
             final int bigramProbability = bigramProbabilities.get(bigram);
             assertEquals(bigramProbability != Dictionary.NOT_A_PROBABILITY,
-                    binaryDictionary.isValidBigram(bigram.first, bigram.second));
+                    isValidBigram(binaryDictionary, bigram.first, bigram.second));
             if (canCheckBigramProbability(formatVersion)) {
                 assertEquals(bigramProbability,
-                        binaryDictionary.getBigramProbability(bigram.first, bigram.second));
+                        getBigramProbability(binaryDictionary, bigram.first, bigram.second));
             }
         }
 
@@ -742,7 +757,7 @@
                     final Pair<String, String> bigram = bigramWords.get(bigramIndex);
                     bigramWords.remove(bigramIndex);
                     bigramProbabilities.remove(bigram);
-                    binaryDictionary.removeBigramWords(bigram.first, bigram.second);
+                    removeBigramEntry(binaryDictionary, bigram.first, bigram.second);
                 }
             }
 
@@ -765,10 +780,10 @@
 
                 if (canCheckBigramProbability(formatVersion)) {
                     assertEquals(probability,
-                            binaryDictionary.getBigramProbability(bigram.first, bigram.second));
+                            getBigramProbability(binaryDictionary, bigram.first, bigram.second));
                 }
                 assertEquals(probability != Dictionary.NOT_A_PROBABILITY,
-                        binaryDictionary.isValidBigram(bigram.first, bigram.second));
+                        isValidBigram(binaryDictionary, bigram.first, bigram.second));
             }
             binaryDictionary.flushWithGC();
             binaryDictionary.close();
@@ -946,10 +961,10 @@
             final String word1 = entry.getKey().second;
             final int bigramProbability = entry.getValue();
             assertEquals(bigramProbability != Dictionary.NOT_A_PROBABILITY,
-                    binaryDictionary.isValidBigram(word0, word1));
+                    isValidBigram(binaryDictionary, word0, word1));
             if (canCheckBigramProbability(formatVersion)) {
                 assertEquals(bigramProbability,
-                        binaryDictionary.getBigramProbability(word0, word1));
+                        getBigramProbability(binaryDictionary, word0, word1));
             }
         }
     }
@@ -993,7 +1008,7 @@
             final boolean isNotAWord = random.nextBoolean();
             final boolean isBlacklisted = random.nextBoolean();
             // TODO: Add tests for historical info.
-            binaryDictionary.addUnigramWord(word, unigramProbability,
+            binaryDictionary.addUnigramEntry(word, unigramProbability,
                     null /* shortcutTarget */, BinaryDictionary.NOT_A_PROBABILITY,
                     isNotAWord, isBlacklisted, BinaryDictionary.NOT_A_VALID_TIMESTAMP);
             if (binaryDictionary.needsToRunGC(false /* mindsBlockByGC */)) {
@@ -1023,8 +1038,7 @@
             final int unigramProbability = wordProbabilities.get(word1);
             final int bigramProbability =
                     unigramProbability + random.nextInt(0xFF - unigramProbability);
-            binaryDictionary.addBigramWords(word0, word1, bigramProbability,
-                    BinaryDictionary.NOT_A_VALID_TIMESTAMP);
+            addBigramWords(binaryDictionary, word0, word1, bigramProbability);
             if (binaryDictionary.needsToRunGC(false /* mindsBlockByGC */)) {
                 binaryDictionary.flushWithGC();
             }
@@ -1112,8 +1126,7 @@
             final int unigramProbability = wordProbabilitiesToCheckLater.get(word1);
             final int bigramProbability =
                     unigramProbability + random.nextInt(0xFF - unigramProbability);
-            binaryDictionary.addBigramWords(word0, word1, bigramProbability,
-                    BinaryDictionary.NOT_A_VALID_TIMESTAMP);
+            addBigramWords(binaryDictionary, word0, word1, bigramProbability);
             if (binaryDictionary.needsToRunGC(false /* mindsBlockByGC */)) {
                 binaryDictionary.flushWithGC();
             }
@@ -1174,7 +1187,7 @@
 
         final int unigramProbability = 100;
         final int shortcutProbability = 10;
-        binaryDictionary.addUnigramWord("aaa", unigramProbability, "zzz",
+        binaryDictionary.addUnigramEntry("aaa", unigramProbability, "zzz",
                 shortcutProbability, false /* isNotAWord */, false /* isBlacklisted */,
                 0 /* timestamp */);
         WordProperty wordProperty = binaryDictionary.getWordProperty("aaa");
@@ -1182,7 +1195,7 @@
         assertEquals("zzz", wordProperty.mShortcutTargets.get(0).mWord);
         assertEquals(shortcutProbability, wordProperty.mShortcutTargets.get(0).getProbability());
         final int updatedShortcutProbability = 2;
-        binaryDictionary.addUnigramWord("aaa", unigramProbability, "zzz",
+        binaryDictionary.addUnigramEntry("aaa", unigramProbability, "zzz",
                 updatedShortcutProbability, false /* isNotAWord */, false /* isBlacklisted */,
                 0 /* timestamp */);
         wordProperty = binaryDictionary.getWordProperty("aaa");
@@ -1190,7 +1203,7 @@
         assertEquals("zzz", wordProperty.mShortcutTargets.get(0).mWord);
         assertEquals(updatedShortcutProbability,
                 wordProperty.mShortcutTargets.get(0).getProbability());
-        binaryDictionary.addUnigramWord("aaa", unigramProbability, "yyy",
+        binaryDictionary.addUnigramEntry("aaa", unigramProbability, "yyy",
                 shortcutProbability, false /* isNotAWord */, false /* isBlacklisted */,
                 0 /* timestamp */);
         final HashMap<String, Integer> shortcutTargets = new HashMap<String, Integer>();
@@ -1261,7 +1274,7 @@
             final int shortcutProbability = random.nextInt(0xF);
             final String word = words.get(random.nextInt(words.size()));
             final int unigramProbability = unigramProbabilities.get(word);
-            binaryDictionary.addUnigramWord(word, unigramProbability, shortcutTarget,
+            binaryDictionary.addUnigramEntry(word, unigramProbability, shortcutTarget,
                     shortcutProbability, false /* isNotAWord */, false /* isBlacklisted */,
                     0 /* timestamp */);
             if (shortcutTargets.containsKey(word)) {
@@ -1317,14 +1330,14 @@
         final int bigramProbability = 150;
         addBigramWords(binaryDictionary, "aaa", "bbb", bigramProbability);
         final int shortcutProbability = 10;
-        binaryDictionary.addUnigramWord("ccc", unigramProbability, "xxx", shortcutProbability,
+        binaryDictionary.addUnigramEntry("ccc", unigramProbability, "xxx", shortcutProbability,
                 false /* isNotAWord */, false /* isBlacklisted */, 0 /* timestamp */);
-        binaryDictionary.addUnigramWord("ddd", unigramProbability, null /* shortcutTarget */,
+        binaryDictionary.addUnigramEntry("ddd", unigramProbability, null /* shortcutTarget */,
                 Dictionary.NOT_A_PROBABILITY, true /* isNotAWord */,
                 true /* isBlacklisted */, 0 /* timestamp */);
         assertEquals(unigramProbability, binaryDictionary.getFrequency("aaa"));
         assertEquals(unigramProbability, binaryDictionary.getFrequency("bbb"));
-        assertTrue(binaryDictionary.isValidBigram("aaa", "bbb"));
+        assertTrue(isValidBigram(binaryDictionary, "aaa", "bbb"));
         assertEquals(fromFormatVersion, binaryDictionary.getFormatVersion());
         assertTrue(binaryDictionary.migrateTo(toFormatVersion));
         assertTrue(binaryDictionary.isValidDictionary());
@@ -1332,9 +1345,9 @@
         assertEquals(unigramProbability, binaryDictionary.getFrequency("aaa"));
         assertEquals(unigramProbability, binaryDictionary.getFrequency("bbb"));
         if (canCheckBigramProbability(toFormatVersion)) {
-            assertEquals(bigramProbability, binaryDictionary.getBigramProbability("aaa", "bbb"));
+            assertEquals(bigramProbability, getBigramProbability(binaryDictionary, "aaa", "bbb"));
         }
-        assertTrue(binaryDictionary.isValidBigram("aaa", "bbb"));
+        assertTrue(isValidBigram(binaryDictionary, "aaa", "bbb"));
         WordProperty wordProperty = binaryDictionary.getWordProperty("ccc");
         assertEquals(1, wordProperty.mShortcutTargets.size());
         assertEquals("xxx", wordProperty.mShortcutTargets.get(0).mWord);
@@ -1395,8 +1408,7 @@
             final int unigramProbability = unigramProbabilities.get(word1);
             final int bigramProbability =
                     random.nextInt(0xFF - unigramProbability) + unigramProbability;
-            binaryDictionary.addBigramWords(word0, word1, bigramProbability,
-                    BinaryDictionary.NOT_A_VALID_TIMESTAMP);
+            addBigramWords(binaryDictionary, word0, word1, bigramProbability);
             if (binaryDictionary.needsToRunGC(true /* mindsBlockByGC */)) {
                 binaryDictionary.flushWithGC();
             }
@@ -1415,9 +1427,9 @@
         for (final Pair<String, String> bigram : bigrams) {
             if (canCheckBigramProbability(toFormatVersion)) {
                 assertEquals((int)bigramProbabilities.get(bigram),
-                        binaryDictionary.getBigramProbability(bigram.first, bigram.second));
+                        getBigramProbability(binaryDictionary, bigram.first, bigram.second));
             }
-            assertTrue(binaryDictionary.isValidBigram(bigram.first, bigram.second));
+            assertTrue(isValidBigram(binaryDictionary, bigram.first, bigram.second));
         }
         assertEquals(bigramProbabilities.size(), Integer.parseInt(
                 binaryDictionary.getPropertyForTest(BinaryDictionary.BIGRAM_COUNT_QUERY)));
diff --git a/tests/src/com/android/inputmethod/latin/WordComposerTests.java b/tests/src/com/android/inputmethod/latin/WordComposerTests.java
index 16e8b36..17e7185 100644
--- a/tests/src/com/android/inputmethod/latin/WordComposerTests.java
+++ b/tests/src/com/android/inputmethod/latin/WordComposerTests.java
@@ -40,8 +40,8 @@
         final int[] COORDINATES_WITHIN_BMP =
                 CoordinateUtils.newCoordinateArray(CODEPOINTS_WITHIN_BMP.length,
                         Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
-        final String PREVWORD = "prevword";
-        wc.setComposingWord(CODEPOINTS_WITHIN_BMP, COORDINATES_WITHIN_BMP, PREVWORD);
+        final PrevWordsInfo PREV_WORDS_INFO = new PrevWordsInfo("prevword");
+        wc.setComposingWord(CODEPOINTS_WITHIN_BMP, COORDINATES_WITHIN_BMP, PREV_WORDS_INFO);
         assertEquals(wc.size(), STR_WITHIN_BMP.codePointCount(0, STR_WITHIN_BMP.length()));
         assertFalse(wc.isCursorFrontOrMiddleOfComposingWord());
         wc.setCursorPositionWithinWord(2);
@@ -57,7 +57,7 @@
         assertTrue(wc.moveCursorByAndReturnIfInsideComposingWord(1));
         assertFalse(wc.isCursorFrontOrMiddleOfComposingWord());
         // Check the previous word is still there
-        assertEquals(PREVWORD, wc.getPrevWordsInfoForSuggestion().mPrevWord);
+        assertEquals(PREV_WORDS_INFO, wc.getPrevWordsInfoForSuggestion());
         // Move the cursor past the end of the word
         assertFalse(wc.moveCursorByAndReturnIfInsideComposingWord(1));
         assertFalse(wc.moveCursorByAndReturnIfInsideComposingWord(15));
@@ -74,7 +74,7 @@
                 CoordinateUtils.newCoordinateArray(CODEPOINTS_WITH_SUPPLEMENTARY_CHAR.length,
                         Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
         wc.setComposingWord(CODEPOINTS_WITH_SUPPLEMENTARY_CHAR, COORDINATES_WITH_SUPPLEMENTARY_CHAR,
-                null /* previousWord */);
+                new PrevWordsInfo(null));
         assertEquals(wc.size(), CODEPOINTS_WITH_SUPPLEMENTARY_CHAR.length);
         assertFalse(wc.isCursorFrontOrMiddleOfComposingWord());
         wc.setCursorPositionWithinWord(3);
@@ -85,46 +85,53 @@
         assertFalse(wc.isCursorFrontOrMiddleOfComposingWord());
         assertNull(wc.getPrevWordsInfoForSuggestion().mPrevWord);
 
+        final PrevWordsInfo PREV_WORDS_INFO_STR_WITHIN_BMP = new PrevWordsInfo(STR_WITHIN_BMP);
         wc.setComposingWord(CODEPOINTS_WITH_SUPPLEMENTARY_CHAR, COORDINATES_WITH_SUPPLEMENTARY_CHAR,
-                STR_WITHIN_BMP);
+                PREV_WORDS_INFO_STR_WITHIN_BMP);
         wc.setCursorPositionWithinWord(3);
         assertTrue(wc.moveCursorByAndReturnIfInsideComposingWord(7));
-        assertEquals(STR_WITHIN_BMP, wc.getPrevWordsInfoForSuggestion().mPrevWord);
+        assertEquals(PREV_WORDS_INFO_STR_WITHIN_BMP, wc.getPrevWordsInfoForSuggestion());
 
+        final PrevWordsInfo PREV_WORDS_INFO_STR_WITH_SUPPLEMENTARY_CHAR =
+                new PrevWordsInfo(STR_WITH_SUPPLEMENTARY_CHAR);
         wc.setComposingWord(CODEPOINTS_WITH_SUPPLEMENTARY_CHAR, COORDINATES_WITH_SUPPLEMENTARY_CHAR,
-                STR_WITH_SUPPLEMENTARY_CHAR);
+                PREV_WORDS_INFO_STR_WITH_SUPPLEMENTARY_CHAR);
         wc.setCursorPositionWithinWord(3);
         assertTrue(wc.moveCursorByAndReturnIfInsideComposingWord(7));
-        assertEquals(STR_WITH_SUPPLEMENTARY_CHAR, wc.getPrevWordsInfoForSuggestion().mPrevWord);
+        assertEquals(PREV_WORDS_INFO_STR_WITH_SUPPLEMENTARY_CHAR,
+                wc.getPrevWordsInfoForSuggestion());
 
         wc.setComposingWord(CODEPOINTS_WITH_SUPPLEMENTARY_CHAR, COORDINATES_WITH_SUPPLEMENTARY_CHAR,
-                STR_WITHIN_BMP);
+                PREV_WORDS_INFO_STR_WITHIN_BMP);
         wc.setCursorPositionWithinWord(3);
         assertTrue(wc.moveCursorByAndReturnIfInsideComposingWord(-3));
         assertFalse(wc.moveCursorByAndReturnIfInsideComposingWord(-1));
-        assertEquals(STR_WITHIN_BMP, wc.getPrevWordsInfoForSuggestion().mPrevWord);
+        assertEquals(PREV_WORDS_INFO_STR_WITHIN_BMP, wc.getPrevWordsInfoForSuggestion());
 
+
+        final PrevWordsInfo PREV_WORDS_INFO_NULL = new PrevWordsInfo(null);
         wc.setComposingWord(CODEPOINTS_WITH_SUPPLEMENTARY_CHAR, COORDINATES_WITH_SUPPLEMENTARY_CHAR,
-                null /* previousWord */);
+                PREV_WORDS_INFO_NULL);
         wc.setCursorPositionWithinWord(3);
         assertFalse(wc.moveCursorByAndReturnIfInsideComposingWord(-9));
         assertNull(wc.getPrevWordsInfoForSuggestion().mPrevWord);
 
         wc.setComposingWord(CODEPOINTS_WITH_SUPPLEMENTARY_CHAR, COORDINATES_WITH_SUPPLEMENTARY_CHAR,
-                STR_WITH_SUPPLEMENTARY_CHAR);
+                PREV_WORDS_INFO_STR_WITH_SUPPLEMENTARY_CHAR);
         assertTrue(wc.moveCursorByAndReturnIfInsideComposingWord(-10));
-        assertEquals(STR_WITH_SUPPLEMENTARY_CHAR, wc.getPrevWordsInfoForSuggestion().mPrevWord);
+        assertEquals(PREV_WORDS_INFO_STR_WITH_SUPPLEMENTARY_CHAR,
+                wc.getPrevWordsInfoForSuggestion());
 
         wc.setComposingWord(CODEPOINTS_WITH_SUPPLEMENTARY_CHAR, COORDINATES_WITH_SUPPLEMENTARY_CHAR,
-                null /* previousWord */);
+                PREV_WORDS_INFO_NULL);
         assertFalse(wc.moveCursorByAndReturnIfInsideComposingWord(-11));
 
         wc.setComposingWord(CODEPOINTS_WITH_SUPPLEMENTARY_CHAR, COORDINATES_WITH_SUPPLEMENTARY_CHAR,
-                null /* previousWord */);
+                PREV_WORDS_INFO_NULL);
         assertTrue(wc.moveCursorByAndReturnIfInsideComposingWord(0));
 
         wc.setComposingWord(CODEPOINTS_WITH_SUPPLEMENTARY_CHAR, COORDINATES_WITH_SUPPLEMENTARY_CHAR,
-                null /* previousWord */);
+                PREV_WORDS_INFO_NULL);
         wc.setCursorPositionWithinWord(2);
         assertTrue(wc.moveCursorByAndReturnIfInsideComposingWord(0));
     }
diff --git a/tests/src/com/android/inputmethod/latin/makedict/Ver4DictEncoder.java b/tests/src/com/android/inputmethod/latin/makedict/Ver4DictEncoder.java
index dab9a43..a04b810 100644
--- a/tests/src/com/android/inputmethod/latin/makedict/Ver4DictEncoder.java
+++ b/tests/src/com/android/inputmethod/latin/makedict/Ver4DictEncoder.java
@@ -19,6 +19,7 @@
 import com.android.inputmethod.annotations.UsedForTesting;
 import com.android.inputmethod.latin.BinaryDictionary;
 import com.android.inputmethod.latin.Dictionary;
+import com.android.inputmethod.latin.PrevWordsInfo;
 import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions;
 import com.android.inputmethod.latin.makedict.FusionDictionary.PtNode;
 import com.android.inputmethod.latin.utils.BinaryDictionaryUtils;
@@ -74,13 +75,13 @@
         for (final WordProperty wordProperty : dict) {
             // TODO: switch to addMultipleDictionaryEntries when they support shortcuts
             if (null == wordProperty.mShortcutTargets || wordProperty.mShortcutTargets.isEmpty()) {
-                binaryDict.addUnigramWord(wordProperty.mWord, wordProperty.getProbability(),
+                binaryDict.addUnigramEntry(wordProperty.mWord, wordProperty.getProbability(),
                         null /* shortcutTarget */, 0 /* shortcutProbability */,
                         wordProperty.mIsNotAWord, wordProperty.mIsBlacklistEntry,
                         0 /* timestamp */);
             } else {
                 for (final WeightedString shortcutTarget : wordProperty.mShortcutTargets) {
-                    binaryDict.addUnigramWord(wordProperty.mWord, wordProperty.getProbability(),
+                    binaryDict.addUnigramEntry(wordProperty.mWord, wordProperty.getProbability(),
                             shortcutTarget.mWord, shortcutTarget.getProbability(),
                             wordProperty.mIsNotAWord, wordProperty.mIsBlacklistEntry,
                             0 /* timestamp */);
@@ -93,8 +94,8 @@
         for (final WordProperty word0Property : dict) {
             if (null == word0Property.mBigrams) continue;
             for (final WeightedString word1 : word0Property.mBigrams) {
-                binaryDict.addBigramWords(word0Property.mWord, word1.mWord, word1.getProbability(),
-                        0 /* timestamp */);
+                binaryDict.addNgramEntry(new PrevWordsInfo(word0Property.mWord), word1.mWord,
+                        word1.getProbability(), 0 /* timestamp */);
                 if (binaryDict.needsToRunGC(true /* mindsBlockByGC */)) {
                     binaryDict.flushWithGC();
                 }
diff --git a/tests/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryTests.java b/tests/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryTests.java
index f2d7b76..bc86864 100644
--- a/tests/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryTests.java
+++ b/tests/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryTests.java
@@ -21,6 +21,7 @@
 import android.util.Log;
 
 import com.android.inputmethod.latin.ExpandableBinaryDictionary;
+import com.android.inputmethod.latin.PrevWordsInfo;
 import com.android.inputmethod.latin.utils.BinaryDictionaryUtils;
 import com.android.inputmethod.latin.utils.CollectionUtils;
 import com.android.inputmethod.latin.utils.FileUtils;
@@ -109,11 +110,11 @@
     }
 
     private static void addToDict(final UserHistoryDictionary dict, final List<String> words) {
-        String prevWord = null;
+        PrevWordsInfo prevWordsInfo = new PrevWordsInfo(null);
         for (String word : words) {
-            UserHistoryDictionary.addToDictionary(dict, prevWord, word, true,
+            UserHistoryDictionary.addToDictionary(dict, prevWordsInfo, word, true,
                     (int)TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()));
-            prevWord = word;
+            prevWordsInfo = new PrevWordsInfo(word);
         }
     }
 
@@ -260,10 +261,10 @@
         final UserHistoryDictionary dict =
                 PersonalizationHelper.getUserHistoryDictionary(getContext(), dummyLocale);
         dict.waitAllTasksForTests();
-        String prevWord = null;
+        PrevWordsInfo prevWordsInfo = new PrevWordsInfo(null);
         for (final String word : words) {
-            UserHistoryDictionary.addToDictionary(dict, prevWord, word, true, mCurrentTime);
-            prevWord = word;
+            UserHistoryDictionary.addToDictionary(dict, prevWordsInfo, word, true, mCurrentTime);
+            prevWordsInfo = new PrevWordsInfo(word);
             dict.waitAllTasksForTests();
             assertTrue(dict.isInUnderlyingBinaryDictionaryForTests(word));
         }