Merge "Fix unit tests."
diff --git a/java/res/drawable-hdpi/btn_keyboard_key_dark_normal.9.png b/java/res/drawable-hdpi/btn_keyboard_key_dark_normal.9.png
deleted file mode 100644
index bc130ca..0000000
--- a/java/res/drawable-hdpi/btn_keyboard_key_dark_normal.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-hdpi/btn_keyboard_key_dark_normal_off_lmp.9.png b/java/res/drawable-hdpi/btn_keyboard_key_dark_normal_off_lmp.9.png
new file mode 100644
index 0000000..44308bf
--- /dev/null
+++ b/java/res/drawable-hdpi/btn_keyboard_key_dark_normal_off_lmp.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/btn_keyboard_key_dark_normal_on_lmp.9.png b/java/res/drawable-hdpi/btn_keyboard_key_dark_normal_on_lmp.9.png
index 814e402..674783d 100644
--- a/java/res/drawable-hdpi/btn_keyboard_key_dark_normal_on_lmp.9.png
+++ b/java/res/drawable-hdpi/btn_keyboard_key_dark_normal_on_lmp.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/btn_keyboard_key_dark_pressed.9.png b/java/res/drawable-hdpi/btn_keyboard_key_dark_pressed.9.png
deleted file mode 100644
index af5ea6b..0000000
--- a/java/res/drawable-hdpi/btn_keyboard_key_dark_pressed.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-hdpi/btn_keyboard_key_dark_pressed_lmp.9.png b/java/res/drawable-hdpi/btn_keyboard_key_dark_pressed_lmp.9.png
deleted file mode 100644
index 90abe39..0000000
--- a/java/res/drawable-hdpi/btn_keyboard_key_dark_pressed_lmp.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-hdpi/btn_keyboard_key_dark_pressed_off_lmp.9.png b/java/res/drawable-hdpi/btn_keyboard_key_dark_pressed_off_lmp.9.png
index 48eeb3f..96b625b 100644
--- a/java/res/drawable-hdpi/btn_keyboard_key_dark_pressed_off_lmp.9.png
+++ b/java/res/drawable-hdpi/btn_keyboard_key_dark_pressed_off_lmp.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/btn_keyboard_key_dark_pressed_on.9.png b/java/res/drawable-hdpi/btn_keyboard_key_dark_pressed_on.9.png
deleted file mode 100644
index fc7ba2a..0000000
--- a/java/res/drawable-hdpi/btn_keyboard_key_dark_pressed_on.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-hdpi/btn_keyboard_key_dark_pressed_on_lmp.9.png b/java/res/drawable-hdpi/btn_keyboard_key_dark_pressed_on_lmp.9.png
index 71e0683..20e53c2 100644
--- a/java/res/drawable-hdpi/btn_keyboard_key_dark_pressed_on_lmp.9.png
+++ b/java/res/drawable-hdpi/btn_keyboard_key_dark_pressed_on_lmp.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/btn_keyboard_key_light_normal.9.png b/java/res/drawable-hdpi/btn_keyboard_key_light_normal.9.png
deleted file mode 100644
index 005c4e4..0000000
--- a/java/res/drawable-hdpi/btn_keyboard_key_light_normal.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-hdpi/btn_keyboard_key_light_popup_selected.9.png b/java/res/drawable-hdpi/btn_keyboard_key_light_popup_selected.9.png
deleted file mode 100644
index 9a07acd..0000000
--- a/java/res/drawable-hdpi/btn_keyboard_key_light_popup_selected.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-hdpi/btn_keyboard_key_light_pressed.9.png b/java/res/drawable-hdpi/btn_keyboard_key_light_pressed.9.png
deleted file mode 100644
index be420a7..0000000
--- a/java/res/drawable-hdpi/btn_keyboard_key_light_pressed.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-hdpi/btn_keyboard_key_light_pressed_lmp.9.png b/java/res/drawable-hdpi/btn_keyboard_key_light_pressed_lmp.9.png
deleted file mode 100644
index 6768241..0000000
--- a/java/res/drawable-hdpi/btn_keyboard_key_light_pressed_lmp.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-hdpi/btn_suggestion_pressed.9.png b/java/res/drawable-hdpi/btn_suggestion_pressed.9.png
deleted file mode 100644
index 7acceae..0000000
--- a/java/res/drawable-hdpi/btn_suggestion_pressed.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_mic_holo_dark.png b/java/res/drawable-hdpi/sym_keyboard_mic_holo_dark.png
deleted file mode 100644
index 3c54694..0000000
--- a/java/res/drawable-hdpi/sym_keyboard_mic_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-mdpi/btn_keyboard_key_dark_normal.9.png b/java/res/drawable-mdpi/btn_keyboard_key_dark_normal.9.png
deleted file mode 100644
index 49329f0..0000000
--- a/java/res/drawable-mdpi/btn_keyboard_key_dark_normal.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-mdpi/btn_keyboard_key_dark_normal_off_lmp.9.png b/java/res/drawable-mdpi/btn_keyboard_key_dark_normal_off_lmp.9.png
new file mode 100644
index 0000000..837df83
--- /dev/null
+++ b/java/res/drawable-mdpi/btn_keyboard_key_dark_normal_off_lmp.9.png
Binary files differ
diff --git a/java/res/drawable-mdpi/btn_keyboard_key_dark_normal_on_lmp.9.png b/java/res/drawable-mdpi/btn_keyboard_key_dark_normal_on_lmp.9.png
index b7b2dca..9772652 100644
--- a/java/res/drawable-mdpi/btn_keyboard_key_dark_normal_on_lmp.9.png
+++ b/java/res/drawable-mdpi/btn_keyboard_key_dark_normal_on_lmp.9.png
Binary files differ
diff --git a/java/res/drawable-mdpi/btn_keyboard_key_dark_pressed.9.png b/java/res/drawable-mdpi/btn_keyboard_key_dark_pressed.9.png
deleted file mode 100644
index c6876f7..0000000
--- a/java/res/drawable-mdpi/btn_keyboard_key_dark_pressed.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-mdpi/btn_keyboard_key_dark_pressed_lmp.9.png b/java/res/drawable-mdpi/btn_keyboard_key_dark_pressed_lmp.9.png
deleted file mode 100644
index 4a92b80..0000000
--- a/java/res/drawable-mdpi/btn_keyboard_key_dark_pressed_lmp.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-mdpi/btn_keyboard_key_dark_pressed_off_lmp.9.png b/java/res/drawable-mdpi/btn_keyboard_key_dark_pressed_off_lmp.9.png
index 72125a0..d213633 100644
--- a/java/res/drawable-mdpi/btn_keyboard_key_dark_pressed_off_lmp.9.png
+++ b/java/res/drawable-mdpi/btn_keyboard_key_dark_pressed_off_lmp.9.png
Binary files differ
diff --git a/java/res/drawable-mdpi/btn_keyboard_key_dark_pressed_on.9.png b/java/res/drawable-mdpi/btn_keyboard_key_dark_pressed_on.9.png
deleted file mode 100644
index 2bb7b64..0000000
--- a/java/res/drawable-mdpi/btn_keyboard_key_dark_pressed_on.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-mdpi/btn_keyboard_key_dark_pressed_on_lmp.9.png b/java/res/drawable-mdpi/btn_keyboard_key_dark_pressed_on_lmp.9.png
index 82413d4..6d20c54 100644
--- a/java/res/drawable-mdpi/btn_keyboard_key_dark_pressed_on_lmp.9.png
+++ b/java/res/drawable-mdpi/btn_keyboard_key_dark_pressed_on_lmp.9.png
Binary files differ
diff --git a/java/res/drawable-mdpi/btn_keyboard_key_light_normal.9.png b/java/res/drawable-mdpi/btn_keyboard_key_light_normal.9.png
deleted file mode 100644
index f5ce40c..0000000
--- a/java/res/drawable-mdpi/btn_keyboard_key_light_normal.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-mdpi/btn_keyboard_key_light_popup_selected.9.png b/java/res/drawable-mdpi/btn_keyboard_key_light_popup_selected.9.png
deleted file mode 100644
index ca73b92..0000000
--- a/java/res/drawable-mdpi/btn_keyboard_key_light_popup_selected.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-mdpi/btn_keyboard_key_light_pressed.9.png b/java/res/drawable-mdpi/btn_keyboard_key_light_pressed.9.png
deleted file mode 100644
index 73f2006..0000000
--- a/java/res/drawable-mdpi/btn_keyboard_key_light_pressed.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-mdpi/btn_keyboard_key_light_pressed_lmp.9.png b/java/res/drawable-mdpi/btn_keyboard_key_light_pressed_lmp.9.png
deleted file mode 100644
index 0493859..0000000
--- a/java/res/drawable-mdpi/btn_keyboard_key_light_pressed_lmp.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-mdpi/btn_suggestion_pressed.9.png b/java/res/drawable-mdpi/btn_suggestion_pressed.9.png
deleted file mode 100644
index 02b4e9a..0000000
--- a/java/res/drawable-mdpi/btn_suggestion_pressed.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_keyboard_mic_holo_dark.png b/java/res/drawable-mdpi/sym_keyboard_mic_holo_dark.png
deleted file mode 100644
index 5e58866..0000000
--- a/java/res/drawable-mdpi/sym_keyboard_mic_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xhdpi/btn_keyboard_key_dark_normal.9.png b/java/res/drawable-xhdpi/btn_keyboard_key_dark_normal.9.png
deleted file mode 100644
index d0090a3..0000000
--- a/java/res/drawable-xhdpi/btn_keyboard_key_dark_normal.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xhdpi/btn_keyboard_key_dark_normal_off_lmp.9.png b/java/res/drawable-xhdpi/btn_keyboard_key_dark_normal_off_lmp.9.png
new file mode 100644
index 0000000..eeb447c
--- /dev/null
+++ b/java/res/drawable-xhdpi/btn_keyboard_key_dark_normal_off_lmp.9.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/btn_keyboard_key_dark_normal_on_lmp.9.png b/java/res/drawable-xhdpi/btn_keyboard_key_dark_normal_on_lmp.9.png
index 20251a0..624ba8c 100644
--- a/java/res/drawable-xhdpi/btn_keyboard_key_dark_normal_on_lmp.9.png
+++ b/java/res/drawable-xhdpi/btn_keyboard_key_dark_normal_on_lmp.9.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/btn_keyboard_key_dark_pressed.9.png b/java/res/drawable-xhdpi/btn_keyboard_key_dark_pressed.9.png
deleted file mode 100644
index a932249..0000000
--- a/java/res/drawable-xhdpi/btn_keyboard_key_dark_pressed.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xhdpi/btn_keyboard_key_dark_pressed_lmp.9.png b/java/res/drawable-xhdpi/btn_keyboard_key_dark_pressed_lmp.9.png
deleted file mode 100644
index 84d1739..0000000
--- a/java/res/drawable-xhdpi/btn_keyboard_key_dark_pressed_lmp.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xhdpi/btn_keyboard_key_dark_pressed_off_lmp.9.png b/java/res/drawable-xhdpi/btn_keyboard_key_dark_pressed_off_lmp.9.png
index ee4490e..2bc16cf 100644
--- a/java/res/drawable-xhdpi/btn_keyboard_key_dark_pressed_off_lmp.9.png
+++ b/java/res/drawable-xhdpi/btn_keyboard_key_dark_pressed_off_lmp.9.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/btn_keyboard_key_dark_pressed_on.9.png b/java/res/drawable-xhdpi/btn_keyboard_key_dark_pressed_on.9.png
deleted file mode 100644
index 3ca93fd..0000000
--- a/java/res/drawable-xhdpi/btn_keyboard_key_dark_pressed_on.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xhdpi/btn_keyboard_key_dark_pressed_on_lmp.9.png b/java/res/drawable-xhdpi/btn_keyboard_key_dark_pressed_on_lmp.9.png
index e812477..80dedd2 100644
--- a/java/res/drawable-xhdpi/btn_keyboard_key_dark_pressed_on_lmp.9.png
+++ b/java/res/drawable-xhdpi/btn_keyboard_key_dark_pressed_on_lmp.9.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/btn_keyboard_key_light_normal.9.png b/java/res/drawable-xhdpi/btn_keyboard_key_light_normal.9.png
deleted file mode 100644
index aa4f44f..0000000
--- a/java/res/drawable-xhdpi/btn_keyboard_key_light_normal.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xhdpi/btn_keyboard_key_light_popup_selected.9.png b/java/res/drawable-xhdpi/btn_keyboard_key_light_popup_selected.9.png
deleted file mode 100644
index 4539255..0000000
--- a/java/res/drawable-xhdpi/btn_keyboard_key_light_popup_selected.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xhdpi/btn_keyboard_key_light_pressed.9.png b/java/res/drawable-xhdpi/btn_keyboard_key_light_pressed.9.png
deleted file mode 100644
index 5683924..0000000
--- a/java/res/drawable-xhdpi/btn_keyboard_key_light_pressed.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xhdpi/btn_keyboard_key_light_pressed_lmp.9.png b/java/res/drawable-xhdpi/btn_keyboard_key_light_pressed_lmp.9.png
deleted file mode 100644
index f770962..0000000
--- a/java/res/drawable-xhdpi/btn_keyboard_key_light_pressed_lmp.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xhdpi/btn_suggestion_pressed.9.png b/java/res/drawable-xhdpi/btn_suggestion_pressed.9.png
deleted file mode 100644
index 41e126a..0000000
--- a/java/res/drawable-xhdpi/btn_suggestion_pressed.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xhdpi/sym_keyboard_mic_holo_dark.png b/java/res/drawable-xhdpi/sym_keyboard_mic_holo_dark.png
deleted file mode 100644
index 566ba1f..0000000
--- a/java/res/drawable-xhdpi/sym_keyboard_mic_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xxhdpi/btn_keyboard_key_dark_normal_off_lmp.9.png b/java/res/drawable-xxhdpi/btn_keyboard_key_dark_normal_off_lmp.9.png
new file mode 100644
index 0000000..97b049e
--- /dev/null
+++ b/java/res/drawable-xxhdpi/btn_keyboard_key_dark_normal_off_lmp.9.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/btn_keyboard_key_dark_normal_on_lmp.9.png b/java/res/drawable-xxhdpi/btn_keyboard_key_dark_normal_on_lmp.9.png
index 97f9625..2e81497 100644
--- a/java/res/drawable-xxhdpi/btn_keyboard_key_dark_normal_on_lmp.9.png
+++ b/java/res/drawable-xxhdpi/btn_keyboard_key_dark_normal_on_lmp.9.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/btn_keyboard_key_dark_pressed_lmp.9.png b/java/res/drawable-xxhdpi/btn_keyboard_key_dark_pressed_lmp.9.png
deleted file mode 100644
index dfb16a7..0000000
--- a/java/res/drawable-xxhdpi/btn_keyboard_key_dark_pressed_lmp.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xxhdpi/btn_keyboard_key_dark_pressed_off_lmp.9.png b/java/res/drawable-xxhdpi/btn_keyboard_key_dark_pressed_off_lmp.9.png
index bf1d346..d844b17 100644
--- a/java/res/drawable-xxhdpi/btn_keyboard_key_dark_pressed_off_lmp.9.png
+++ b/java/res/drawable-xxhdpi/btn_keyboard_key_dark_pressed_off_lmp.9.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/btn_keyboard_key_dark_pressed_on_lmp.9.png b/java/res/drawable-xxhdpi/btn_keyboard_key_dark_pressed_on_lmp.9.png
index 9622771..9661f4a 100644
--- a/java/res/drawable-xxhdpi/btn_keyboard_key_dark_pressed_on_lmp.9.png
+++ b/java/res/drawable-xxhdpi/btn_keyboard_key_dark_pressed_on_lmp.9.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/btn_keyboard_key_light_pressed_lmp.9.png b/java/res/drawable-xxhdpi/btn_keyboard_key_light_pressed_lmp.9.png
deleted file mode 100644
index 17144b6..0000000
--- a/java/res/drawable-xxhdpi/btn_keyboard_key_light_pressed_lmp.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xxhdpi/sym_keyboard_mic_holo_dark.png b/java/res/drawable-xxhdpi/sym_keyboard_mic_holo_dark.png
deleted file mode 100644
index f55af30..0000000
--- a/java/res/drawable-xxhdpi/sym_keyboard_mic_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable/btn_keyboard_key_functional_lmp.xml b/java/res/drawable/btn_keyboard_key_functional_lmp.xml
index 427b8d5..57a8355 100644
--- a/java/res/drawable/btn_keyboard_key_functional_lmp.xml
+++ b/java/res/drawable/btn_keyboard_key_functional_lmp.xml
@@ -17,6 +17,6 @@
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
     <!-- Functional keys. -->
     <item android:state_pressed="true"
-          android:drawable="@drawable/btn_keyboard_key_dark_pressed_lmp" />
-    <item android:drawable="@android:color/transparent" />
+          android:drawable="@color/key_background_pressed_lmp" />
+    <item android:drawable="@color/key_background_lmp" />
 </selector>
diff --git a/java/res/drawable/btn_keyboard_key_lmp.xml b/java/res/drawable/btn_keyboard_key_lmp.xml
index fdd19df..4aaafb5 100644
--- a/java/res/drawable/btn_keyboard_key_lmp.xml
+++ b/java/res/drawable/btn_keyboard_key_lmp.xml
@@ -17,32 +17,32 @@
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
     <!-- Functional keys. -->
     <item android:state_single="true" android:state_pressed="true"
-          android:drawable="@drawable/btn_keyboard_key_dark_pressed_lmp" />
+          android:drawable="@color/key_background_pressed_lmp" />
     <item android:state_single="true"
-          android:drawable="@android:color/transparent" />
+          android:drawable="@color/key_background_lmp" />
 
     <!-- Action keys. -->
     <item android:state_active="true" android:state_pressed="true"
-          android:drawable="@drawable/btn_keyboard_key_dark_pressed_lmp" />
+          android:drawable="@color/key_background_pressed_lmp" />
     <item android:state_active="true"
-          android:drawable="@android:color/transparent" />
+          android:drawable="@color/key_background_lmp" />
 
     <!-- Toggle keys. Use checkable/checked state. -->
     <item android:state_checkable="true" android:state_checked="true" android:state_pressed="true"
           android:drawable="@drawable/btn_keyboard_key_dark_pressed_on_lmp" />
     <item android:state_checkable="true" android:state_pressed="true"
-          android:drawable="@drawable/btn_keyboard_key_dark_pressed_lmp" />
+          android:drawable="@drawable/btn_keyboard_key_dark_pressed_off_lmp" />
     <item android:state_checkable="true" android:state_checked="true"
           android:drawable="@drawable/btn_keyboard_key_dark_normal_on_lmp" />
     <item android:state_checkable="true"
-          android:drawable="@android:color/transparent" />
+          android:drawable="@drawable/btn_keyboard_key_dark_normal_off_lmp" />
 
     <!-- Empty background keys. -->
     <item android:state_empty="true"
-          android:drawable="@android:color/transparent" />
+          android:drawable="@color/key_background_lmp" />
 
     <!-- Normal keys. -->
     <item android:state_pressed="true"
-          android:drawable="@drawable/btn_keyboard_key_light_pressed_lmp" />
-    <item android:drawable="@android:color/transparent" />
+          android:drawable="@color/key_background_pressed_lmp" />
+    <item android:drawable="@color/key_background_lmp" />
 </selector>
diff --git a/java/res/drawable/btn_keyboard_spacebar_lmp.xml b/java/res/drawable/btn_keyboard_spacebar_lmp.xml
index 516cb07..d05972f 100644
--- a/java/res/drawable/btn_keyboard_spacebar_lmp.xml
+++ b/java/res/drawable/btn_keyboard_spacebar_lmp.xml
@@ -16,6 +16,6 @@
 
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
     <item android:state_pressed="true"
-          android:drawable="@drawable/btn_keyboard_key_light_pressed_lmp" />
-    <item android:drawable="@android:color/transparent" />
+          android:drawable="@color/key_background_pressed_lmp" />
+    <item android:drawable="@color/key_background_lmp" />
 </selector>
diff --git a/java/res/drawable/btn_suggestion_lmp.xml b/java/res/drawable/btn_suggestion_lmp.xml
index c778e23..5c6b373 100644
--- a/java/res/drawable/btn_suggestion_lmp.xml
+++ b/java/res/drawable/btn_suggestion_lmp.xml
@@ -23,5 +23,5 @@
 >
     <item
         android:state_pressed="true"
-        android:drawable="@drawable/btn_keyboard_key_popup_selected_lmp" />
+        android:drawable="@color/suggested_word_background_selected_lmp" />
 </selector>
diff --git a/java/res/layout/suggestions_strip.xml b/java/res/layout/suggestions_strip.xml
index 36898c8..3d2f07f 100644
--- a/java/res/layout/suggestions_strip.xml
+++ b/java/res/layout/suggestions_strip.xml
@@ -71,5 +71,6 @@
         android:layout_alignParentEnd="true"
         android:layout_alignParentRight="true"
         android:layout_centerVertical="true"
+        android:contentDescription="@string/spoken_description_mic"
         style="?attr/suggestionWordStyle" />
 </merge>
diff --git a/java/res/values-km-rKH/strings.xml b/java/res/values-km-rKH/strings.xml
index 519aa44..4d7de93 100644
--- a/java/res/values-km-rKH/strings.xml
+++ b/java/res/values-km-rKH/strings.xml
@@ -29,7 +29,7 @@
     <string name="popup_on_keypress" msgid="123894815723512944">"លេច​ឡើង​នៅ​​ពេល​ចុច​គ្រាប់​ចុច"</string>
     <string name="general_category" msgid="1859088467017573195">"ទូទៅ"</string>
     <string name="correction_category" msgid="2236750915056607613">"ការ​កែ​អត្ថបទ"</string>
-    <string name="gesture_typing_category" msgid="497263612130532630">"បញ្ចូល​ដោយ​ប្រើ​កាយវិការ"</string>
+    <string name="gesture_typing_category" msgid="497263612130532630">"បញ្ចូល​ដោយ​ប្រើ​កាយវិការ​"</string>
     <string name="misc_category" msgid="6894192814868233453">"ជម្រើស​ផ្សេងទៀត"</string>
     <string name="advanced_settings" msgid="362895144495591463">"ការ​កំណត់​កម្រិត​ខ្ពស់"</string>
     <string name="advanced_settings_summary" msgid="4487980456152830271">"ជម្រើស​សម្រាប់​អ្នក​ជំនាញ"</string>
@@ -39,7 +39,7 @@
     <string name="show_language_switch_key_summary" msgid="7343403647474265713">"បង្ហាញ​នៅ​ពេល​ដែល​បើក​ភាសា​បញ្ចូល​ច្រើន"</string>
     <string name="sliding_key_input_preview" msgid="6604262359510068370">"បង្ហាញ​ទ្រនិច​បង្ហាញ​ស្លាយ"</string>
     <string name="sliding_key_input_preview_summary" msgid="6340524345729093886">"បង្ហាញ​​សញ្ញា​មើល​​ឃើញ​ខណៈ​ពេល​ដែល​រុញ​ពី​ឆ្វេង ឬ​​គ្រាប់​ចុច​​និមិត្ត​សញ្ញា"</string>
-    <string name="key_preview_popup_dismiss_delay" msgid="6213164897443068248">"​សោ​លេចឡើង​បោះបង់​ការ​​ពន្យារពេល"</string>
+    <string name="key_preview_popup_dismiss_delay" msgid="6213164897443068248">"​សោ​លេចឡើង​បោះបង់​ការ​​ពន្យារពេល​"</string>
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"គ្មាន​ការ​ពន្យារពេល"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"លំនាំដើម"</string>
     <string name="abbreviation_unit_milliseconds" msgid="8700286094028323363">"<xliff:g id="MILLISECONDS">%s</xliff:g> មិល្លី​វិនាទី"</string>
@@ -49,7 +49,7 @@
     <string name="use_personalized_dicts" msgid="5167396352105467626">"ការ​ស្នើ​ផ្ទាល់​ខ្លួន"</string>
     <string name="use_double_space_period" msgid="8781529969425082860">"រយៈ​ពេល​ចុច​ដកឃ្លា​ពីរដង"</string>
     <string name="use_double_space_period_summary" msgid="6532892187247952799">"ប៉ះ​ដកឃ្លា​ពីរ​​ដង​បញ្ចូល​​​រយៈ​ពេល​ដែល​អនុវត្ត​តាម​ដកឃ្លា"</string>
-    <string name="auto_cap" msgid="1719746674854628252">"ការ​សរសេរ​ជា​អក្សរ​ធំ​​ស្វ័យប្រវត្តិ"</string>
+    <string name="auto_cap" msgid="1719746674854628252">"ការ​សរសេរ​ជា​អក្សរ​ធំ​​ស្វ័យប្រវត្តិ​"</string>
     <string name="auto_cap_summary" msgid="7934452761022946874">"សរសេរ​ពាក្យ​ដំបូង​​​ជា​អក្សរ​ធំ​​នៃ​ប្រយោគ​នីមួយ​ៗ"</string>
     <string name="edit_personal_dictionary" msgid="3996910038952940420">"វចនានុក្រម​ផ្ទាល់ខ្លួន"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"ផ្នែក​បន្ថែម​វចនានុក្រម"</string>
@@ -60,7 +60,7 @@
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3859783767435239118">"បង្ហាញ​នៅ​ក្នុង​របៀប​បញ្ឈរ"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"លាក់​ជានិច្ច"</string>
     <string name="prefs_block_potentially_offensive_title" msgid="5078480071057408934">"ទប់ស្កាត់​​ពាក្យ​​បំពាន"</string>
-    <string name="prefs_block_potentially_offensive_summary" msgid="2371835479734991364">"កុំ​ស្នើ​ឲ្យ​ពាក្យ​បំពាន​មាន​សក្ដានុពល"</string>
+    <string name="prefs_block_potentially_offensive_summary" msgid="2371835479734991364">"កុំ​ស្នើ​ឲ្យ​ពាក្យ​បំពាន​មាន​សក្ដានុពល​"</string>
     <string name="auto_correction" msgid="7630720885194996950">"ការ​កែ​​​ស្វ័យប្រវត្តិ"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"ចន្លោះ​មិន​ឃើញ ​និង​សញ្ញា​​វណ្ណយុត្ត​កែ​ពាក្យ​ដែល​បាន​វាយ​ខុស​ស្វ័យប្រវត្តិ"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"បិទ"</string>
@@ -123,7 +123,7 @@
     <string name="prefs_keypress_vibration_duration_settings" msgid="7918341459947439226">"ថិរវេលា​​ញ័រ​​ពេល​ចុច​គ្រាប់ចុច"</string>
     <string name="prefs_keypress_sound_volume_settings" msgid="6027007337036891623">"កម្រិត​សំឡេង​ពេល​ចុច​គ្រាប់​ចុច"</string>
     <string name="prefs_read_external_dictionary" msgid="2588931418575013067">"អាន​ឯកសារ​វចនានុក្រម​ខាង​ក្រៅ"</string>
-    <string name="read_external_dictionary_no_files_message" msgid="4947420942224623792">"គ្មាន​ឯកសារ​វចនានុក្រម​នៅ​ក្នុង​ថត​ទាញ​យក"</string>
+    <string name="read_external_dictionary_no_files_message" msgid="4947420942224623792">"គ្មាន​ឯកសារ​វចនានុក្រម​នៅ​ក្នុង​ថត​ទាញ​យក​​"</string>
     <string name="read_external_dictionary_multiple_files_title" msgid="7637749044265808628">"ជ្រើស​ឯកសារ​វចនានុក្រម​ ដើម្បី​ដំឡើង"</string>
     <string name="read_external_dictionary_confirm_install_message" msgid="4782116251651288054">"ពិត​ជា​ដំឡើង​ឯកសារ​នេះ​សម្រាប់ <xliff:g id="LANGUAGE_NAME">%s</xliff:g>?"</string>
     <string name="error" msgid="8940763624668513648">"មាន​កំហុស"</string>
@@ -154,7 +154,7 @@
     <string name="dictionary_provider_name" msgid="3027315045397363079">"កម្មវិធី​ផ្ដល់​វចនានុក្រម"</string>
     <string name="dictionary_service_name" msgid="6237472350693511448">"សេវាកម្ម​​វចនានុក្រម"</string>
     <string name="download_description" msgid="6014835283119198591">"ព័ត៌មាន​បច្ចុប្បន្នភាព​វចនានុក្រម"</string>
-    <string name="dictionary_settings_title" msgid="8091417676045693313">"ផ្នែក​បន្ថែម​វចនានុក្រម"</string>
+    <string name="dictionary_settings_title" msgid="8091417676045693313">"ផ្នែក​បន្ថែម​វចនានុក្រម​​"</string>
     <string name="dictionary_install_over_metered_network_prompt" msgid="3587517870006332980">"វចនានុក្រម​​​​​អាច​ប្រើ​បាន"</string>
     <string name="dictionary_settings_summary" msgid="5305694987799824349">"ការ​កំណត់​សម្រាប់​វចនានុក្រម"</string>
     <string name="user_dictionaries" msgid="3582332055892252845">"វចនានុក្រម​​​អ្នក​ប្រើ"</string>
@@ -170,10 +170,10 @@
     <string name="message_updating" msgid="4457761393932375219">"ពិនិត្យមើល​បច្ចុប្បន្នភាព"</string>
     <string name="message_loading" msgid="5638680861387748936">"កំពុង​ផ្ទុក..."</string>
     <string name="main_dict_description" msgid="3072821352793492143">"វចនានុក្រម​ចម្បង"</string>
-    <string name="cancel" msgid="6830980399865683324">"បោះ​បង់"</string>
+    <string name="cancel" msgid="6830980399865683324">"បោះ​បង់​"</string>
     <string name="go_to_settings" msgid="3876892339342569259">"ការ​កំណត់"</string>
     <string name="install_dict" msgid="180852772562189365">"ដំឡើង"</string>
-    <string name="cancel_download_dict" msgid="7843340278507019303">"បោះ​បង់"</string>
+    <string name="cancel_download_dict" msgid="7843340278507019303">"បោះ​បង់​"</string>
     <string name="delete_dict" msgid="756853268088330054">"លុប"</string>
     <string name="should_download_over_metered_prompt" msgid="1583881200688185508">"ភាសា​ដែល​បាន​ជ្រើស​នៅ​លើ​ឧបករណ៍​ចល័ត​មាន​វចនានុក្រម​អាច​ប្រើ​បាន។&lt;br/&gt; យើង​ផ្ដល់​អនុសាសន៍​ឲ្យ &lt;b&gt;ទាញ​យក&lt;/b&gt; វចនានុក្រម​ភាសា <xliff:g id="LANGUAGE_NAME">%1$s</xliff:g> ដើម្បី​បង្កើន​បទពិសោធន៍​វាយ​បញ្ចូល​របស់​អ្នក។&lt;br/&gt; &lt;br/&gt; ការ​ទាញ​យក​អាច​ចំណាយ​ពេល​ប្រហែល​ពីរ​នាទី​នៅ​តាម 3G។ ការ​គិត​ថ្លៃ​អាច​អនុវត្ត​ប្រសិន​បើ​អ្នក​មិន​ប្រើ &lt;b&gt;ផែនការ​ទិន្នន័យ​គ្មាន​ដែន​កំណត់&lt;/b&gt;.&lt;br/&gt; បើ​អ្នក​មិន​ប្រាកដ​​ថា​ផែនការ​ណា​មួយ​ដែល​អ្នក​មាន យើង​ផ្ដល់​អនុសាសន៍​ឲ្យ​​ភ្ជាប់​វ៉ាយហ្វាយ ដើម្បី​ចាប់ផ្ដើម​ទាញ​យក​ដោយ​ស្វ័យ​ប្រវត្តិ។&lt;br/&gt; &lt;br/&gt; ជំនួយ៖ អ្នក​អាច​ទាញ​យក និង​លុប​វចនានុក្រម​ដោយ​ចូល​ទៅ​ &lt;b&gt;ភាសា &amp; ការ​បញ្ចូល&lt;/b&gt; នៅ​ក្នុង​ម៉ឺនុយ &lt;b&gt;ការ​កំណត់&lt;/b&gt; សម្រាប់​ឧបករណ៍​ចល័ត។"</string>
     <string name="download_over_metered" msgid="1643065851159409546">"ទាញ​យក​ឥឡូវ​នេះ (<xliff:g id="SIZE_IN_MEGABYTES">%1$.1f</xliff:g> មេកាបៃ)"</string>
@@ -191,7 +191,7 @@
     <string name="user_dict_settings_add_word_option_name" msgid="6665558053408962865">"ពាក្យ៖"</string>
     <string name="user_dict_settings_add_shortcut_option_name" msgid="3094731590655523777">"ផ្លូវកាត់​៖"</string>
     <string name="user_dict_settings_add_locale_option_name" msgid="4738643440987277705">"ភាសា៖"</string>
-    <string name="user_dict_settings_add_word_hint" msgid="4902434148985906707">"វាយ​បញ្ចូល​ពាក្យ"</string>
+    <string name="user_dict_settings_add_word_hint" msgid="4902434148985906707">"វាយ​បញ្ចូល​ពាក្យ​"</string>
     <string name="user_dict_settings_add_shortcut_hint" msgid="2265453012555060178">"ផ្លូវកាត់​ជា​ជម្រើស"</string>
     <string name="user_dict_settings_edit_dialog_title" msgid="3765774633869590352">"កែ​ពាក្យ"</string>
     <string name="user_dict_settings_context_menu_edit_title" msgid="6812255903472456302">"កែ"</string>
diff --git a/java/res/values/attrs.xml b/java/res/values/attrs.xml
index 475e92f..769a1d9 100644
--- a/java/res/values/attrs.xml
+++ b/java/res/values/attrs.xml
@@ -74,6 +74,7 @@
         <!-- Size of the text for spacebar language label, in the proportion of key height. -->
         <attr name="languageOnSpacebarTextRatio" format="fraction" />
         <attr name="languageOnSpacebarTextColor" format="color" />
+        <attr name="languageOnSpacebarTextShadowRadius" format="float" />
         <attr name="languageOnSpacebarTextShadowColor" format="color" />
         <!-- Background image for the spacebar. -->
         <attr name="spacebarBackground" format="reference" />
@@ -217,7 +218,12 @@
         <attr name="iconSettingsKey" format="reference" />
         <attr name="iconSpaceKey" format="reference" />
         <attr name="iconEnterKey" format="reference" />
+        <attr name="iconGoKey" format="reference" />
         <attr name="iconSearchKey" format="reference" />
+        <attr name="iconSendKey" format="reference" />
+        <attr name="iconNextKey" format="reference" />
+        <attr name="iconDoneKey" format="reference" />
+        <attr name="iconPreviousKey" format="reference" />
         <attr name="iconTabKey" format="reference" />
         <attr name="iconShortcutKey" format="reference" />
         <attr name="iconSpaceKeyForNumberLayout" format="reference" />
@@ -431,6 +437,7 @@
             <!--  This should be aligned with KeyboardId.IME_ACTION_* -->
             <enum name="actionCustomLabel" value="0x100" />
         </attr>
+        <attr name="isIconDefined" format="string" />
         <attr name="localeCode" format="string" />
         <attr name="languageCode" format="string" />
         <attr name="countryCode" format="string" />
diff --git a/java/res/values/colors.xml b/java/res/values/colors.xml
index dabb4d6..db9a27a 100644
--- a/java/res/values/colors.xml
+++ b/java/res/values/colors.xml
@@ -26,7 +26,6 @@
     <color name="suggested_word_color_ics">#B233B5E5</color>
     <color name="highlight_translucent_color_ics">#9933B5E5</color>
     <color name="key_text_color_holo">@android:color/white</color>
-    <color name="key_text_shadow_color_holo">@android:color/transparent</color>
     <color name="key_text_inactivated_color_holo">#66E0E4E5</color>
     <color name="key_hint_letter_color_holo">#80000000</color>
     <color name="key_hint_label_color_holo">#A0FFFFFF</color>
@@ -41,11 +40,17 @@
     <color name="suggested_word_color_klp">#B2F0F0F0</color>
     <color name="highlight_translucent_color_klp">#99E0E0E0</color>
     <!-- Color resources for LMP theme. Base color = F0F0F0 -->
-    <color name="key_hint_letter_color_lmp">@android:color/white</color>
-    <color name="highlight_color_lmp">#FFF0F0F0</color>
-    <color name="typed_word_color_lmp">#D8F0F0F0</color>
-    <color name="suggested_word_color_lmp">#B2F0F0F0</color>
-    <color name="highlight_translucent_color_lmp">#99E0E0E0</color>
+    <color name="key_text_inactive_color_lmp">#808184</color>
+    <color name="key_hint_letter_color_lmp">#808184</color>
+    <color name="highlight_color_lmp">#7FCAC3</color>
+    <color name="typed_word_color_lmp">#D87FCAC3</color>
+    <color name="suggested_word_color_lmp">#B27FCAC3</color>
+    <color name="highlight_translucent_color_lmp">#997FCAC3</color>
+    <color name="keyboard_background_lmp">#384248</color>
+    <color name="key_background_lmp">#384248</color>
+    <color name="key_background_pressed_lmp">#546872</color>
+    <color name="suggestions_strip_background_lmp">#263238</color>
+    <color name="suggested_word_background_selected_lmp">#384248</color>
     <!-- Color resources for setup wizard and tutorial -->
     <color name="setup_background">#FFEBEBEB</color>
     <color name="setup_text_dark">#FF707070</color>
diff --git a/java/res/values/config-common.xml b/java/res/values/config-common.xml
index 1962c0d..ad27ab4 100644
--- a/java/res/values/config-common.xml
+++ b/java/res/values/config-common.xml
@@ -133,7 +133,7 @@
     <integer name="config_gesture_trail_shadow_ratio">-1</integer>
 
     <!-- Common configuration of Emoji keyboard -->
-    <dimen name="config_emoji_category_page_id_height">3dp</dimen>
+    <dimen name="config_emoji_category_page_id_height">2dp</dimen>
 
     <!-- Inset used in Accessibility mode to avoid accidental key presses when a finger slides off the screen. -->
     <dimen name="config_accessibility_edge_slop">8dp</dimen>
diff --git a/java/res/values/keyboard-icons-lmp.xml b/java/res/values/keyboard-icons-lmp.xml
index a9cbabc..39e0fe3 100644
--- a/java/res/values/keyboard-icons-lmp.xml
+++ b/java/res/values/keyboard-icons-lmp.xml
@@ -26,7 +26,13 @@
         <item name="iconSettingsKey">@drawable/sym_keyboard_settings_holo_dark</item>
         <item name="iconSpaceKey">@drawable/sym_keyboard_space_holo_dark</item>
         <item name="iconEnterKey">@drawable/sym_keyboard_return_holo_dark</item>
+        <!-- TODO: Uncomment those icon definitions once we have those icon assets. -->
+        <!-- <item name="iconGoKey">@drawable/sym_keyboard_go_holo_dark</item> -->
         <item name="iconSearchKey">@drawable/sym_keyboard_search_holo_dark</item>
+        <!-- <item name="iconSendKey">@drawable/sym_keyboard_send_holo_dark</item> -->
+        <!-- <item name="iconNextKey">@drawable/sym_keyboard_next_holo_dark</item> -->
+        <!-- <item name="iconDoneKey">@drawable/sym_keyboard_done_holo_dark</item> -->
+        <!-- <item name="iconPreviousKey">@drawable/sym_keyboard_previous_holo_dark</item> -->
         <item name="iconTabKey">@drawable/sym_keyboard_tab_holo_dark</item>
         <item name="iconShortcutKey">@drawable/sym_keyboard_voice_holo_dark</item>
         <item name="iconSpaceKeyForNumberLayout">@drawable/sym_keyboard_space_holo_dark</item>
diff --git a/java/res/values/strings-talkback-descriptions.xml b/java/res/values/strings-talkback-descriptions.xml
index 4ffca10..80406d0 100644
--- a/java/res/values/strings-talkback-descriptions.xml
+++ b/java/res/values/strings-talkback-descriptions.xml
@@ -35,10 +35,14 @@
     <string name="spoken_description_unknown">Key code %d</string>
     <!-- Spoken description for the "Shift" keyboard key when "Shift" is off. -->
     <string name="spoken_description_shift">Shift</string>
+    <!-- Spoken description for the "Shift" keyboard key in symbols mode. -->
+    <string name="spoken_description_symbols_shift">More symbols</string>
     <!-- Spoken description for the "Shift" keyboard key when "Shift" is on. -->
-    <string name="spoken_description_shift_shifted">Shift on (tap to disable)</string>
+    <string name="spoken_description_shift_shifted">Shift</string>
+    <!-- Spoken description for the "Shift" keyboard key in 2nd symbols (a.k.a. symbols shift) mode. -->
+    <string name="spoken_description_symbols_shift_shifted">Symbols</string>
     <!-- Spoken description for the "Shift" keyboard key when "Caps lock" is on. -->
-    <string name="spoken_description_caps_lock">Caps lock on (tap to disable)</string>
+    <string name="spoken_description_caps_lock">Shift</string>
     <!-- Spoken description for the "Delete" keyboard key. -->
     <string name="spoken_description_delete">Delete</string>
     <!-- Spoken description for the "To Symbol" keyboard key. -->
@@ -76,8 +80,8 @@
     <string name="spoken_description_shiftmode_locked">Caps lock enabled</string>
     <!-- Spoken feedback after changing to the symbols keyboard. -->
     <string name="spoken_description_mode_symbol">Symbols mode</string>
-    <!-- Spoken feedback after changing to the symbols shift keyboard. -->
-    <string name="spoken_description_mode_symbol_shift">Symbols shift mode</string>
+    <!-- Spoken feedback after changing to the 2nd symbols (a.k.a. symbols shift) keyboard. -->
+    <string name="spoken_description_mode_symbol_shift">More symbols mode</string>
     <!-- Spoken feedback after changing to the alphanumeric keyboard. -->
     <string name="spoken_description_mode_alpha">Letters mode</string>
     <!-- Spoken feedback after changing to the phone dialer keyboard. -->
diff --git a/java/res/values/themes-ics.xml b/java/res/values/themes-ics.xml
index 616dbd8..d7943ee 100644
--- a/java/res/values/themes-ics.xml
+++ b/java/res/values/themes-ics.xml
@@ -56,8 +56,8 @@
         <item name="keyShiftedLetterHintInactivatedColor">@color/key_shifted_letter_hint_inactivated_color_holo</item>
         <item name="keyShiftedLetterHintActivatedColor">@color/key_shifted_letter_hint_activated_color_holo</item>
         <item name="keyPreviewTextColor">@color/key_text_color_holo</item>
-        <item name="keyTextShadowColor">@color/key_text_shadow_color_holo</item>
-        <item name="keyTextShadowRadius">0.0</item>
+        <!-- A negative value to disable key text shadow layer. -->
+        <item name="keyTextShadowRadius">-1.0</item>
     </style>
     <style
         name="MainKeyboardView.ICS"
@@ -71,6 +71,7 @@
         <item name="autoCorrectionSpacebarLedEnabled">false</item>
         <item name="autoCorrectionSpacebarLedIcon">@drawable/sym_keyboard_space_led_holo</item>
         <item name="languageOnSpacebarTextColor">@color/spacebar_text_color_holo</item>
+        <item name="languageOnSpacebarTextShadowRadius">1.0</item>
         <item name="languageOnSpacebarTextShadowColor">@color/spacebar_text_shadow_color_holo</item>
         <item name="spacebarBackground">@drawable/btn_keyboard_spacebar_ics</item>
     </style>
diff --git a/java/res/values/themes-klp.xml b/java/res/values/themes-klp.xml
index 9bb3d79..13500fe 100644
--- a/java/res/values/themes-klp.xml
+++ b/java/res/values/themes-klp.xml
@@ -56,8 +56,8 @@
         <item name="keyShiftedLetterHintInactivatedColor">@color/key_shifted_letter_hint_inactivated_color_holo</item>
         <item name="keyShiftedLetterHintActivatedColor">@color/key_shifted_letter_hint_activated_color_holo</item>
         <item name="keyPreviewTextColor">@color/key_text_color_holo</item>
-        <item name="keyTextShadowColor">@color/key_text_shadow_color_holo</item>
-        <item name="keyTextShadowRadius">0.0</item>
+        <!-- A negative value to disable key text shadow layer. -->
+        <item name="keyTextShadowRadius">-1.0</item>
     </style>
     <style
         name="MainKeyboardView.KLP"
@@ -71,6 +71,7 @@
         <item name="autoCorrectionSpacebarLedEnabled">false</item>
         <item name="autoCorrectionSpacebarLedIcon">@drawable/sym_keyboard_space_led_holo</item>
         <item name="languageOnSpacebarTextColor">@color/spacebar_text_color_holo</item>
+        <item name="languageOnSpacebarTextShadowRadius">1.0</item>
         <item name="languageOnSpacebarTextShadowColor">@color/spacebar_text_shadow_color_holo</item>
         <item name="spacebarBackground">@drawable/btn_keyboard_spacebar_klp</item>
     </style>
diff --git a/java/res/values/themes-lmp.xml b/java/res/values/themes-lmp.xml
index 773da19..c05190b 100644
--- a/java/res/values/themes-lmp.xml
+++ b/java/res/values/themes-lmp.xml
@@ -35,7 +35,7 @@
         parent="Keyboard"
     >
         <!-- This should be aligned with KeyboardSwitcher.KEYBOARD_THEMES[] -->
-        <item name="themeId">0</item>
+        <item name="themeId">3</item>
         <item name="keyboardTopPadding">@fraction/config_keyboard_top_padding_holo</item>
         <item name="keyboardBottomPadding">@fraction/config_keyboard_bottom_padding_holo</item>
         <item name="horizontalGap">@fraction/config_key_horizontal_gap_holo</item>
@@ -46,18 +46,18 @@
         name="KeyboardView.LMP"
         parent="KeyboardView"
     >
-        <item name="android:background">@drawable/keyboard_background_holo</item>
+        <item name="android:background">@color/keyboard_background_lmp</item>
         <item name="keyBackground">@drawable/btn_keyboard_key_lmp</item>
         <item name="keyTypeface">bold</item>
         <item name="keyTextColor">@color/key_text_color_holo</item>
-        <item name="keyTextInactivatedColor">@color/key_text_inactivated_color_holo</item>
+        <item name="keyTextInactivatedColor">@color/key_text_inactive_color_lmp</item>
         <item name="keyHintLetterColor">@color/key_hint_letter_color_lmp</item>
-        <item name="keyHintLabelColor">@color/key_hint_label_color_holo</item>
-        <item name="keyShiftedLetterHintInactivatedColor">@color/key_shifted_letter_hint_inactivated_color_holo</item>
-        <item name="keyShiftedLetterHintActivatedColor">@color/key_shifted_letter_hint_activated_color_holo</item>
+        <item name="keyHintLabelColor">@color/key_text_inactive_color_lmp</item>
+        <item name="keyShiftedLetterHintInactivatedColor">@color/key_text_inactive_color_lmp</item>
+        <item name="keyShiftedLetterHintActivatedColor">@color/key_text_color_holo</item>
         <item name="keyPreviewTextColor">@color/key_text_color_holo</item>
-        <item name="keyTextShadowColor">@color/key_text_shadow_color_holo</item>
-        <item name="keyTextShadowRadius">0.0</item>
+        <!-- A negative value to disable key text shadow layer. -->
+        <item name="keyTextShadowRadius">-1.0</item>
     </style>
     <style
         name="MainKeyboardView.LMP"
@@ -70,8 +70,9 @@
         <item name="slidingKeyInputPreviewColor">@color/highlight_translucent_color_lmp</item>
         <item name="autoCorrectionSpacebarLedEnabled">false</item>
         <item name="autoCorrectionSpacebarLedIcon">@drawable/sym_keyboard_space_led_holo</item>
-        <item name="languageOnSpacebarTextColor">@color/spacebar_text_color_holo</item>
-        <item name="languageOnSpacebarTextShadowColor">@color/spacebar_text_shadow_color_holo</item>
+        <item name="languageOnSpacebarTextColor">@color/key_text_inactive_color_lmp</item>
+        <!-- A negative value to disable text shadow layer. -->
+        <item name="languageOnSpacebarTextShadowRadius">-1.0</item>
         <item name="spacebarBackground">@drawable/btn_keyboard_spacebar_lmp</item>
     </style>
     <style
diff --git a/java/res/xml-sw600dp/key_styles_enter.xml b/java/res/xml-sw600dp/key_styles_enter.xml
index 0699e45..99ac108 100644
--- a/java/res/xml-sw600dp/key_styles_enter.xml
+++ b/java/res/xml-sw600dp/key_styles_enter.xml
@@ -117,6 +117,16 @@
         </case>
         <case
             latin:imeAction="actionGo"
+            latin:isIconDefined="go_key"
+        >
+            <key-style
+                latin:styleName="enterKeyStyle"
+                latin:keySpec="!icon/go_key|!code/key_enter"
+                latin:backgroundType="action"
+                latin:parentStyle="defaultEnterKeyStyle" />
+        </case>
+        <case
+            latin:imeAction="actionGo"
         >
             <key-style
                 latin:styleName="enterKeyStyle"
@@ -126,6 +136,16 @@
         </case>
         <case
             latin:imeAction="actionNext"
+            latin:isIconDefined="next_key"
+        >
+            <key-style
+                latin:styleName="enterKeyStyle"
+                latin:keySpec="!icon/next_key|!code/key_enter"
+                latin:backgroundType="action"
+                latin:parentStyle="defaultEnterKeyStyle" />
+        </case>
+        <case
+            latin:imeAction="actionNext"
         >
             <key-style
                 latin:styleName="enterKeyStyle"
@@ -135,6 +155,16 @@
         </case>
         <case
             latin:imeAction="actionPrevious"
+            latin:isIconDefined="previous_key"
+        >
+            <key-style
+                latin:styleName="enterKeyStyle"
+                latin:keySpec="!icon/previous_key|!code/key_enter"
+                latin:backgroundType="action"
+                latin:parentStyle="defaultEnterKeyStyle" />
+        </case>
+        <case
+            latin:imeAction="actionPrevious"
         >
             <key-style
                 latin:styleName="enterKeyStyle"
@@ -144,6 +174,16 @@
         </case>
         <case
             latin:imeAction="actionDone"
+            latin:isIconDefined="done_key"
+        >
+            <key-style
+                latin:styleName="enterKeyStyle"
+                latin:keySpec="!icon/done_key|!code/key_enter"
+                latin:backgroundType="action"
+                latin:parentStyle="defaultEnterKeyStyle" />
+        </case>
+        <case
+            latin:imeAction="actionDone"
         >
             <key-style
                 latin:styleName="enterKeyStyle"
@@ -153,6 +193,16 @@
         </case>
         <case
             latin:imeAction="actionSend"
+            latin:isIconDefined="send_key"
+        >
+            <key-style
+                latin:styleName="enterKeyStyle"
+                latin:keySpec="!icon/send_key|!code/key_enter"
+                latin:backgroundType="action"
+                latin:parentStyle="defaultEnterKeyStyle" />
+        </case>
+        <case
+            latin:imeAction="actionSend"
         >
             <key-style
                 latin:styleName="enterKeyStyle"
diff --git a/java/res/xml/key_styles_enter.xml b/java/res/xml/key_styles_enter.xml
index acb27ab..8bba136 100644
--- a/java/res/xml/key_styles_enter.xml
+++ b/java/res/xml/key_styles_enter.xml
@@ -284,6 +284,16 @@
         </case>
         <case
             latin:imeAction="actionGo"
+            latin:isIconDefined="go_key"
+        >
+            <key-style
+                latin:styleName="enterKeyStyle"
+                latin:keySpec="!icon/go_key|!code/key_enter"
+                latin:backgroundType="action"
+                latin:parentStyle="defaultEnterKeyStyle" />
+        </case>
+        <case
+            latin:imeAction="actionGo"
         >
             <key-style
                 latin:styleName="enterKeyStyle"
@@ -293,6 +303,16 @@
         </case>
         <case
             latin:imeAction="actionNext"
+            latin:isIconDefined="next_key"
+        >
+            <key-style
+                latin:styleName="enterKeyStyle"
+                latin:keySpec="!icon/next_key|!code/key_enter"
+                latin:backgroundType="action"
+                latin:parentStyle="defaultEnterKeyStyle" />
+        </case>
+        <case
+            latin:imeAction="actionNext"
         >
             <key-style
                 latin:styleName="enterKeyStyle"
@@ -302,6 +322,16 @@
         </case>
         <case
             latin:imeAction="actionPrevious"
+            latin:isIconDefined="previous_key"
+        >
+            <key-style
+                latin:styleName="enterKeyStyle"
+                latin:keySpec="!icon/previous_key|!code/key_enter"
+                latin:backgroundType="action"
+                latin:parentStyle="defaultEnterKeyStyle" />
+        </case>
+        <case
+            latin:imeAction="actionPrevious"
         >
             <key-style
                 latin:styleName="enterKeyStyle"
@@ -311,6 +341,16 @@
         </case>
         <case
             latin:imeAction="actionDone"
+            latin:isIconDefined="done_key"
+        >
+            <key-style
+                latin:styleName="enterKeyStyle"
+                latin:keySpec="!icon/done_key|!code/key_enter"
+                latin:backgroundType="action"
+                latin:parentStyle="defaultEnterKeyStyle" />
+        </case>
+        <case
+            latin:imeAction="actionDone"
         >
             <key-style
                 latin:styleName="enterKeyStyle"
@@ -320,6 +360,16 @@
         </case>
         <case
             latin:imeAction="actionSend"
+            latin:isIconDefined="send_key"
+        >
+            <key-style
+                latin:styleName="enterKeyStyle"
+                latin:keySpec="!icon/send_key|!code/key_enter"
+                latin:backgroundType="action"
+                latin:parentStyle="defaultEnterKeyStyle" />
+        </case>
+        <case
+            latin:imeAction="actionSend"
         >
             <key-style
                 latin:styleName="enterKeyStyle"
diff --git a/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java b/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
index 2e6649b..0499a34 100644
--- a/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
+++ b/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
@@ -189,9 +189,14 @@
             break;
         case KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED:
         case KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED:
-        case KeyboardId.ELEMENT_SYMBOLS_SHIFTED:
             resId = R.string.spoken_description_shift_shifted;
             break;
+        case KeyboardId.ELEMENT_SYMBOLS:
+            resId = R.string.spoken_description_symbols_shift;
+            break;
+        case KeyboardId.ELEMENT_SYMBOLS_SHIFTED:
+            resId = R.string.spoken_description_symbols_shift_shifted;
+            break;
         default:
             resId = R.string.spoken_description_shift;
         }
diff --git a/java/src/com/android/inputmethod/keyboard/EmojiCategoryPageIndicatorView.java b/java/src/com/android/inputmethod/keyboard/EmojiCategoryPageIndicatorView.java
index d56a3cf..9922f90 100644
--- a/java/src/com/android/inputmethod/keyboard/EmojiCategoryPageIndicatorView.java
+++ b/java/src/com/android/inputmethod/keyboard/EmojiCategoryPageIndicatorView.java
@@ -24,8 +24,9 @@
 import android.util.AttributeSet;
 import android.widget.LinearLayout;
 
-public class EmojiCategoryPageIndicatorView extends LinearLayout {
-    private static final float BOTTOM_MARGIN_RATIO = 0.66f;
+//TODO: Move this class to com.android.inputmethod.emoji package.
+public final class EmojiCategoryPageIndicatorView extends LinearLayout {
+    private static final float BOTTOM_MARGIN_RATIO = 1.0f;
     private final Paint mPaint = new Paint();
     private int mCategoryPageSize = 0;
     private int mCurrentCategoryPageId = 0;
diff --git a/java/src/com/android/inputmethod/keyboard/EmojiPalettesView.java b/java/src/com/android/inputmethod/keyboard/EmojiPalettesView.java
index d8b5758..d5f166c 100644
--- a/java/src/com/android/inputmethod/keyboard/EmojiPalettesView.java
+++ b/java/src/com/android/inputmethod/keyboard/EmojiPalettesView.java
@@ -19,50 +19,36 @@
 import static com.android.inputmethod.latin.Constants.NOT_A_COORDINATE;
 
 import android.content.Context;
-import android.content.SharedPreferences;
 import android.content.res.ColorStateList;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.graphics.Color;
-import android.graphics.Rect;
-import android.os.Build;
 import android.os.CountDownTimer;
 import android.preference.PreferenceManager;
-import android.support.v4.view.PagerAdapter;
 import android.support.v4.view.ViewPager;
 import android.util.AttributeSet;
-import android.util.Log;
 import android.util.Pair;
-import android.util.SparseArray;
 import android.util.TypedValue;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
-import android.view.ViewGroup;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.TabHost;
 import android.widget.TabHost.OnTabChangeListener;
 import android.widget.TextView;
 
-import com.android.inputmethod.keyboard.internal.DynamicGridKeyboard;
+import com.android.inputmethod.keyboard.internal.EmojiCategory;
 import com.android.inputmethod.keyboard.internal.EmojiLayoutParams;
 import com.android.inputmethod.keyboard.internal.EmojiPageKeyboardView;
+import com.android.inputmethod.keyboard.internal.EmojiPalettesAdapter;
 import com.android.inputmethod.keyboard.internal.KeyDrawParams;
 import com.android.inputmethod.keyboard.internal.KeyVisualAttributes;
 import com.android.inputmethod.latin.Constants;
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.SubtypeSwitcher;
-import com.android.inputmethod.latin.settings.Settings;
-import com.android.inputmethod.latin.utils.CollectionUtils;
 import com.android.inputmethod.latin.utils.ResourceUtils;
 
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.List;
-import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -76,11 +62,10 @@
  * </ol>
  * Because of the above reasons, this class doesn't extend {@link KeyboardView}.
  */
+// TODO: Move this class to com.android.inputmethod.emoji package.
 public final class EmojiPalettesView extends LinearLayout implements OnTabChangeListener,
         ViewPager.OnPageChangeListener, View.OnClickListener, View.OnTouchListener,
         EmojiPageKeyboardView.OnKeyEventListener {
-    static final String TAG = EmojiPalettesView.class.getSimpleName();
-    private static final boolean DEBUG_PAGER = false;
     private final int mKeyBackgroundId;
     private final int mEmojiFunctionalKeyBackgroundId;
     private final ColorStateList mTabLabelColor;
@@ -97,317 +82,6 @@
 
     private KeyboardActionListener mKeyboardActionListener = KeyboardActionListener.EMPTY_LISTENER;
 
-    private static final int CATEGORY_ID_UNSPECIFIED = -1;
-    public static final int CATEGORY_ID_RECENTS = 0;
-    public static final int CATEGORY_ID_PEOPLE = 1;
-    public static final int CATEGORY_ID_OBJECTS = 2;
-    public static final int CATEGORY_ID_NATURE = 3;
-    public static final int CATEGORY_ID_PLACES = 4;
-    public static final int CATEGORY_ID_SYMBOLS = 5;
-    public static final int CATEGORY_ID_EMOTICONS = 6;
-
-    private static class CategoryProperties {
-        public int mCategoryId;
-        public int mPageCount;
-        public CategoryProperties(final int categoryId, final int pageCount) {
-            mCategoryId = categoryId;
-            mPageCount = pageCount;
-        }
-    }
-
-    private static class EmojiCategory {
-        private static final String[] sCategoryName = {
-                "recents",
-                "people",
-                "objects",
-                "nature",
-                "places",
-                "symbols",
-                "emoticons" };
-        private static final int[] sCategoryIcon = {
-                R.drawable.ic_emoji_recent_light,
-                R.drawable.ic_emoji_people_light,
-                R.drawable.ic_emoji_objects_light,
-                R.drawable.ic_emoji_nature_light,
-                R.drawable.ic_emoji_places_light,
-                R.drawable.ic_emoji_symbols_light,
-                0 };
-        private static final String[] sCategoryLabel =
-                { null, null, null, null, null, null, ":-)" };
-        private static final int[] sAccessibilityDescriptionResourceIdsForCategories = {
-                R.string.spoken_descrption_emoji_category_recents,
-                R.string.spoken_descrption_emoji_category_people,
-                R.string.spoken_descrption_emoji_category_objects,
-                R.string.spoken_descrption_emoji_category_nature,
-                R.string.spoken_descrption_emoji_category_places,
-                R.string.spoken_descrption_emoji_category_symbols,
-                R.string.spoken_descrption_emoji_category_emoticons };
-        private static final int[] sCategoryElementId = {
-                KeyboardId.ELEMENT_EMOJI_RECENTS,
-                KeyboardId.ELEMENT_EMOJI_CATEGORY1,
-                KeyboardId.ELEMENT_EMOJI_CATEGORY2,
-                KeyboardId.ELEMENT_EMOJI_CATEGORY3,
-                KeyboardId.ELEMENT_EMOJI_CATEGORY4,
-                KeyboardId.ELEMENT_EMOJI_CATEGORY5,
-                KeyboardId.ELEMENT_EMOJI_CATEGORY6 };
-        private final SharedPreferences mPrefs;
-        private final Resources mRes;
-        private final int mMaxPageKeyCount;
-        private final KeyboardLayoutSet mLayoutSet;
-        private final HashMap<String, Integer> mCategoryNameToIdMap = CollectionUtils.newHashMap();
-        private final ArrayList<CategoryProperties> mShownCategories =
-                CollectionUtils.newArrayList();
-        private final ConcurrentHashMap<Long, DynamicGridKeyboard>
-                mCategoryKeyboardMap = new ConcurrentHashMap<Long, DynamicGridKeyboard>();
-
-        private int mCurrentCategoryId = CATEGORY_ID_UNSPECIFIED;
-        private int mCurrentCategoryPageId = 0;
-
-        public EmojiCategory(final SharedPreferences prefs, final Resources res,
-                final KeyboardLayoutSet layoutSet) {
-            mPrefs = prefs;
-            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);
-            }
-            addShownCategoryId(CATEGORY_ID_RECENTS);
-            if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR2
-                    || android.os.Build.VERSION.CODENAME.equalsIgnoreCase("KeyLimePie")
-                    || android.os.Build.VERSION.CODENAME.equalsIgnoreCase("KitKat")) {
-                addShownCategoryId(CATEGORY_ID_PEOPLE);
-                addShownCategoryId(CATEGORY_ID_OBJECTS);
-                addShownCategoryId(CATEGORY_ID_NATURE);
-                addShownCategoryId(CATEGORY_ID_PLACES);
-                mCurrentCategoryId =
-                        Settings.readLastShownEmojiCategoryId(mPrefs, CATEGORY_ID_PEOPLE);
-            } else {
-                mCurrentCategoryId =
-                        Settings.readLastShownEmojiCategoryId(mPrefs, CATEGORY_ID_SYMBOLS);
-            }
-            addShownCategoryId(CATEGORY_ID_SYMBOLS);
-            addShownCategoryId(CATEGORY_ID_EMOTICONS);
-            getKeyboard(CATEGORY_ID_RECENTS, 0 /* cagetoryPageId */)
-                    .loadRecentKeys(mCategoryKeyboardMap.values());
-        }
-
-        private void addShownCategoryId(final int categoryId) {
-            // Load a keyboard of categoryId
-            getKeyboard(categoryId, 0 /* cagetoryPageId */);
-            final CategoryProperties properties =
-                    new CategoryProperties(categoryId, getCategoryPageCount(categoryId));
-            mShownCategories.add(properties);
-        }
-
-        public String getCategoryName(final int categoryId, final int categoryPageId) {
-            return sCategoryName[categoryId] + "-" + categoryPageId;
-        }
-
-        public int getCategoryId(final String name) {
-            final String[] strings = name.split("-");
-            return mCategoryNameToIdMap.get(strings[0]);
-        }
-
-        public int getCategoryIcon(final int categoryId) {
-            return sCategoryIcon[categoryId];
-        }
-
-        public String getCategoryLabel(final int categoryId) {
-            return sCategoryLabel[categoryId];
-        }
-
-        public String getAccessibilityDescription(final int categoryId) {
-            return mRes.getString(sAccessibilityDescriptionResourceIdsForCategories[categoryId]);
-        }
-
-        public ArrayList<CategoryProperties> getShownCategories() {
-            return mShownCategories;
-        }
-
-        public int getCurrentCategoryId() {
-            return mCurrentCategoryId;
-        }
-
-        public int getCurrentCategoryPageSize() {
-            return getCategoryPageSize(mCurrentCategoryId);
-        }
-
-        public int getCategoryPageSize(final int categoryId) {
-            for (final CategoryProperties prop : mShownCategories) {
-                if (prop.mCategoryId == categoryId) {
-                    return prop.mPageCount;
-                }
-            }
-            Log.w(TAG, "Invalid category id: " + categoryId);
-            // Should not reach here.
-            return 0;
-        }
-
-        public void setCurrentCategoryId(final int categoryId) {
-            mCurrentCategoryId = categoryId;
-            Settings.writeLastShownEmojiCategoryId(mPrefs, categoryId);
-        }
-
-        public void setCurrentCategoryPageId(final int id) {
-            mCurrentCategoryPageId = id;
-        }
-
-        public int getCurrentCategoryPageId() {
-            return mCurrentCategoryPageId;
-        }
-
-        public void saveLastTypedCategoryPage() {
-            Settings.writeLastTypedEmojiCategoryPageId(
-                    mPrefs, mCurrentCategoryId, mCurrentCategoryPageId);
-        }
-
-        public boolean isInRecentTab() {
-            return mCurrentCategoryId == CATEGORY_ID_RECENTS;
-        }
-
-        public int getTabIdFromCategoryId(final int categoryId) {
-            for (int i = 0; i < mShownCategories.size(); ++i) {
-                if (mShownCategories.get(i).mCategoryId == categoryId) {
-                    return i;
-                }
-            }
-            Log.w(TAG, "categoryId not found: " + categoryId);
-            return 0;
-        }
-
-        // Returns the view pager's page position for the categoryId
-        public int getPageIdFromCategoryId(final int categoryId) {
-            final int lastSavedCategoryPageId =
-                    Settings.readLastTypedEmojiCategoryPageId(mPrefs, categoryId);
-            int sum = 0;
-            for (int i = 0; i < mShownCategories.size(); ++i) {
-                final CategoryProperties props = mShownCategories.get(i);
-                if (props.mCategoryId == categoryId) {
-                    return sum + lastSavedCategoryPageId;
-                }
-                sum += props.mPageCount;
-            }
-            Log.w(TAG, "categoryId not found: " + categoryId);
-            return 0;
-        }
-
-        public int getRecentTabId() {
-            return getTabIdFromCategoryId(CATEGORY_ID_RECENTS);
-        }
-
-        private int getCategoryPageCount(final int categoryId) {
-            final Keyboard keyboard = mLayoutSet.getKeyboard(sCategoryElementId[categoryId]);
-            return (keyboard.getSortedKeys().size() - 1) / mMaxPageKeyCount + 1;
-        }
-
-        // Returns a pair of the category id and the category page id from the view pager's page
-        // position. The category page id is numbered in each category. And the view page position
-        // is the position of the current shown page in the view pager which contains all pages of
-        // all categories.
-        public Pair<Integer, Integer> getCategoryIdAndPageIdFromPagePosition(final int position) {
-            int sum = 0;
-            for (final CategoryProperties properties : mShownCategories) {
-                final int temp = sum;
-                sum += properties.mPageCount;
-                if (sum > position) {
-                    return new Pair<Integer, Integer>(properties.mCategoryId, position - temp);
-                }
-            }
-            return null;
-        }
-
-        // Returns a keyboard from the view pager's page position.
-        public DynamicGridKeyboard getKeyboardFromPagePosition(final int position) {
-            final Pair<Integer, Integer> categoryAndId =
-                    getCategoryIdAndPageIdFromPagePosition(position);
-            if (categoryAndId != null) {
-                return getKeyboard(categoryAndId.first, categoryAndId.second);
-            }
-            return null;
-        }
-
-        private static final Long getCategoryKeyboardMapKey(final int categoryId, final int id) {
-            return (((long) categoryId) << Constants.MAX_INT_BIT_COUNT) | id;
-        }
-
-        public DynamicGridKeyboard getKeyboard(final int categoryId, final int id) {
-            synchronized (mCategoryKeyboardMap) {
-                final Long categotyKeyboardMapKey = getCategoryKeyboardMapKey(categoryId, id);
-                if (mCategoryKeyboardMap.containsKey(categotyKeyboardMapKey)) {
-                    return mCategoryKeyboardMap.get(categotyKeyboardMapKey);
-                }
-
-                if (categoryId == CATEGORY_ID_RECENTS) {
-                    final DynamicGridKeyboard kbd = new DynamicGridKeyboard(mPrefs,
-                            mLayoutSet.getKeyboard(KeyboardId.ELEMENT_EMOJI_RECENTS),
-                            mMaxPageKeyCount, categoryId);
-                    mCategoryKeyboardMap.put(categotyKeyboardMapKey, kbd);
-                    return kbd;
-                }
-
-                final Keyboard keyboard = mLayoutSet.getKeyboard(sCategoryElementId[categoryId]);
-                final Key[][] sortedKeys = sortKeysIntoPages(
-                        keyboard.getSortedKeys(), mMaxPageKeyCount);
-                for (int pageId = 0; pageId < sortedKeys.length; ++pageId) {
-                    final DynamicGridKeyboard tempKeyboard = new DynamicGridKeyboard(mPrefs,
-                            mLayoutSet.getKeyboard(KeyboardId.ELEMENT_EMOJI_RECENTS),
-                            mMaxPageKeyCount, categoryId);
-                    for (final Key emojiKey : sortedKeys[pageId]) {
-                        if (emojiKey == null) {
-                            break;
-                        }
-                        tempKeyboard.addKeyLast(emojiKey);
-                    }
-                    mCategoryKeyboardMap.put(
-                            getCategoryKeyboardMapKey(categoryId, pageId), tempKeyboard);
-                }
-                return mCategoryKeyboardMap.get(categotyKeyboardMapKey);
-            }
-        }
-
-        public int getTotalPageCountOfAllCategories() {
-            int sum = 0;
-            for (CategoryProperties properties : mShownCategories) {
-                sum += properties.mPageCount;
-            }
-            return sum;
-        }
-
-        private static Comparator<Key> EMOJI_KEY_COMPARATOR = new Comparator<Key>() {
-            @Override
-            public int compare(final Key lhs, final Key rhs) {
-                final Rect lHitBox = lhs.getHitBox();
-                final Rect rHitBox = rhs.getHitBox();
-                if (lHitBox.top < rHitBox.top) {
-                    return -1;
-                } else if (lHitBox.top > rHitBox.top) {
-                    return 1;
-                }
-                if (lHitBox.left < rHitBox.left) {
-                    return -1;
-                } else if (lHitBox.left > rHitBox.left) {
-                    return 1;
-                }
-                if (lhs.getCode() == rhs.getCode()) {
-                    return 0;
-                }
-                return lhs.getCode() < rhs.getCode() ? -1 : 1;
-            }
-        };
-
-        private static Key[][] sortKeysIntoPages(final List<Key> inKeys, final int maxPageCount) {
-            final ArrayList<Key> keys = CollectionUtils.newArrayList(inKeys);
-            Collections.sort(keys, EMOJI_KEY_COMPARATOR);
-            final int pageCount = (keys.size() - 1) / maxPageCount + 1;
-            final Key[][] retval = new Key[pageCount][maxPageCount];
-            for (int i = 0; i < keys.size(); ++i) {
-                retval[i / maxPageCount][i % maxPageCount] = keys.get(i);
-            }
-            return retval;
-        }
-    }
-
     private final EmojiCategory mEmojiCategory;
 
     public EmojiPalettesView(final Context context, final AttributeSet attrs) {
@@ -481,7 +155,8 @@
     protected void onFinishInflate() {
         mTabHost = (TabHost)findViewById(R.id.emoji_category_tabhost);
         mTabHost.setup();
-        for (final CategoryProperties properties : mEmojiCategory.getShownCategories()) {
+        for (final EmojiCategory.CategoryProperties properties
+                : mEmojiCategory.getShownCategories()) {
             addTab(mTabHost, properties.mCategoryId);
         }
         mTabHost.setOnTabChangedListener(this);
@@ -675,9 +350,6 @@
 
     public void startEmojiPalettes(final String switchToAlphaLabel,
             final KeyVisualAttributes keyVisualAttr) {
-        if (DEBUG_PAGER) {
-            Log.d(TAG, "allocate emoji palettes memory " + mCurrentPagerPosition);
-        }
         final KeyDrawParams params = new KeyDrawParams();
         params.updateParams(mEmojiLayoutParams.getActionBarHeight(), keyVisualAttr);
         setupAlphabetKey(mAlphabetKeyLeft, switchToAlphaLabel, params);
@@ -687,9 +359,6 @@
     }
 
     public void stopEmojiPalettes() {
-        if (DEBUG_PAGER) {
-            Log.d(TAG, "deallocate emoji palettes memory");
-        }
         mEmojiPalettesAdapter.flushPendingRecentKeys();
         mEmojiPager.setAdapter(null);
     }
@@ -714,7 +383,7 @@
             return;
         }
 
-        if (oldCategoryId == CATEGORY_ID_RECENTS) {
+        if (oldCategoryId == EmojiCategory.ID_RECENTS) {
             // Needs to save pending updates for recent keys when we get out of the recents
             // category because we don't want to move the recent emojis around while the user
             // is in the recents category.
@@ -733,124 +402,11 @@
         }
     }
 
-    private static class EmojiPalettesAdapter extends PagerAdapter {
-        private final EmojiPageKeyboardView.OnKeyEventListener mListener;
-        private final DynamicGridKeyboard mRecentsKeyboard;
-        private final SparseArray<EmojiPageKeyboardView> mActiveKeyboardViews =
-                CollectionUtils.newSparseArray();
-        private final EmojiCategory mEmojiCategory;
-        private int mActivePosition = 0;
-
-        public EmojiPalettesAdapter(final EmojiCategory emojiCategory,
-                final EmojiPageKeyboardView.OnKeyEventListener listener) {
-            mEmojiCategory = emojiCategory;
-            mListener = listener;
-            mRecentsKeyboard = mEmojiCategory.getKeyboard(CATEGORY_ID_RECENTS, 0);
-        }
-
-        public void flushPendingRecentKeys() {
-            mRecentsKeyboard.flushPendingRecentKeys();
-            final KeyboardView recentKeyboardView =
-                    mActiveKeyboardViews.get(mEmojiCategory.getRecentTabId());
-            if (recentKeyboardView != null) {
-                recentKeyboardView.invalidateAllKeys();
-            }
-        }
-
-        public void addRecentKey(final Key key) {
-            if (mEmojiCategory.isInRecentTab()) {
-                mRecentsKeyboard.addPendingKey(key);
-                return;
-            }
-            mRecentsKeyboard.addKeyFirst(key);
-            final KeyboardView recentKeyboardView =
-                    mActiveKeyboardViews.get(mEmojiCategory.getRecentTabId());
-            if (recentKeyboardView != null) {
-                recentKeyboardView.invalidateAllKeys();
-            }
-        }
-
-        public void onPageScrolled() {
-            // Make sure the delayed key-down event (highlight effect and haptic feedback) will be
-            // canceled.
-            final EmojiPageKeyboardView currentKeyboardView =
-                    mActiveKeyboardViews.get(mActivePosition);
-            if (currentKeyboardView != null) {
-                currentKeyboardView.releaseCurrentKey();
-            }
-        }
-
-        @Override
-        public int getCount() {
-            return mEmojiCategory.getTotalPageCountOfAllCategories();
-        }
-
-        @Override
-        public void setPrimaryItem(final ViewGroup container, final int position,
-                final Object object) {
-            if (mActivePosition == position) {
-                return;
-            }
-            final EmojiPageKeyboardView oldKeyboardView = mActiveKeyboardViews.get(mActivePosition);
-            if (oldKeyboardView != null) {
-                oldKeyboardView.releaseCurrentKey();
-                oldKeyboardView.deallocateMemory();
-            }
-            mActivePosition = position;
-        }
-
-        @Override
-        public Object instantiateItem(final ViewGroup container, final int position) {
-            if (DEBUG_PAGER) {
-                Log.d(TAG, "instantiate item: " + position);
-            }
-            final EmojiPageKeyboardView oldKeyboardView = mActiveKeyboardViews.get(position);
-            if (oldKeyboardView != null) {
-                oldKeyboardView.deallocateMemory();
-                // This may be redundant but wanted to be safer..
-                mActiveKeyboardViews.remove(position);
-            }
-            final Keyboard keyboard =
-                    mEmojiCategory.getKeyboardFromPagePosition(position);
-            final LayoutInflater inflater = LayoutInflater.from(container.getContext());
-            final EmojiPageKeyboardView keyboardView = (EmojiPageKeyboardView)inflater.inflate(
-                    R.layout.emoji_keyboard_page, container, false /* attachToRoot */);
-            keyboardView.setKeyboard(keyboard);
-            keyboardView.setOnKeyEventListener(mListener);
-            container.addView(keyboardView);
-            mActiveKeyboardViews.put(position, keyboardView);
-            return keyboardView;
-        }
-
-        @Override
-        public boolean isViewFromObject(final View view, final Object object) {
-            return view == object;
-        }
-
-        @Override
-        public void destroyItem(final ViewGroup container, final int position,
-                final Object object) {
-            if (DEBUG_PAGER) {
-                Log.d(TAG, "destroy item: " + position + ", " + object.getClass().getSimpleName());
-            }
-            final EmojiPageKeyboardView keyboardView = mActiveKeyboardViews.get(position);
-            if (keyboardView != null) {
-                keyboardView.deallocateMemory();
-                mActiveKeyboardViews.remove(position);
-            }
-            if (object instanceof View) {
-                container.removeView((View)object);
-            } else {
-                Log.w(TAG, "Warning!!! Emoji palette may be leaking. " + object);
-            }
-        }
-    }
-
     private static class DeleteKeyOnTouchListener implements OnTouchListener {
-        private static final long MAX_REPEAT_COUNT_TIME = TimeUnit.SECONDS.toMillis(30);
-        private final int mDeleteKeyPressedBackgroundColor;
-        private final long mKeyRepeatStartTimeout;
-        private final long mKeyRepeatInterval;
+        static final long MAX_REPEAT_COUNT_TIME = TimeUnit.SECONDS.toMillis(30);
+        final int mDeleteKeyPressedBackgroundColor;
+        final long mKeyRepeatStartTimeout;
+        final long mKeyRepeatInterval;
 
         public DeleteKeyOnTouchListener(Context context) {
             final Resources res = context.getResources();
@@ -953,7 +509,7 @@
         }
 
         // Called by {@link #mTimer} in the UI thread as an auto key-repeat signal.
-        private void onKeyRepeat() {
+        void onKeyRepeat() {
             switch (mState) {
             case KEY_REPEAT_STATE_INITIALIZED:
                 // Basically this should not happen.
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
index 8ca00b0..4450a44 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
@@ -82,6 +82,7 @@
     private final float mVerticalCorrection;
     private final Drawable mKeyBackground;
     private final Rect mKeyBackgroundPadding = new Rect();
+    private static final float KET_TEXT_SHADOW_RADIUS_DISABLED = -1.0f;
 
     // HORIZONTAL ELLIPSIS "...", character for popup hint.
     private static final String POPUP_HINT_CHAR = "\u2026";
@@ -133,7 +134,7 @@
         mKeyShiftedLetterHintPadding = keyboardViewAttr.getDimension(
                 R.styleable.KeyboardView_keyShiftedLetterHintPadding, 0.0f);
         mKeyTextShadowRadius = keyboardViewAttr.getFloat(
-                R.styleable.KeyboardView_keyTextShadowRadius, 0.0f);
+                R.styleable.KeyboardView_keyTextShadowRadius, KET_TEXT_SHADOW_RADIUS_DISABLED);
         mVerticalCorrection = keyboardViewAttr.getDimension(
                 R.styleable.KeyboardView_verticalCorrection, 0.0f);
         keyboardViewAttr.recycle();
@@ -414,18 +415,23 @@
                 }
             }
 
-            paint.setColor(key.selectTextColor(params));
             if (key.isEnabled()) {
-                // Set a drop shadow for the text
-                paint.setShadowLayer(mKeyTextShadowRadius, 0.0f, 0.0f, params.mTextShadowColor);
+                paint.setColor(key.selectTextColor(params));
+                // Set a drop shadow for the text if the shadow radius is positive value.
+                if (mKeyTextShadowRadius > 0.0f) {
+                    paint.setShadowLayer(mKeyTextShadowRadius, 0.0f, 0.0f, params.mTextShadowColor);
+                } else {
+                    paint.clearShadowLayer();
+                }
             } else {
                 // Make label invisible
                 paint.setColor(Color.TRANSPARENT);
+                paint.clearShadowLayer();
             }
             blendAlpha(paint, params.mAnimAlpha);
             canvas.drawText(label, 0, label.length(), positionX, baseline, paint);
             // Turn off drop shadow and reset x-scale.
-            paint.setShadowLayer(0.0f, 0.0f, 0.0f, Color.TRANSPARENT);
+            paint.clearShadowLayer();
             paint.setTextScaleX(1.0f);
 
             if (icon != null) {
diff --git a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
index 8f79a91..1a8e4b7 100644
--- a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
@@ -74,6 +74,7 @@
  * @attr ref R.styleable#MainKeyboardView_autoCorrectionSpacebarLedIcon
  * @attr ref R.styleable#MainKeyboardView_languageOnSpacebarTextRatio
  * @attr ref R.styleable#MainKeyboardView_languageOnSpacebarTextColor
+ * @attr ref R.styleable#MainKeyboardView_languageOnSpacebarTextShadowRadius
  * @attr ref R.styleable#MainKeyboardView_languageOnSpacebarTextShadowColor
  * @attr ref R.styleable#MainKeyboardView_spacebarBackground
  * @attr ref R.styleable#MainKeyboardView_languageOnSpacebarFinalAlpha
@@ -129,7 +130,9 @@
     private final float mLanguageOnSpacebarTextRatio;
     private float mLanguageOnSpacebarTextSize;
     private final int mLanguageOnSpacebarTextColor;
+    private final float mLanguageOnSpacebarTextShadowRadius;
     private final int mLanguageOnSpacebarTextShadowColor;
+    private static final float LANGUAGE_ON_SPACEBAR_TEXT_SHADOW_RADIUS_DISABLED = -1.0f;
     // The minimum x-scale to fit the language name on spacebar.
     private static final float MINIMUM_XSCALE_OF_LANGUAGE_NAME = 0.8f;
     // Stuff to draw auto correction LED on spacebar.
@@ -231,6 +234,9 @@
                 R.styleable.MainKeyboardView_languageOnSpacebarTextRatio, 1, 1, 1.0f);
         mLanguageOnSpacebarTextColor = mainKeyboardViewAttr.getColor(
                 R.styleable.MainKeyboardView_languageOnSpacebarTextColor, 0);
+        mLanguageOnSpacebarTextShadowRadius = mainKeyboardViewAttr.getFloat(
+                R.styleable.MainKeyboardView_languageOnSpacebarTextShadowRadius,
+                LANGUAGE_ON_SPACEBAR_TEXT_SHADOW_RADIUS_DISABLED);
         mLanguageOnSpacebarTextShadowColor = mainKeyboardViewAttr.getColor(
                 R.styleable.MainKeyboardView_languageOnSpacebarTextShadowColor, 0);
         mLanguageOnSpacebarFinalAlpha = mainKeyboardViewAttr.getInt(
@@ -948,12 +954,17 @@
             final float descent = paint.descent();
             final float textHeight = -paint.ascent() + descent;
             final float baseline = height / 2 + textHeight / 2;
-            paint.setColor(mLanguageOnSpacebarTextShadowColor);
-            paint.setAlpha(mLanguageOnSpacebarAnimAlpha);
-            canvas.drawText(language, width / 2, baseline - descent - 1, paint);
+            if (mLanguageOnSpacebarTextShadowRadius > 0.0f) {
+                paint.setShadowLayer(mLanguageOnSpacebarTextShadowRadius, 0, 0,
+                        mLanguageOnSpacebarTextShadowColor);
+            } else {
+                paint.clearShadowLayer();
+            }
             paint.setColor(mLanguageOnSpacebarTextColor);
             paint.setAlpha(mLanguageOnSpacebarAnimAlpha);
             canvas.drawText(language, width / 2, baseline - descent, paint);
+            paint.clearShadowLayer();
+            paint.setTextScaleX(1.0f);
         }
 
         // Draw the spacebar icon at the bottom
diff --git a/java/src/com/android/inputmethod/keyboard/internal/DynamicGridKeyboard.java b/java/src/com/android/inputmethod/keyboard/internal/DynamicGridKeyboard.java
index 67a2227..a4879b8 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/DynamicGridKeyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/DynamicGridKeyboard.java
@@ -20,7 +20,6 @@
 import android.text.TextUtils;
 import android.util.Log;
 
-import com.android.inputmethod.keyboard.EmojiPalettesView;
 import com.android.inputmethod.keyboard.Key;
 import com.android.inputmethod.keyboard.Keyboard;
 import com.android.inputmethod.latin.settings.Settings;
@@ -36,6 +35,7 @@
 /**
  * This is a Keyboard class where you can add keys dynamically shown in a grid layout
  */
+// TODO: Move this class to com.android.inputmethod.emoji package.
 public class DynamicGridKeyboard extends Keyboard {
     private static final String TAG = DynamicGridKeyboard.class.getSimpleName();
     private static final int TEMPLATE_KEY_CODE_0 = 0x30;
@@ -62,7 +62,7 @@
         mVerticalStep = key0.getHeight() + mVerticalGap;
         mColumnsNum = mBaseWidth / mHorizontalStep;
         mMaxKeyCount = maxKeyCount;
-        mIsRecents = categoryId == EmojiPalettesView.CATEGORY_ID_RECENTS;
+        mIsRecents = categoryId == EmojiCategory.ID_RECENTS;
         mPrefs = prefs;
     }
 
diff --git a/java/src/com/android/inputmethod/keyboard/internal/EmojiCategory.java b/java/src/com/android/inputmethod/keyboard/internal/EmojiCategory.java
new file mode 100644
index 0000000..10bd621
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/internal/EmojiCategory.java
@@ -0,0 +1,359 @@
+/*
+ * 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.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.keyboard.internal;
+
+import android.content.SharedPreferences;
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.os.Build;
+import android.util.Log;
+import android.util.Pair;
+
+import com.android.inputmethod.keyboard.Key;
+import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.keyboard.KeyboardId;
+import com.android.inputmethod.keyboard.KeyboardLayoutSet;
+import com.android.inputmethod.latin.Constants;
+import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.settings.Settings;
+import com.android.inputmethod.latin.utils.CollectionUtils;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+
+// TODO: Move this class to com.android.inputmethod.emoji package.
+public final class EmojiCategory {
+    private final String TAG = EmojiCategory.class.getSimpleName();
+
+    private static final int ID_UNSPECIFIED = -1;
+    public static final int ID_RECENTS = 0;
+    private static final int ID_PEOPLE = 1;
+    private static final int ID_OBJECTS = 2;
+    private static final int ID_NATURE = 3;
+    private static final int ID_PLACES = 4;
+    private static final int ID_SYMBOLS = 5;
+    private static final int ID_EMOTICONS = 6;
+
+    public final class CategoryProperties {
+        public final int mCategoryId;
+        public final int mPageCount;
+        public CategoryProperties(final int categoryId, final int pageCount) {
+            mCategoryId = categoryId;
+            mPageCount = pageCount;
+        }
+    }
+
+    private static final String[] sCategoryName = {
+            "recents",
+            "people",
+            "objects",
+            "nature",
+            "places",
+            "symbols",
+            "emoticons" };
+
+    private static final int[] sCategoryIcon = {
+            R.drawable.ic_emoji_recent_light,
+            R.drawable.ic_emoji_people_light,
+            R.drawable.ic_emoji_objects_light,
+            R.drawable.ic_emoji_nature_light,
+            R.drawable.ic_emoji_places_light,
+            R.drawable.ic_emoji_symbols_light,
+            0 };
+
+    private static final String[] sCategoryLabel =
+            { null, null, null, null, null, null, ":-)" };
+
+    private static final int[] sAccessibilityDescriptionResourceIdsForCategories = {
+            R.string.spoken_descrption_emoji_category_recents,
+            R.string.spoken_descrption_emoji_category_people,
+            R.string.spoken_descrption_emoji_category_objects,
+            R.string.spoken_descrption_emoji_category_nature,
+            R.string.spoken_descrption_emoji_category_places,
+            R.string.spoken_descrption_emoji_category_symbols,
+            R.string.spoken_descrption_emoji_category_emoticons };
+
+    private static final int[] sCategoryElementId = {
+            KeyboardId.ELEMENT_EMOJI_RECENTS,
+            KeyboardId.ELEMENT_EMOJI_CATEGORY1,
+            KeyboardId.ELEMENT_EMOJI_CATEGORY2,
+            KeyboardId.ELEMENT_EMOJI_CATEGORY3,
+            KeyboardId.ELEMENT_EMOJI_CATEGORY4,
+            KeyboardId.ELEMENT_EMOJI_CATEGORY5,
+            KeyboardId.ELEMENT_EMOJI_CATEGORY6 };
+
+    private final SharedPreferences mPrefs;
+    private final Resources mRes;
+    private final int mMaxPageKeyCount;
+    private final KeyboardLayoutSet mLayoutSet;
+    private final HashMap<String, Integer> mCategoryNameToIdMap = CollectionUtils.newHashMap();
+    private final ArrayList<CategoryProperties> mShownCategories =
+            CollectionUtils.newArrayList();
+    private final ConcurrentHashMap<Long, DynamicGridKeyboard>
+            mCategoryKeyboardMap = new ConcurrentHashMap<Long, DynamicGridKeyboard>();
+
+    private int mCurrentCategoryId = EmojiCategory.ID_UNSPECIFIED;
+    private int mCurrentCategoryPageId = 0;
+
+    public EmojiCategory(final SharedPreferences prefs, final Resources res,
+            final KeyboardLayoutSet layoutSet) {
+        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);
+        }
+        addShownCategoryId(EmojiCategory.ID_RECENTS);
+        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR2
+                || android.os.Build.VERSION.CODENAME.equalsIgnoreCase("KeyLimePie")
+                || android.os.Build.VERSION.CODENAME.equalsIgnoreCase("KitKat")) {
+            addShownCategoryId(EmojiCategory.ID_PEOPLE);
+            addShownCategoryId(EmojiCategory.ID_OBJECTS);
+            addShownCategoryId(EmojiCategory.ID_NATURE);
+            addShownCategoryId(EmojiCategory.ID_PLACES);
+            mCurrentCategoryId =
+                    Settings.readLastShownEmojiCategoryId(mPrefs, EmojiCategory.ID_PEOPLE);
+        } else {
+            mCurrentCategoryId =
+                    Settings.readLastShownEmojiCategoryId(mPrefs, EmojiCategory.ID_SYMBOLS);
+        }
+        addShownCategoryId(EmojiCategory.ID_SYMBOLS);
+        addShownCategoryId(EmojiCategory.ID_EMOTICONS);
+        getKeyboard(EmojiCategory.ID_RECENTS, 0 /* cagetoryPageId */)
+                .loadRecentKeys(mCategoryKeyboardMap.values());
+    }
+
+    private void addShownCategoryId(final int categoryId) {
+        // Load a keyboard of categoryId
+        getKeyboard(categoryId, 0 /* cagetoryPageId */);
+        final CategoryProperties properties =
+                new CategoryProperties(categoryId, getCategoryPageCount(categoryId));
+        mShownCategories.add(properties);
+    }
+
+    public String getCategoryName(final int categoryId, final int categoryPageId) {
+        return sCategoryName[categoryId] + "-" + categoryPageId;
+    }
+
+    public int getCategoryId(final String name) {
+        final String[] strings = name.split("-");
+        return mCategoryNameToIdMap.get(strings[0]);
+    }
+
+    public int getCategoryIcon(final int categoryId) {
+        return sCategoryIcon[categoryId];
+    }
+
+    public String getCategoryLabel(final int categoryId) {
+        return sCategoryLabel[categoryId];
+    }
+
+    public String getAccessibilityDescription(final int categoryId) {
+        return mRes.getString(sAccessibilityDescriptionResourceIdsForCategories[categoryId]);
+    }
+
+    public ArrayList<CategoryProperties> getShownCategories() {
+        return mShownCategories;
+    }
+
+    public int getCurrentCategoryId() {
+        return mCurrentCategoryId;
+    }
+
+    public int getCurrentCategoryPageSize() {
+        return getCategoryPageSize(mCurrentCategoryId);
+    }
+
+    public int getCategoryPageSize(final int categoryId) {
+        for (final CategoryProperties prop : mShownCategories) {
+            if (prop.mCategoryId == categoryId) {
+                return prop.mPageCount;
+            }
+        }
+        Log.w(TAG, "Invalid category id: " + categoryId);
+        // Should not reach here.
+        return 0;
+    }
+
+    public void setCurrentCategoryId(final int categoryId) {
+        mCurrentCategoryId = categoryId;
+        Settings.writeLastShownEmojiCategoryId(mPrefs, categoryId);
+    }
+
+    public void setCurrentCategoryPageId(final int id) {
+        mCurrentCategoryPageId = id;
+    }
+
+    public int getCurrentCategoryPageId() {
+        return mCurrentCategoryPageId;
+    }
+
+    public void saveLastTypedCategoryPage() {
+        Settings.writeLastTypedEmojiCategoryPageId(
+                mPrefs, mCurrentCategoryId, mCurrentCategoryPageId);
+    }
+
+    public boolean isInRecentTab() {
+        return mCurrentCategoryId == EmojiCategory.ID_RECENTS;
+    }
+
+    public int getTabIdFromCategoryId(final int categoryId) {
+        for (int i = 0; i < mShownCategories.size(); ++i) {
+            if (mShownCategories.get(i).mCategoryId == categoryId) {
+                return i;
+            }
+        }
+        Log.w(TAG, "categoryId not found: " + categoryId);
+        return 0;
+    }
+
+    // Returns the view pager's page position for the categoryId
+    public int getPageIdFromCategoryId(final int categoryId) {
+        final int lastSavedCategoryPageId =
+                Settings.readLastTypedEmojiCategoryPageId(mPrefs, categoryId);
+        int sum = 0;
+        for (int i = 0; i < mShownCategories.size(); ++i) {
+            final CategoryProperties props = mShownCategories.get(i);
+            if (props.mCategoryId == categoryId) {
+                return sum + lastSavedCategoryPageId;
+            }
+            sum += props.mPageCount;
+        }
+        Log.w(TAG, "categoryId not found: " + categoryId);
+        return 0;
+    }
+
+    public int getRecentTabId() {
+        return getTabIdFromCategoryId(EmojiCategory.ID_RECENTS);
+    }
+
+    private int getCategoryPageCount(final int categoryId) {
+        final Keyboard keyboard = mLayoutSet.getKeyboard(sCategoryElementId[categoryId]);
+        return (keyboard.getSortedKeys().size() - 1) / mMaxPageKeyCount + 1;
+    }
+
+    // Returns a pair of the category id and the category page id from the view pager's page
+    // position. The category page id is numbered in each category. And the view page position
+    // is the position of the current shown page in the view pager which contains all pages of
+    // all categories.
+    public Pair<Integer, Integer> getCategoryIdAndPageIdFromPagePosition(final int position) {
+        int sum = 0;
+        for (final CategoryProperties properties : mShownCategories) {
+            final int temp = sum;
+            sum += properties.mPageCount;
+            if (sum > position) {
+                return new Pair<Integer, Integer>(properties.mCategoryId, position - temp);
+            }
+        }
+        return null;
+    }
+
+    // Returns a keyboard from the view pager's page position.
+    public DynamicGridKeyboard getKeyboardFromPagePosition(final int position) {
+        final Pair<Integer, Integer> categoryAndId =
+                getCategoryIdAndPageIdFromPagePosition(position);
+        if (categoryAndId != null) {
+            return getKeyboard(categoryAndId.first, categoryAndId.second);
+        }
+        return null;
+    }
+
+    private static final Long getCategoryKeyboardMapKey(final int categoryId, final int id) {
+        return (((long) categoryId) << Constants.MAX_INT_BIT_COUNT) | id;
+    }
+
+    public DynamicGridKeyboard getKeyboard(final int categoryId, final int id) {
+        synchronized (mCategoryKeyboardMap) {
+            final Long categotyKeyboardMapKey = getCategoryKeyboardMapKey(categoryId, id);
+            if (mCategoryKeyboardMap.containsKey(categotyKeyboardMapKey)) {
+                return mCategoryKeyboardMap.get(categotyKeyboardMapKey);
+            }
+
+            if (categoryId == EmojiCategory.ID_RECENTS) {
+                final DynamicGridKeyboard kbd = new DynamicGridKeyboard(mPrefs,
+                        mLayoutSet.getKeyboard(KeyboardId.ELEMENT_EMOJI_RECENTS),
+                        mMaxPageKeyCount, categoryId);
+                mCategoryKeyboardMap.put(categotyKeyboardMapKey, kbd);
+                return kbd;
+            }
+
+            final Keyboard keyboard = mLayoutSet.getKeyboard(sCategoryElementId[categoryId]);
+            final Key[][] sortedKeys = sortKeysIntoPages(
+                    keyboard.getSortedKeys(), mMaxPageKeyCount);
+            for (int pageId = 0; pageId < sortedKeys.length; ++pageId) {
+                final DynamicGridKeyboard tempKeyboard = new DynamicGridKeyboard(mPrefs,
+                        mLayoutSet.getKeyboard(KeyboardId.ELEMENT_EMOJI_RECENTS),
+                        mMaxPageKeyCount, categoryId);
+                for (final Key emojiKey : sortedKeys[pageId]) {
+                    if (emojiKey == null) {
+                        break;
+                    }
+                    tempKeyboard.addKeyLast(emojiKey);
+                }
+                mCategoryKeyboardMap.put(
+                        getCategoryKeyboardMapKey(categoryId, pageId), tempKeyboard);
+            }
+            return mCategoryKeyboardMap.get(categotyKeyboardMapKey);
+        }
+    }
+
+    public int getTotalPageCountOfAllCategories() {
+        int sum = 0;
+        for (CategoryProperties properties : mShownCategories) {
+            sum += properties.mPageCount;
+        }
+        return sum;
+    }
+
+    private static Comparator<Key> EMOJI_KEY_COMPARATOR = new Comparator<Key>() {
+        @Override
+        public int compare(final Key lhs, final Key rhs) {
+            final Rect lHitBox = lhs.getHitBox();
+            final Rect rHitBox = rhs.getHitBox();
+            if (lHitBox.top < rHitBox.top) {
+                return -1;
+            } else if (lHitBox.top > rHitBox.top) {
+                return 1;
+            }
+            if (lHitBox.left < rHitBox.left) {
+                return -1;
+            } else if (lHitBox.left > rHitBox.left) {
+                return 1;
+            }
+            if (lhs.getCode() == rhs.getCode()) {
+                return 0;
+            }
+            return lhs.getCode() < rhs.getCode() ? -1 : 1;
+        }
+    };
+
+    private static Key[][] sortKeysIntoPages(final List<Key> inKeys, final int maxPageCount) {
+        final ArrayList<Key> keys = CollectionUtils.newArrayList(inKeys);
+        Collections.sort(keys, EMOJI_KEY_COMPARATOR);
+        final int pageCount = (keys.size() - 1) / maxPageCount + 1;
+        final Key[][] retval = new Key[pageCount][maxPageCount];
+        for (int i = 0; i < keys.size(); ++i) {
+            retval[i / maxPageCount][i % maxPageCount] = keys.get(i);
+        }
+        return retval;
+    }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/EmojiLayoutParams.java b/java/src/com/android/inputmethod/keyboard/internal/EmojiLayoutParams.java
index d57ea5a..78af66b 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/EmojiLayoutParams.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/EmojiLayoutParams.java
@@ -24,7 +24,8 @@
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 
-public class EmojiLayoutParams {
+//TODO: Move this class to com.android.inputmethod.emoji package.
+public final class EmojiLayoutParams {
     private static final int DEFAULT_KEYBOARD_ROWS = 4;
 
     public final int mEmojiPagerHeight;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/EmojiPageKeyboardView.java b/java/src/com/android/inputmethod/keyboard/internal/EmojiPageKeyboardView.java
index e175a05..2f67d19 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/EmojiPageKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/EmojiPageKeyboardView.java
@@ -33,6 +33,7 @@
  * This is an extended {@link KeyboardView} class that hosts an emoji page keyboard.
  * Multi-touch unsupported. No {@link PointerTracker}s. No gesture support.
  */
+// TODO: Move this class to com.android.inputmethod.emoji package.
 // TODO: Implement key popup preview.
 public final class EmojiPageKeyboardView extends KeyboardView implements
         GestureDetector.OnGestureListener {
diff --git a/java/src/com/android/inputmethod/keyboard/internal/EmojiPalettesAdapter.java b/java/src/com/android/inputmethod/keyboard/internal/EmojiPalettesAdapter.java
new file mode 100644
index 0000000..a44d134
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/internal/EmojiPalettesAdapter.java
@@ -0,0 +1,147 @@
+/*
+ * 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.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.keyboard.internal;
+
+import android.support.v4.view.PagerAdapter;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.inputmethod.keyboard.Key;
+import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.keyboard.KeyboardView;
+import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.utils.CollectionUtils;
+
+// TODO: Move this class to com.android.inputmethod.emoji package.
+public final class EmojiPalettesAdapter extends PagerAdapter {
+    private static final String TAG = EmojiPalettesAdapter.class.getSimpleName();
+    private static final boolean DEBUG_PAGER = false;
+
+    private final EmojiPageKeyboardView.OnKeyEventListener mListener;
+    private final DynamicGridKeyboard mRecentsKeyboard;
+    private final SparseArray<EmojiPageKeyboardView> mActiveKeyboardViews =
+            CollectionUtils.newSparseArray();
+    private final EmojiCategory mEmojiCategory;
+    private int mActivePosition = 0;
+
+    public EmojiPalettesAdapter(final EmojiCategory emojiCategory,
+            final EmojiPageKeyboardView.OnKeyEventListener listener) {
+        mEmojiCategory = emojiCategory;
+        mListener = listener;
+        mRecentsKeyboard = mEmojiCategory.getKeyboard(EmojiCategory.ID_RECENTS, 0);
+    }
+
+    public void flushPendingRecentKeys() {
+        mRecentsKeyboard.flushPendingRecentKeys();
+        final KeyboardView recentKeyboardView =
+                mActiveKeyboardViews.get(mEmojiCategory.getRecentTabId());
+        if (recentKeyboardView != null) {
+            recentKeyboardView.invalidateAllKeys();
+        }
+    }
+
+    public void addRecentKey(final Key key) {
+        if (mEmojiCategory.isInRecentTab()) {
+            mRecentsKeyboard.addPendingKey(key);
+            return;
+        }
+        mRecentsKeyboard.addKeyFirst(key);
+        final KeyboardView recentKeyboardView =
+                mActiveKeyboardViews.get(mEmojiCategory.getRecentTabId());
+        if (recentKeyboardView != null) {
+            recentKeyboardView.invalidateAllKeys();
+        }
+    }
+
+    public void onPageScrolled() {
+        // Make sure the delayed key-down event (highlight effect and haptic feedback) will be
+        // canceled.
+        final EmojiPageKeyboardView currentKeyboardView =
+                mActiveKeyboardViews.get(mActivePosition);
+        if (currentKeyboardView != null) {
+            currentKeyboardView.releaseCurrentKey();
+        }
+    }
+
+    @Override
+    public int getCount() {
+        return mEmojiCategory.getTotalPageCountOfAllCategories();
+    }
+
+    @Override
+    public void setPrimaryItem(final ViewGroup container, final int position,
+            final Object object) {
+        if (mActivePosition == position) {
+            return;
+        }
+        final EmojiPageKeyboardView oldKeyboardView = mActiveKeyboardViews.get(mActivePosition);
+        if (oldKeyboardView != null) {
+            oldKeyboardView.releaseCurrentKey();
+            oldKeyboardView.deallocateMemory();
+        }
+        mActivePosition = position;
+    }
+
+    @Override
+    public Object instantiateItem(final ViewGroup container, final int position) {
+        if (DEBUG_PAGER) {
+            Log.d(TAG, "instantiate item: " + position);
+        }
+        final EmojiPageKeyboardView oldKeyboardView = mActiveKeyboardViews.get(position);
+        if (oldKeyboardView != null) {
+            oldKeyboardView.deallocateMemory();
+            // This may be redundant but wanted to be safer..
+            mActiveKeyboardViews.remove(position);
+        }
+        final Keyboard keyboard =
+                mEmojiCategory.getKeyboardFromPagePosition(position);
+        final LayoutInflater inflater = LayoutInflater.from(container.getContext());
+        final EmojiPageKeyboardView keyboardView = (EmojiPageKeyboardView)inflater.inflate(
+                R.layout.emoji_keyboard_page, container, false /* attachToRoot */);
+        keyboardView.setKeyboard(keyboard);
+        keyboardView.setOnKeyEventListener(mListener);
+        container.addView(keyboardView);
+        mActiveKeyboardViews.put(position, keyboardView);
+        return keyboardView;
+    }
+
+    @Override
+    public boolean isViewFromObject(final View view, final Object object) {
+        return view == object;
+    }
+
+    @Override
+    public void destroyItem(final ViewGroup container, final int position,
+            final Object object) {
+        if (DEBUG_PAGER) {
+            Log.d(TAG, "destroy item: " + position + ", " + object.getClass().getSimpleName());
+        }
+        final EmojiPageKeyboardView keyboardView = mActiveKeyboardViews.get(position);
+        if (keyboardView != null) {
+            keyboardView.deallocateMemory();
+            mActiveKeyboardViews.remove(position);
+        }
+        if (object instanceof View) {
+            container.removeView((View)object);
+        } else {
+            Log.w(TAG, "Warning!!! Emoji palette may be leaking. " + object);
+        }
+    }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java
index dfe0df0..2aeeed8 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java
@@ -664,6 +664,8 @@
                     R.styleable.Keyboard_Case_isMultiLine, id.isMultiLine());
             final boolean imeActionMatched = matchInteger(caseAttr,
                     R.styleable.Keyboard_Case_imeAction, id.imeAction());
+            final boolean isIconDefinedMatched = isIconDefined(caseAttr,
+                    R.styleable.Keyboard_Case_isIconDefined, mParams.mIconsSet);
             final boolean localeCodeMatched = matchString(caseAttr,
                     R.styleable.Keyboard_Case_localeCode, id.mLocale.toString());
             final boolean languageCodeMatched = matchString(caseAttr,
@@ -675,10 +677,11 @@
                     && passwordInputMatched && clobberSettingsKeyMatched
                     && supportsSwitchingToShortcutImeMatched && hasShortcutKeyMatched
                     && languageSwitchKeyEnabledMatched && isMultiLineMatched && imeActionMatched
-                    && localeCodeMatched && languageCodeMatched && countryCodeMatched;
+                    && isIconDefinedMatched && localeCodeMatched && languageCodeMatched
+                    && countryCodeMatched;
 
             if (DEBUG) {
-                startTag("<%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s>%s", TAG_CASE,
+                startTag("<%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s>%s", TAG_CASE,
                         textAttr(caseAttr.getString(
                                 R.styleable.Keyboard_Case_keyboardLayoutSet), "keyboardLayoutSet"),
                         textAttr(caseAttr.getString(
@@ -704,6 +707,8 @@
                                 "languageSwitchKeyEnabled"),
                         booleanAttr(caseAttr, R.styleable.Keyboard_Case_isMultiLine,
                                 "isMultiLine"),
+                        textAttr(caseAttr.getString(R.styleable.Keyboard_Case_isIconDefined),
+                                "isIconDefined"),
                         textAttr(caseAttr.getString(R.styleable.Keyboard_Case_localeCode),
                                 "localeCode"),
                         textAttr(caseAttr.getString(R.styleable.Keyboard_Case_languageCode),
@@ -755,6 +760,16 @@
         return false;
     }
 
+    private static boolean isIconDefined(final TypedArray a, final int index,
+            final KeyboardIconsSet iconsSet) {
+        if (!a.hasValue(index)) {
+            return true;
+        }
+        final String iconName = a.getString(index);
+        final int iconId = KeyboardIconsSet.getIconId(iconName);
+        return iconsSet.getIconDrawable(iconId) != null;
+    }
+
     private boolean parseDefault(final XmlPullParser parser, final KeyboardRow row,
             final boolean skip) throws XmlPullParserException, IOException {
         if (DEBUG) startTag("<%s>", TAG_DEFAULT);
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java
index 6c9b5ad..65d6a56 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java
@@ -42,7 +42,12 @@
     public static final String NAME_SPACE_KEY = "space_key";
     public static final String NAME_SPACE_KEY_FOR_NUMBER_LAYOUT = "space_key_for_number_layout";
     public static final String NAME_ENTER_KEY = "enter_key";
+    public static final String NAME_GO_KEY = "go_key";
     public static final String NAME_SEARCH_KEY = "search_key";
+    public static final String NAME_SEND_KEY = "send_key";
+    public static final String NAME_NEXT_KEY = "next_key";
+    public static final String NAME_DONE_KEY = "done_key";
+    public static final String NAME_PREVIOUS_KEY = "previous_key";
     public static final String NAME_TAB_KEY = "tab_key";
     public static final String NANE_TAB_KEY_PREVIEW = "tab_key_preview";
     public static final String NAME_SHORTCUT_KEY = "shortcut_key";
@@ -64,7 +69,12 @@
         NAME_SETTINGS_KEY,                R.styleable.Keyboard_iconSettingsKey,
         NAME_SPACE_KEY,                   R.styleable.Keyboard_iconSpaceKey,
         NAME_ENTER_KEY,                   R.styleable.Keyboard_iconEnterKey,
+        NAME_GO_KEY,                      R.styleable.Keyboard_iconGoKey,
         NAME_SEARCH_KEY,                  R.styleable.Keyboard_iconSearchKey,
+        NAME_SEND_KEY,                    R.styleable.Keyboard_iconSendKey,
+        NAME_NEXT_KEY,                    R.styleable.Keyboard_iconNextKey,
+        NAME_DONE_KEY,                    R.styleable.Keyboard_iconDoneKey,
+        NAME_PREVIOUS_KEY,                R.styleable.Keyboard_iconPreviousKey,
         NAME_TAB_KEY,                     R.styleable.Keyboard_iconTabKey,
         NAME_SHORTCUT_KEY,                R.styleable.Keyboard_iconShortcutKey,
         NAME_SPACE_KEY_FOR_NUMBER_LAYOUT, R.styleable.Keyboard_iconSpaceKeyForNumberLayout,
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
index 94a1e36..999508e 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
@@ -414,8 +414,8 @@
         public WordProperty mWordProperty;
         public int mNextToken;
 
-        public GetNextWordPropertyResult(final WordProperty wordPreperty, final int nextToken) {
-            mWordProperty = wordPreperty;
+        public GetNextWordPropertyResult(final WordProperty wordProperty, final int nextToken) {
+            mWordProperty = wordProperty;
             mNextToken = nextToken;
         }
     }
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 7dc566a..8a2ed10 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -540,18 +540,6 @@
         refreshPersonalizationDictionarySession();
     }
 
-    private DistracterFilter createDistracterFilter() {
-        final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
-        // TODO: Create Keyboard when mainKeyboardView is null.
-        // TODO: Figure out the most reasonable keyboard for the filter. Refer to the
-        // spellchecker's logic.
-        final Keyboard keyboard = (mainKeyboardView != null) ?
-                mainKeyboardView.getKeyboard() : null;
-        final DistracterFilter distracterFilter = new DistracterFilter(mInputLogic.mSuggest,
-                keyboard);
-        return distracterFilter;
-    }
-
     private void refreshPersonalizationDictionarySession() {
         final DictionaryFacilitatorForSuggest dictionaryFacilitator =
                 mInputLogic.mSuggest.mDictionaryFacilitator;
@@ -1755,6 +1743,11 @@
         mInputLogic.mSuggest.mDictionaryFacilitator.clearPersonalizationDictionary();
     }
 
+    @UsedForTesting
+    /* package for test */ DistracterFilter createDistracterFilter() {
+        return DistracterFilter.createDistracterFilter(mInputLogic.mSuggest, mKeyboardSwitcher);
+    }
+
     public void dumpDictionaryForDebug(final String dictName) {
         final DictionaryFacilitatorForSuggest dictionaryFacilitator =
                 mInputLogic.mSuggest.mDictionaryFacilitator;
diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java
index 8bfa63c..810bda7 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java
@@ -381,6 +381,7 @@
         }
 
         // Disable this suggestion if the suggestion is null or empty.
+        // TODO: Fix disabled {@link TextView}'s content description.
         wordView.setEnabled(!TextUtils.isEmpty(word));
         final CharSequence text = getEllipsizedText(word, width, wordView.getPaint());
         final float scaleX = getTextScaleX(word, width, wordView.getPaint());
@@ -424,7 +425,9 @@
             final int countInStrip) {
         // Clear all suggestions first
         for (int positionInStrip = 0; positionInStrip < countInStrip; ++positionInStrip) {
-            mWordViews.get(positionInStrip).setText(null);
+            final TextView wordView = mWordViews.get(positionInStrip);
+            wordView.setText(null);
+            wordView.setTag(null);
             // Make this inactive for touches in {@link #layoutWord(int,int)}.
             if (SuggestionStripView.DBG) {
                 mDebugInfoViews.get(positionInStrip).setText(null);
diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
index d4f7f36..619804a 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
@@ -124,9 +124,9 @@
             mImportantNoticeStrip.setVisibility(INVISIBLE);
         }
 
-        public void showImportantNoticeStrip() {
+        public void showImportantNoticeStrip(final boolean enableVoiceKey) {
             mSuggestionsStrip.setVisibility(INVISIBLE);
-            mVoiceKey.setVisibility(INVISIBLE);
+            mVoiceKey.setVisibility(enableVoiceKey ? VISIBLE : INVISIBLE);
             mAddToDictionaryStrip.setVisibility(INVISIBLE);
             mImportantNoticeStrip.setVisibility(VISIBLE);
         }
@@ -274,7 +274,7 @@
             dismissMoreSuggestionsPanel();
         }
         mLayoutHelper.layoutImportantNotice(mImportantNoticeStrip, importantNoticeTitle);
-        mStripVisibilityGroup.showImportantNoticeStrip();
+        mStripVisibilityGroup.showImportantNoticeStrip(isVoiceKeyEnabled());
         mImportantNoticeStrip.setOnClickListener(this);
         return true;
     }
diff --git a/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java b/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java
index 48e43d6..55cbf79 100644
--- a/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java
+++ b/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java
@@ -17,21 +17,35 @@
 package com.android.inputmethod.latin.utils;
 
 import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.keyboard.KeyboardSwitcher;
+import com.android.inputmethod.keyboard.MainKeyboardView;
+import com.android.inputmethod.latin.Constants;
 import com.android.inputmethod.latin.Suggest;
+import com.android.inputmethod.latin.Suggest.OnGetSuggestedWordsCallback;
+import com.android.inputmethod.latin.SuggestedWords;
+import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
+import com.android.inputmethod.latin.WordComposer;
 
 /**
- * This class is used to prevent distracters/misspellings being added to personalization
+ * This class is used to prevent distracters being added to personalization
  * or user history dictionaries
  */
 public class DistracterFilter {
     private final Suggest mSuggest;
     private final Keyboard mKeyboard;
 
+    // If the score of the top suggestion exceeds this value, the tested word (e.g.,
+    // an OOV, a misspelling, or an in-vocabulary word) would be considered as a distracter to
+    // words in dictionary. The greater the threshold is, the less likely the tested word would
+    // become a distracter, which means the tested word will be more likely to be added to
+    // the dictionary.
+    private static final float DISTRACTER_WORD_SCORE_THRESHOLD = 2.0f;
+
     /**
      * Create a DistracterFilter instance.
      *
      * @param suggest an instance of Suggest which will be used to obtain a list of suggestions
-     *                for a potential distracter/misspelling
+     *                for a potential distracter
      * @param keyboard the keyboard that is currently being used. This information is needed
      *                 when calling mSuggest.getSuggestedWords(...) to obtain a list of suggestions.
      */
@@ -40,9 +54,79 @@
         mKeyboard = keyboard;
     }
 
-    public boolean isDistracterToWordsInDictionaries(final String prevWord,
-            final String targetWord) {
-        // TODO: to be implemented
+    public static DistracterFilter createDistracterFilter(final Suggest suggest,
+            final KeyboardSwitcher keyboardSwitcher) {
+        final MainKeyboardView mainKeyboardView = keyboardSwitcher.getMainKeyboardView();
+        // TODO: Create Keyboard when mainKeyboardView is null.
+        // TODO: Figure out the most reasonable keyboard for the filter. Refer to the
+        // spellchecker's logic.
+        final Keyboard keyboard = (mainKeyboardView != null) ?
+                mainKeyboardView.getKeyboard() : null;
+        final DistracterFilter distracterFilter = new DistracterFilter(suggest, keyboard);
+        return distracterFilter;
+    }
+
+    private static boolean suggestionExceedsDistracterThreshold(
+            final SuggestedWordInfo suggestion, final String consideredWord,
+            final float distracterThreshold) {
+        if (null != suggestion) {
+            final int suggestionScore = suggestion.mScore;
+            final float normalizedScore = BinaryDictionaryUtils.calcNormalizedScore(
+                    consideredWord, suggestion.mWord, suggestionScore);
+            if (normalizedScore > distracterThreshold) {
+                return true;
+            }
+        }
         return false;
     }
+
+    /**
+     * Determine whether a word is a distracter to words in dictionaries.
+     *
+     * @param prevWord the previous word, or null if none.
+     * @param testedWord the word that will be tested to see whether it is a distracter to words
+     *                   in dictionaries.
+     * @return true if testedWord is a distracter, otherwise false.
+     */
+    public boolean isDistracterToWordsInDictionaries(final String prevWord,
+            final String testedWord) {
+        if (mSuggest == null) {
+            return false;
+        }
+
+        final WordComposer composer = new WordComposer();
+        final int[] codePoints = StringUtils.toCodePointArray(testedWord);
+        final int[] coordinates;
+        if (null == mKeyboard) {
+            coordinates = CoordinateUtils.newCoordinateArray(codePoints.length,
+                    Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
+        } else {
+            coordinates = mKeyboard.getCoordinates(codePoints);
+        }
+        composer.setComposingWord(codePoints, coordinates, prevWord);
+
+        final int trailingSingleQuotesCount = composer.trailingSingleQuotesCount();
+        final String consideredWord = trailingSingleQuotesCount > 0 ? testedWord.substring(0,
+                testedWord.length() - trailingSingleQuotesCount) : testedWord;
+        final AsyncResultHolder<Boolean> holder = new AsyncResultHolder<Boolean>();
+        final OnGetSuggestedWordsCallback callback = new OnGetSuggestedWordsCallback() {
+            @Override
+            public void onGetSuggestedWords(final SuggestedWords suggestedWords) {
+                if (suggestedWords != null && suggestedWords.size() > 1) {
+                    // The suggestedWordInfo at 0 is the typed word. The 1st suggestion from
+                    // the decoder is at index 1.
+                    final SuggestedWordInfo firstSuggestion = suggestedWords.getInfo(1);
+                    final boolean hasStrongDistractor = suggestionExceedsDistracterThreshold(
+                            firstSuggestion, consideredWord, DISTRACTER_WORD_SCORE_THRESHOLD);
+                    holder.set(hasStrongDistractor);
+                }
+            }
+        };
+        mSuggest.getSuggestedWords(composer, prevWord, mKeyboard.getProximityInfo(),
+                true /* blockOffensiveWords */, true /* isCorrectionEnbaled */,
+                null /* additionalFeaturesOptions */, 0 /* sessionId */,
+                SuggestedWords.NOT_A_SEQUENCE_NUMBER, callback);
+
+        return holder.get(false /* defaultValue */, Constants.GET_SUGGESTED_WORDS_TIMEOUT);
+    }
 }
diff --git a/java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java b/java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java
index 55061f4..74e7db9 100644
--- a/java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java
+++ b/java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java
@@ -129,6 +129,9 @@
         if (locale == null) {
             return null;
         }
+        // TODO: Though targetWord is an IV (in-vocabulary) word, we should still apply
+        // distracterFilter in the following code. If targetWord is a distracter,
+        // it should be filtered out.
         if (dictionaryFacilitator.isValidWord(targetWord, false /* ignoreCase */)) {
             return createAndGetLanguageModelParamOfWord(prevWord, targetWord, timestamp,
                     true /* isValidWord */, locale);
diff --git a/native/jni/NativeFileList.mk b/native/jni/NativeFileList.mk
index 34c1907..6ccfec9 100644
--- a/native/jni/NativeFileList.mk
+++ b/native/jni/NativeFileList.mk
@@ -46,23 +46,22 @@
     $(addprefix suggest/policyimpl/dictionary/, \
         header/header_policy.cpp \
         header/header_read_write_utils.cpp \
-        shortcut/shortcut_list_reading_utils.cpp \
         structure/dictionary_structure_with_buffer_policy_factory.cpp) \
-    $(addprefix suggest/policyimpl/dictionary/bigram/, \
-        bigram_list_read_write_utils.cpp \
-        ver4_bigram_list_policy.cpp) \
     $(addprefix suggest/policyimpl/dictionary/structure/pt_common/, \
+        bigram/bigram_list_read_write_utils.cpp \
         dynamic_pt_gc_event_listeners.cpp \
         dynamic_pt_reading_helper.cpp \
         dynamic_pt_reading_utils.cpp \
         dynamic_pt_updating_helper.cpp \
         dynamic_pt_writing_utils.cpp \
-        patricia_trie_reading_utils.cpp) \
+        patricia_trie_reading_utils.cpp \
+        shortcut/shortcut_list_reading_utils.cpp ) \
     $(addprefix suggest/policyimpl/dictionary/structure/v2/, \
         patricia_trie_policy.cpp \
         ver2_patricia_trie_node_reader.cpp \
         ver2_pt_node_array_reader.cpp) \
     $(addprefix suggest/policyimpl/dictionary/structure/v4/, \
+        bigram/ver4_bigram_list_policy.cpp \
         ver4_dict_buffers.cpp \
         ver4_dict_constants.cpp \
         ver4_patricia_trie_node_reader.cpp \
diff --git a/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
index a3d8ec1..a55b2da 100644
--- a/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
+++ b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
@@ -335,7 +335,7 @@
     if (!shortcutTargetCodePoints.empty()) {
         shortcuts.emplace_back(&shortcutTargetCodePoints, shortcutProbability);
     }
-    // Use 1 for count to indicate the word has inputed.
+    // Use 1 for count to indicate the word has inputted.
     const UnigramProperty unigramProperty(isNotAWord, isBlacklisted,
             probability, timestamp, 0 /* level */, 1 /* count */, &shortcuts);
     dictionary->addUnigramWord(codePoints, codePointCount, &unigramProperty);
@@ -353,8 +353,12 @@
     jsize word1Length = env->GetArrayLength(word1);
     int word1CodePoints[word1Length];
     env->GetIntArrayRegion(word1, 0, word1Length, word1CodePoints);
-    dictionary->addBigramWords(word0CodePoints, word0Length, word1CodePoints,
-            word1Length, probability, timestamp);
+    const std::vector<int> bigramTargetCodePoints(
+            word1CodePoints, word1CodePoints + word1Length);
+    // Use 1 for count to indicate the bigram has inputted.
+    const BigramProperty bigramProperty(&bigramTargetCodePoints, probability,
+            timestamp, 0 /* level */, 1 /* count */);
+    dictionary->addBigramWords(word0CodePoints, word0Length, &bigramProperty);
 }
 
 static void latinime_BinaryDictionary_removeBigramWords(JNIEnv *env, jclass clazz, jlong dict,
@@ -437,14 +441,18 @@
                     env->GetIntField(languageModelParam, shortcutProbabilityFieldId);
             shortcuts.emplace_back(&shortcutTargetCodePoints, shortcutProbability);
         }
-        // Use 1 for count to indicate the word has inputed.
+        // Use 1 for count to indicate the word has inputted.
         const UnigramProperty unigramProperty(isNotAWord, isBlacklisted,
                 unigramProbability, timestamp, 0 /* level */, 1 /* count */, &shortcuts);
         dictionary->addUnigramWord(word1CodePoints, word1Length, &unigramProperty);
         if (word0) {
             jint bigramProbability = env->GetIntField(languageModelParam, bigramProbabilityFieldId);
-            dictionary->addBigramWords(word0CodePoints, word0Length, word1CodePoints, word1Length,
-                    bigramProbability, timestamp);
+            const std::vector<int> bigramTargetCodePoints(
+                    word1CodePoints, word1CodePoints + word1Length);
+            // Use 1 for count to indicate the bigram has inputted.
+            const BigramProperty bigramProperty(&bigramTargetCodePoints, bigramProbability,
+                    timestamp, 0 /* level */, 1 /* count */);
+            dictionary->addBigramWords(word0CodePoints, word0Length, &bigramProperty);
         }
         if (dictionary->needsToRunGC(true /* mindsBlockByGC */)) {
             return i + 1;
@@ -558,11 +566,9 @@
                 return false;
             }
         }
-        for (const BigramProperty &bigarmProperty : *wordProperty.getBigramProperties()) {
-            const std::vector<int> *targetCodePoints = bigarmProperty.getTargetCodePoints();
+        for (const BigramProperty &bigramProperty : *wordProperty.getBigramProperties()) {
             if (!dictionaryStructureWithBufferPolicy->addBigramWords(wordCodePoints, wordLength,
-                    targetCodePoints->data(), targetCodePoints->size(),
-                    bigarmProperty.getProbability(), bigarmProperty.getTimestamp())) {
+                    &bigramProperty)) {
                 LogUtils::logToJava(env, "Cannot add bigram to the new dict.");
                 return false;
             }
diff --git a/native/jni/src/suggest/core/dictionary/dictionary.cpp b/native/jni/src/suggest/core/dictionary/dictionary.cpp
index e288413..fdc8936 100644
--- a/native/jni/src/suggest/core/dictionary/dictionary.cpp
+++ b/native/jni/src/suggest/core/dictionary/dictionary.cpp
@@ -88,11 +88,10 @@
     mDictionaryStructureWithBufferPolicy->addUnigramWord(word, length, unigramProperty);
 }
 
-void Dictionary::addBigramWords(const int *const word0, const int length0, const int *const word1,
-        const int length1, const int probability, const int timestamp) {
+void Dictionary::addBigramWords(const int *const word0, const int length0,
+        const BigramProperty *const bigramProperty) {
     TimeKeeper::setCurrentTime();
-    mDictionaryStructureWithBufferPolicy->addBigramWords(word0, length0, word1, length1,
-            probability, timestamp);
+    mDictionaryStructureWithBufferPolicy->addBigramWords(word0, length0, bigramProperty);
 }
 
 void Dictionary::removeBigramWords(const int *const word0, const int length0,
diff --git a/native/jni/src/suggest/core/dictionary/dictionary.h b/native/jni/src/suggest/core/dictionary/dictionary.h
index b6149b3..f0a7e5b 100644
--- a/native/jni/src/suggest/core/dictionary/dictionary.h
+++ b/native/jni/src/suggest/core/dictionary/dictionary.h
@@ -76,8 +76,8 @@
     void addUnigramWord(const int *const codePoints, const int codePointCount,
             const UnigramProperty *const unigramProperty);
 
-    void addBigramWords(const int *const word0, const int length0, const int *const word1,
-            const int length1, const int probability, const int timestamp);
+    void addBigramWords(const int *const word0, const int length0,
+            const BigramProperty *const bigramProperty);
 
     void removeBigramWords(const int *const word0, const int length0, const int *const word1,
             const int length1);
diff --git a/native/jni/src/suggest/core/layout/proximity_info.cpp b/native/jni/src/suggest/core/layout/proximity_info.cpp
index c40a2bd..4c75a18 100644
--- a/native/jni/src/suggest/core/layout/proximity_info.cpp
+++ b/native/jni/src/suggest/core/layout/proximity_info.cpp
@@ -226,7 +226,7 @@
 // When the referencePointY is NOT_A_COORDINATE, this method calculates the return value without
 // using the line segment.
 int ProximityInfo::getKeyCenterYOfKeyIdG(
-        const int keyId,  const int referencePointY, const bool isGeometric) const {
+        const int keyId, const int referencePointY, const bool isGeometric) const {
     // TODO: Remove "isGeometric" and have separate "proximity_info"s for gesture and typing.
     if (keyId < 0) {
         return 0;
diff --git a/native/jni/src/suggest/core/layout/proximity_info_state_utils.h b/native/jni/src/suggest/core/layout/proximity_info_state_utils.h
index 71e83a8..211a797 100644
--- a/native/jni/src/suggest/core/layout/proximity_info_state_utils.h
+++ b/native/jni/src/suggest/core/layout/proximity_info_state_utils.h
@@ -56,7 +56,7 @@
             const std::vector<int> *const sampledLengthCache,
             const std::vector<int> *const sampledInputIndice,
             std::vector<float> *sampledSpeedRates, std::vector<float> *sampledDirections);
-    static void refreshBeelineSpeedRates(const int mostCommonKeyWidth,  const float averageSpeed,
+    static void refreshBeelineSpeedRates(const int mostCommonKeyWidth, const float averageSpeed,
             const int inputSize, const int *const xCoordinates, const int *const yCoordinates,
             const int *times, const int sampledInputSize,
             const std::vector<int> *const sampledInputXs,
diff --git a/native/jni/src/suggest/core/policy/dictionary_structure_with_buffer_policy.h b/native/jni/src/suggest/core/policy/dictionary_structure_with_buffer_policy.h
index 807f9b8..ce5a49f 100644
--- a/native/jni/src/suggest/core/policy/dictionary_structure_with_buffer_policy.h
+++ b/native/jni/src/suggest/core/policy/dictionary_structure_with_buffer_policy.h
@@ -73,8 +73,8 @@
             const UnigramProperty *const unigramProperty) = 0;
 
     // Returns whether the update was success or not.
-    virtual bool addBigramWords(const int *const word0, const int length0, const int *const word1,
-            const int length1, const int probability, const int timestamp) = 0;
+    virtual bool addBigramWords(const int *const word0, const int length0,
+            const BigramProperty *const bigramProperty) = 0;
 
     // Returns whether the update was success or not.
     virtual bool removeBigramWords(const int *const word0, const int length0,
diff --git a/native/jni/src/suggest/policyimpl/dictionary/bigram/bigram_list_read_write_utils.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/bigram/bigram_list_read_write_utils.cpp
similarity index 96%
rename from native/jni/src/suggest/policyimpl/dictionary/bigram/bigram_list_read_write_utils.cpp
rename to native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/bigram/bigram_list_read_write_utils.cpp
index 7d0d096..08b4e0b 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/bigram/bigram_list_read_write_utils.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/bigram/bigram_list_read_write_utils.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include "suggest/policyimpl/dictionary/bigram/bigram_list_read_write_utils.h"
+#include "suggest/policyimpl/dictionary/structure/pt_common/bigram/bigram_list_read_write_utils.h"
 
 #include "suggest/policyimpl/dictionary/utils/byte_array_utils.h"
 #include "suggest/policyimpl/dictionary/utils/buffer_with_extendable_buffer.h"
diff --git a/native/jni/src/suggest/policyimpl/dictionary/bigram/bigram_list_read_write_utils.h b/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/bigram/bigram_list_read_write_utils.h
similarity index 100%
rename from native/jni/src/suggest/policyimpl/dictionary/bigram/bigram_list_read_write_utils.h
rename to native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/bigram/bigram_list_read_write_utils.h
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/dynamic_pt_updating_helper.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/dynamic_pt_updating_helper.cpp
index a527f03..9e57585 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/dynamic_pt_updating_helper.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/dynamic_pt_updating_helper.cpp
@@ -85,13 +85,13 @@
 }
 
 bool DynamicPtUpdatingHelper::addBigramWords(const int word0Pos, const int word1Pos,
-        const int probability, const int timestamp, bool *const outAddedNewBigram) {
+        const BigramProperty *const bigramProperty, bool *const outAddedNewBigram) {
     const PtNodeParams sourcePtNodeParams(
             mPtNodeReader->fetchNodeInfoInBufferFromPtNodePos(word0Pos));
     const PtNodeParams targetPtNodeParams(
             mPtNodeReader->fetchNodeInfoInBufferFromPtNodePos(word1Pos));
-    return mPtNodeWriter->addNewBigramEntry(&sourcePtNodeParams, &targetPtNodeParams, probability,
-            timestamp, outAddedNewBigram);
+    return mPtNodeWriter->addNewBigramEntry(&sourcePtNodeParams, &targetPtNodeParams,
+            bigramProperty, outAddedNewBigram);
 }
 
 // Remove a bigram relation from word0Pos to word1Pos.
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/dynamic_pt_updating_helper.h b/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/dynamic_pt_updating_helper.h
index 44914fe..f10d15a 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/dynamic_pt_updating_helper.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/dynamic_pt_updating_helper.h
@@ -22,6 +22,7 @@
 
 namespace latinime {
 
+class BigramProperty;
 class BufferWithExtendableBuffer;
 class DynamicPtReadingHelper;
 class PtNodeReader;
@@ -42,8 +43,8 @@
             const UnigramProperty *const unigramProperty, bool *const outAddedNewUnigram);
 
     // Add a bigram relation from word0Pos to word1Pos.
-    bool addBigramWords(const int word0Pos, const int word1Pos, const int probability,
-            const int timestamp, bool *const outAddedNewBigram);
+    bool addBigramWords(const int word0Pos, const int word1Pos,
+            const BigramProperty *const bigramProperty, bool *const outAddedNewBigram);
 
     // Remove a bigram relation from word0Pos to word1Pos.
     bool removeBigramWords(const int word0Pos, const int word1Pos);
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/pt_node_params.h b/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/pt_node_params.h
index 91192fc..bef401f 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/pt_node_params.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/pt_node_params.h
@@ -23,6 +23,7 @@
 #include "suggest/policyimpl/dictionary/structure/pt_common/dynamic_pt_reading_utils.h"
 #include "suggest/policyimpl/dictionary/structure/pt_common/patricia_trie_reading_utils.h"
 #include "suggest/policyimpl/dictionary/structure/v4/ver4_dict_constants.h"
+#include "utils/char_utils.h"
 
 namespace latinime {
 
@@ -158,6 +159,10 @@
         return PatriciaTrieReadingUtils::hasShortcutTargets(mFlags);
     }
 
+    AK_FORCE_INLINE bool representsNonWordInfo() const {
+        return getCodePointCount() > 0 && CharUtils::isInUnicodeSpace(getCodePoints()[0]);
+    }
+
     // Parent node position
     AK_FORCE_INLINE int getParentPos() const {
         return mParentPos;
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/pt_node_writer.h b/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/pt_node_writer.h
index cbca3fe..a8029f7 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/pt_node_writer.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/pt_node_writer.h
@@ -24,6 +24,7 @@
 
 namespace latinime {
 
+class BigramProperty;
 class UnigramProperty;
 
 // Interface class used to write PtNode information.
@@ -70,7 +71,7 @@
             const UnigramProperty *const unigramProperty, int *const ptNodeWritingPos) = 0;
 
     virtual bool addNewBigramEntry(const PtNodeParams *const sourcePtNodeParams,
-            const PtNodeParams *const targetPtNodeParam, const int probability, const int timestamp,
+            const PtNodeParams *const targetPtNodeParam, const BigramProperty *const bigramProperty,
             bool *const outAddedNewBigram) = 0;
 
     virtual bool removeBigramEntry(const PtNodeParams *const sourcePtNodeParams,
diff --git a/native/jni/src/suggest/policyimpl/dictionary/shortcut/shortcut_list_reading_utils.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/shortcut/shortcut_list_reading_utils.cpp
similarity index 90%
rename from native/jni/src/suggest/policyimpl/dictionary/shortcut/shortcut_list_reading_utils.cpp
rename to native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/shortcut/shortcut_list_reading_utils.cpp
index 847dcde..91c7694 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/shortcut/shortcut_list_reading_utils.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/shortcut/shortcut_list_reading_utils.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include "suggest/policyimpl/dictionary/shortcut/shortcut_list_reading_utils.h"
+#include "suggest/policyimpl/dictionary/structure/pt_common/shortcut/shortcut_list_reading_utils.h"
 
 #include "suggest/policyimpl/dictionary/utils/byte_array_utils.h"
 
@@ -44,7 +44,7 @@
 }
 
 /* static */ int ShortcutListReadingUtils::readShortcutTarget(
-        const uint8_t *const dictRoot, const int maxLength,  int *const outWord, int *const pos) {
+        const uint8_t *const dictRoot, const int maxLength, int *const outWord, int *const pos) {
     return ByteArrayUtils::readStringAndAdvancePosition(dictRoot, maxLength, outWord, pos);
 }
 
diff --git a/native/jni/src/suggest/policyimpl/dictionary/shortcut/shortcut_list_reading_utils.h b/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/shortcut/shortcut_list_reading_utils.h
similarity index 100%
rename from native/jni/src/suggest/policyimpl/dictionary/shortcut/shortcut_list_reading_utils.h
rename to native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/shortcut/shortcut_list_reading_utils.h
diff --git a/native/jni/src/suggest/policyimpl/dictionary/bigram/bigram_list_policy.h b/native/jni/src/suggest/policyimpl/dictionary/structure/v2/bigram/bigram_list_policy.h
similarity index 94%
rename from native/jni/src/suggest/policyimpl/dictionary/bigram/bigram_list_policy.h
rename to native/jni/src/suggest/policyimpl/dictionary/structure/v2/bigram/bigram_list_policy.h
index a898e2a..00bb502 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/bigram/bigram_list_policy.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v2/bigram/bigram_list_policy.h
@@ -21,7 +21,7 @@
 
 #include "defines.h"
 #include "suggest/core/policy/dictionary_bigrams_structure_policy.h"
-#include "suggest/policyimpl/dictionary/bigram/bigram_list_read_write_utils.h"
+#include "suggest/policyimpl/dictionary/structure/pt_common/bigram/bigram_list_read_write_utils.h"
 
 namespace latinime {
 
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v2/patricia_trie_policy.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/v2/patricia_trie_policy.cpp
index b3af1f4..30dcfba 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/v2/patricia_trie_policy.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v2/patricia_trie_policy.cpp
@@ -24,6 +24,7 @@
 #include "suggest/policyimpl/dictionary/structure/pt_common/dynamic_pt_reading_helper.h"
 #include "suggest/policyimpl/dictionary/structure/pt_common/patricia_trie_reading_utils.h"
 #include "suggest/policyimpl/dictionary/utils/probability_utils.h"
+#include "utils/char_utils.h"
 
 namespace latinime {
 
@@ -318,12 +319,15 @@
     PatriciaTrieReadingUtils::readPtNodeInfo(mDictRoot, ptNodePos, getShortcutsStructurePolicy(),
             getBigramsStructurePolicy(), &flags, &mergedNodeCodePointCount, mergedNodeCodePoints,
             &probability, &childrenPos, &shortcutPos, &bigramPos, &siblingPos);
-    childDicNodes->pushLeavingChild(dicNode, ptNodePos, childrenPos, probability,
-            PatriciaTrieReadingUtils::isTerminal(flags),
-            PatriciaTrieReadingUtils::hasChildrenInFlags(flags),
-            PatriciaTrieReadingUtils::isBlacklisted(flags)
-                    || PatriciaTrieReadingUtils::isNotAWord(flags),
-            mergedNodeCodePointCount, mergedNodeCodePoints);
+    // Skip PtNodes don't start with Unicode code point because they represent non-word information.
+    if (CharUtils::isInUnicodeSpace(mergedNodeCodePoints[0])) {
+        childDicNodes->pushLeavingChild(dicNode, ptNodePos, childrenPos, probability,
+                PatriciaTrieReadingUtils::isTerminal(flags),
+                PatriciaTrieReadingUtils::hasChildrenInFlags(flags),
+                PatriciaTrieReadingUtils::isBlacklisted(flags)
+                        || PatriciaTrieReadingUtils::isNotAWord(flags),
+                mergedNodeCodePointCount, mergedNodeCodePoints);
+    }
     return siblingPos;
 }
 
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v2/patricia_trie_policy.h b/native/jni/src/suggest/policyimpl/dictionary/structure/v2/patricia_trie_policy.h
index 85f4660..54d1e0f 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/v2/patricia_trie_policy.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v2/patricia_trie_policy.h
@@ -22,9 +22,9 @@
 
 #include "defines.h"
 #include "suggest/core/policy/dictionary_structure_with_buffer_policy.h"
-#include "suggest/policyimpl/dictionary/bigram/bigram_list_policy.h"
 #include "suggest/policyimpl/dictionary/header/header_policy.h"
-#include "suggest/policyimpl/dictionary/shortcut/shortcut_list_policy.h"
+#include "suggest/policyimpl/dictionary/structure/v2/bigram/bigram_list_policy.h"
+#include "suggest/policyimpl/dictionary/structure/v2/shortcut/shortcut_list_policy.h"
 #include "suggest/policyimpl/dictionary/structure/v2/ver2_patricia_trie_node_reader.h"
 #include "suggest/policyimpl/dictionary/structure/v2/ver2_pt_node_array_reader.h"
 #include "suggest/policyimpl/dictionary/utils/format_utils.h"
@@ -88,8 +88,8 @@
         return false;
     }
 
-    bool addBigramWords(const int *const word0, const int length0, const int *const word1,
-            const int length1, const int probability, const int timestamp) {
+    bool addBigramWords(const int *const word0, const int length0,
+            const BigramProperty *const bigramProperty) {
         // This method should not be called for non-updatable dictionary.
         AKLOGI("Warning: addBigramWords() is called for non-updatable dictionary.");
         return false;
diff --git a/native/jni/src/suggest/policyimpl/dictionary/shortcut/shortcut_list_policy.h b/native/jni/src/suggest/policyimpl/dictionary/structure/v2/shortcut/shortcut_list_policy.h
similarity index 95%
rename from native/jni/src/suggest/policyimpl/dictionary/shortcut/shortcut_list_policy.h
rename to native/jni/src/suggest/policyimpl/dictionary/structure/v2/shortcut/shortcut_list_policy.h
index 6d2b477..8e16ccc 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/shortcut/shortcut_list_policy.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v2/shortcut/shortcut_list_policy.h
@@ -21,7 +21,7 @@
 
 #include "defines.h"
 #include "suggest/core/policy/dictionary_shortcuts_structure_policy.h"
-#include "suggest/policyimpl/dictionary/shortcut/shortcut_list_reading_utils.h"
+#include "suggest/policyimpl/dictionary/structure/pt_common/shortcut/shortcut_list_reading_utils.h"
 
 namespace latinime {
 
diff --git a/native/jni/src/suggest/policyimpl/dictionary/bigram/ver4_bigram_list_policy.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/bigram/ver4_bigram_list_policy.cpp
similarity index 93%
rename from native/jni/src/suggest/policyimpl/dictionary/bigram/ver4_bigram_list_policy.cpp
rename to native/jni/src/suggest/policyimpl/dictionary/structure/v4/bigram/ver4_bigram_list_policy.cpp
index 1645039..7a52fd1 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/bigram/ver4_bigram_list_policy.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/bigram/ver4_bigram_list_policy.cpp
@@ -14,10 +14,11 @@
  * limitations under the License.
  */
 
-#include "suggest/policyimpl/dictionary/bigram/ver4_bigram_list_policy.h"
+#include "suggest/policyimpl/dictionary/structure/v4/bigram/ver4_bigram_list_policy.h"
 
-#include "suggest/policyimpl/dictionary/bigram/bigram_list_read_write_utils.h"
+#include "suggest/core/dictionary/property/bigram_property.h"
 #include "suggest/policyimpl/dictionary/header/header_policy.h"
+#include "suggest/policyimpl/dictionary/structure/pt_common/bigram/bigram_list_read_write_utils.h"
 #include "suggest/policyimpl/dictionary/structure/v4/content/bigram_dict_content.h"
 #include "suggest/policyimpl/dictionary/structure/v4/content/terminal_position_lookup_table.h"
 #include "suggest/policyimpl/dictionary/structure/v4/ver4_dict_constants.h"
@@ -49,13 +50,12 @@
 }
 
 bool Ver4BigramListPolicy::addNewEntry(const int terminalId, const int newTargetTerminalId,
-        const int newProbability, const int timestamp, bool *const outAddedNewEntry) {
+        const BigramProperty *const bigramProperty, bool *const outAddedNewEntry) {
     // 1. The word has no bigrams yet.
     // 2. The word has bigrams, and there is the target in the list.
     // 3. The word has bigrams, and there is an invalid entry that can be reclaimed.
     // 4. The word has bigrams. We have to append new bigram entry to the list.
     // 5. Same as 4, but the list is the last entry of the content file.
-
     if (outAddedNewEntry) {
         *outAddedNewEntry = false;
     }
@@ -69,7 +69,7 @@
         const BigramEntry newBigramEntry(false /* hasNext */, NOT_A_PROBABILITY,
                 newTargetTerminalId);
         const BigramEntry bigramEntryToWrite = createUpdatedBigramEntryFrom(&newBigramEntry,
-                newProbability, timestamp);
+                bigramProperty);
         // Write an entry.
         const int writingPos =  mBigramDictContent->getBigramListHeadPos(terminalId);
         if (!mBigramDictContent->writeBigramEntry(&bigramEntryToWrite, writingPos)) {
@@ -102,7 +102,7 @@
         const BigramEntry newBigramEntry(false /* hasNext */, NOT_A_PROBABILITY,
                 newTargetTerminalId);
         const BigramEntry bigramEntryToWrite = createUpdatedBigramEntryFrom(
-                &newBigramEntry, newProbability, timestamp);
+                &newBigramEntry, bigramProperty);
         if (!mBigramDictContent->writeBigramEntryAtTail(&bigramEntryToWrite)) {
             return false;
         }
@@ -128,7 +128,7 @@
     const BigramEntry updatedBigramEntry =
             originalBigramEntry.updateTargetTerminalIdAndGetEntry(newTargetTerminalId);
     const BigramEntry bigramEntryToWrite = createUpdatedBigramEntryFrom(
-            &updatedBigramEntry, newProbability, timestamp);
+            &updatedBigramEntry, bigramProperty);
     return mBigramDictContent->writeBigramEntry(&bigramEntryToWrite, entryPosToUpdate);
 }
 
@@ -253,19 +253,19 @@
 }
 
 const BigramEntry Ver4BigramListPolicy::createUpdatedBigramEntryFrom(
-        const BigramEntry *const originalBigramEntry, const int newProbability,
-        const int timestamp) const {
+        const BigramEntry *const originalBigramEntry,
+        const BigramProperty *const bigramProperty) const {
     // TODO: Consolidate historical info and probability.
     if (mHeaderPolicy->hasHistoricalInfoOfWords()) {
-        // Use 1 for count to indicate the bigram has inputed.
-        const HistoricalInfo historicalInfoForUpdate(timestamp, 0 /* level */, 1 /* count */);
+        const HistoricalInfo historicalInfoForUpdate(bigramProperty->getTimestamp(),
+                bigramProperty->getLevel(), bigramProperty->getCount());
         const HistoricalInfo updatedHistoricalInfo =
                 ForgettingCurveUtils::createUpdatedHistoricalInfo(
-                        originalBigramEntry->getHistoricalInfo(), newProbability,
+                        originalBigramEntry->getHistoricalInfo(), bigramProperty->getProbability(),
                         &historicalInfoForUpdate, mHeaderPolicy);
         return originalBigramEntry->updateHistoricalInfoAndGetEntry(&updatedHistoricalInfo);
     } else {
-        return originalBigramEntry->updateProbabilityAndGetEntry(newProbability);
+        return originalBigramEntry->updateProbabilityAndGetEntry(bigramProperty->getProbability());
     }
 }
 
diff --git a/native/jni/src/suggest/policyimpl/dictionary/bigram/ver4_bigram_list_policy.h b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/bigram/ver4_bigram_list_policy.h
similarity index 93%
rename from native/jni/src/suggest/policyimpl/dictionary/bigram/ver4_bigram_list_policy.h
rename to native/jni/src/suggest/policyimpl/dictionary/structure/v4/bigram/ver4_bigram_list_policy.h
index c1f3335..1613941 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/bigram/ver4_bigram_list_policy.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/bigram/ver4_bigram_list_policy.h
@@ -24,6 +24,7 @@
 namespace latinime {
 
 class BigramDictContent;
+class BigramProperty;
 class HeaderPolicy;
 class TerminalPositionLookupTable;
 
@@ -43,8 +44,8 @@
         // Do nothing because we don't need to skip bigram lists in ver4 dictionaries.
     }
 
-    bool addNewEntry(const int terminalId, const int newTargetTerminalId, const int newProbability,
-            const int timestamp, bool *const outAddedNewEntry);
+    bool addNewEntry(const int terminalId, const int newTargetTerminalId,
+            const BigramProperty *const bigramProperty, bool *const outAddedNewEntry);
 
     bool removeEntry(const int terminalId, const int targetTerminalId);
 
@@ -60,7 +61,7 @@
             int *const outTailEntryPos) const;
 
     const BigramEntry createUpdatedBigramEntryFrom(const BigramEntry *const originalBigramEntry,
-            const int newProbability, const int timestamp) const;
+            const BigramProperty *const bigramProperty) const;
 
     bool updateHasNextFlag(const bool hasNext, const int bigramEntryPos);
 
diff --git a/native/jni/src/suggest/policyimpl/dictionary/shortcut/ver4_shortcut_list_policy.h b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/shortcut/ver4_shortcut_list_policy.h
similarity index 97%
rename from native/jni/src/suggest/policyimpl/dictionary/shortcut/ver4_shortcut_list_policy.h
rename to native/jni/src/suggest/policyimpl/dictionary/structure/v4/shortcut/ver4_shortcut_list_policy.h
index fe98461..7902735 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/shortcut/ver4_shortcut_list_policy.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/shortcut/ver4_shortcut_list_policy.h
@@ -19,7 +19,7 @@
 
 #include "defines.h"
 #include "suggest/core/policy/dictionary_shortcuts_structure_policy.h"
-#include "suggest/policyimpl/dictionary/shortcut/shortcut_list_reading_utils.h"
+#include "suggest/policyimpl/dictionary/structure/pt_common/shortcut/shortcut_list_reading_utils.h"
 #include "suggest/policyimpl/dictionary/structure/v4/content/shortcut_dict_content.h"
 #include "suggest/policyimpl/dictionary/structure/v4/content/terminal_position_lookup_table.h"
 
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_node_reader.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_node_reader.cpp
index 67420a2..0a435e9 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_node_reader.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_node_reader.cpp
@@ -95,4 +95,4 @@
     }
 }
 
-}
+} // namespace latinime
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_node_writer.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_node_writer.cpp
index cc3a24a..f89d3d7 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_node_writer.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_node_writer.cpp
@@ -17,13 +17,13 @@
 #include "suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_node_writer.h"
 
 #include "suggest/core/dictionary/property/unigram_property.h"
-#include "suggest/policyimpl/dictionary/bigram/ver4_bigram_list_policy.h"
 #include "suggest/policyimpl/dictionary/header/header_policy.h"
-#include "suggest/policyimpl/dictionary/shortcut/ver4_shortcut_list_policy.h"
 #include "suggest/policyimpl/dictionary/structure/pt_common/dynamic_pt_reading_utils.h"
 #include "suggest/policyimpl/dictionary/structure/pt_common/dynamic_pt_writing_utils.h"
 #include "suggest/policyimpl/dictionary/structure/pt_common/patricia_trie_reading_utils.h"
+#include "suggest/policyimpl/dictionary/structure/v4/bigram/ver4_bigram_list_policy.h"
 #include "suggest/policyimpl/dictionary/structure/v4/content/probability_entry.h"
+#include "suggest/policyimpl/dictionary/structure/v4/shortcut/ver4_shortcut_list_policy.h"
 #include "suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_node_reader.h"
 #include "suggest/policyimpl/dictionary/structure/v4/ver4_dict_buffers.h"
 #include "suggest/policyimpl/dictionary/utils/buffer_with_extendable_buffer.h"
@@ -76,7 +76,7 @@
             PatriciaTrieReadingUtils::getFlagsAndAdvancePosition(dictBuf, &pos);
     const PatriciaTrieReadingUtils::NodeFlags updatedFlags =
             DynamicPtReadingUtils::updateAndGetFlags(originalFlags, true /* isMoved */,
-                    false /* isDeleted */,  false /* willBecomeNonTerminal */);
+                    false /* isDeleted */, false /* willBecomeNonTerminal */);
     int writingPos = toBeUpdatedPtNodeParams->getHeadPos();
     // Update flags.
     if (!DynamicPtWritingUtils::writeFlagsAndAdvancePosition(mTrieBuffer, updatedFlags,
@@ -223,11 +223,10 @@
 }
 
 bool Ver4PatriciaTrieNodeWriter::addNewBigramEntry(
-        const PtNodeParams *const sourcePtNodeParams,
-        const PtNodeParams *const targetPtNodeParam, const int probability, const int timestamp,
-        bool *const outAddedNewBigram) {
+        const PtNodeParams *const sourcePtNodeParams, const PtNodeParams *const targetPtNodeParam,
+        const BigramProperty *const bigramProperty, bool *const outAddedNewBigram) {
     if (!mBigramPolicy->addNewEntry(sourcePtNodeParams->getTerminalId(),
-            targetPtNodeParam->getTerminalId(), probability, timestamp, outAddedNewBigram)) {
+            targetPtNodeParam->getTerminalId(), bigramProperty, outAddedNewBigram)) {
         AKLOGE("Cannot add new bigram entry. terminalId: %d, targetTerminalId: %d",
                 sourcePtNodeParams->getTerminalId(), targetPtNodeParam->getTerminalId());
         return false;
@@ -416,4 +415,4 @@
     return true;
 }
 
-}
+} // namespace latinime
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_node_writer.h b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_node_writer.h
index f20d3a2..e90bc44 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_node_writer.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_node_writer.h
@@ -76,7 +76,7 @@
             const UnigramProperty *const unigramProperty, int *const ptNodeWritingPos);
 
     virtual bool addNewBigramEntry(const PtNodeParams *const sourcePtNodeParams,
-            const PtNodeParams *const targetPtNodeParam, const int probability, const int timestamp,
+            const PtNodeParams *const targetPtNodeParam, const BigramProperty *const bigramProperty,
             bool *const outAddedNewBigram);
 
     virtual bool removeBigramEntry(const PtNodeParams *const sourcePtNodeParams,
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_policy.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_policy.cpp
index 9999e06..8373dc5 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_policy.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_policy.cpp
@@ -59,13 +59,17 @@
             // valid terminal DicNode.
             isTerminal = ptNodeParams.getProbability() != NOT_A_PROBABILITY;
         }
+        readingHelper.readNextSiblingNode(ptNodeParams);
+        if (!ptNodeParams.representsNonWordInfo()) {
+            // Skip PtNodes that represent non-word information.
+            continue;
+        }
         childDicNodes->pushLeavingChild(dicNode, ptNodeParams.getHeadPos(),
                 ptNodeParams.getChildrenPos(), ptNodeParams.getProbability(), isTerminal,
                 ptNodeParams.hasChildren(),
                 ptNodeParams.isBlacklisted()
                         || ptNodeParams.isNotAWord() /* isBlacklistedOrNotAWord */,
                 ptNodeParams.getCodePointCount(), ptNodeParams.getCodePoints());
-        readingHelper.readNextSiblingNode(ptNodeParams);
     }
     if (readingHelper.isError()) {
         mIsCorrupted = true;
@@ -209,8 +213,7 @@
 }
 
 bool Ver4PatriciaTriePolicy::addBigramWords(const int *const word0, const int length0,
-        const int *const word1, const int length1, const int probability,
-        const int timestamp) {
+        const BigramProperty *const bigramProperty) {
     if (!mBuffers->isUpdatable()) {
         AKLOGI("Warning: addBigramWords() is called for non-updatable dictionary.");
         return false;
@@ -220,9 +223,10 @@
                 mDictBuffer->getTailPosition());
         return false;
     }
-    if (length0 > MAX_WORD_LENGTH || length1 > MAX_WORD_LENGTH) {
+    if (length0 > MAX_WORD_LENGTH
+            || bigramProperty->getTargetCodePoints()->size() > MAX_WORD_LENGTH) {
         AKLOGE("Either src word or target word is too long to insert the bigram to the dictionary. "
-                "length0: %d, length1: %d", length0, length1);
+                "length0: %d, length1: %d", length0, bigramProperty->getTargetCodePoints()->size());
         return false;
     }
     const int word0Pos = getTerminalPtNodePositionOfWord(word0, length0,
@@ -230,14 +234,14 @@
     if (word0Pos == NOT_A_DICT_POS) {
         return false;
     }
-    const int word1Pos = getTerminalPtNodePositionOfWord(word1, length1,
-            false /* forceLowerCaseSearch */);
+    const int word1Pos = getTerminalPtNodePositionOfWord(
+            bigramProperty->getTargetCodePoints()->data(),
+            bigramProperty->getTargetCodePoints()->size(), false /* forceLowerCaseSearch */);
     if (word1Pos == NOT_A_DICT_POS) {
         return false;
     }
     bool addedNewBigram = false;
-    if (mUpdatingHelper.addBigramWords(word0Pos, word1Pos, probability, timestamp,
-            &addedNewBigram)) {
+    if (mUpdatingHelper.addBigramWords(word0Pos, word1Pos, bigramProperty, &addedNewBigram)) {
         if (addedNewBigram) {
             mBigramCount++;
         }
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_policy.h b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_policy.h
index 8f981de..b785764 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_policy.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_policy.h
@@ -21,10 +21,10 @@
 
 #include "defines.h"
 #include "suggest/core/policy/dictionary_structure_with_buffer_policy.h"
-#include "suggest/policyimpl/dictionary/bigram/ver4_bigram_list_policy.h"
 #include "suggest/policyimpl/dictionary/header/header_policy.h"
-#include "suggest/policyimpl/dictionary/shortcut/ver4_shortcut_list_policy.h"
 #include "suggest/policyimpl/dictionary/structure/pt_common/dynamic_pt_updating_helper.h"
+#include "suggest/policyimpl/dictionary/structure/v4/bigram/ver4_bigram_list_policy.h"
+#include "suggest/policyimpl/dictionary/structure/v4/shortcut/ver4_shortcut_list_policy.h"
 #include "suggest/policyimpl/dictionary/structure/v4/ver4_dict_buffers.h"
 #include "suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_node_reader.h"
 #include "suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_node_writer.h"
@@ -93,8 +93,8 @@
     bool addUnigramWord(const int *const word, const int length,
             const UnigramProperty *const unigramProperty);
 
-    bool addBigramWords(const int *const word0, const int length0, const int *const word1,
-            const int length1, const int probability, const int timestamp);
+    bool addBigramWords(const int *const word0, const int length0,
+            const BigramProperty *const bigramProperty);
 
     bool removeBigramWords(const int *const word0, const int length0, const int *const word1,
             const int length1);
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_writing_helper.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_writing_helper.cpp
index 12298d9..f31c502 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_writing_helper.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_writing_helper.cpp
@@ -19,9 +19,9 @@
 #include <cstring>
 #include <queue>
 
-#include "suggest/policyimpl/dictionary/bigram/ver4_bigram_list_policy.h"
 #include "suggest/policyimpl/dictionary/header/header_policy.h"
-#include "suggest/policyimpl/dictionary/shortcut/ver4_shortcut_list_policy.h"
+#include "suggest/policyimpl/dictionary/structure/v4/bigram/ver4_bigram_list_policy.h"
+#include "suggest/policyimpl/dictionary/structure/v4/shortcut/ver4_shortcut_list_policy.h"
 #include "suggest/policyimpl/dictionary/structure/v4/ver4_dict_buffers.h"
 #include "suggest/policyimpl/dictionary/structure/v4/ver4_dict_constants.h"
 #include "suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_node_reader.h"
diff --git a/native/jni/src/utils/char_utils.cpp b/native/jni/src/utils/char_utils.cpp
index adc474b..b17e084 100644
--- a/native/jni/src/utils/char_utils.cpp
+++ b/native/jni/src/utils/char_utils.cpp
@@ -22,6 +22,9 @@
 
 namespace latinime {
 
+const int CharUtils::MIN_UNICODE_CODE_POINT = 0;
+const int CharUtils::MAX_UNICODE_CODE_POINT = 0x10FFFF;
+
 struct LatinCapitalSmallPair {
   unsigned short capital;
   unsigned short small;
diff --git a/native/jni/src/utils/char_utils.h b/native/jni/src/utils/char_utils.h
index 239419d..634c45b 100644
--- a/native/jni/src/utils/char_utils.h
+++ b/native/jni/src/utils/char_utils.h
@@ -86,12 +86,19 @@
         return spaceCount;
     }
 
+    static AK_FORCE_INLINE int isInUnicodeSpace(const int codePoint) {
+        return codePoint >= MIN_UNICODE_CODE_POINT && codePoint <= MAX_UNICODE_CODE_POINT;
+    }
+
     static unsigned short latin_tolower(const unsigned short c);
     static const std::vector<int> EMPTY_STRING;
 
  private:
     DISALLOW_IMPLICIT_CONSTRUCTORS(CharUtils);
 
+    static const int MIN_UNICODE_CODE_POINT;
+    static const int MAX_UNICODE_CODE_POINT;
+
     /**
      * Table mapping most combined Latin, Greek, and Cyrillic characters
      * to their base characters.  If c is in range, BASE_CHARS[c] == c
diff --git a/tests/src/com/android/inputmethod/latin/BinaryDictionaryDecayingTests.java b/tests/src/com/android/inputmethod/latin/BinaryDictionaryDecayingTests.java
index aed24c5..35d9a4e 100644
--- a/tests/src/com/android/inputmethod/latin/BinaryDictionaryDecayingTests.java
+++ b/tests/src/com/android/inputmethod/latin/BinaryDictionaryDecayingTests.java
@@ -580,7 +580,6 @@
         final BinaryDictionary binaryDictionary = new BinaryDictionary(dictFile.getAbsolutePath(),
                 0 /* offset */, dictFile.length(), true /* useFullEditDistance */,
                 Locale.getDefault(), TEST_LOCALE, true /* isUpdatable */);
-        // TODO: Add tests for bigrams when the implementation gets ready.
         addUnigramWord(binaryDictionary, "aaa", DUMMY_PROBABILITY);
         assertTrue(binaryDictionary.isValidWord("aaa"));
         addUnigramWord(binaryDictionary, "bbb", Dictionary.NOT_A_PROBABILITY);
@@ -590,6 +589,11 @@
         addUnigramWord(binaryDictionary, "ccc", DUMMY_PROBABILITY);
         addUnigramWord(binaryDictionary, "ccc", DUMMY_PROBABILITY);
         addUnigramWord(binaryDictionary, "ccc", DUMMY_PROBABILITY);
+        addUnigramWord(binaryDictionary, "abc", DUMMY_PROBABILITY);
+        addBigramWords(binaryDictionary, "aaa", "abc", DUMMY_PROBABILITY);
+        assertTrue(binaryDictionary.isValidBigram("aaa", "abc"));
+        addBigramWords(binaryDictionary, "aaa", "bbb", Dictionary.NOT_A_PROBABILITY);
+        assertFalse(binaryDictionary.isValidBigram("aaa", "bbb"));
 
         assertEquals(fromFormatVersion, binaryDictionary.getFormatVersion());
         assertTrue(binaryDictionary.migrateTo(toFormatVersion));
@@ -600,6 +604,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"));
+        addBigramWords(binaryDictionary, "aaa", "bbb", Dictionary.NOT_A_PROBABILITY);
+        assertTrue(binaryDictionary.isValidBigram("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 9ceafa7..770e76e 100644
--- a/tests/src/com/android/inputmethod/latin/BinaryDictionaryTests.java
+++ b/tests/src/com/android/inputmethod/latin/BinaryDictionaryTests.java
@@ -1245,6 +1245,12 @@
         addUnigramWord(binaryDictionary, "bbb", unigramProbability);
         final int bigramProbability = 10;
         addBigramWords(binaryDictionary, "aaa", "bbb", bigramProbability);
+        final int shortcutProbability = 10;
+        binaryDictionary.addUnigramWord("ccc", unigramProbability, "xxx", shortcutProbability,
+                false /* isNotAWord */, false /* isBlacklisted */, 0 /* timestamp */);
+        binaryDictionary.addUnigramWord("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"));
@@ -1256,5 +1262,84 @@
         assertEquals(unigramProbability, binaryDictionary.getFrequency("bbb"));
         // TODO: Add tests for bigram frequency when the implementation gets ready.
         assertTrue(binaryDictionary.isValidBigram("aaa", "bbb"));
+        WordProperty wordProperty = binaryDictionary.getWordProperty("ccc");
+        assertEquals(1, wordProperty.mShortcutTargets.size());
+        assertEquals("xxx", wordProperty.mShortcutTargets.get(0).mWord);
+        wordProperty = binaryDictionary.getWordProperty("ddd");
+        assertTrue(wordProperty.mIsBlacklistEntry);
+        assertTrue(wordProperty.mIsNotAWord);
+    }
+
+    public void testLargeDictMigration() {
+        testLargeDictMigration(FormatSpec.VERSION4_ONLY_FOR_TESTING, FormatSpec.VERSION4);
+    }
+
+    private void testLargeDictMigration(final int fromFormatVersion, final int toFormatVersion) {
+        final int UNIGRAM_COUNT = 3000;
+        final int BIGRAM_COUNT = 3000;
+        final int codePointSetSize = 50;
+        final long seed = System.currentTimeMillis();
+        final Random random = new Random(seed);
+
+        File dictFile = null;
+        try {
+            dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary", fromFormatVersion);
+        } catch (IOException e) {
+            fail("IOException while writing an initial dictionary : " + e);
+        }
+        final BinaryDictionary binaryDictionary = new BinaryDictionary(dictFile.getAbsolutePath(),
+                0 /* offset */, dictFile.length(), true /* useFullEditDistance */,
+                Locale.getDefault(), TEST_LOCALE, true /* isUpdatable */);
+
+        final ArrayList<String> words = new ArrayList<String>();
+        final ArrayList<Pair<String, String>> bigrams = new ArrayList<Pair<String,String>>();
+        final int[] codePointSet = CodePointUtils.generateCodePointSet(codePointSetSize, random);
+        final HashMap<String, Integer> unigramProbabilities = new HashMap<String, Integer>();
+        final HashMap<Pair<String, String>, Integer> bigramProbabilities =
+                new HashMap<Pair<String, String>, Integer>();
+
+        for (int i = 0; i < UNIGRAM_COUNT; i++) {
+            final String word = CodePointUtils.generateWord(random, codePointSet);
+            final int unigramProbability = random.nextInt(0xFF);
+            addUnigramWord(binaryDictionary, word, unigramProbability);
+            if (binaryDictionary.needsToRunGC(true /* mindsBlockByGC */)) {
+                binaryDictionary.flushWithGC();
+            }
+            words.add(word);
+            unigramProbabilities.put(word, unigramProbability);
+        }
+
+        for (int i = 0; i < BIGRAM_COUNT; i++) {
+            final int word0Index = random.nextInt(words.size());
+            final int word1Index = random.nextInt(words.size());
+            if (word0Index == word1Index) {
+                continue;
+            }
+            final String word0 = words.get(word0Index);
+            final String word1 = words.get(word1Index);
+            final int bigramProbability = random.nextInt(0xF);
+            binaryDictionary.addBigramWords(word0, word1, bigramProbability,
+                    BinaryDictionary.NOT_A_VALID_TIMESTAMP);
+            if (binaryDictionary.needsToRunGC(true /* mindsBlockByGC */)) {
+                binaryDictionary.flushWithGC();
+            }
+            final Pair<String, String> bigram = new Pair<String, String>(word0, word1);
+            bigrams.add(bigram);
+            bigramProbabilities.put(bigram, bigramProbability);
+        }
+        assertTrue(binaryDictionary.migrateTo(toFormatVersion));
+
+        for (final String word : words) {
+            assertEquals((int)unigramProbabilities.get(word), binaryDictionary.getFrequency(word));
+        }
+        assertEquals(unigramProbabilities.size(), Integer.parseInt(
+                binaryDictionary.getPropertyForTest(BinaryDictionary.UNIGRAM_COUNT_QUERY)));
+
+        for (final Pair<String, String> bigram : bigrams) {
+            // TODO: Add tests for bigram frequency when the implementation gets ready.
+            assertTrue(binaryDictionary.isValidBigram(bigram.first, bigram.second));
+        }
+        assertEquals(bigramProbabilities.size(), Integer.parseInt(
+                binaryDictionary.getPropertyForTest(BinaryDictionary.BIGRAM_COUNT_QUERY)));
     }
 }
diff --git a/tests/src/com/android/inputmethod/latin/DistracterFilterTest.java b/tests/src/com/android/inputmethod/latin/DistracterFilterTest.java
new file mode 100644
index 0000000..186542a
--- /dev/null
+++ b/tests/src/com/android/inputmethod/latin/DistracterFilterTest.java
@@ -0,0 +1,73 @@
+/*
+ * 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.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import android.test.suitebuilder.annotation.LargeTest;
+
+import com.android.inputmethod.latin.utils.DistracterFilter;
+
+/**
+ * Unit test for DistracterFilter
+ */
+@LargeTest
+public class DistracterFilterTest extends InputTestsBase {
+    private DistracterFilter mDistracterFilter;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mDistracterFilter = mLatinIME.createDistracterFilter();
+    }
+
+    public void testIsDistractorToWordsInDictionaries() {
+        final String EMPTY_PREV_WORD = null;
+        String typedWord = "alot";
+        // For this test case, we consider "alot" is a distracter to "a lot".
+        assertTrue(mDistracterFilter.isDistracterToWordsInDictionaries(EMPTY_PREV_WORD, typedWord));
+
+        typedWord = "mot";
+        // For this test case, we consider "mot" is a distracter to "not".
+        assertTrue(mDistracterFilter.isDistracterToWordsInDictionaries(EMPTY_PREV_WORD, typedWord));
+
+        typedWord = "wierd";
+        // For this test case, we consider "wierd" is a distracter to "weird".
+        assertTrue(mDistracterFilter.isDistracterToWordsInDictionaries(EMPTY_PREV_WORD, typedWord));
+
+        typedWord = "hoe";
+        // For this test case, we consider "hoe" is a distracter to "how".
+        assertTrue(mDistracterFilter.isDistracterToWordsInDictionaries(EMPTY_PREV_WORD, typedWord));
+
+        typedWord = "nit";
+        // For this test case, we consider "nit" is a distracter to "not".
+        assertTrue(mDistracterFilter.isDistracterToWordsInDictionaries(EMPTY_PREV_WORD, typedWord));
+
+        typedWord = "ill";
+        // For this test case, we consider "ill" is a distracter to "I'll".
+        assertTrue(mDistracterFilter.isDistracterToWordsInDictionaries(EMPTY_PREV_WORD, typedWord));
+
+        typedWord = "asdfd";
+        // For this test case, we consider "asdfd" is not a distracter to any word in dictionaries.
+        assertFalse(
+                mDistracterFilter.isDistracterToWordsInDictionaries(EMPTY_PREV_WORD, typedWord));
+
+        typedWord = "thank";
+        // For this test case, we consider "thank" is not a distracter to any other word
+        // in dictionaries.
+        assertFalse(
+                mDistracterFilter.isDistracterToWordsInDictionaries(EMPTY_PREV_WORD, typedWord));
+    }
+}