Merge "Improve multi-word suggestion handling for user history."
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
new file mode 100644
index 0000000..814e402
--- /dev/null
+++ 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_lmp.9.png b/java/res/drawable-hdpi/btn_keyboard_key_dark_pressed_lmp.9.png
new file mode 100644
index 0000000..90abe39
--- /dev/null
+++ b/java/res/drawable-hdpi/btn_keyboard_key_dark_pressed_lmp.9.png
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
new file mode 100644
index 0000000..48eeb3f
--- /dev/null
+++ 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_lmp.9.png b/java/res/drawable-hdpi/btn_keyboard_key_dark_pressed_on_lmp.9.png
new file mode 100644
index 0000000..71e0683
--- /dev/null
+++ 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_pressed_lmp.9.png b/java/res/drawable-hdpi/btn_keyboard_key_light_pressed_lmp.9.png
new file mode 100644
index 0000000..6768241
--- /dev/null
+++ b/java/res/drawable-hdpi/btn_keyboard_key_light_pressed_lmp.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/btn_keyboard_key_popup_selected_lmp.9.png b/java/res/drawable-hdpi/btn_keyboard_key_popup_selected_lmp.9.png
new file mode 100644
index 0000000..10f8e97
--- /dev/null
+++ b/java/res/drawable-hdpi/btn_keyboard_key_popup_selected_lmp.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/keyboard_key_feedback_background_lmp.9.png b/java/res/drawable-hdpi/keyboard_key_feedback_background_lmp.9.png
new file mode 100644
index 0000000..be39415
--- /dev/null
+++ b/java/res/drawable-hdpi/keyboard_key_feedback_background_lmp.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/keyboard_key_feedback_left_background_lmp.9.png b/java/res/drawable-hdpi/keyboard_key_feedback_left_background_lmp.9.png
new file mode 100644
index 0000000..9fa6d00
--- /dev/null
+++ b/java/res/drawable-hdpi/keyboard_key_feedback_left_background_lmp.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/keyboard_key_feedback_left_more_background_lmp.9.png b/java/res/drawable-hdpi/keyboard_key_feedback_left_more_background_lmp.9.png
new file mode 100644
index 0000000..c73269b
--- /dev/null
+++ b/java/res/drawable-hdpi/keyboard_key_feedback_left_more_background_lmp.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/keyboard_key_feedback_more_background_lmp.9.png b/java/res/drawable-hdpi/keyboard_key_feedback_more_background_lmp.9.png
new file mode 100644
index 0000000..fffd402
--- /dev/null
+++ b/java/res/drawable-hdpi/keyboard_key_feedback_more_background_lmp.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/keyboard_key_feedback_right_background_lmp.9.png b/java/res/drawable-hdpi/keyboard_key_feedback_right_background_lmp.9.png
new file mode 100644
index 0000000..61c23c1
--- /dev/null
+++ b/java/res/drawable-hdpi/keyboard_key_feedback_right_background_lmp.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/keyboard_key_feedback_right_more_background_lmp.9.png b/java/res/drawable-hdpi/keyboard_key_feedback_right_more_background_lmp.9.png
new file mode 100644
index 0000000..827d743
--- /dev/null
+++ b/java/res/drawable-hdpi/keyboard_key_feedback_right_more_background_lmp.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/keyboard_popup_panel_background_lmp.9.png b/java/res/drawable-hdpi/keyboard_popup_panel_background_lmp.9.png
new file mode 100644
index 0000000..f9dd3b8
--- /dev/null
+++ b/java/res/drawable-hdpi/keyboard_popup_panel_background_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
new file mode 100644
index 0000000..b7b2dca
--- /dev/null
+++ 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_lmp.9.png b/java/res/drawable-mdpi/btn_keyboard_key_dark_pressed_lmp.9.png
new file mode 100644
index 0000000..4a92b80
--- /dev/null
+++ b/java/res/drawable-mdpi/btn_keyboard_key_dark_pressed_lmp.9.png
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
new file mode 100644
index 0000000..72125a0
--- /dev/null
+++ 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_lmp.9.png b/java/res/drawable-mdpi/btn_keyboard_key_dark_pressed_on_lmp.9.png
new file mode 100644
index 0000000..82413d4
--- /dev/null
+++ 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_pressed_lmp.9.png b/java/res/drawable-mdpi/btn_keyboard_key_light_pressed_lmp.9.png
new file mode 100644
index 0000000..0493859
--- /dev/null
+++ b/java/res/drawable-mdpi/btn_keyboard_key_light_pressed_lmp.9.png
Binary files differ
diff --git a/java/res/drawable-mdpi/btn_keyboard_key_popup_selected_lmp.9.png b/java/res/drawable-mdpi/btn_keyboard_key_popup_selected_lmp.9.png
new file mode 100644
index 0000000..ee0aae2
--- /dev/null
+++ b/java/res/drawable-mdpi/btn_keyboard_key_popup_selected_lmp.9.png
Binary files differ
diff --git a/java/res/drawable-mdpi/keyboard_key_feedback_background_lmp.9.png b/java/res/drawable-mdpi/keyboard_key_feedback_background_lmp.9.png
new file mode 100644
index 0000000..625490b
--- /dev/null
+++ b/java/res/drawable-mdpi/keyboard_key_feedback_background_lmp.9.png
Binary files differ
diff --git a/java/res/drawable-mdpi/keyboard_key_feedback_left_background_lmp.9.png b/java/res/drawable-mdpi/keyboard_key_feedback_left_background_lmp.9.png
new file mode 100644
index 0000000..427c870
--- /dev/null
+++ b/java/res/drawable-mdpi/keyboard_key_feedback_left_background_lmp.9.png
Binary files differ
diff --git a/java/res/drawable-mdpi/keyboard_key_feedback_left_more_background_lmp.9.png b/java/res/drawable-mdpi/keyboard_key_feedback_left_more_background_lmp.9.png
new file mode 100644
index 0000000..ea75729
--- /dev/null
+++ b/java/res/drawable-mdpi/keyboard_key_feedback_left_more_background_lmp.9.png
Binary files differ
diff --git a/java/res/drawable-mdpi/keyboard_key_feedback_more_background_lmp.9.png b/java/res/drawable-mdpi/keyboard_key_feedback_more_background_lmp.9.png
new file mode 100644
index 0000000..1911c42
--- /dev/null
+++ b/java/res/drawable-mdpi/keyboard_key_feedback_more_background_lmp.9.png
Binary files differ
diff --git a/java/res/drawable-mdpi/keyboard_key_feedback_right_background_lmp.9.png b/java/res/drawable-mdpi/keyboard_key_feedback_right_background_lmp.9.png
new file mode 100644
index 0000000..cdef116
--- /dev/null
+++ b/java/res/drawable-mdpi/keyboard_key_feedback_right_background_lmp.9.png
Binary files differ
diff --git a/java/res/drawable-mdpi/keyboard_key_feedback_right_more_background_lmp.9.png b/java/res/drawable-mdpi/keyboard_key_feedback_right_more_background_lmp.9.png
new file mode 100644
index 0000000..dea5d07
--- /dev/null
+++ b/java/res/drawable-mdpi/keyboard_key_feedback_right_more_background_lmp.9.png
Binary files differ
diff --git a/java/res/drawable-mdpi/keyboard_popup_panel_background_lmp.9.png b/java/res/drawable-mdpi/keyboard_popup_panel_background_lmp.9.png
new file mode 100644
index 0000000..8965055
--- /dev/null
+++ b/java/res/drawable-mdpi/keyboard_popup_panel_background_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
new file mode 100644
index 0000000..20251a0
--- /dev/null
+++ 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_lmp.9.png b/java/res/drawable-xhdpi/btn_keyboard_key_dark_pressed_lmp.9.png
new file mode 100644
index 0000000..84d1739
--- /dev/null
+++ b/java/res/drawable-xhdpi/btn_keyboard_key_dark_pressed_lmp.9.png
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
new file mode 100644
index 0000000..ee4490e
--- /dev/null
+++ 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_lmp.9.png b/java/res/drawable-xhdpi/btn_keyboard_key_dark_pressed_on_lmp.9.png
new file mode 100644
index 0000000..e812477
--- /dev/null
+++ 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_pressed_lmp.9.png b/java/res/drawable-xhdpi/btn_keyboard_key_light_pressed_lmp.9.png
new file mode 100644
index 0000000..f770962
--- /dev/null
+++ b/java/res/drawable-xhdpi/btn_keyboard_key_light_pressed_lmp.9.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/btn_keyboard_key_popup_selected_lmp.9.png b/java/res/drawable-xhdpi/btn_keyboard_key_popup_selected_lmp.9.png
new file mode 100644
index 0000000..891d000
--- /dev/null
+++ b/java/res/drawable-xhdpi/btn_keyboard_key_popup_selected_lmp.9.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/keyboard_key_feedback_background_lmp.9.png b/java/res/drawable-xhdpi/keyboard_key_feedback_background_lmp.9.png
new file mode 100644
index 0000000..c211d89
--- /dev/null
+++ b/java/res/drawable-xhdpi/keyboard_key_feedback_background_lmp.9.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/keyboard_key_feedback_left_background_lmp.9.png b/java/res/drawable-xhdpi/keyboard_key_feedback_left_background_lmp.9.png
new file mode 100644
index 0000000..543bc76
--- /dev/null
+++ b/java/res/drawable-xhdpi/keyboard_key_feedback_left_background_lmp.9.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/keyboard_key_feedback_left_more_background_lmp.9.png b/java/res/drawable-xhdpi/keyboard_key_feedback_left_more_background_lmp.9.png
new file mode 100644
index 0000000..ec42aad
--- /dev/null
+++ b/java/res/drawable-xhdpi/keyboard_key_feedback_left_more_background_lmp.9.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/keyboard_key_feedback_more_background_lmp.9.png b/java/res/drawable-xhdpi/keyboard_key_feedback_more_background_lmp.9.png
new file mode 100644
index 0000000..319e9d7
--- /dev/null
+++ b/java/res/drawable-xhdpi/keyboard_key_feedback_more_background_lmp.9.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/keyboard_key_feedback_right_background_lmp.9.png b/java/res/drawable-xhdpi/keyboard_key_feedback_right_background_lmp.9.png
new file mode 100644
index 0000000..052032b
--- /dev/null
+++ b/java/res/drawable-xhdpi/keyboard_key_feedback_right_background_lmp.9.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/keyboard_key_feedback_right_more_background_lmp.9.png b/java/res/drawable-xhdpi/keyboard_key_feedback_right_more_background_lmp.9.png
new file mode 100644
index 0000000..c7e9d1c
--- /dev/null
+++ b/java/res/drawable-xhdpi/keyboard_key_feedback_right_more_background_lmp.9.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/keyboard_popup_panel_background_lmp.9.png b/java/res/drawable-xhdpi/keyboard_popup_panel_background_lmp.9.png
new file mode 100644
index 0000000..36df715
--- /dev/null
+++ b/java/res/drawable-xhdpi/keyboard_popup_panel_background_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
new file mode 100644
index 0000000..97f9625
--- /dev/null
+++ 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
new file mode 100644
index 0000000..dfb16a7
--- /dev/null
+++ b/java/res/drawable-xxhdpi/btn_keyboard_key_dark_pressed_lmp.9.png
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
new file mode 100644
index 0000000..bf1d346
--- /dev/null
+++ 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
new file mode 100644
index 0000000..9622771
--- /dev/null
+++ 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
new file mode 100644
index 0000000..17144b6
--- /dev/null
+++ b/java/res/drawable-xxhdpi/btn_keyboard_key_light_pressed_lmp.9.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/btn_keyboard_key_popup_selected_lmp.9.png b/java/res/drawable-xxhdpi/btn_keyboard_key_popup_selected_lmp.9.png
new file mode 100644
index 0000000..0cbb2ec
--- /dev/null
+++ b/java/res/drawable-xxhdpi/btn_keyboard_key_popup_selected_lmp.9.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/keyboard_key_feedback_background_lmp.9.png b/java/res/drawable-xxhdpi/keyboard_key_feedback_background_lmp.9.png
new file mode 100644
index 0000000..fd2f9e5
--- /dev/null
+++ b/java/res/drawable-xxhdpi/keyboard_key_feedback_background_lmp.9.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/keyboard_key_feedback_left_background_lmp.9.png b/java/res/drawable-xxhdpi/keyboard_key_feedback_left_background_lmp.9.png
new file mode 100644
index 0000000..3ab7900
--- /dev/null
+++ b/java/res/drawable-xxhdpi/keyboard_key_feedback_left_background_lmp.9.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/keyboard_key_feedback_left_more_background_lmp.9.png b/java/res/drawable-xxhdpi/keyboard_key_feedback_left_more_background_lmp.9.png
new file mode 100644
index 0000000..99543a1
--- /dev/null
+++ b/java/res/drawable-xxhdpi/keyboard_key_feedback_left_more_background_lmp.9.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/keyboard_key_feedback_more_background_lmp.9.png b/java/res/drawable-xxhdpi/keyboard_key_feedback_more_background_lmp.9.png
new file mode 100644
index 0000000..121411a
--- /dev/null
+++ b/java/res/drawable-xxhdpi/keyboard_key_feedback_more_background_lmp.9.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/keyboard_key_feedback_right_background_lmp.9.png b/java/res/drawable-xxhdpi/keyboard_key_feedback_right_background_lmp.9.png
new file mode 100644
index 0000000..e9e3792
--- /dev/null
+++ b/java/res/drawable-xxhdpi/keyboard_key_feedback_right_background_lmp.9.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/keyboard_key_feedback_right_more_background_lmp.9.png b/java/res/drawable-xxhdpi/keyboard_key_feedback_right_more_background_lmp.9.png
new file mode 100644
index 0000000..6c1143a
--- /dev/null
+++ b/java/res/drawable-xxhdpi/keyboard_key_feedback_right_more_background_lmp.9.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/keyboard_popup_panel_background_lmp.9.png b/java/res/drawable-xxhdpi/keyboard_popup_panel_background_lmp.9.png
new file mode 100644
index 0000000..91d5d7f
--- /dev/null
+++ b/java/res/drawable-xxhdpi/keyboard_popup_panel_background_lmp.9.png
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
new file mode 100644
index 0000000..427b8d5
--- /dev/null
+++ b/java/res/drawable/btn_keyboard_key_functional_lmp.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<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" />
+</selector>
diff --git a/java/res/drawable/btn_keyboard_key_ics.xml b/java/res/drawable/btn_keyboard_key_ics.xml
index 259bb9b..9db0eee 100644
--- a/java/res/drawable/btn_keyboard_key_ics.xml
+++ b/java/res/drawable/btn_keyboard_key_ics.xml
@@ -39,7 +39,7 @@
 
     <!-- Empty background keys. -->
     <item android:state_empty="true"
-          android:drawable="@drawable/transparent" />
+          android:drawable="@android:color/transparent" />
 
     <!-- Normal keys. -->
     <item android:state_pressed="true"
diff --git a/java/res/drawable/btn_keyboard_key_klp.xml b/java/res/drawable/btn_keyboard_key_klp.xml
index 16b5fa0..500e3ea 100644
--- a/java/res/drawable/btn_keyboard_key_klp.xml
+++ b/java/res/drawable/btn_keyboard_key_klp.xml
@@ -39,7 +39,7 @@
 
     <!-- Empty background keys. -->
     <item android:state_empty="true"
-          android:drawable="@drawable/transparent" />
+          android:drawable="@android:color/transparent" />
 
     <!-- Normal keys. -->
     <item android:state_pressed="true"
diff --git a/java/res/drawable/btn_keyboard_key_lmp.xml b/java/res/drawable/btn_keyboard_key_lmp.xml
new file mode 100644
index 0000000..fdd19df
--- /dev/null
+++ b/java/res/drawable/btn_keyboard_key_lmp.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<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" />
+    <item android:state_single="true"
+          android:drawable="@android:color/transparent" />
+
+    <!-- Action keys. -->
+    <item android:state_active="true" android:state_pressed="true"
+          android:drawable="@drawable/btn_keyboard_key_dark_pressed_lmp" />
+    <item android:state_active="true"
+          android:drawable="@android:color/transparent" />
+
+    <!-- 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" />
+    <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" />
+
+    <!-- Empty background keys. -->
+    <item android:state_empty="true"
+          android:drawable="@android:color/transparent" />
+
+    <!-- Normal keys. -->
+    <item android:state_pressed="true"
+          android:drawable="@drawable/btn_keyboard_key_light_pressed_lmp" />
+    <item android:drawable="@android:color/transparent" />
+</selector>
diff --git a/java/res/drawable/btn_keyboard_key_popup_ics.xml b/java/res/drawable/btn_keyboard_key_popup_ics.xml
index 31b6131..17d646b 100644
--- a/java/res/drawable/btn_keyboard_key_popup_ics.xml
+++ b/java/res/drawable/btn_keyboard_key_popup_ics.xml
@@ -17,5 +17,5 @@
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
     <item android:state_pressed="true"
           android:drawable="@drawable/btn_keyboard_key_popup_selected_ics" />
-    <item android:drawable="@drawable/transparent" />
+    <item android:drawable="@android:color/transparent" />
 </selector>
diff --git a/java/res/drawable/btn_keyboard_key_popup_klp.xml b/java/res/drawable/btn_keyboard_key_popup_klp.xml
index 62cbca8..9dfc93a 100644
--- a/java/res/drawable/btn_keyboard_key_popup_klp.xml
+++ b/java/res/drawable/btn_keyboard_key_popup_klp.xml
@@ -17,5 +17,5 @@
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
     <item android:state_pressed="true"
           android:drawable="@drawable/btn_keyboard_key_popup_selected_klp" />
-    <item android:drawable="@drawable/transparent" />
+    <item android:drawable="@android:color/transparent" />
 </selector>
diff --git a/java/res/drawable/btn_keyboard_key_popup_lmp.xml b/java/res/drawable/btn_keyboard_key_popup_lmp.xml
new file mode 100644
index 0000000..ebedaea
--- /dev/null
+++ b/java/res/drawable/btn_keyboard_key_popup_lmp.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true"
+          android:drawable="@drawable/btn_keyboard_key_popup_selected_lmp" />
+    <item android:drawable="@android:color/transparent" />
+</selector>
diff --git a/java/res/drawable/btn_keyboard_spacebar_lmp.xml b/java/res/drawable/btn_keyboard_spacebar_lmp.xml
new file mode 100644
index 0000000..516cb07
--- /dev/null
+++ b/java/res/drawable/btn_keyboard_spacebar_lmp.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<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" />
+</selector>
diff --git a/java/res/drawable/transparent.xml b/java/res/drawable/btn_suggestion_lmp.xml
similarity index 74%
rename from java/res/drawable/transparent.xml
rename to java/res/drawable/btn_suggestion_lmp.xml
index 855cf2a..c778e23 100644
--- a/java/res/drawable/transparent.xml
+++ b/java/res/drawable/btn_suggestion_lmp.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2011, The Android Open Source Project
+** Copyright 2014, The Android Open Source Project
 **
 ** Licensed under the Apache License, Version 2.0 (the "License");
 ** you may not use this file except in compliance with the License.
@@ -18,13 +18,10 @@
 */
 -->
 
-<shape
+<selector
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shape="rectangle"
 >
-    <solid
-        android:color="@android:color/transparent" />
-    <size
-        android:width="50dp"
-        android:height="40dp" />
-</shape>
+    <item
+        android:state_pressed="true"
+        android:drawable="@drawable/btn_keyboard_key_popup_selected_lmp" />
+</selector>
diff --git a/java/res/drawable/keyboard_key_feedback_lmp.xml b/java/res/drawable/keyboard_key_feedback_lmp.xml
new file mode 100644
index 0000000..cdbe64c
--- /dev/null
+++ b/java/res/drawable/keyboard_key_feedback_lmp.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<selector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <!-- Left edge -->
+    <item latin:state_left_edge="true" latin:state_has_morekeys="true"
+          android:drawable="@drawable/keyboard_key_feedback_left_more_background_lmp" />
+    <item latin:state_left_edge="true"
+          android:drawable="@drawable/keyboard_key_feedback_left_background_lmp" />
+
+    <!-- Right edge -->
+    <item latin:state_right_edge="true" latin:state_has_morekeys="true"
+          android:drawable="@drawable/keyboard_key_feedback_right_more_background_lmp" />
+    <item latin:state_right_edge="true"
+          android:drawable="@drawable/keyboard_key_feedback_right_background_lmp" />
+
+    <item latin:state_has_morekeys="true"
+          android:drawable="@drawable/keyboard_key_feedback_more_background_lmp" />
+    <item android:drawable="@drawable/keyboard_key_feedback_background_lmp" />
+</selector>
diff --git a/java/res/layout/suggestions_strip.xml b/java/res/layout/suggestions_strip.xml
index bff9a1e..36898c8 100644
--- a/java/res/layout/suggestions_strip.xml
+++ b/java/res/layout/suggestions_strip.xml
@@ -64,4 +64,12 @@
             android:textSize="16sp"
             style="?attr/suggestionWordStyle" />
     </LinearLayout>
+    <ImageButton
+        android:id="@+id/suggestions_strip_voice_key"
+        android:layout_width="@dimen/config_suggestions_strip_edge_key_width"
+        android:layout_height="fill_parent"
+        android:layout_alignParentEnd="true"
+        android:layout_alignParentRight="true"
+        android:layout_centerVertical="true"
+        style="?attr/suggestionWordStyle" />
 </merge>
diff --git a/java/res/values-km-rKH/strings.xml b/java/res/values-km-rKH/strings.xml
index 4d7de93..519aa44 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-land/config.xml b/java/res/values-land/config.xml
index f72d64f..37bf22f 100644
--- a/java/res/values-land/config.xml
+++ b/java/res/values-land/config.xml
@@ -59,6 +59,7 @@
 
     <dimen name="config_suggestions_strip_height">36dp</dimen>
     <dimen name="config_suggestions_strip_horizontal_margin">54dp</dimen>
+    <dimen name="config_suggestions_strip_edge_key_width">54dp</dimen>
     <dimen name="config_more_suggestions_row_height">36dp</dimen>
     <integer name="config_max_more_suggestions_row">2</integer>
     <fraction name="config_min_more_suggestions_width">60%</fraction>
diff --git a/java/res/values-sw600dp-land/config.xml b/java/res/values-sw600dp-land/config.xml
index 8789e72..ba8b52f 100644
--- a/java/res/values-sw600dp-land/config.xml
+++ b/java/res/values-sw600dp-land/config.xml
@@ -50,6 +50,7 @@
 
     <dimen name="config_suggestions_strip_height">44dp</dimen>
     <dimen name="config_suggestions_strip_horizontal_margin">180.0dp</dimen>
+    <dimen name="config_suggestions_strip_edge_key_width">54dp</dimen>
     <integer name="config_max_more_suggestions_row">5</integer>
     <fraction name="config_min_more_suggestions_width">50%</fraction>
 
diff --git a/java/res/values-sw600dp/config.xml b/java/res/values-sw600dp/config.xml
index 12e9832..d97538d 100644
--- a/java/res/values-sw600dp/config.xml
+++ b/java/res/values-sw600dp/config.xml
@@ -63,7 +63,8 @@
     <fraction name="config_key_shifted_letter_hint_ratio_5row">27%</fraction>
 
     <dimen name="config_suggestions_strip_height">44dp</dimen>
-    <dimen name="config_suggestions_strip_horizontal_margin">0dp</dimen>
+    <dimen name="config_suggestions_strip_horizontal_margin">54dp</dimen>
+    <dimen name="config_suggestions_strip_edge_key_width">54dp</dimen>
     <dimen name="config_more_suggestions_row_height">44dp</dimen>
     <integer name="config_max_more_suggestions_row">6</integer>
     <fraction name="config_min_more_suggestions_width">90%</fraction>
diff --git a/java/res/values-sw768dp-land/config.xml b/java/res/values-sw768dp-land/config.xml
index 17733f0..63f86ba 100644
--- a/java/res/values-sw768dp-land/config.xml
+++ b/java/res/values-sw768dp-land/config.xml
@@ -51,6 +51,7 @@
 
     <dimen name="config_suggestions_strip_height">44dp</dimen>
     <dimen name="config_suggestions_strip_horizontal_margin">340dp</dimen>
+    <dimen name="config_suggestions_strip_edge_key_width">54dp</dimen>
     <fraction name="config_min_more_suggestions_width">50%</fraction>
 
     <!-- Gesture floating preview text parameters -->
diff --git a/java/res/values-sw768dp/config.xml b/java/res/values-sw768dp/config.xml
index 647cca9..94b38d8 100644
--- a/java/res/values-sw768dp/config.xml
+++ b/java/res/values-sw768dp/config.xml
@@ -62,6 +62,7 @@
 
     <dimen name="config_suggestions_strip_height">44dp</dimen>
     <dimen name="config_suggestions_strip_horizontal_margin">100dp</dimen>
+    <dimen name="config_suggestions_strip_edge_key_width">54dp</dimen>
     <dimen name="config_more_suggestions_row_height">44dp</dimen>
     <integer name="config_max_more_suggestions_row">6</integer>
     <fraction name="config_min_more_suggestions_width">90%</fraction>
diff --git a/java/res/values/colors.xml b/java/res/values/colors.xml
index 824928c..dabb4d6 100644
--- a/java/res/values/colors.xml
+++ b/java/res/values/colors.xml
@@ -40,6 +40,12 @@
     <color name="typed_word_color_klp">#D8F0F0F0</color>
     <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 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 3fe4b94..1962c0d 100644
--- a/java/res/values/config-common.xml
+++ b/java/res/values/config-common.xml
@@ -24,9 +24,6 @@
          at input history to suggest a hopefully helpful suggestions for the next word? -->
     <bool name="config_default_next_word_prediction">true</bool>
 
-    <!-- This configuration must be aligned with {@link KeyboardTheme#DEFAULT_THEME_ID}. -->
-    <string name="config_default_keyboard_theme_id" translatable="false">2</string>
-
     <integer name="config_delay_update_shift_state">100</integer>
     <integer name="config_double_space_period_timeout">1100</integer>
 
diff --git a/java/res/values/config.xml b/java/res/values/config.xml
index 1ab4927..9f556a6 100644
--- a/java/res/values/config.xml
+++ b/java/res/values/config.xml
@@ -64,7 +64,8 @@
     <fraction name="config_key_shifted_letter_hint_ratio_5row">41%</fraction>
 
     <dimen name="config_suggestions_strip_height">40dp</dimen>
-    <dimen name="config_suggestions_strip_horizontal_margin">0dp</dimen>
+    <dimen name="config_suggestions_strip_horizontal_margin">36dp</dimen>
+    <dimen name="config_suggestions_strip_edge_key_width">36dp</dimen>
     <dimen name="config_more_suggestions_row_height">40dp</dimen>
     <integer name="config_max_more_suggestions_row">6</integer>
     <fraction name="config_min_more_suggestions_width">90%</fraction>
diff --git a/java/res/values/donottranslate.xml b/java/res/values/donottranslate.xml
index 9a610a0..415dd0b 100644
--- a/java/res/values/donottranslate.xml
+++ b/java/res/values/donottranslate.xml
@@ -47,17 +47,6 @@
     <string name="prefs_debug_mode">Debug Mode</string>
     <string name="prefs_force_non_distinct_multitouch">Force non-distinct multitouch</string>
 
-    <!-- For keyboard color scheme option dialog. -->
-    <string-array name="keyboard_theme_names">
-        <item>@string/keyboard_color_scheme_white</item>
-        <item>@string/keyboard_color_scheme_blue</item>
-    </string-array>
-    <!-- An element must be a keyboard theme id of {@link KeyboardTheme#THEME_ID_*}. -->
-    <string-array name="keyboard_theme_ids">
-        <item>2</item>
-        <item>0</item>
-    </string-array>
-
     <!-- Subtype locale display name exceptions.
          For each exception, there should be related string resources for display name that may have
          explicit keyboard layout. The string resource name must be "subtype_<locale>" or
diff --git a/java/res/values/keyboard-icons-holo.xml b/java/res/values/keyboard-icons-holo.xml
index 4c888d5..669d2c0 100644
--- a/java/res/values/keyboard-icons-holo.xml
+++ b/java/res/values/keyboard-icons-holo.xml
@@ -21,9 +21,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android">
     <style name="KeyboardIcons.Holo">
         <!-- Keyboard icons -->
-        <!-- TODO: The following holo icon for phone (drawable-hdpi and drawable-xhdpi) are missing.
-             sym_keyboard_123_mic_holo
-             -->
         <item name="iconShiftKey">@drawable/sym_keyboard_shift_holo_dark</item>
         <item name="iconDeleteKey">@drawable/sym_keyboard_delete_holo_dark</item>
         <item name="iconSettingsKey">@drawable/sym_keyboard_settings_holo_dark</item>
diff --git a/java/res/values/keyboard-icons-lmp.xml b/java/res/values/keyboard-icons-lmp.xml
new file mode 100644
index 0000000..a9cbabc
--- /dev/null
+++ b/java/res/values/keyboard-icons-lmp.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 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.
+*/
+-->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+    <style name="KeyboardIcons.LMP">
+        <!-- Keyboard icons -->
+        <item name="iconShiftKey">@drawable/sym_keyboard_shift_holo_dark</item>
+        <item name="iconDeleteKey">@drawable/sym_keyboard_delete_holo_dark</item>
+        <item name="iconSettingsKey">@drawable/sym_keyboard_settings_holo_dark</item>
+        <item name="iconSpaceKey">@drawable/sym_keyboard_space_holo_dark</item>
+        <item name="iconEnterKey">@drawable/sym_keyboard_return_holo_dark</item>
+        <item name="iconSearchKey">@drawable/sym_keyboard_search_holo_dark</item>
+        <item name="iconTabKey">@drawable/sym_keyboard_tab_holo_dark</item>
+        <item name="iconShortcutKey">@drawable/sym_keyboard_voice_holo_dark</item>
+        <item name="iconSpaceKeyForNumberLayout">@drawable/sym_keyboard_space_holo_dark</item>
+        <item name="iconShiftKeyShifted">@drawable/sym_keyboard_shift_locked_holo_dark</item>
+        <item name="iconShortcutKeyDisabled">@drawable/sym_keyboard_voice_off_holo_dark</item>
+        <item name="iconTabKeyPreview">@drawable/sym_keyboard_feedback_tab</item>
+        <item name="iconLanguageSwitchKey">@drawable/sym_keyboard_language_switch_dark</item>
+        <item name="iconZwnjKey">@drawable/sym_keyboard_zwnj_holo_dark</item>
+        <item name="iconZwjKey">@drawable/sym_keyboard_zwj_holo_dark</item>
+        <item name="iconEmojiKey">@drawable/sym_keyboard_smiley_holo_dark</item>
+    </style>
+</resources>
diff --git a/java/res/values/keyboard-themes.xml b/java/res/values/keyboard-themes.xml
new file mode 100644
index 0000000..0325f5b
--- /dev/null
+++ b/java/res/values/keyboard-themes.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 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.
+*/
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- For keyboard color scheme option dialog. -->
+    <string-array name="keyboard_theme_names" translatable="false">
+        <item>@string/keyboard_color_scheme_white</item>
+        <item>@string/keyboard_color_scheme_blue</item>
+        <!-- TODO: Make this item as translatable string resource. -->
+        <item>Quantum</item>
+    </string-array>
+    <!-- An element must be a keyboard theme id of {@link KeyboardTheme#THEME_ID_*}. -->
+    <string-array name="keyboard_theme_ids" translatable="false">
+        <item>2</item>
+        <item>0</item>
+        <item>3</item>
+    </string-array>
+</resources>
diff --git a/java/res/values/themes-lmp.xml b/java/res/values/themes-lmp.xml
new file mode 100644
index 0000000..773da19
--- /dev/null
+++ b/java/res/values/themes-lmp.xml
@@ -0,0 +1,133 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 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.
+*/
+-->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+    <style name="KeyboardTheme.LMP" parent="KeyboardIcons.LMP">
+        <item name="keyboardStyle">@style/Keyboard.LMP</item>
+        <item name="keyboardViewStyle">@style/KeyboardView.LMP</item>
+        <item name="mainKeyboardViewStyle">@style/MainKeyboardView.LMP</item>
+        <item name="keyPreviewTextViewStyle">@style/KeyPreviewTextView.LMP</item>
+        <item name="emojiPalettesViewStyle">@style/EmojiPalettesView.LMP</item>
+        <item name="moreKeysKeyboardStyle">@style/MoreKeysKeyboard.LMP</item>
+        <item name="moreKeysKeyboardViewStyle">@style/MoreKeysKeyboardView.LMP</item>
+        <item name="suggestionStripViewStyle">@style/SuggestionStripView.LMP</item>
+        <item name="suggestionWordStyle">@style/SuggestionWord.LMP</item>
+    </style>
+    <style
+        name="Keyboard.LMP"
+        parent="Keyboard"
+    >
+        <!-- This should be aligned with KeyboardSwitcher.KEYBOARD_THEMES[] -->
+        <item name="themeId">0</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>
+        <item name="verticalGap">@fraction/config_key_vertical_gap_holo</item>
+        <item name="touchPositionCorrectionData">@array/touch_position_correction_data_holo</item>
+    </style>
+    <style
+        name="KeyboardView.LMP"
+        parent="KeyboardView"
+    >
+        <item name="android:background">@drawable/keyboard_background_holo</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="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="keyPreviewTextColor">@color/key_text_color_holo</item>
+        <item name="keyTextShadowColor">@color/key_text_shadow_color_holo</item>
+        <item name="keyTextShadowRadius">0.0</item>
+    </style>
+    <style
+        name="MainKeyboardView.LMP"
+        parent="KeyboardView.LMP"
+    >
+        <item name="keyPreviewOffset">@dimen/config_key_preview_offset_holo</item>
+        <item name="gestureFloatingPreviewTextColor">@color/highlight_color_lmp</item>
+        <item name="gestureFloatingPreviewColor">@color/gesture_floating_preview_color_holo</item>
+        <item name="gestureTrailColor">@color/highlight_color_lmp</item>
+        <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="spacebarBackground">@drawable/btn_keyboard_spacebar_lmp</item>
+    </style>
+    <style
+        name="KeyPreviewTextView.LMP"
+        parent="KeyPreviewTextView"
+    >
+        <item name="android:background">@drawable/keyboard_key_feedback_lmp</item>
+    </style>
+    <!-- Though {@link EmojiPalettesView} doesn't extend {@link KeyboardView}, some views inside it,
+         for instance delete button, need themed {@link KeyboardView} attributes. -->
+    <style
+        name="EmojiPalettesView.LMP"
+        parent="KeyboardView.LMP"
+    >
+        <item name="keyBackgroundEmojiFunctional">@drawable/btn_keyboard_key_functional_lmp</item>
+        <item name="emojiTabLabelColor">@color/emoji_tab_label_color_holo</item>
+    </style>
+    <style
+        name="MoreKeysKeyboard.LMP"
+        parent="Keyboard.LMP"
+    >
+        <item name="keyboardTopPadding">0%p</item>
+        <item name="keyboardBottomPadding">0%p</item>
+        <item name="horizontalGap">0%p</item>
+        <item name="touchPositionCorrectionData">@null</item>
+    </style>
+    <style
+        name="MoreKeysKeyboardView.LMP"
+        parent="KeyboardView.LMP"
+    >
+        <item name="android:background">@drawable/keyboard_popup_panel_background_lmp</item>
+        <item name="keyBackground">@drawable/btn_keyboard_key_popup_lmp</item>
+        <item name="keyTypeface">normal</item>
+        <item name="verticalCorrection">@dimen/config_more_keys_keyboard_vertical_correction_holo</item>
+    </style>
+    <style
+        name="SuggestionStripView.LMP"
+        parent="KeyboardView.LMP"
+    >
+        <item name="suggestionsCountInStrip">@integer/config_suggestions_count_in_strip</item>
+        <item name="centerSuggestionPercentile">@fraction/config_center_suggestion_percentile</item>
+        <item name="maxMoreSuggestionsRow">@integer/config_max_more_suggestions_row</item>
+        <item name="minMoreSuggestionsWidth">@fraction/config_min_more_suggestions_width</item>
+        <item name="android:background">@drawable/keyboard_suggest_strip_holo</item>
+        <item name="suggestionStripOptions">autoCorrectBold|validTypedWordBold</item>
+        <item name="colorValidTypedWord">@color/typed_word_color_lmp</item>
+        <item name="colorTypedWord">@color/typed_word_color_lmp</item>
+        <item name="colorAutoCorrect">@color/highlight_color_lmp</item>
+        <item name="colorSuggested">@color/suggested_word_color_lmp</item>
+        <item name="alphaObsoleted">70%</item>
+    </style>
+    <style
+        name="SuggestionWord.LMP"
+        parent="SuggestionWord"
+    >
+        <item name="android:background">@drawable/btn_suggestion_lmp</item>
+        <item name="android:textColor">@color/highlight_color_lmp</item>
+    </style>
+</resources>
diff --git a/java/res/xml-sw600dp/key_settings.xml b/java/res/xml-sw600dp/key_settings.xml
new file mode 100644
index 0000000..45915e9
--- /dev/null
+++ b/java/res/xml-sw600dp/key_settings.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 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.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <switch>
+        <case
+            latin:clobberSettingsKey="false"
+        >
+            <Key
+                latin:keyStyle="settingsKeyStyle" />
+        </case>
+        <!-- clobberSettingsKey="true" -->
+        <default>
+            <Spacer />
+        </default>
+    </switch>
+</merge>
diff --git a/java/res/xml-sw600dp/key_shortcut.xml b/java/res/xml-sw600dp/key_shortcut.xml
deleted file mode 100644
index d24e81f..0000000
--- a/java/res/xml-sw600dp/key_shortcut.xml
+++ /dev/null
@@ -1,53 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <switch>
-        <case
-            latin:supportsSwitchingToShortcutIme="true"
-            latin:clobberSettingsKey="false"
-        >
-            <Key
-                latin:keyStyle="shortcutKeyStyle"
-                latin:keyLabelFlags="hasPopupHint|preserveCase"
-                latin:moreKeys="!text/keyspec_settings" />
-        </case>
-        <case
-            latin:supportsSwitchingToShortcutIme="true"
-            latin:clobberSettingsKey="true"
-        >
-            <Key
-                latin:keyStyle="shortcutKeyStyle" />
-        </case>
-        <case
-            latin:supportsSwitchingToShortcutIme="false"
-            latin:clobberSettingsKey="false"
-        >
-            <Key
-                latin:keyStyle="settingsKeyStyle" />
-        </case>
-        <!-- supportsSwitchingToShortcutIme="false" clobberSettingsKey="true" -->
-        <default>
-            <Spacer />
-        </default>
-    </switch>
-</merge>
diff --git a/java/res/xml-sw600dp/key_styles_common.xml b/java/res/xml-sw600dp/key_styles_common.xml
index 3d5556f..f9b959b 100644
--- a/java/res/xml-sw600dp/key_styles_common.xml
+++ b/java/res/xml-sw600dp/key_styles_common.xml
@@ -78,7 +78,7 @@
         latin:keyboardLayout="@xml/key_styles_enter" />
     <key-style
         latin:styleName="spaceKeyStyle"
-        latin:keySpec=" |!code/key_space"
+        latin:keySpec="!icon/space_key|!code/key_space"
         latin:keyActionFlags="noKeyPreview|enableLongPress" />
     <!-- U+200C: ZERO WIDTH NON-JOINER
          U+200D: ZERO WIDTH JOINER -->
diff --git a/java/res/xml-sw600dp/row_dvorak4.xml b/java/res/xml-sw600dp/row_dvorak4.xml
index 2ba6a49..d3709ef 100644
--- a/java/res/xml-sw600dp/row_dvorak4.xml
+++ b/java/res/xml-sw600dp/row_dvorak4.xml
@@ -29,7 +29,7 @@
             latin:keyStyle="toSymbolKeyStyle"
             latin:keyWidth="10.0%p" />
         <include
-            latin:keyboardLayout="@xml/key_shortcut" />
+            latin:keyboardLayout="@xml/key_settings" />
         <include
             latin:keyboardLayout="@xml/key_f1" />
         <include
diff --git a/java/res/xml-sw600dp/row_pcqwerty5.xml b/java/res/xml-sw600dp/row_pcqwerty5.xml
index 52b581a..ac07f11 100644
--- a/java/res/xml-sw600dp/row_pcqwerty5.xml
+++ b/java/res/xml-sw600dp/row_pcqwerty5.xml
@@ -26,7 +26,7 @@
     >
         <include
             latin:keyWidth="9.0%p"
-            latin:keyboardLayout="@xml/key_shortcut" />
+            latin:keyboardLayout="@xml/key_settings" />
         <switch>
             <case
                 latin:languageSwitchKeyEnabled="true"
diff --git a/java/res/xml-sw600dp/row_qwerty4.xml b/java/res/xml-sw600dp/row_qwerty4.xml
index 7969dd8..d931437 100644
--- a/java/res/xml-sw600dp/row_qwerty4.xml
+++ b/java/res/xml-sw600dp/row_qwerty4.xml
@@ -29,7 +29,7 @@
             latin:keyStyle="toSymbolKeyStyle"
             latin:keyWidth="10.0%p" />
         <include
-            latin:keyboardLayout="@xml/key_shortcut" />
+            latin:keyboardLayout="@xml/key_settings" />
         <include
             latin:keyboardLayout="@xml/key_f1" />
         <include
diff --git a/java/res/xml/key_f1.xml b/java/res/xml/key_f1.xml
index c96ddca..3471c85 100644
--- a/java/res/xml/key_f1.xml
+++ b/java/res/xml/key_f1.xml
@@ -36,27 +36,10 @@
                 latin:keySpec="\@"
                 latin:keyStyle="f1MoreKeysStyle" />
         </case>
-        <case
-            latin:supportsSwitchingToShortcutIme="false"
-        >
-            <Key
-                latin:keySpec="!text/keyspec_comma"
-                latin:keyLabelFlags="hasPopupHint"
-                latin:keyStyle="f1MoreKeysStyle" />
-        </case>
-        <!-- latin:supportsSwitchingToShortcutIme="true" -->
-        <case
-            latin:hasShortcutKey="true"
-        >
-            <Key
-                latin:keyStyle="shortcutKeyStyle" />
-        </case>
-        <!-- latin:hasShortcutKey="false" -->
         <default>
             <Key
                 latin:keySpec="!text/keyspec_comma"
                 latin:keyLabelFlags="hasPopupHint"
-                latin:additionalMoreKeys="!text/keyspec_shortcut"
                 latin:keyStyle="f1MoreKeysStyle" />
         </default>
     </switch>
diff --git a/java/res/xml/key_styles_common.xml b/java/res/xml/key_styles_common.xml
index 78e0301..773995f 100644
--- a/java/res/xml/key_styles_common.xml
+++ b/java/res/xml/key_styles_common.xml
@@ -87,7 +87,7 @@
         latin:keyboardLayout="@xml/key_styles_enter" />
     <key-style
         latin:styleName="spaceKeyStyle"
-        latin:keySpec=" |!code/key_space"
+        latin:keySpec="!icon/space_key|!code/key_space"
         latin:keyActionFlags="noKeyPreview|enableLongPress" />
     <!-- U+200C: ZERO WIDTH NON-JOINER
          U+200D: ZERO WIDTH JOINER -->
diff --git a/java/res/xml/method.xml b/java/res/xml/method.xml
index 0156996..2adc957 100644
--- a/java/res/xml/method.xml
+++ b/java/res/xml/method.xml
@@ -503,7 +503,7 @@
             android:subtypeId="0xea266ea4"
             android:imeSubtypeLocale="my_MM"
             android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="KeyboardLayoutSet=myanmar,EmojiCapable"
+            android:imeSubtypeExtraValue="KeyboardLayoutSet=myanmar,EmojiCapable,CombiningRules=MyanmarReordering"
             android:isAsciiCapable="false"
     />
     <subtype android:icon="@drawable/ic_ime_switcher_dark"
diff --git a/java/res/xml/prefs.xml b/java/res/xml/prefs.xml
index 7d86dbd..61ebb69 100644
--- a/java/res/xml/prefs.xml
+++ b/java/res/xml/prefs.xml
@@ -158,12 +158,11 @@
                 android:persistent="true"
                 android:defaultValue="false" />
             <ListPreference
-                android:key="pref_keyboard_layout_20110916"
+                android:key="pref_keyboard_theme"
                 android:title="@string/keyboard_color_scheme"
                 android:persistent="true"
                 android:entryValues="@array/keyboard_theme_ids"
-                android:entries="@array/keyboard_theme_names"
-                android:defaultValue="@string/config_default_keyboard_theme_id" />
+                android:entries="@array/keyboard_theme_names" />
             <PreferenceScreen
                 android:fragment="com.android.inputmethod.latin.settings.AdditionalSubtypeSettings"
                 android:key="custom_input_styles"
diff --git a/java/res/xml/row_dvorak4.xml b/java/res/xml/row_dvorak4.xml
index 91462cb..279f646 100644
--- a/java/res/xml/row_dvorak4.xml
+++ b/java/res/xml/row_dvorak4.xml
@@ -30,7 +30,6 @@
         <Key
             latin:keySpec="q"
             latin:backgroundType="normal"
-            latin:additionalMoreKeys="!text/keyspec_shortcut"
             latin:keyStyle="f1MoreKeysStyle" />
         <include
             latin:keyXPos="25%p"
diff --git a/java/res/xml/row_pcqwerty5.xml b/java/res/xml/row_pcqwerty5.xml
index 3782763..32c5389 100644
--- a/java/res/xml/row_pcqwerty5.xml
+++ b/java/res/xml/row_pcqwerty5.xml
@@ -26,13 +26,6 @@
     >
         <switch>
             <case
-                latin:hasShortcutKey="true"
-            >
-                <Key
-                    latin:keyStyle="shortcutKeyStyle"
-                    latin:keyWidth="11.538%p" />
-            </case>
-            <case
                 latin:clobberSettingsKey="false"
             >
                 <Key
diff --git a/java/src/com/android/inputmethod/event/CombinerChain.java b/java/src/com/android/inputmethod/event/CombinerChain.java
index 8b59dc5..990f7de 100644
--- a/java/src/com/android/inputmethod/event/CombinerChain.java
+++ b/java/src/com/android/inputmethod/event/CombinerChain.java
@@ -23,6 +23,7 @@
 import com.android.inputmethod.latin.utils.CollectionUtils;
 
 import java.util.ArrayList;
+import java.util.HashMap;
 
 /**
  * This class implements the logic chain between receiving events and generating code points.
@@ -43,6 +44,13 @@
     private SpannableStringBuilder mStateFeedback;
     private final ArrayList<Combiner> mCombiners;
 
+    private static final HashMap<String, Class> IMPLEMENTED_COMBINERS
+            = new HashMap<String, Class>();
+    static {
+        IMPLEMENTED_COMBINERS.put("MyanmarReordering", MyanmarReordering.class);
+    }
+    private static final String COMBINER_SPEC_SEPARATOR = ";";
+
     /**
      * Create an combiner chain.
      *
@@ -56,6 +64,9 @@
         mCombiners = CollectionUtils.newArrayList();
         // The dead key combiner is always active, and always first
         mCombiners.add(new DeadKeyCombiner());
+        for (final Combiner combiner : combinerList) {
+            mCombiners.add(combiner);
+        }
         mCombinedText = new StringBuilder();
         mStateFeedback = new SpannableStringBuilder();
     }
@@ -114,4 +125,29 @@
         final SpannableStringBuilder s = new SpannableStringBuilder(mCombinedText);
         return s.append(mStateFeedback);
     }
+
+    public static Combiner[] createCombiners(final String spec) {
+        if (TextUtils.isEmpty(spec)) {
+            return new Combiner[0];
+        }
+        final String[] combinerDescriptors = spec.split(COMBINER_SPEC_SEPARATOR);
+        final Combiner[] combiners = new Combiner[combinerDescriptors.length];
+        int i = 0;
+        for (final String combinerDescriptor : combinerDescriptors) {
+            final Class combinerClass = IMPLEMENTED_COMBINERS.get(combinerDescriptor);
+            if (null == combinerClass) {
+                throw new RuntimeException("Unknown combiner descriptor: " + combinerDescriptor);
+            }
+            try {
+                combiners[i++] = (Combiner)combinerClass.newInstance();
+            } catch (InstantiationException e) {
+                throw new RuntimeException("Unable to instantiate combiner: " + combinerDescriptor,
+                        e);
+            } catch (IllegalAccessException e) {
+                throw new RuntimeException("Unable to instantiate combiner: " + combinerDescriptor,
+                        e);
+            }
+        }
+        return combiners;
+    }
 }
diff --git a/java/src/com/android/inputmethod/event/MyanmarReordering.java b/java/src/com/android/inputmethod/event/MyanmarReordering.java
new file mode 100644
index 0000000..0831b63
--- /dev/null
+++ b/java/src/com/android/inputmethod/event/MyanmarReordering.java
@@ -0,0 +1,38 @@
+/*
+ * 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.event;
+
+import java.util.ArrayList;
+
+/**
+ * A combiner that reorders input for Myanmar.
+ */
+public class MyanmarReordering implements Combiner {
+    @Override
+    public Event processEvent(ArrayList<Event> previousEvents, Event event) {
+        return event;
+    }
+
+    @Override
+    public CharSequence getCombiningStateFeedback() {
+        return "";
+    }
+
+    @Override
+    public void reset() {
+    }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
index 1cd6ef2..4a46a4a 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
@@ -64,7 +64,7 @@
      * what user actually typed. */
     private boolean mIsAutoCorrectionActive;
 
-    private KeyboardTheme mKeyboardTheme = KeyboardTheme.getDefaultKeyboardTheme();
+    private KeyboardTheme mKeyboardTheme;
     private Context mThemeContext;
 
     private static final KeyboardSwitcher sInstance = new KeyboardSwitcher();
@@ -101,7 +101,7 @@
 
     private boolean updateKeyboardThemeAndContextThemeWrapper(final Context context,
             final KeyboardTheme keyboardTheme) {
-        if (mThemeContext == null || mKeyboardTheme.mThemeId != keyboardTheme.mThemeId) {
+        if (mThemeContext == null || !keyboardTheme.equals(mKeyboardTheme)) {
             mKeyboardTheme = keyboardTheme;
             mThemeContext = new ContextThemeWrapper(context, keyboardTheme.mStyleId);
             KeyboardLayoutSet.clearKeyboardCache();
@@ -342,7 +342,8 @@
             mKeyboardView.closing();
         }
 
-        updateKeyboardThemeAndContextThemeWrapper(mLatinIME, mKeyboardTheme);
+        updateKeyboardThemeAndContextThemeWrapper(
+                mLatinIME, KeyboardTheme.getKeyboardTheme(mPrefs));
         mCurrentInputView = (InputView)LayoutInflater.from(mThemeContext).inflate(
                 R.layout.input_view, null);
         mMainKeyboardFrame = mCurrentInputView.findViewById(R.id.main_keyboard_frame);
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardTheme.java b/java/src/com/android/inputmethod/keyboard/KeyboardTheme.java
index 4db72ad..429c7dd 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardTheme.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardTheme.java
@@ -17,34 +17,85 @@
 package com.android.inputmethod.keyboard;
 
 import android.content.SharedPreferences;
+import android.os.Build;
+import android.os.Build.VERSION_CODES;
 import android.util.Log;
 
 import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.settings.Settings;
+
+import java.util.Arrays;
+import java.util.Comparator;
 
 public final class KeyboardTheme {
     private static final String TAG = KeyboardTheme.class.getSimpleName();
 
-    public static final int THEME_ID_ICS = 0;
-    public static final int THEME_ID_KLP = 2;
-    private static final int DEFAULT_THEME_ID = THEME_ID_KLP;
+    static final String KITKAT_KEYBOARD_THEME_KEY = "pref_keyboard_layout_20110916";
+    static final String KEYBOARD_THEME_KEY = "pref_keyboard_theme_20140509";
+
+    static final int THEME_ID_ICS = 0;
+    static final int THEME_ID_KLP = 2;
+    static final int THEME_ID_LMP = 3;
+    static final int DEFAULT_THEME_ID = THEME_ID_KLP;
 
     private static final KeyboardTheme[] KEYBOARD_THEMES = {
-        new KeyboardTheme(THEME_ID_ICS, R.style.KeyboardTheme_ICS),
-        new KeyboardTheme(THEME_ID_KLP, R.style.KeyboardTheme_KLP),
+        new KeyboardTheme(THEME_ID_ICS, R.style.KeyboardTheme_ICS,
+                VERSION_CODES.ICE_CREAM_SANDWICH),
+        new KeyboardTheme(THEME_ID_KLP, R.style.KeyboardTheme_KLP,
+                VERSION_CODES.KITKAT),
+        new KeyboardTheme(THEME_ID_LMP, R.style.KeyboardTheme_LMP,
+                // TODO: Update this constant once the *next* version becomes available.
+                VERSION_CODES.CUR_DEVELOPMENT),
     };
+    static {
+        // Sort {@link #KEYBOARD_THEME} by descending order of {@link #mMinApiVersion}.
+        Arrays.sort(KEYBOARD_THEMES, new Comparator<KeyboardTheme>() {
+            @Override
+            public int compare(final KeyboardTheme lhs, final KeyboardTheme rhs) {
+                if (lhs.mMinApiVersion > rhs.mMinApiVersion) return -1;
+                if (lhs.mMinApiVersion < rhs.mMinApiVersion) return 1;
+                return 0;
+            }
+        });
+    }
 
     public final int mThemeId;
     public final int mStyleId;
+    final int mMinApiVersion;
 
     // Note: The themeId should be aligned with "themeId" attribute of Keyboard style
-    // in values/style.xml.
-    public KeyboardTheme(final int themeId, final int styleId) {
+    // in values/themes-<style>.xml.
+    private KeyboardTheme(final int themeId, final int styleId, final int minApiVersion) {
         mThemeId = themeId;
         mStyleId = styleId;
+        mMinApiVersion = minApiVersion;
     }
 
-    private static KeyboardTheme searchKeyboardTheme(final int themeId) {
+    @Override
+    public boolean equals(final Object o) {
+        if (o == this) return true;
+        return (o instanceof KeyboardTheme) && ((KeyboardTheme)o).mThemeId == mThemeId;
+    }
+
+    @Override
+    public int hashCode() {
+        return mThemeId;
+    }
+
+    // TODO: This method should be removed when {@link LatinImeLogger} is removed.
+    public int getCompatibleThemeIdForLogging() {
+        switch (mThemeId) {
+        case THEME_ID_ICS:
+            return 5;
+        case THEME_ID_KLP:
+            return 9;
+        case THEME_ID_LMP:
+            return 10;
+        default: // Invalid theme
+            return -1;
+        }
+    }
+
+    private static KeyboardTheme searchKeyboardThemeById(final int themeId) {
         // TODO: This search algorithm isn't optimal if there are many themes.
         for (final KeyboardTheme theme : KEYBOARD_THEMES) {
             if (theme.mThemeId == themeId) {
@@ -54,18 +105,57 @@
         return null;
     }
 
-    public static KeyboardTheme getDefaultKeyboardTheme() {
-        return searchKeyboardTheme(DEFAULT_THEME_ID);
+    private static int getSdkVersion() {
+        final int sdkVersion = Build.VERSION.SDK_INT;
+        // TODO: Consider to remove this check once the *next* version becomes available.
+        if (sdkVersion == VERSION_CODES.KITKAT && Build.VERSION.CODENAME.startsWith("L")) {
+            return VERSION_CODES.CUR_DEVELOPMENT;
+        }
+        return sdkVersion;
+    }
+
+    static KeyboardTheme getDefaultKeyboardTheme(final SharedPreferences prefs,
+            final int sdkVersion) {
+        final String obsoleteIdString = prefs.getString(KITKAT_KEYBOARD_THEME_KEY, null);
+        if (obsoleteIdString != null) {
+            // Remove old preference.
+            prefs.edit().remove(KITKAT_KEYBOARD_THEME_KEY).apply();
+            if (sdkVersion <= VERSION_CODES.KITKAT) {
+                try {
+                    final int themeId = Integer.parseInt(obsoleteIdString);
+                    final KeyboardTheme theme = searchKeyboardThemeById(themeId);
+                    if (theme != null) {
+                        return theme;
+                    }
+                    Log.w(TAG, "Unknown keyboard theme in preference: " + obsoleteIdString);
+                } catch (final NumberFormatException e) {
+                    Log.w(TAG, "Illegal keyboard theme in preference: " + obsoleteIdString);
+                }
+            }
+        }
+        // TODO: This search algorithm isn't optimal if there are many themes.
+        for (final KeyboardTheme theme : KEYBOARD_THEMES) {
+            if (sdkVersion >= theme.mMinApiVersion) {
+                return theme;
+            }
+        }
+        return searchKeyboardThemeById(DEFAULT_THEME_ID);
+    }
+
+    public static void saveKeyboardThemeId(final String themeIdString,
+            final SharedPreferences prefs) {
+        prefs.edit().putString(KEYBOARD_THEME_KEY, themeIdString).apply();
     }
 
     public static KeyboardTheme getKeyboardTheme(final SharedPreferences prefs) {
-        final String themeIdString = prefs.getString(Settings.PREF_KEYBOARD_LAYOUT, null);
+        final int sdkVersion = getSdkVersion();
+        final String themeIdString = prefs.getString(KEYBOARD_THEME_KEY, null);
         if (themeIdString == null) {
-            return getDefaultKeyboardTheme();
+            return getDefaultKeyboardTheme(prefs, sdkVersion);
         }
         try {
             final int themeId = Integer.parseInt(themeIdString);
-            final KeyboardTheme theme = searchKeyboardTheme(themeId);
+            final KeyboardTheme theme = searchKeyboardThemeById(themeId);
             if (theme != null) {
                 return theme;
             }
@@ -73,9 +163,8 @@
         } catch (final NumberFormatException e) {
             Log.w(TAG, "Illegal keyboard theme in preference: " + themeIdString);
         }
-        // Reset preference to default value.
-        final String defaultThemeIdString = Integer.toString(DEFAULT_THEME_ID);
-        prefs.edit().putString(Settings.PREF_KEYBOARD_LAYOUT, defaultThemeIdString).apply();
-        return getDefaultKeyboardTheme();
+        // Remove preference.
+        prefs.edit().remove(KEYBOARD_THEME_KEY).apply();
+        return getDefaultKeyboardTheme(prefs, sdkVersion);
     }
 }
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsTable.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsTable.java
index 0f9645e..7e6181a 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsTable.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsTable.java
@@ -1067,33 +1067,33 @@
         // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
         // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON
         /* morekeys_a */ "\u00E0,\u00E1,\u00E2,\u00E4,\u00E6,\u00E3,\u00E5,\u0101",
+        // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
         // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
         // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
         // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE
-        // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
         // U+0153: "œ" LATIN SMALL LIGATURE OE
         // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
         // U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
         // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
-        /* morekeys_o */ "\u00F4,\u00F6,\u00F2,\u00F3,\u0153,\u00F8,\u014D,\u00F5",
+        /* morekeys_o */ "\u00F3,\u00F4,\u00F6,\u00F2,\u0153,\u00F8,\u014D,\u00F5",
+        // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
         // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
         // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
         // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
-        // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
         // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
-        /* morekeys_u */ "\u00FB,\u00FC,\u00F9,\u00FA,\u016B",
-        // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
+        /* morekeys_u */ "\u00FA,\u00FB,\u00FC,\u00F9,\u016B",
         // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
+        // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
         // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
         // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
         // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON
-        /* morekeys_e */ "\u00E8,\u00E9,\u00EA,\u00EB,\u0113",
+        /* morekeys_e */ "\u00E9,\u00E8,\u00EA,\u00EB,\u0113",
+        // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
         // U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
         // U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
-        // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
         // U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
         // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
-        /* morekeys_i */ "\u00EE,\u00EF,\u00ED,\u012B,\u00EC",
+        /* morekeys_i */ "\u00ED,\u00EE,\u00EF,\u012B,\u00EC",
         // U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
         /* morekeys_c */ "\u00E7",
         /* double_quotes */ null,
@@ -3588,33 +3588,33 @@
         // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
         // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON
         /* morekeys_a */ "\u00E0,\u00E1,\u00E2,\u00E4,\u00E6,\u00E3,\u00E5,\u0101",
+        // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
         // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
         // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
         // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE
-        // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
         // U+0153: "œ" LATIN SMALL LIGATURE OE
         // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
         // U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
         // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
-        /* morekeys_o */ "\u00F4,\u00F6,\u00F2,\u00F3,\u0153,\u00F8,\u014D,\u00F5",
+        /* morekeys_o */ "\u00F3,\u00F4,\u00F6,\u00F2,\u0153,\u00F8,\u014D,\u00F5",
+        // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
         // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
         // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
         // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
-        // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
         // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
-        /* morekeys_u */ "\u00FB,\u00FC,\u00F9,\u00FA,\u016B",
-        // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
+        /* morekeys_u */ "\u00FA,\u00FB,\u00FC,\u00F9,\u016B",
         // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
+        // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
         // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
         // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
         // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON
-        /* morekeys_e */ "\u00E8,\u00E9,\u00EA,\u00EB,\u0113",
+        /* morekeys_e */ "\u00E9,\u00E8,\u00EA,\u00EB,\u0113",
+        // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
         // U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
         // U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
-        // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
         // U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
         // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
-        /* morekeys_i */ "\u00EE,\u00EF,\u00ED,\u012B,\u00EC",
+        /* morekeys_i */ "\u00ED,\u00EE,\u00EF,\u012B,\u00EC",
         // U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
         /* morekeys_c */ "\u00E7",
         /* double_quotes */ null,
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
index b88509f..94a1e36 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
@@ -218,6 +218,8 @@
             int bigramProbability);
     private static native String getPropertyNative(long dict, String query);
     private static native boolean isCorruptedNative(long dict);
+    private static native boolean migrateNative(long dict, String dictFilePath,
+            long newFormatVersion);
 
     // TODO: Move native dict into session
     private final void loadDictionary(final String path, final long startOffset,
@@ -371,8 +373,7 @@
         return getProbabilityNative(mNativeDict, codePoints);
     }
 
-    // TODO: Add a batch process version (isValidBigramMultiple?) to avoid excessive numbers of jni
-    // calls when checking for changes in an entire dictionary.
+    @UsedForTesting
     public boolean isValidBigram(final String word0, final String word1) {
         return getBigramProbability(word0, word1) != NOT_A_PROBABILITY;
     }
@@ -533,11 +534,15 @@
             return false;
         }
         final String tmpDictFilePath = mDictFilePath + DICT_FILE_NAME_SUFFIX_FOR_MIGRATION;
-        // TODO: Implement migrateNative(tmpDictFilePath, newFormatVersion).
+        if (!migrateNative(mNativeDict, tmpDictFilePath, newFormatVersion)) {
+            return false;
+        }
         close();
         final File dictFile = new File(mDictFilePath);
         final File tmpDictFile = new File(tmpDictFilePath);
-        FileUtils.deleteRecursively(dictFile);
+        if (!FileUtils.deleteRecursively(dictFile)) {
+            return false;
+        }
         if (!BinaryDictionaryUtils.renameDict(tmpDictFile, dictFile)) {
             return false;
         }
diff --git a/java/src/com/android/inputmethod/latin/Constants.java b/java/src/com/android/inputmethod/latin/Constants.java
index a1973ac..67ca595 100644
--- a/java/src/com/android/inputmethod/latin/Constants.java
+++ b/java/src/com/android/inputmethod/latin/Constants.java
@@ -115,6 +115,11 @@
              */
             public static final String IS_ADDITIONAL_SUBTYPE = "isAdditionalSubtype";
 
+            /**
+             * The subtype extra value used to specify the combining rules.
+             */
+            public static final String COMBINING_RULES = "CombiningRules";
+
             private ExtraValue() {
                 // This utility class is not publicly instantiable.
             }
diff --git a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
index 9bc01a2..e04fcda 100644
--- a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
@@ -31,9 +31,12 @@
 
 import com.android.inputmethod.annotations.UsedForTesting;
 import com.android.inputmethod.latin.personalization.AccountUtils;
+import com.android.inputmethod.latin.utils.CollectionUtils;
+import com.android.inputmethod.latin.utils.ExecutorUtils;
 import com.android.inputmethod.latin.utils.StringUtils;
 
 import java.io.File;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Locale;
 
@@ -60,7 +63,10 @@
     private static final int INDEX_NAME = 1;
 
     /** The number of contacts in the most recent dictionary rebuild. */
-    static private int sContactCountAtLastRebuild = 0;
+    private int mContactCountAtLastRebuild = 0;
+
+    /** The hash code of ArrayList of contacts names in the most recent dictionary rebuild. */
+    private int mHashCodeAtLastRebuild = 0;
 
     private ContentObserver mObserver;
 
@@ -96,7 +102,14 @@
                 new ContentObserver(null) {
                     @Override
                     public void onChange(boolean self) {
-                        setNeedsToReload();
+                        ExecutorUtils.getExecutor("Check Contacts").execute(new Runnable() {
+                            @Override
+                            public void run() {
+                                if (haveContentsChanged()) {
+                                    setNeedsToRecreate();
+                                }
+                            }
+                        });
                     }
                 });
     }
@@ -143,7 +156,7 @@
                 return;
             }
             if (cursor.moveToFirst()) {
-                sContactCountAtLastRebuild = getContactCount();
+                mContactCountAtLastRebuild = getContactCount();
                 addWordsLocked(cursor);
             }
         } catch (final SQLiteException e) {
@@ -167,9 +180,11 @@
 
     private void addWordsLocked(final Cursor cursor) {
         int count = 0;
+        final ArrayList<String> names = CollectionUtils.newArrayList();
         while (!cursor.isAfterLast() && count < MAX_CONTACT_COUNT) {
             String name = cursor.getString(INDEX_NAME);
             if (isValidName(name)) {
+                names.add(name);
                 addNameLocked(name);
                 ++count;
             } else {
@@ -179,6 +194,7 @@
             }
             cursor.moveToNext();
         }
+        mHashCodeAtLastRebuild = names.hashCode();
     }
 
     private int getContactCount() {
@@ -258,8 +274,7 @@
         return end;
     }
 
-    @Override
-    protected boolean haveContentsChanged() {
+    private boolean haveContentsChanged() {
         final long startTime = SystemClock.uptimeMillis();
         final int contactCount = getContactCount();
         if (contactCount > MAX_CONTACT_COUNT) {
@@ -268,9 +283,9 @@
             // TODO: Sort and check only the MAX_CONTACT_COUNT most recent contacts?
             return false;
         }
-        if (contactCount != sContactCountAtLastRebuild) {
+        if (contactCount != mContactCountAtLastRebuild) {
             if (DEBUG) {
-                Log.d(TAG, "Contact count changed: " + sContactCountAtLastRebuild + " to "
+                Log.d(TAG, "Contact count changed: " + mContactCountAtLastRebuild + " to "
                         + contactCount);
             }
             return true;
@@ -283,20 +298,20 @@
         if (null == cursor) {
             return false;
         }
+        final ArrayList<String> names = CollectionUtils.newArrayList();
         try {
             if (cursor.moveToFirst()) {
                 while (!cursor.isAfterLast()) {
                     String name = cursor.getString(INDEX_NAME);
-                    if (isValidName(name) && !isNameInDictionaryLocked(name)) {
-                        if (DEBUG) {
-                            Log.d(TAG, "Contact name missing: " + name + " (runtime = "
-                                    + (SystemClock.uptimeMillis() - startTime) + " ms)");
-                        }
-                        return true;
+                    if (isValidName(name)) {
+                        names.add(name);
                     }
                     cursor.moveToNext();
                 }
             }
+            if (names.hashCode() != mHashCodeAtLastRebuild) {
+                return true;
+            }
         } finally {
             cursor.close();
         }
@@ -313,33 +328,4 @@
         }
         return false;
     }
-
-    /**
-     * Checks if the words in a name are in the current binary dictionary.
-     */
-    private boolean isNameInDictionaryLocked(final String name) {
-        int len = StringUtils.codePointCount(name);
-        String prevWord = null;
-        for (int i = 0; i < len; i++) {
-            if (Character.isLetter(name.codePointAt(i))) {
-                int end = getWordEndPosition(name, len, i);
-                String word = name.substring(i, end);
-                i = end - 1;
-                final int wordLen = StringUtils.codePointCount(word);
-                if (wordLen < MAX_WORD_LENGTH && wordLen > 1) {
-                    if (!TextUtils.isEmpty(prevWord) && mUseFirstLastBigrams) {
-                        if (!isValidBigramLocked(prevWord, word)) {
-                            return false;
-                        }
-                    } else {
-                        if (!isValidWordLocked(word)) {
-                            return false;
-                        }
-                    }
-                    prevWord = word;
-                }
-            }
-        }
-        return true;
-    }
 }
diff --git a/java/src/com/android/inputmethod/latin/Dictionary.java b/java/src/com/android/inputmethod/latin/Dictionary.java
index 0742fbd..cd380db 100644
--- a/java/src/com/android/inputmethod/latin/Dictionary.java
+++ b/java/src/com/android/inputmethod/latin/Dictionary.java
@@ -57,6 +57,8 @@
     public static final String TYPE_USER_HISTORY = "history";
     // Personalization dictionary.
     public static final String TYPE_PERSONALIZATION = "personalization";
+    // Contextual dictionary.
+    public static final String TYPE_CONTEXTUAL = "contextual";
     public final String mDictType;
 
     public Dictionary(final String dictType) {
diff --git a/java/src/com/android/inputmethod/latin/DictionaryFacilitatorForSuggest.java b/java/src/com/android/inputmethod/latin/DictionaryFacilitatorForSuggest.java
index a834902..3babb41 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryFacilitatorForSuggest.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryFacilitatorForSuggest.java
@@ -23,6 +23,7 @@
 import com.android.inputmethod.annotations.UsedForTesting;
 import com.android.inputmethod.keyboard.ProximityInfo;
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
+import com.android.inputmethod.latin.personalization.ContextualDictionary;
 import com.android.inputmethod.latin.personalization.PersonalizationDictionary;
 import com.android.inputmethod.latin.personalization.UserHistoryDictionary;
 import com.android.inputmethod.latin.utils.CollectionUtils;
@@ -63,7 +64,8 @@
                 Dictionary.TYPE_USER_HISTORY,
                 Dictionary.TYPE_PERSONALIZATION,
                 Dictionary.TYPE_USER,
-                Dictionary.TYPE_CONTACTS
+                Dictionary.TYPE_CONTACTS,
+                Dictionary.TYPE_CONTEXTUAL
             };
 
     private static final Map<String, Class<? extends ExpandableBinaryDictionary>>
@@ -74,6 +76,7 @@
         DICT_TYPE_TO_CLASS.put(Dictionary.TYPE_PERSONALIZATION, PersonalizationDictionary.class);
         DICT_TYPE_TO_CLASS.put(Dictionary.TYPE_USER, UserBinaryDictionary.class);
         DICT_TYPE_TO_CLASS.put(Dictionary.TYPE_CONTACTS, ContactsBinaryDictionary.class);
+        DICT_TYPE_TO_CLASS.put(Dictionary.TYPE_CONTEXTUAL, ContextualDictionary.class);
     }
 
     private static final String DICT_FACTORY_METHOD_NAME = "getDictionary";
@@ -201,6 +204,7 @@
         if (usePersonalizedDicts) {
             subDictTypesToUse.add(Dictionary.TYPE_USER_HISTORY);
             subDictTypesToUse.add(Dictionary.TYPE_PERSONALIZATION);
+            subDictTypesToUse.add(Dictionary.TYPE_CONTEXTUAL);
         }
 
         final Dictionary newMainDict;
diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
index c825ca4..6818c15 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
@@ -92,8 +92,8 @@
     /** Indicates whether a task for reloading the dictionary has been scheduled. */
     private final AtomicBoolean mIsReloading;
 
-    /** Indicates whether the current dictionary needs to be reloaded. */
-    private boolean mNeedsToReload;
+    /** Indicates whether the current dictionary needs to be recreated. */
+    private boolean mNeedsToRecreate;
 
     private final ReentrantReadWriteLock mLock;
 
@@ -107,20 +107,14 @@
      */
     protected abstract void loadInitialContentsLocked();
 
-    /**
-     * Indicates that the source dictionary contents have changed and a rebuild of the binary file
-     * is required. If it returns false, the next reload will only read the current binary
-     * dictionary from file.
-     */
-    protected abstract boolean haveContentsChanged();
-
     private boolean matchesExpectedBinaryDictFormatVersionForThisType(final int formatVersion) {
         return formatVersion == FormatSpec.VERSION4;
     }
 
     private boolean needsToMigrateDictionary(final int formatVersion) {
-        // TODO: Check version.
-        return false;
+        // When we bump up the dictionary format version, the old version should be added to here
+        // for supporting migration. Note that native code has to support reading such formats.
+        return formatVersion == FormatSpec.VERSION4_ONLY_FOR_TESTING;
     }
 
     public boolean isValidDictionaryLocked() {
@@ -147,7 +141,7 @@
         mDictFile = getDictFile(context, dictName, dictFile);
         mBinaryDictionary = null;
         mIsReloading = new AtomicBoolean();
-        mNeedsToReload = false;
+        mNeedsToRecreate = false;
         mLock = new ReentrantReadWriteLock();
     }
 
@@ -470,7 +464,10 @@
         }
         if (mBinaryDictionary.isValidDictionary()
                 && needsToMigrateDictionary(mBinaryDictionary.getFormatVersion())) {
-            mBinaryDictionary.migrateTo(DICTIONARY_FORMAT_VERSION);
+            if (!mBinaryDictionary.migrateTo(DICTIONARY_FORMAT_VERSION)) {
+                Log.e(TAG, "Dictionary migration failed: " + mDictName);
+                removeBinaryDictionaryLocked();
+            }
         }
     }
 
@@ -486,11 +483,11 @@
     }
 
     /**
-     * Marks that the dictionary needs to be reloaded.
+     * Marks that the dictionary needs to be recreated.
      *
      */
-    protected void setNeedsToReload() {
-        mNeedsToReload = true;
+    protected void setNeedsToRecreate() {
+        mNeedsToRecreate = true;
     }
 
     /**
@@ -508,7 +505,7 @@
      * Returns whether a dictionary reload is required.
      */
     private boolean isReloadRequired() {
-        return mBinaryDictionary == null || mNeedsToReload;
+        return mBinaryDictionary == null || mNeedsToRecreate;
     }
 
     /**
@@ -520,8 +517,7 @@
                 @Override
                 public void run() {
                     try {
-                        // TODO: Quit checking contents in ExpandableBinaryDictionary.
-                        if (!mDictFile.exists() || (mNeedsToReload && haveContentsChanged())) {
+                        if (!mDictFile.exists() || mNeedsToRecreate) {
                             // If the dictionary file does not exist or contents have been updated,
                             // generate a new one.
                             createNewDictionaryLocked();
@@ -533,12 +529,12 @@
                                     && matchesExpectedBinaryDictFormatVersionForThisType(
                                             mBinaryDictionary.getFormatVersion()))) {
                                 // Binary dictionary or its format version is not valid. Regenerate
-                                // the dictionary file. writeBinaryDictionary will remove the
+                                // the dictionary file. createNewDictionaryLocked will remove the
                                 // existing files if appropriate.
                                 createNewDictionaryLocked();
                             }
                         }
-                        mNeedsToReload = false;
+                        mNeedsToRecreate = false;
                     } finally {
                         mIsReloading.set(false);
                     }
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index d64a1a6..7dc566a 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -733,6 +733,7 @@
         // Note that the calling sequence of onCreate() and onCurrentInputMethodSubtypeChanged()
         // is not guaranteed. It may even be called at the same time on a different thread.
         mSubtypeSwitcher.onSubtypeChanged(subtype);
+        mInputLogic.onSubtypeChanged(SubtypeLocaleUtils.getCombiningRulesExtraValue(subtype));
         loadKeyboard();
     }
 
@@ -808,7 +809,10 @@
 
         // The app calling setText() has the effect of clearing the composing
         // span, so we should reset our state unconditionally, even if restarting is true.
-        mInputLogic.startInput(restarting, editorInfo);
+        // We also tell the input logic about the combining rules for the current subtype, so
+        // it can adjust its combiners if needed.
+        mInputLogic.startInput(restarting, editorInfo,
+                mSubtypeSwitcher.getCombiningRulesExtraValueOfCurrentSubtype());
 
         // Note: the following does a round-trip IPC on the main thread: be careful
         final Locale currentLocale = mSubtypeSwitcher.getCurrentSubtypeLocale();
diff --git a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
index 0211339..c8a2fb2 100644
--- a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
+++ b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
@@ -52,7 +52,6 @@
 
     private /* final */ RichInputMethodManager mRichImm;
     private /* final */ Resources mResources;
-    private /* final */ ConnectivityManager mConnectivityManager;
 
     private final LanguageOnSpacebarHelper mLanguageOnSpacebarHelper =
             new LanguageOnSpacebarHelper();
@@ -111,10 +110,10 @@
         }
         mResources = context.getResources();
         mRichImm = RichInputMethodManager.getInstance();
-        mConnectivityManager = (ConnectivityManager) context.getSystemService(
+        ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(
                 Context.CONNECTIVITY_SERVICE);
 
-        final NetworkInfo info = mConnectivityManager.getActiveNetworkInfo();
+        final NetworkInfo info = connectivityManager.getActiveNetworkInfo();
         mIsNetworkConnected = (info != null && info.isConnected());
 
         onSubtypeChanged(getCurrentSubtype());
@@ -327,4 +326,8 @@
                 + DUMMY_EMOJI_SUBTYPE);
         return DUMMY_EMOJI_SUBTYPE;
     }
+
+    public String getCombiningRulesExtraValueOfCurrentSubtype() {
+        return SubtypeLocaleUtils.getCombiningRulesExtraValue(getCurrentSubtype());
+    }
 }
diff --git a/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java b/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
index e1cda69..c8ffbe4 100644
--- a/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
@@ -98,7 +98,7 @@
             // devices. On older versions of the platform, the hook above will be called instead.
             @Override
             public void onChange(final boolean self, final Uri uri) {
-                setNeedsToReload();
+                setNeedsToRecreate();
             }
         };
         cres.registerContentObserver(Words.CONTENT_URI, true, mObserver);
@@ -272,9 +272,4 @@
             }
         }
     }
-
-    @Override
-    protected boolean haveContentsChanged() {
-        return true;
-    }
 }
diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java
index d755195..cdee496 100644
--- a/java/src/com/android/inputmethod/latin/WordComposer.java
+++ b/java/src/com/android/inputmethod/latin/WordComposer.java
@@ -41,6 +41,7 @@
     public static final int CAPS_MODE_AUTO_SHIFT_LOCKED = 0x7;
 
     private CombinerChain mCombinerChain;
+    private String mCombiningSpec; // Memory so that we don't uselessly recreate the combiner chain
 
     // The list of events that served to compose this string.
     private final ArrayList<Event> mEvents;
@@ -91,6 +92,21 @@
     }
 
     /**
+     * Restart input with a new combining spec.
+     * @param combiningSpec The spec string for combining. This is found in the extra value.
+     */
+    public void restart(final String combiningSpec) {
+        final String nonNullCombiningSpec = null == combiningSpec ? "" : combiningSpec;
+        if (nonNullCombiningSpec.equals(mCombiningSpec)) {
+            mCombinerChain.reset();
+        } else {
+            mCombinerChain = new CombinerChain(CombinerChain.createCombiners(nonNullCombiningSpec));
+            mCombiningSpec = nonNullCombiningSpec;
+        }
+        reset();
+    }
+
+    /**
      * Clear out the keys registered so far.
      */
     public void reset() {
diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
index 127db8d..6a42074 100644
--- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
+++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
@@ -97,6 +97,11 @@
     private boolean mIsAutoCorrectionIndicatorOn;
     private long mDoubleSpacePeriodCountdownStart;
 
+    /**
+     * Create a new instance of the input logic.
+     * @param latinIME the instance of the parent LatinIME. We should remove this when we can.
+     * @param suggestionStripViewAccessor an object to access the suggestion strip view.
+     */
     public InputLogic(final LatinIME latinIME,
             final SuggestionStripViewAccessor suggestionStripViewAccessor) {
         mLatinIME = latinIME;
@@ -117,9 +122,12 @@
      *
      * @param restarting whether input is starting in the same field as before. Unused for now.
      * @param editorInfo the editorInfo associated with the editor.
+     * @param combiningSpec the combining spec string for this subtype
      */
-    public void startInput(final boolean restarting, final EditorInfo editorInfo) {
+    public void startInput(final boolean restarting, final EditorInfo editorInfo,
+            final String combiningSpec) {
         mEnteredText = null;
+        mWordComposer.restart(combiningSpec);
         resetComposingState(true /* alsoResetLastComposedWord */);
         mDeleteCount = 0;
         mSpaceState = SpaceState.NONE;
@@ -138,6 +146,14 @@
     }
 
     /**
+     * Call this when the subtype changes.
+     * @param combiningSpec the spec string for the combining rules
+     */
+    public void onSubtypeChanged(final String combiningSpec) {
+        mWordComposer.restart(combiningSpec);
+    }
+
+    /**
      * Clean up the input logic after input is finished.
      */
     public void finishInput() {
diff --git a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
index f255034..613ff2b 100644
--- a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
+++ b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
@@ -186,7 +186,12 @@
     // From version 4 on, we use version * 100 + revision as a version number. That allows
     // us to change the format during development while having testing devices remove
     // older files with each upgrade, while still having a readable versioning scheme.
+    // When we bump up the dictionary format version, we should update
+    // ExpandableDictionary.needsToMigrateDictionary() and
+    // ExpandableDictionary.matchesExpectedBinaryDictFormatVersionForThisType().
     public static final int VERSION2 = 2;
+    // Dictionary version used for testing.
+    public static final int VERSION4_ONLY_FOR_TESTING = 399;
     public static final int VERSION4 = 401;
     static final int MINIMUM_SUPPORTED_VERSION = VERSION2;
     static final int MAXIMUM_SUPPORTED_VERSION = VERSION4;
diff --git a/java/src/com/android/inputmethod/latin/personalization/ContextualDictionary.java b/java/src/com/android/inputmethod/latin/personalization/ContextualDictionary.java
new file mode 100644
index 0000000..96f03f9
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/personalization/ContextualDictionary.java
@@ -0,0 +1,53 @@
+/*
+ * 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.personalization;
+
+import android.content.Context;
+
+import com.android.inputmethod.annotations.UsedForTesting;
+import com.android.inputmethod.latin.Dictionary;
+import com.android.inputmethod.latin.ExpandableBinaryDictionary;
+
+import java.io.File;
+import java.util.Locale;
+
+public class ContextualDictionary extends ExpandableBinaryDictionary {
+    /* package */ static final String NAME = PersonalizationDictionary.class.getSimpleName();
+
+    private ContextualDictionary(final Context context, final Locale locale,
+            final File dictFile) {
+        super(context, getDictName(NAME, locale, dictFile), locale, Dictionary.TYPE_CONTEXTUAL,
+                dictFile);
+        // Always reset the contents.
+        clear();
+    }
+    @UsedForTesting
+    public static ContextualDictionary getDictionary(final Context context, final Locale locale,
+            final File dictFile) {
+        return new ContextualDictionary(context, locale, dictFile);
+    }
+
+    @Override
+    public boolean isValidWord(final String word) {
+        // Strings out of this dictionary should not be considered existing words.
+        return false;
+    }
+
+    @Override
+    protected void loadInitialContentsLocked() {
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java b/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java
index 38c28a7..06bdba0 100644
--- a/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java
+++ b/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java
@@ -74,11 +74,6 @@
     }
 
     @Override
-    protected boolean haveContentsChanged() {
-        return false;
-    }
-
-    @Override
     protected void loadInitialContentsLocked() {
         // No initial contents.
     }
diff --git a/java/src/com/android/inputmethod/latin/personalization/DictionaryDecayBroadcastReciever.java b/java/src/com/android/inputmethod/latin/personalization/DictionaryDecayBroadcastReciever.java
index de2744f..221bb9a 100644
--- a/java/src/com/android/inputmethod/latin/personalization/DictionaryDecayBroadcastReciever.java
+++ b/java/src/com/android/inputmethod/latin/personalization/DictionaryDecayBroadcastReciever.java
@@ -61,6 +61,7 @@
         final String action = intent.getAction();
         if (action.equals(DICTIONARY_DECAY_INTENT_ACTION)) {
             PersonalizationHelper.runGCOnAllOpenedUserHistoryDictionaries();
+            PersonalizationHelper.runGCOnAllOpenedPersonalizationDictionaries();
         }
     }
 }
diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java
index 7c43182..afacd08 100644
--- a/java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java
+++ b/java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java
@@ -16,7 +16,6 @@
 
 package com.android.inputmethod.latin.personalization;
 
-import com.android.inputmethod.annotations.UsedForTesting;
 import com.android.inputmethod.latin.utils.CollectionUtils;
 import com.android.inputmethod.latin.utils.FileUtils;
 
@@ -66,8 +65,8 @@
         if (TimeUnit.MILLISECONDS.toSeconds(
                 DictionaryDecayBroadcastReciever.DICTIONARY_DECAY_INTERVAL)
                         < currentTimestamp - sCurrentTimestampForTesting) {
-            // TODO: Run GC for both PersonalizationDictionary and UserHistoryDictionary.
             runGCOnAllOpenedUserHistoryDictionaries();
+            runGCOnAllOpenedPersonalizationDictionaries();
         }
     }
 
@@ -75,7 +74,6 @@
         runGCOnAllDictionariesIfRequired(sLangUserHistoryDictCache);
     }
 
-    @UsedForTesting
     public static void runGCOnAllOpenedPersonalizationDictionaries() {
         runGCOnAllDictionariesIfRequired(sLangPersonalizationDictCache);
     }
diff --git a/java/src/com/android/inputmethod/latin/settings/Settings.java b/java/src/com/android/inputmethod/latin/settings/Settings.java
index a3aae8c..4e4c888 100644
--- a/java/src/com/android/inputmethod/latin/settings/Settings.java
+++ b/java/src/com/android/inputmethod/latin/settings/Settings.java
@@ -64,7 +64,7 @@
             "pref_show_language_switch_key";
     public static final String PREF_INCLUDE_OTHER_IMES_IN_LANGUAGE_SWITCH_LIST =
             "pref_include_other_imes_in_language_switch_list";
-    public static final String PREF_KEYBOARD_LAYOUT = "pref_keyboard_layout_20110916";
+    public static final String PREF_KEYBOARD_THEME = "pref_keyboard_theme";
     public static final String PREF_CUSTOM_INPUT_STYLES = "custom_input_styles";
     // TODO: consolidate key preview dismiss delay with the key preview animation parameters.
     public static final String PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY =
diff --git a/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java b/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java
index 22cbd20..e1d38e7 100644
--- a/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java
+++ b/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java
@@ -37,6 +37,7 @@
 import android.view.inputmethod.InputMethodSubtype;
 
 import com.android.inputmethod.dictionarypack.DictionarySettingsActivity;
+import com.android.inputmethod.keyboard.KeyboardTheme;
 import com.android.inputmethod.latin.AudioAndHapticFeedbackManager;
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.SubtypeSwitcher;
@@ -253,11 +254,31 @@
         }
         updateListPreferenceSummaryToCurrentValue(Settings.PREF_SHOW_SUGGESTIONS_SETTING);
         updateListPreferenceSummaryToCurrentValue(Settings.PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY);
-        updateListPreferenceSummaryToCurrentValue(Settings.PREF_KEYBOARD_LAYOUT);
+        final ListPreference keyboardThemePref = (ListPreference)findPreference(
+                Settings.PREF_KEYBOARD_THEME);
+        if (keyboardThemePref != null) {
+            final KeyboardTheme keyboardTheme = KeyboardTheme.getKeyboardTheme(prefs);
+            final String value = Integer.toString(keyboardTheme.mThemeId);
+            final CharSequence entries[] = keyboardThemePref.getEntries();
+            final int entryIndex = keyboardThemePref.findIndexOfValue(value);
+            keyboardThemePref.setSummary(entryIndex < 0 ? null : entries[entryIndex]);
+            keyboardThemePref.setValue(value);
+        }
         updateCustomInputStylesSummary(prefs, res);
     }
 
     @Override
+    public void onPause() {
+        super.onPause();
+        final SharedPreferences prefs = getPreferenceManager().getSharedPreferences();
+        final ListPreference keyboardThemePref = (ListPreference)findPreference(
+                Settings.PREF_KEYBOARD_THEME);
+        if (keyboardThemePref != null) {
+            KeyboardTheme.saveKeyboardThemeId(keyboardThemePref.getValue(), prefs);
+        }
+    }
+
+    @Override
     public void onDestroy() {
         getPreferenceManager().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(
                 this);
@@ -287,7 +308,7 @@
         ensureConsistencyOfAutoCorrectionSettings();
         updateListPreferenceSummaryToCurrentValue(Settings.PREF_SHOW_SUGGESTIONS_SETTING);
         updateListPreferenceSummaryToCurrentValue(Settings.PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY);
-        updateListPreferenceSummaryToCurrentValue(Settings.PREF_KEYBOARD_LAYOUT);
+        updateListPreferenceSummaryToCurrentValue(Settings.PREF_KEYBOARD_THEME);
         refreshEnablingsOfKeypressSoundAndVibrationSettings(prefs, getResources());
     }
 
diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
index a578fa4..d4f7f36 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
@@ -18,7 +18,9 @@
 
 import android.content.Context;
 import android.content.res.Resources;
+import android.content.res.TypedArray;
 import android.graphics.Color;
+import android.graphics.drawable.Drawable;
 import android.support.v4.view.ViewCompat;
 import android.text.TextUtils;
 import android.util.AttributeSet;
@@ -31,6 +33,7 @@
 import android.view.View.OnLongClickListener;
 import android.view.ViewGroup;
 import android.view.ViewParent;
+import android.widget.ImageButton;
 import android.widget.RelativeLayout;
 import android.widget.TextView;
 
@@ -59,12 +62,14 @@
         public void addWordToUserDictionary(String word);
         public void showImportantNoticeContents();
         public void pickSuggestionManually(int index, SuggestedWordInfo word);
+        public void onCodeInput(int primaryCode, int x, int y, boolean isKeyRepeat);
     }
 
     static final boolean DBG = LatinImeLogger.sDBG;
     private static final float DEBUG_INFO_TEXT_SIZE_IN_DIP = 6.0f;
 
     private final ViewGroup mSuggestionsStrip;
+    private final ImageButton mVoiceKey;
     private final ViewGroup mAddToDictionaryStrip;
     private final View mImportantNoticeStrip;
     MainKeyboardView mMainKeyboardView;
@@ -86,39 +91,42 @@
 
     private static class StripVisibilityGroup {
         private final View mSuggestionsStrip;
+        private final View mVoiceKey;
         private final View mAddToDictionaryStrip;
         private final View mImportantNoticeStrip;
 
-        public StripVisibilityGroup(final View suggestionsStrip, final View addToDictionaryStrip,
-                final View importantNoticeStrip) {
+        public StripVisibilityGroup(final View suggestionsStrip, final View voiceKey,
+                final View addToDictionaryStrip, final View importantNoticeStrip) {
             mSuggestionsStrip = suggestionsStrip;
+            mVoiceKey = voiceKey;
             mAddToDictionaryStrip = addToDictionaryStrip;
             mImportantNoticeStrip = importantNoticeStrip;
-            showSuggestionsStrip();
+            showSuggestionsStrip(false /* voiceKeyEnabled */);
         }
 
-        public void setLayoutDirection(final boolean isRtlLanguage) {
-            final int layoutDirection = isRtlLanguage ? ViewCompat.LAYOUT_DIRECTION_RTL
-                    : ViewCompat.LAYOUT_DIRECTION_LTR;
+        public void setLayoutDirection(final int layoutDirection) {
             ViewCompat.setLayoutDirection(mSuggestionsStrip, layoutDirection);
             ViewCompat.setLayoutDirection(mAddToDictionaryStrip, layoutDirection);
             ViewCompat.setLayoutDirection(mImportantNoticeStrip, layoutDirection);
         }
 
-        public void showSuggestionsStrip() {
+        public void showSuggestionsStrip(final boolean enableVoiceKey) {
             mSuggestionsStrip.setVisibility(VISIBLE);
+            mVoiceKey.setVisibility(enableVoiceKey ? VISIBLE : INVISIBLE);
             mAddToDictionaryStrip.setVisibility(INVISIBLE);
             mImportantNoticeStrip.setVisibility(INVISIBLE);
         }
 
         public void showAddToDictionaryStrip() {
             mSuggestionsStrip.setVisibility(INVISIBLE);
+            mVoiceKey.setVisibility(INVISIBLE);
             mAddToDictionaryStrip.setVisibility(VISIBLE);
             mImportantNoticeStrip.setVisibility(INVISIBLE);
         }
 
         public void showImportantNoticeStrip() {
             mSuggestionsStrip.setVisibility(INVISIBLE);
+            mVoiceKey.setVisibility(INVISIBLE);
             mAddToDictionaryStrip.setVisibility(INVISIBLE);
             mImportantNoticeStrip.setVisibility(VISIBLE);
         }
@@ -145,10 +153,11 @@
         inflater.inflate(R.layout.suggestions_strip, this);
 
         mSuggestionsStrip = (ViewGroup)findViewById(R.id.suggestions_strip);
+        mVoiceKey = (ImageButton)findViewById(R.id.suggestions_strip_voice_key);
         mAddToDictionaryStrip = (ViewGroup)findViewById(R.id.add_to_dictionary_strip);
         mImportantNoticeStrip = findViewById(R.id.important_notice_strip);
-        mStripVisibilityGroup = new StripVisibilityGroup(mSuggestionsStrip, mAddToDictionaryStrip,
-                mImportantNoticeStrip);
+        mStripVisibilityGroup = new StripVisibilityGroup(mSuggestionsStrip, mVoiceKey,
+                mAddToDictionaryStrip, mImportantNoticeStrip);
 
         for (int pos = 0; pos < SuggestedWords.MAX_SUGGESTIONS; pos++) {
             final TextView word = new TextView(context, null, R.attr.suggestionWordStyle);
@@ -177,6 +186,13 @@
                 R.dimen.config_more_suggestions_modal_tolerance);
         mMoreSuggestionsSlidingDetector = new GestureDetector(
                 context, mMoreSuggestionsSlidingListener);
+
+        final TypedArray keyboardAttr = context.obtainStyledAttributes(attrs,
+                R.styleable.Keyboard, defStyle, R.style.SuggestionStripView);
+        final Drawable iconVoice = keyboardAttr.getDrawable(R.styleable.Keyboard_iconShortcutKey);
+        keyboardAttr.recycle();
+        mVoiceKey.setImageDrawable(iconVoice);
+        mVoiceKey.setOnClickListener(this);
     }
 
     /**
@@ -188,16 +204,30 @@
         mMainKeyboardView = (MainKeyboardView)inputView.findViewById(R.id.keyboard_view);
     }
 
+    private boolean isVoiceKeyEnabled() {
+        if (mMainKeyboardView == null) {
+            return false;
+        }
+        final Keyboard keyboard = mMainKeyboardView.getKeyboard();
+        if (keyboard == null) {
+            return false;
+        }
+        return keyboard.mId.mHasShortcutKey;
+    }
+
     public void setSuggestions(final SuggestedWords suggestedWords, final boolean isRtlLanguage) {
         clear();
-        mStripVisibilityGroup.setLayoutDirection(isRtlLanguage);
+        final int layoutDirection = isRtlLanguage ? ViewCompat.LAYOUT_DIRECTION_RTL
+                : ViewCompat.LAYOUT_DIRECTION_LTR;
+        setLayoutDirection(layoutDirection);
+        mStripVisibilityGroup.setLayoutDirection(layoutDirection);
         mSuggestedWords = suggestedWords;
         mSuggestionsCountInStrip = mLayoutHelper.layoutAndReturnSuggestionCountInStrip(
                 mSuggestedWords, mSuggestionsStrip, this);
         if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
             ResearchLogger.suggestionStripView_setSuggestions(mSuggestedWords);
         }
-        mStripVisibilityGroup.showSuggestionsStrip();
+        mStripVisibilityGroup.showSuggestionsStrip(isVoiceKeyEnabled());
     }
 
     public int setMoreSuggestionsHeight(final int remainingHeight) {
@@ -252,7 +282,7 @@
     public void clear() {
         mSuggestionsStrip.removeAllViews();
         removeAllDebugInfoViews();
-        mStripVisibilityGroup.showSuggestionsStrip();
+        mStripVisibilityGroup.showSuggestionsStrip(false /* enableVoiceKey */);
         dismissMoreSuggestionsPanel();
     }
 
@@ -415,6 +445,12 @@
             mListener.showImportantNoticeContents();
             return;
         }
+        if (view == mVoiceKey) {
+            mListener.onCodeInput(Constants.CODE_SHORTCUT,
+                    Constants.SUGGESTION_STRIP_COORDINATE, Constants.SUGGESTION_STRIP_COORDINATE,
+                    false /* isKeyRepeat */);
+            return;
+        }
         final Object tag = view.getTag();
         // {@link String} tag is set at {@link #showAddToDictionaryHint(String,CharSequence)}.
         if (tag instanceof String) {
diff --git a/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java b/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java
index b37779b..938d271 100644
--- a/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java
@@ -324,4 +324,8 @@
     public static boolean isRtlLanguage(final InputMethodSubtype subtype) {
         return isRtlLanguage(getSubtypeLocale(subtype));
     }
+
+    public static String getCombiningRulesExtraValue(final InputMethodSubtype subtype) {
+        return subtype.getExtraValueOf(Constants.Subtype.ExtraValue.COMBINING_RULES);
+    }
 }
diff --git a/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
index 9016cae..a3d8ec1 100644
--- a/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
+++ b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
@@ -32,6 +32,7 @@
 #include "suggest/policyimpl/dictionary/structure/dictionary_structure_with_buffer_policy_factory.h"
 #include "utils/char_utils.h"
 #include "utils/jni_data_utils.h"
+#include "utils/log_utils.h"
 #include "utils/time_keeper.h"
 
 namespace latinime {
@@ -334,8 +335,9 @@
     if (!shortcutTargetCodePoints.empty()) {
         shortcuts.emplace_back(&shortcutTargetCodePoints, shortcutProbability);
     }
+    // Use 1 for count to indicate the word has inputed.
     const UnigramProperty unigramProperty(isNotAWord, isBlacklisted,
-            probability, timestamp, 0 /* level */, 0 /* count */, &shortcuts);
+            probability, timestamp, 0 /* level */, 1 /* count */, &shortcuts);
     dictionary->addUnigramWord(codePoints, codePointCount, &unigramProperty);
 }
 
@@ -435,8 +437,9 @@
                     env->GetIntField(languageModelParam, shortcutProbabilityFieldId);
             shortcuts.emplace_back(&shortcutTargetCodePoints, shortcutProbability);
         }
+        // Use 1 for count to indicate the word has inputed.
         const UnigramProperty unigramProperty(isNotAWord, isBlacklisted,
-                unigramProbability, timestamp, 0 /* level */, 0 /* count */, &shortcuts);
+                unigramProbability, timestamp, 0 /* level */, 1 /* count */, &shortcuts);
         dictionary->addUnigramWord(word1CodePoints, word1Length, &unigramProperty);
         if (word0) {
             jint bigramProbability = env->GetIntField(languageModelParam, bigramProbabilityFieldId);
@@ -489,6 +492,87 @@
     return dictionary->getDictionaryStructurePolicy()->isCorrupted();
 }
 
+static DictionaryStructureWithBufferPolicy::StructurePolicyPtr runGCAndGetNewStructurePolicy(
+        DictionaryStructureWithBufferPolicy::StructurePolicyPtr structurePolicy,
+        const char *const dictFilePath) {
+    structurePolicy->flushWithGC(dictFilePath);
+    structurePolicy.release();
+    return DictionaryStructureWithBufferPolicyFactory::newPolicyForExistingDictFile(
+            dictFilePath, 0 /* offset */, 0 /* size */, true /* isUpdatable */);
+}
+
+static bool latinime_BinaryDictionary_migrateNative(JNIEnv *env, jclass clazz, jlong dict,
+        jstring dictFilePath, jlong newFormatVersion) {
+    Dictionary *dictionary = reinterpret_cast<Dictionary *>(dict);
+    if (!dictionary) {
+        return false;
+    }
+    const jsize filePathUtf8Length = env->GetStringUTFLength(dictFilePath);
+    char dictFilePathChars[filePathUtf8Length + 1];
+    env->GetStringUTFRegion(dictFilePath, 0, env->GetStringLength(dictFilePath), dictFilePathChars);
+    dictFilePathChars[filePathUtf8Length] = '\0';
+
+    const DictionaryHeaderStructurePolicy *const headerPolicy =
+            dictionary->getDictionaryStructurePolicy()->getHeaderStructurePolicy();
+    DictionaryStructureWithBufferPolicy::StructurePolicyPtr dictionaryStructureWithBufferPolicy =
+            DictionaryStructureWithBufferPolicyFactory::newPolicyForOnMemoryDict(
+                    newFormatVersion, *headerPolicy->getLocale(), headerPolicy->getAttributeMap());
+    if (!dictionaryStructureWithBufferPolicy) {
+        LogUtils::logToJava(env, "Cannot migrate header.");
+        return false;
+    }
+
+    // TODO: Migrate historical information.
+    int wordCodePoints[MAX_WORD_LENGTH];
+    int token = 0;
+    // Add unigrams.
+    do {
+        token = dictionary->getNextWordAndNextToken(token, wordCodePoints);
+        const int wordLength = CharUtils::getCodePointCount(MAX_WORD_LENGTH, wordCodePoints);
+        const WordProperty wordProperty = dictionary->getWordProperty(wordCodePoints, wordLength);
+        if (dictionaryStructureWithBufferPolicy->needsToRunGC(true /* mindsBlockByGC */)) {
+            dictionaryStructureWithBufferPolicy = runGCAndGetNewStructurePolicy(
+                    std::move(dictionaryStructureWithBufferPolicy), dictFilePathChars);
+            if (!dictionaryStructureWithBufferPolicy) {
+                LogUtils::logToJava(env, "Cannot open dict after GC.");
+                return false;
+            }
+        }
+        if (!dictionaryStructureWithBufferPolicy->addUnigramWord(wordCodePoints, wordLength,
+                wordProperty.getUnigramProperty())) {
+            LogUtils::logToJava(env, "Cannot add unigram to the new dict.");
+            return false;
+        }
+    } while (token != 0);
+
+    // Add bigrams.
+    do {
+        token = dictionary->getNextWordAndNextToken(token, wordCodePoints);
+        const int wordLength = CharUtils::getCodePointCount(MAX_WORD_LENGTH, wordCodePoints);
+        const WordProperty wordProperty = dictionary->getWordProperty(wordCodePoints, wordLength);
+        if (dictionaryStructureWithBufferPolicy->needsToRunGC(true /* mindsBlockByGC */)) {
+            dictionaryStructureWithBufferPolicy = runGCAndGetNewStructurePolicy(
+                    std::move(dictionaryStructureWithBufferPolicy), dictFilePathChars);
+            if (!dictionaryStructureWithBufferPolicy) {
+                LogUtils::logToJava(env, "Cannot open dict after GC.");
+                return false;
+            }
+        }
+        for (const BigramProperty &bigarmProperty : *wordProperty.getBigramProperties()) {
+            const std::vector<int> *targetCodePoints = bigarmProperty.getTargetCodePoints();
+            if (!dictionaryStructureWithBufferPolicy->addBigramWords(wordCodePoints, wordLength,
+                    targetCodePoints->data(), targetCodePoints->size(),
+                    bigarmProperty.getProbability(), bigarmProperty.getTimestamp())) {
+                LogUtils::logToJava(env, "Cannot add bigram to the new dict.");
+                return false;
+            }
+        }
+    } while (token != 0);
+    // Save to File.
+    dictionaryStructureWithBufferPolicy->flushWithGC(dictFilePathChars);
+    return true;
+}
+
 static const JNINativeMethod sMethods[] = {
     {
         const_cast<char *>("openNative"),
@@ -591,6 +675,11 @@
         const_cast<char *>("isCorruptedNative"),
         const_cast<char *>("(J)Z"),
         reinterpret_cast<void *>(latinime_BinaryDictionary_isCorruptedNative)
+    },
+    {
+        const_cast<char *>("migrateNative"),
+        const_cast<char *>("(JLjava/lang/String;J)Z"),
+        reinterpret_cast<void *>(latinime_BinaryDictionary_migrateNative)
     }
 };
 
diff --git a/native/jni/src/suggest/core/dictionary/property/word_property.h b/native/jni/src/suggest/core/dictionary/property/word_property.h
index 5519a91..aa3e0b6 100644
--- a/native/jni/src/suggest/core/dictionary/property/word_property.h
+++ b/native/jni/src/suggest/core/dictionary/property/word_property.h
@@ -42,6 +42,14 @@
             jintArray outProbabilityInfo, jobject outBigramTargets, jobject outBigramProbabilities,
             jobject outShortcutTargets, jobject outShortcutProbabilities) const;
 
+    const UnigramProperty *getUnigramProperty() const {
+        return &mUnigramProperty;
+    }
+
+    const std::vector<BigramProperty> *getBigramProperties() const {
+        return &mBigrams;
+    }
+
  private:
     // Default copy constructor is used for using as a return value.
     DISALLOW_ASSIGNMENT_OPERATOR(WordProperty);
diff --git a/native/jni/src/suggest/core/policy/dictionary_header_structure_policy.h b/native/jni/src/suggest/core/policy/dictionary_header_structure_policy.h
index a8dab9f..845e629 100644
--- a/native/jni/src/suggest/core/policy/dictionary_header_structure_policy.h
+++ b/native/jni/src/suggest/core/policy/dictionary_header_structure_policy.h
@@ -49,6 +49,8 @@
 
     virtual bool shouldBoostExactMatches() const = 0;
 
+    virtual const std::vector<int> *getLocale() const = 0;
+
  protected:
     DictionaryHeaderStructurePolicy() {}
 
diff --git a/native/jni/src/suggest/policyimpl/dictionary/bigram/ver4_bigram_list_policy.cpp b/native/jni/src/suggest/policyimpl/dictionary/bigram/ver4_bigram_list_policy.cpp
index 04e768f..1645039 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/bigram/ver4_bigram_list_policy.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/bigram/ver4_bigram_list_policy.cpp
@@ -50,12 +50,18 @@
 
 bool Ver4BigramListPolicy::addNewEntry(const int terminalId, const int newTargetTerminalId,
         const int newProbability, const int timestamp, 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;
     }
     const int bigramListPos = mBigramDictContent->getBigramListHeadPos(terminalId);
     if (bigramListPos == NOT_A_DICT_POS) {
-        // Updating PtNode that doesn't have a bigram list.
+        // Case 1. PtNode that doesn't have a bigram list.
         // Create new bigram list.
         if (!mBigramDictContent->createNewBigramList(terminalId)) {
             return false;
@@ -75,50 +81,55 @@
         return true;
     }
 
-    const int entryPosToUpdate = getEntryPosToUpdate(newTargetTerminalId, bigramListPos);
-    if (entryPosToUpdate != NOT_A_DICT_POS) {
-        // Overwrite existing entry.
-        const BigramEntry originalBigramEntry =
-                mBigramDictContent->getBigramEntry(entryPosToUpdate);
-        if (!originalBigramEntry.isValid()) {
-            // Reuse invalid entry.
-            if (outAddedNewEntry) {
-                *outAddedNewEntry = true;
+    int tailEntryPos = NOT_A_DICT_POS;
+    const int entryPosToUpdate = getEntryPosToUpdate(newTargetTerminalId, bigramListPos,
+            &tailEntryPos);
+    if (tailEntryPos != NOT_A_DICT_POS || entryPosToUpdate == NOT_A_DICT_POS) {
+        // Case 4, 5.
+        // Add new entry to the bigram list.
+        if (tailEntryPos == NOT_A_DICT_POS) {
+            // Case 4. Create new bigram list.
+            if (!mBigramDictContent->createNewBigramList(terminalId)) {
+                return false;
+            }
+            const int destPos = mBigramDictContent->getBigramListHeadPos(terminalId);
+            // Copy existing bigram list.
+            if (!mBigramDictContent->copyBigramList(bigramListPos, destPos, &tailEntryPos)) {
+                return false;
             }
         }
-        const BigramEntry updatedBigramEntry =
-                originalBigramEntry.updateTargetTerminalIdAndGetEntry(newTargetTerminalId);
+        // Write new entry at the tail position of the bigram content.
+        const BigramEntry newBigramEntry(false /* hasNext */, NOT_A_PROBABILITY,
+                newTargetTerminalId);
         const BigramEntry bigramEntryToWrite = createUpdatedBigramEntryFrom(
-                &updatedBigramEntry, newProbability, timestamp);
-        return mBigramDictContent->writeBigramEntry(&bigramEntryToWrite, entryPosToUpdate);
+                &newBigramEntry, newProbability, timestamp);
+        if (!mBigramDictContent->writeBigramEntryAtTail(&bigramEntryToWrite)) {
+            return false;
+        }
+        // Update has next flag of the tail entry.
+        if (!updateHasNextFlag(true /* hasNext */, tailEntryPos)) {
+            return false;
+        }
+        if (outAddedNewEntry) {
+            *outAddedNewEntry = true;
+        }
+        return true;
     }
 
-    // Add new entry to the bigram list.
-    // Create new bigram list.
-    if (!mBigramDictContent->createNewBigramList(terminalId)) {
-        return false;
+    // Case 2. Overwrite the existing entry. Case 3. Reclaim and reuse the existing invalid entry.
+    const BigramEntry originalBigramEntry = mBigramDictContent->getBigramEntry(entryPosToUpdate);
+    if (!originalBigramEntry.isValid()) {
+        // Case 3. Reuse the existing invalid entry. outAddedNewEntry is false when an existing
+        // entry is updated.
+        if (outAddedNewEntry) {
+            *outAddedNewEntry = true;
+        }
     }
-    int writingPos = mBigramDictContent->getBigramListHeadPos(terminalId);
-    int tailEntryPos = NOT_A_DICT_POS;
-    // Copy existing bigram list.
-    if (!mBigramDictContent->copyBigramList(bigramListPos, writingPos, &tailEntryPos)) {
-        return false;
-    }
-    // Write new entry at the tail position of the bigram content.
-    const BigramEntry newBigramEntry(false /* hasNext */, NOT_A_PROBABILITY, newTargetTerminalId);
+    const BigramEntry updatedBigramEntry =
+            originalBigramEntry.updateTargetTerminalIdAndGetEntry(newTargetTerminalId);
     const BigramEntry bigramEntryToWrite = createUpdatedBigramEntryFrom(
-            &newBigramEntry, newProbability, timestamp);
-    if (!mBigramDictContent->writeBigramEntryAtTail(&bigramEntryToWrite)) {
-        return false;
-    }
-    // Update has next flag of the tail entry.
-    if (!updateHasNextFlag(true /* hasNext */, tailEntryPos)) {
-        return false;
-    }
-    if (outAddedNewEntry) {
-        *outAddedNewEntry = true;
-    }
-    return true;
+            &updatedBigramEntry, newProbability, timestamp);
+    return mBigramDictContent->writeBigramEntry(&bigramEntryToWrite, entryPosToUpdate);
 }
 
 bool Ver4BigramListPolicy::removeEntry(const int terminalId, const int targetTerminalId) {
@@ -127,7 +138,8 @@
         // Bigram list doesn't exist.
         return false;
     }
-    const int entryPosToUpdate = getEntryPosToUpdate(targetTerminalId, bigramListPos);
+    const int entryPosToUpdate = getEntryPosToUpdate(targetTerminalId, bigramListPos,
+            nullptr /* outTailEntryPos */);
     if (entryPosToUpdate == NOT_A_DICT_POS) {
         // Bigram entry doesn't exist.
         return false;
@@ -212,7 +224,10 @@
 }
 
 int Ver4BigramListPolicy::getEntryPosToUpdate(const int targetTerminalIdToFind,
-        const int bigramListPos) const {
+        const int bigramListPos, int *const outTailEntryPos) const {
+    if (outTailEntryPos) {
+        *outTailEntryPos = NOT_A_DICT_POS;
+    }
     bool hasNext = true;
     int invalidEntryPos = NOT_A_DICT_POS;
     int readingPos = bigramListPos;
@@ -228,6 +243,11 @@
             // Invalid entry that can be reused is found.
             invalidEntryPos = entryPos;
         }
+        if (!hasNext && mBigramDictContent->isContentTailPos(readingPos)) {
+            if (outTailEntryPos) {
+                *outTailEntryPos = entryPos;
+            }
+        }
     }
     return invalidEntryPos;
 }
@@ -237,10 +257,12 @@
         const int timestamp) 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 updatedHistoricalInfo =
                 ForgettingCurveUtils::createUpdatedHistoricalInfo(
-                        originalBigramEntry->getHistoricalInfo(), newProbability, timestamp,
-                        mHeaderPolicy);
+                        originalBigramEntry->getHistoricalInfo(), newProbability,
+                        &historicalInfoForUpdate, mHeaderPolicy);
         return originalBigramEntry->updateHistoricalInfoAndGetEntry(&updatedHistoricalInfo);
     } else {
         return originalBigramEntry->updateProbabilityAndGetEntry(newProbability);
diff --git a/native/jni/src/suggest/policyimpl/dictionary/bigram/ver4_bigram_list_policy.h b/native/jni/src/suggest/policyimpl/dictionary/bigram/ver4_bigram_list_policy.h
index d8f7be6..c1f3335 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/bigram/ver4_bigram_list_policy.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/bigram/ver4_bigram_list_policy.h
@@ -56,7 +56,8 @@
  private:
     DISALLOW_IMPLICIT_CONSTRUCTORS(Ver4BigramListPolicy);
 
-    int getEntryPosToUpdate(const int targetTerminalIdToFind, const int bigramListPos) const;
+    int getEntryPosToUpdate(const int targetTerminalIdToFind, const int bigramListPos,
+            int *const outTailEntryPos) const;
 
     const BigramEntry createUpdatedBigramEntryFrom(const BigramEntry *const originalBigramEntry,
             const int newProbability, const int timestamp) const;
diff --git a/native/jni/src/suggest/policyimpl/dictionary/header/header_policy.h b/native/jni/src/suggest/policyimpl/dictionary/header/header_policy.h
index 251a719..da24302 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/header/header_policy.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/header/header_policy.h
@@ -139,6 +139,8 @@
         switch (mDictFormatVersion) {
             case FormatUtils::VERSION_2:
                 return FormatUtils::VERSION_2;
+            case FormatUtils::VERSION_4_ONLY_FOR_TESTING:
+                return FormatUtils::VERSION_4_ONLY_FOR_TESTING;
             case FormatUtils::VERSION_4:
                 return FormatUtils::VERSION_4;
             default:
@@ -238,6 +240,10 @@
             const int unigramCount, const int bigramCount, const int extendedRegionSize,
             DictionaryHeaderStructurePolicy::AttributeMap *outAttributeMap) const;
 
+    AK_FORCE_INLINE const std::vector<int> *getLocale() const {
+        return &mLocale;
+    }
+
  private:
     DISALLOW_COPY_AND_ASSIGN(HeaderPolicy);
 
diff --git a/native/jni/src/suggest/policyimpl/dictionary/header/header_read_write_utils.cpp b/native/jni/src/suggest/policyimpl/dictionary/header/header_read_write_utils.cpp
index 5608e27..2a9028a 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/header/header_read_write_utils.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/header/header_read_write_utils.cpp
@@ -98,8 +98,9 @@
         case FormatUtils::VERSION_2:
             // Version 2 dictionary writing is not supported.
             return false;
+        case FormatUtils::VERSION_4_ONLY_FOR_TESTING:
         case FormatUtils::VERSION_4:
-            return buffer->writeUintAndAdvancePosition(FormatUtils::VERSION_4 /* data */,
+            return buffer->writeUintAndAdvancePosition(version /* data */,
                     HEADER_DICTIONARY_VERSION_SIZE, writingPos);
         default:
             return false;
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/dictionary_structure_with_buffer_policy_factory.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/dictionary_structure_with_buffer_policy_factory.cpp
index be7e43b..c4d1860 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/dictionary_structure_with_buffer_policy_factory.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/dictionary_structure_with_buffer_policy_factory.cpp
@@ -52,9 +52,11 @@
         DictionaryStructureWithBufferPolicyFactory:: newPolicyForOnMemoryDict(
                 const int formatVersion, const std::vector<int> &locale,
                 const DictionaryHeaderStructurePolicy::AttributeMap *const attributeMap) {
-    switch (formatVersion) {
+    FormatUtils::FORMAT_VERSION dictFormatVersion = FormatUtils::getFormatVersion(formatVersion);
+    switch (dictFormatVersion) {
+        case FormatUtils::VERSION_4_ONLY_FOR_TESTING:
         case FormatUtils::VERSION_4: {
-            HeaderPolicy headerPolicy(FormatUtils::VERSION_4, locale, attributeMap);
+            HeaderPolicy headerPolicy(dictFormatVersion, locale, attributeMap);
             Ver4DictBuffers::Ver4DictBuffersPtr dictBuffers =
                     Ver4DictBuffers::createVer4DictBuffers(&headerPolicy,
                             Ver4DictConstants::MAX_DICT_EXTENDED_REGION_SIZE);
@@ -87,11 +89,13 @@
     if (!mmappedBuffer) {
         return DictionaryStructureWithBufferPolicy::StructurePolicyPtr(nullptr);
     }
-    switch (FormatUtils::detectFormatVersion(mmappedBuffer->getBuffer(),
-            mmappedBuffer->getBufferSize())) {
+    const FormatUtils::FORMAT_VERSION formatVersion = FormatUtils::detectFormatVersion(
+            mmappedBuffer->getBuffer(), mmappedBuffer->getBufferSize());
+    switch (formatVersion) {
         case FormatUtils::VERSION_2:
             AKLOGE("Given path is a directory but the format is version 2. path: %s", path);
             break;
+        case FormatUtils::VERSION_4_ONLY_FOR_TESTING:
         case FormatUtils::VERSION_4: {
             const int dictDirPathBufSize = strlen(headerFilePath) + 1 /* terminator */;
             char dictPath[dictDirPathBufSize];
@@ -102,7 +106,8 @@
                 return DictionaryStructureWithBufferPolicy::StructurePolicyPtr(nullptr);
             }
             Ver4DictBuffers::Ver4DictBuffersPtr dictBuffers(
-                    Ver4DictBuffers::openVer4DictBuffers(dictPath, std::move(mmappedBuffer)));
+                    Ver4DictBuffers::openVer4DictBuffers(dictPath, std::move(mmappedBuffer),
+                            formatVersion));
             if (!dictBuffers || !dictBuffers->isValid()) {
                 AKLOGE("DICT: The dictionary doesn't satisfy ver4 format requirements. path: %s",
                         path);
@@ -135,6 +140,7 @@
         case FormatUtils::VERSION_2:
             return DictionaryStructureWithBufferPolicy::StructurePolicyPtr(
                     new PatriciaTriePolicy(std::move(mmappedBuffer)));
+        case FormatUtils::VERSION_4_ONLY_FOR_TESTING:
         case FormatUtils::VERSION_4:
             AKLOGE("Given path is a file but the format is version 4. path: %s", path);
             break;
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 e02dd55..a527f03 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
@@ -16,6 +16,7 @@
 
 #include "suggest/policyimpl/dictionary/structure/pt_common/dynamic_pt_updating_helper.h"
 
+#include "suggest/core/dictionary/property/unigram_property.h"
 #include "suggest/policyimpl/dictionary/structure/pt_common/dynamic_pt_reading_helper.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"
@@ -29,9 +30,8 @@
 
 bool DynamicPtUpdatingHelper::addUnigramWord(
         DynamicPtReadingHelper *const readingHelper,
-        const int *const wordCodePoints, const int codePointCount, const int probability,
-        const bool isNotAWord, const bool isBlacklisted, const int timestamp,
-        bool *const outAddedNewUnigram) {
+        const int *const wordCodePoints, const int codePointCount,
+        const UnigramProperty *const unigramProperty, bool *const outAddedNewUnigram) {
     int parentPos = NOT_A_DICT_POS;
     while (!readingHelper->isEnd()) {
         const PtNodeParams ptNodeParams(readingHelper->getPtNodeParams());
@@ -53,20 +53,18 @@
             if (nextIndex >= codePointCount || !readingHelper->isMatchedCodePoint(ptNodeParams, j,
                     wordCodePoints[matchedCodePointCount + j])) {
                 *outAddedNewUnigram = true;
-                return reallocatePtNodeAndAddNewPtNodes(&ptNodeParams, j, isNotAWord, isBlacklisted,
-                        probability, timestamp, wordCodePoints + matchedCodePointCount,
+                return reallocatePtNodeAndAddNewPtNodes(&ptNodeParams, j, unigramProperty,
+                        wordCodePoints + matchedCodePointCount,
                         codePointCount - matchedCodePointCount);
             }
         }
         // All characters are matched.
         if (codePointCount == readingHelper->getTotalCodePointCount(ptNodeParams)) {
-            return setPtNodeProbability(&ptNodeParams, isNotAWord, isBlacklisted, probability,
-                    timestamp, outAddedNewUnigram);
+            return setPtNodeProbability(&ptNodeParams, unigramProperty, outAddedNewUnigram);
         }
         if (!ptNodeParams.hasChildren()) {
             *outAddedNewUnigram = true;
-            return createChildrenPtNodeArrayAndAChildPtNode(&ptNodeParams,
-                    isNotAWord, isBlacklisted, probability, timestamp,
+            return createChildrenPtNodeArrayAndAChildPtNode(&ptNodeParams, unigramProperty,
                     wordCodePoints + readingHelper->getTotalCodePointCount(ptNodeParams),
                     codePointCount - readingHelper->getTotalCodePointCount(ptNodeParams));
         }
@@ -83,7 +81,7 @@
     return createAndInsertNodeIntoPtNodeArray(parentPos,
             wordCodePoints + readingHelper->getPrevTotalCodePointCount(),
             codePointCount - readingHelper->getPrevTotalCodePointCount(),
-            isNotAWord, isBlacklisted, probability, timestamp, &pos);
+            unigramProperty, &pos);
 }
 
 bool DynamicPtUpdatingHelper::addBigramWords(const int word0Pos, const int word1Pos,
@@ -115,36 +113,34 @@
 
 bool DynamicPtUpdatingHelper::createAndInsertNodeIntoPtNodeArray(const int parentPos,
         const int *const nodeCodePoints, const int nodeCodePointCount,
-        const bool isNotAWord, const bool isBlacklisted, const int probability,
-        const int timestamp,  int *const forwardLinkFieldPos) {
+        const UnigramProperty *const unigramProperty, int *const forwardLinkFieldPos) {
     const int newPtNodeArrayPos = mBuffer->getTailPosition();
     if (!DynamicPtWritingUtils::writeForwardLinkPositionAndAdvancePosition(mBuffer,
             newPtNodeArrayPos, forwardLinkFieldPos)) {
         return false;
     }
     return createNewPtNodeArrayWithAChildPtNode(parentPos, nodeCodePoints, nodeCodePointCount,
-            isNotAWord, isBlacklisted, probability, timestamp);
+            unigramProperty);
 }
 
-bool DynamicPtUpdatingHelper::setPtNodeProbability(
-        const PtNodeParams *const originalPtNodeParams, const bool isNotAWord,
-        const bool isBlacklisted, const int probability, const int timestamp,
-        bool *const outAddedNewUnigram) {
+bool DynamicPtUpdatingHelper::setPtNodeProbability(const PtNodeParams *const originalPtNodeParams,
+        const UnigramProperty *const unigramProperty, bool *const outAddedNewUnigram) {
     if (originalPtNodeParams->isTerminal()) {
         // Overwrites the probability.
         *outAddedNewUnigram = false;
-        return mPtNodeWriter->updatePtNodeProbability(originalPtNodeParams, probability, timestamp);
+        return mPtNodeWriter->updatePtNodeUnigramProperty(originalPtNodeParams, unigramProperty);
     } else {
         // Make the node terminal and write the probability.
         *outAddedNewUnigram = true;
         const int movedPos = mBuffer->getTailPosition();
         int writingPos = movedPos;
         const PtNodeParams ptNodeParamsToWrite(getUpdatedPtNodeParams(originalPtNodeParams,
-                isNotAWord, isBlacklisted, true /* isTerminal */,
-                originalPtNodeParams->getParentPos(), originalPtNodeParams->getCodePointCount(),
-                originalPtNodeParams->getCodePoints(), probability));
+                unigramProperty->isNotAWord(), unigramProperty->isBlacklisted(),
+                true /* isTerminal */, originalPtNodeParams->getParentPos(),
+                originalPtNodeParams->getCodePointCount(), originalPtNodeParams->getCodePoints(),
+                unigramProperty->getProbability()));
         if (!mPtNodeWriter->writeNewTerminalPtNodeAndAdvancePosition(&ptNodeParamsToWrite,
-                timestamp, &writingPos)) {
+                unigramProperty, &writingPos)) {
             return false;
         }
         if (!mPtNodeWriter->markPtNodeAsMoved(originalPtNodeParams, movedPos, movedPos)) {
@@ -155,31 +151,30 @@
 }
 
 bool DynamicPtUpdatingHelper::createChildrenPtNodeArrayAndAChildPtNode(
-        const PtNodeParams *const parentPtNodeParams, const bool isNotAWord,
-        const bool isBlacklisted, const int probability, const int timestamp,
+        const PtNodeParams *const parentPtNodeParams, const UnigramProperty *const unigramProperty,
         const int *const codePoints, const int codePointCount) {
     const int newPtNodeArrayPos = mBuffer->getTailPosition();
     if (!mPtNodeWriter->updateChildrenPosition(parentPtNodeParams, newPtNodeArrayPos)) {
         return false;
     }
     return createNewPtNodeArrayWithAChildPtNode(parentPtNodeParams->getHeadPos(), codePoints,
-            codePointCount, isNotAWord, isBlacklisted, probability, timestamp);
+            codePointCount, unigramProperty);
 }
 
 bool DynamicPtUpdatingHelper::createNewPtNodeArrayWithAChildPtNode(
         const int parentPtNodePos, const int *const nodeCodePoints, const int nodeCodePointCount,
-        const bool isNotAWord, const bool isBlacklisted, const int probability,
-        const int timestamp) {
+        const UnigramProperty *const unigramProperty) {
     int writingPos = mBuffer->getTailPosition();
     if (!DynamicPtWritingUtils::writePtNodeArraySizeAndAdvancePosition(mBuffer,
             1 /* arraySize */, &writingPos)) {
         return false;
     }
     const PtNodeParams ptNodeParamsToWrite(getPtNodeParamsForNewPtNode(
-            isNotAWord, isBlacklisted, true /* isTerminal */,
-            parentPtNodePos, nodeCodePointCount, nodeCodePoints, probability));
-    if (!mPtNodeWriter->writeNewTerminalPtNodeAndAdvancePosition(&ptNodeParamsToWrite, timestamp,
-            &writingPos)) {
+            unigramProperty->isNotAWord(), unigramProperty->isBlacklisted(), true /* isTerminal */,
+            parentPtNodePos, nodeCodePointCount, nodeCodePoints,
+            unigramProperty->getProbability()));
+    if (!mPtNodeWriter->writeNewTerminalPtNodeAndAdvancePosition(&ptNodeParamsToWrite,
+            unigramProperty, &writingPos)) {
         return false;
     }
     if (!DynamicPtWritingUtils::writeForwardLinkPositionAndAdvancePosition(mBuffer,
@@ -192,13 +187,13 @@
 // Returns whether the dictionary updating was succeeded or not.
 bool DynamicPtUpdatingHelper::reallocatePtNodeAndAddNewPtNodes(
         const PtNodeParams *const reallocatingPtNodeParams, const int overlappingCodePointCount,
-        const bool isNotAWord, const bool isBlacklisted, const int probabilityOfNewPtNode,
-        const int timestamp, const int *const newNodeCodePoints, const int newNodeCodePointCount) {
+        const UnigramProperty *const unigramProperty, const int *const newNodeCodePoints,
+        const int newNodeCodePointCount) {
     // When addsExtraChild is true, split the reallocating PtNode and add new child.
     // Reallocating PtNode: abcde, newNode: abcxy.
     // abc (1st, not terminal) __ de (2nd)
     //                         \_ xy (extra child, terminal)
-    // Otherwise, this method makes 1st part terminal and write probabilityOfNewPtNode.
+    // Otherwise, this method makes 1st part terminal and write information in unigramProperty.
     // Reallocating PtNode: abcde, newNode: abc.
     // abc (1st, terminal) __ de (2nd)
     const bool addsExtraChild = newNodeCodePointCount > overlappingCodePointCount;
@@ -216,11 +211,12 @@
         }
     } else {
         const PtNodeParams ptNodeParamsToWrite(getPtNodeParamsForNewPtNode(
-                isNotAWord, isBlacklisted, true /* isTerminal */,
-                reallocatingPtNodeParams->getParentPos(), overlappingCodePointCount,
-                reallocatingPtNodeParams->getCodePoints(), probabilityOfNewPtNode));
+                unigramProperty->isNotAWord(), unigramProperty->isBlacklisted(),
+                true /* isTerminal */, reallocatingPtNodeParams->getParentPos(),
+                overlappingCodePointCount, reallocatingPtNodeParams->getCodePoints(),
+                unigramProperty->getProbability()));
         if (!mPtNodeWriter->writeNewTerminalPtNodeAndAdvancePosition(&ptNodeParamsToWrite,
-                timestamp, &writingPos)) {
+                unigramProperty, &writingPos)) {
             return false;
         }
     }
@@ -244,11 +240,12 @@
     }
     if (addsExtraChild) {
         const PtNodeParams extraChildPtNodeParams(getPtNodeParamsForNewPtNode(
-                isNotAWord, isBlacklisted, true /* isTerminal */,
-                firstPartOfReallocatedPtNodePos, newNodeCodePointCount - overlappingCodePointCount,
-                newNodeCodePoints + overlappingCodePointCount, probabilityOfNewPtNode));
+                unigramProperty->isNotAWord(), unigramProperty->isBlacklisted(),
+                true /* isTerminal */, firstPartOfReallocatedPtNodePos,
+                newNodeCodePointCount - overlappingCodePointCount,
+                newNodeCodePoints + overlappingCodePointCount, unigramProperty->getProbability()));
         if (!mPtNodeWriter->writeNewTerminalPtNodeAndAdvancePosition(&extraChildPtNodeParams,
-                timestamp, &writingPos)) {
+                unigramProperty, &writingPos)) {
             return false;
         }
     }
@@ -269,8 +266,8 @@
 }
 
 const PtNodeParams DynamicPtUpdatingHelper::getUpdatedPtNodeParams(
-        const PtNodeParams *const originalPtNodeParams, const bool isNotAWord,
-        const bool isBlacklisted, const bool isTerminal, const int parentPos,
+        const PtNodeParams *const originalPtNodeParams,
+        const bool isNotAWord, const bool isBlacklisted, const bool isTerminal, const int parentPos,
         const int codePointCount, const int *const codePoints, const int probability) const {
     const PatriciaTrieReadingUtils::NodeFlags flags = PatriciaTrieReadingUtils::createAndGetFlags(
             isBlacklisted, isNotAWord, isTerminal, originalPtNodeParams->hasShortcutTargets(),
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 9b28152..44914fe 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
@@ -26,6 +26,7 @@
 class DynamicPtReadingHelper;
 class PtNodeReader;
 class PtNodeWriter;
+class UnigramProperty;
 
 class DynamicPtUpdatingHelper {
  public:
@@ -37,9 +38,8 @@
 
     // Add a word to the dictionary. If the word already exists, update the probability.
     bool addUnigramWord(DynamicPtReadingHelper *const readingHelper,
-            const int *const wordCodePoints, const int codePointCount, const int probability,
-            const bool isNotAWord, const bool isBlacklisted, const int timestamp,
-            bool *const outAddedNewUnigram);
+            const int *const wordCodePoints, const int codePointCount,
+            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,
@@ -62,25 +62,22 @@
     PtNodeWriter *const mPtNodeWriter;
 
     bool createAndInsertNodeIntoPtNodeArray(const int parentPos, const int *const nodeCodePoints,
-            const int nodeCodePointCount, const bool isNotAWord, const bool isBlacklisted,
-            const int probability, const int timestamp, int *const forwardLinkFieldPos);
+            const int nodeCodePointCount, const UnigramProperty *const unigramProperty,
+            int *const forwardLinkFieldPos);
 
-    bool setPtNodeProbability(const PtNodeParams *const originalPtNodeParams, const bool isNotAWord,
-            const bool isBlacklisted, const int probability, const int timestamp,
-            bool *const outAddedNewUnigram);
+    bool setPtNodeProbability(const PtNodeParams *const originalPtNodeParams,
+            const UnigramProperty *const unigramProperty, bool *const outAddedNewUnigram);
 
     bool createChildrenPtNodeArrayAndAChildPtNode(const PtNodeParams *const parentPtNodeParams,
-            const bool isNotAWord, const bool isBlacklisted, const int probability,
-            const int timestamp, const int *const codePoints, const int codePointCount);
+            const UnigramProperty *const unigramProperty, const int *const codePoints,
+            const int codePointCount);
 
     bool createNewPtNodeArrayWithAChildPtNode(const int parentPos, const int *const nodeCodePoints,
-            const int nodeCodePointCount, const bool isNotAWord, const bool isBlacklisted,
-            const int probability, const int timestamp);
+            const int nodeCodePointCount, const UnigramProperty *const unigramProperty);
 
     bool reallocatePtNodeAndAddNewPtNodes(
             const PtNodeParams *const reallocatingPtNodeParams, const int overlappingCodePointCount,
-            const bool isNotAWord, const bool isBlacklisted, const int probabilityOfNewPtNode,
-            const int timestamp, const int *const newNodeCodePoints,
+            const UnigramProperty *const unigramProperty, const int *const newNodeCodePoints,
             const int newNodeCodePointCount);
 
     const PtNodeParams getUpdatedPtNodeParams(const PtNodeParams *const originalPtNodeParams,
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 e843f07..cbca3fe 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,8 @@
 
 namespace latinime {
 
+class UnigramProperty;
+
 // Interface class used to write PtNode information.
 class PtNodeWriter {
  public:
@@ -51,8 +53,8 @@
     virtual bool markPtNodeAsWillBecomeNonTerminal(
             const PtNodeParams *const toBeUpdatedPtNodeParams) = 0;
 
-    virtual bool updatePtNodeProbability(const PtNodeParams *const toBeUpdatedPtNodeParams,
-            const int probability, const int timestamp) = 0;
+    virtual bool updatePtNodeUnigramProperty(const PtNodeParams *const toBeUpdatedPtNodeParams,
+            const UnigramProperty *const unigramProperty) = 0;
 
     virtual bool updatePtNodeProbabilityAndGetNeedsToKeepPtNodeAfterGC(
             const PtNodeParams *const toBeUpdatedPtNodeParams,
@@ -65,7 +67,7 @@
             int *const ptNodeWritingPos) = 0;
 
     virtual bool writeNewTerminalPtNodeAndAdvancePosition(const PtNodeParams *const ptNodeParams,
-            const int timestamp, int *const ptNodeWritingPos) = 0;
+            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,
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/content/bigram_dict_content.h b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/content/bigram_dict_content.h
index 40ece76..944e0f9 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/content/bigram_dict_content.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/content/bigram_dict_content.h
@@ -88,6 +88,10 @@
             const BigramDictContent *const originalBigramDictContent,
             int *const outBigramEntryCount);
 
+    bool isContentTailPos(const int pos) const {
+        return pos == getContentBuffer()->getTailPosition();
+    }
+
  private:
     DISALLOW_COPY_AND_ASSIGN(BigramDictContent);
 
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_dict_buffers.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_dict_buffers.cpp
index 95f6544..77ed38b 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_dict_buffers.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_dict_buffers.cpp
@@ -27,7 +27,8 @@
 namespace latinime {
 
 /* static */ Ver4DictBuffers::Ver4DictBuffersPtr Ver4DictBuffers::openVer4DictBuffers(
-        const char *const dictPath, MmappedBuffer::MmappedBufferPtr headerBuffer) {
+        const char *const dictPath, MmappedBuffer::MmappedBufferPtr headerBuffer,
+        const FormatUtils::FORMAT_VERSION formatVersion) {
     if (!headerBuffer) {
         ASSERT(false);
         AKLOGE("The header buffer must be valid to open ver4 dict buffers.");
@@ -35,7 +36,8 @@
     }
     // TODO: take only dictDirPath, and open both header and trie files in the constructor below
     const bool isUpdatable = headerBuffer->isUpdatable();
-    return Ver4DictBuffersPtr(new Ver4DictBuffers(dictPath, std::move(headerBuffer), isUpdatable));
+    return Ver4DictBuffersPtr(new Ver4DictBuffers(dictPath, std::move(headerBuffer), isUpdatable,
+            formatVersion));
 }
 
 bool Ver4DictBuffers::flushHeaderAndDictBuffers(const char *const dictDirPath,
@@ -113,11 +115,12 @@
 }
 
 Ver4DictBuffers::Ver4DictBuffers(const char *const dictPath,
-        MmappedBuffer::MmappedBufferPtr headerBuffer, const bool isUpdatable)
+        MmappedBuffer::MmappedBufferPtr headerBuffer, const bool isUpdatable,
+        const FormatUtils::FORMAT_VERSION formatVersion)
         : mHeaderBuffer(std::move(headerBuffer)),
           mDictBuffer(MmappedBuffer::openBuffer(dictPath,
                   Ver4DictConstants::TRIE_FILE_EXTENSION, isUpdatable)),
-          mHeaderPolicy(mHeaderBuffer->getBuffer(), FormatUtils::VERSION_4),
+          mHeaderPolicy(mHeaderBuffer->getBuffer(), formatVersion),
           mExpandableHeaderBuffer(mHeaderBuffer ? mHeaderBuffer->getBuffer() : nullptr,
                   mHeaderPolicy.getSize(),
                   BufferWithExtendableBuffer::DEFAULT_MAX_ADDITIONAL_BUFFER_SIZE),
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_dict_buffers.h b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_dict_buffers.h
index fc41432..df177c1 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_dict_buffers.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_dict_buffers.h
@@ -36,7 +36,8 @@
     typedef std::unique_ptr<Ver4DictBuffers> Ver4DictBuffersPtr;
 
     static Ver4DictBuffersPtr openVer4DictBuffers(const char *const dictDirPath,
-            MmappedBuffer::MmappedBufferPtr headerBuffer);
+            MmappedBuffer::MmappedBufferPtr headerBuffer,
+            const FormatUtils::FORMAT_VERSION formatVersion);
 
     static AK_FORCE_INLINE Ver4DictBuffersPtr createVer4DictBuffers(
             const HeaderPolicy *const headerPolicy, const int maxTrieSize) {
@@ -120,7 +121,8 @@
     DISALLOW_COPY_AND_ASSIGN(Ver4DictBuffers);
 
     Ver4DictBuffers(const char *const dictDirPath,
-            const MmappedBuffer::MmappedBufferPtr headerBuffer, const bool isUpdatable);
+            const MmappedBuffer::MmappedBufferPtr headerBuffer, const bool isUpdatable,
+            const FormatUtils::FORMAT_VERSION formatVersion);
 
     Ver4DictBuffers(const HeaderPolicy *const headerPolicy, const int maxTrieSize);
 
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 38ff42f..cc3a24a 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
@@ -16,6 +16,7 @@
 
 #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"
@@ -133,9 +134,11 @@
             &writingPos);
 }
 
-bool Ver4PatriciaTrieNodeWriter::updatePtNodeProbability(
-        const PtNodeParams *const toBeUpdatedPtNodeParams, const int newProbability,
-        const int timestamp) {
+bool Ver4PatriciaTrieNodeWriter::updatePtNodeUnigramProperty(
+        const PtNodeParams *const toBeUpdatedPtNodeParams,
+        const UnigramProperty *const unigramProperty) {
+    // Update probability and historical information.
+    // TODO: Update other information in the unigram property.
     if (!toBeUpdatedPtNodeParams->isTerminal()) {
         return false;
     }
@@ -143,7 +146,7 @@
             mBuffers->getProbabilityDictContent()->getProbabilityEntry(
                     toBeUpdatedPtNodeParams->getTerminalId());
     const ProbabilityEntry probabilityEntry = createUpdatedEntryFrom(&originalProbabilityEntry,
-            newProbability, timestamp);
+            unigramProperty);
     return mBuffers->getMutableProbabilityDictContent()->setProbabilityEntry(
             toBeUpdatedPtNodeParams->getTerminalId(), &probabilityEntry);
 }
@@ -204,7 +207,8 @@
 
 
 bool Ver4PatriciaTrieNodeWriter::writeNewTerminalPtNodeAndAdvancePosition(
-        const PtNodeParams *const ptNodeParams, const int timestamp, int *const ptNodeWritingPos) {
+        const PtNodeParams *const ptNodeParams, const UnigramProperty *const unigramProperty,
+        int *const ptNodeWritingPos) {
     int terminalId = Ver4DictConstants::NOT_A_TERMINAL_ID;
     if (!writePtNodeAndGetTerminalIdAndAdvancePosition(ptNodeParams, &terminalId,
             ptNodeWritingPos)) {
@@ -213,7 +217,7 @@
     // Write probability.
     ProbabilityEntry newProbabilityEntry;
     const ProbabilityEntry probabilityEntryToWrite = createUpdatedEntryFrom(
-            &newProbabilityEntry, ptNodeParams->getProbability(), timestamp);
+            &newProbabilityEntry, unigramProperty);
     return mBuffers->getMutableProbabilityDictContent()->setProbabilityEntry(terminalId,
             &probabilityEntryToWrite);
 }
@@ -379,18 +383,21 @@
 }
 
 const ProbabilityEntry Ver4PatriciaTrieNodeWriter::createUpdatedEntryFrom(
-        const ProbabilityEntry *const originalProbabilityEntry, const int newProbability,
-        const int timestamp) const {
+        const ProbabilityEntry *const originalProbabilityEntry,
+        const UnigramProperty *const unigramProperty) const {
     // TODO: Consolidate historical info and probability.
     if (mHeaderPolicy->hasHistoricalInfoOfWords()) {
+        const HistoricalInfo historicalInfoForUpdate(unigramProperty->getTimestamp(),
+                unigramProperty->getLevel(), unigramProperty->getCount());
         const HistoricalInfo updatedHistoricalInfo =
                 ForgettingCurveUtils::createUpdatedHistoricalInfo(
-                        originalProbabilityEntry->getHistoricalInfo(), newProbability, timestamp,
-                        mHeaderPolicy);
+                        originalProbabilityEntry->getHistoricalInfo(),
+                        unigramProperty->getProbability(), &historicalInfoForUpdate, mHeaderPolicy);
         return originalProbabilityEntry->createEntryWithUpdatedHistoricalInfo(
                 &updatedHistoricalInfo);
     } else {
-        return originalProbabilityEntry->createEntryWithUpdatedProbability(newProbability);
+        return originalProbabilityEntry->createEntryWithUpdatedProbability(
+                unigramProperty->getProbability());
     }
 }
 
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 b2b0504..f20d3a2 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
@@ -57,8 +57,8 @@
     virtual bool markPtNodeAsWillBecomeNonTerminal(
             const PtNodeParams *const toBeUpdatedPtNodeParams);
 
-    virtual bool updatePtNodeProbability(const PtNodeParams *const toBeUpdatedPtNodeParams,
-            const int newProbability, const int timestamp);
+    virtual bool updatePtNodeUnigramProperty(const PtNodeParams *const toBeUpdatedPtNodeParams,
+            const UnigramProperty *const unigramProperty);
 
     virtual bool updatePtNodeProbabilityAndGetNeedsToKeepPtNodeAfterGC(
             const PtNodeParams *const toBeUpdatedPtNodeParams, bool *const outNeedsToKeepPtNode);
@@ -73,7 +73,7 @@
             int *const ptNodeWritingPos);
 
     virtual bool writeNewTerminalPtNodeAndAdvancePosition(const PtNodeParams *const ptNodeParams,
-            const int timestamp, int *const ptNodeWritingPos);
+            const UnigramProperty *const unigramProperty, int *const ptNodeWritingPos);
 
     virtual bool addNewBigramEntry(const PtNodeParams *const sourcePtNodeParams,
             const PtNodeParams *const targetPtNodeParam, const int probability, const int timestamp,
@@ -102,11 +102,12 @@
             const PtNodeParams *const ptNodeParams, int *const outTerminalId,
             int *const ptNodeWritingPos);
 
-    // Create updated probability entry using given probability and timestamp. In addition to the
+    // Create updated probability entry using given unigram property. In addition to the
     // probability, this method updates historical information if needed.
+    // TODO: Update flags belonging to the unigram property.
     const ProbabilityEntry createUpdatedEntryFrom(
-            const ProbabilityEntry *const originalProbabilityEntry, const int newProbability,
-            const int timestamp) const;
+            const ProbabilityEntry *const originalProbabilityEntry,
+            const UnigramProperty *const unigramProperty) const;
 
     bool updatePtNodeFlags(const int ptNodePos, const bool isBlacklisted, const bool isNotAWord,
             const bool isTerminal, const bool hasShortcutTargets, const bool hasBigrams,
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 bbfd22e..9999e06 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
@@ -179,9 +179,7 @@
     readingHelper.initWithPtNodeArrayPos(getRootPosition());
     bool addedNewUnigram = false;
     if (mUpdatingHelper.addUnigramWord(&readingHelper, word, length,
-            unigramProperty->getProbability(), unigramProperty->isNotAWord(),
-            unigramProperty->isBlacklisted(), unigramProperty->getTimestamp(),
-            &addedNewUnigram)) {
+            unigramProperty, &addedNewUnigram)) {
         if (addedNewUnigram) {
             mUnigramCount++;
         }
@@ -427,6 +425,9 @@
 }
 
 int Ver4PatriciaTriePolicy::getNextWordAndNextToken(const int token, int *const outCodePoints) {
+    // TODO: Return code point count like other methods.
+    // Null termination.
+    outCodePoints[0] = 0;
     if (token == 0) {
         mTerminalPtNodePositionsForIteratingWords.clear();
         DynamicPtReadingHelper::TraversePolicyToGetAllTerminalPtNodePositions traversePolicy(
@@ -443,8 +444,13 @@
     }
     const int terminalPtNodePos = mTerminalPtNodePositionsForIteratingWords[token];
     int unigramProbability = NOT_A_PROBABILITY;
-    getCodePointsAndProbabilityAndReturnCodePointCount(terminalPtNodePos, MAX_WORD_LENGTH,
-            outCodePoints, &unigramProbability);
+    const int codePointCount = getCodePointsAndProbabilityAndReturnCodePointCount(
+            terminalPtNodePos, MAX_WORD_LENGTH, outCodePoints, &unigramProbability);
+    if (codePointCount < MAX_WORD_LENGTH) {
+        // Null termination. outCodePoints have to be null terminated or contain MAX_WORD_LENGTH
+        // code points.
+        outCodePoints[codePointCount] = 0;
+    }
     const int nextToken = token + 1;
     if (nextToken >= terminalPtNodePositionsVectorSize) {
         // All words have been iterated.
diff --git a/native/jni/src/suggest/policyimpl/dictionary/utils/dict_file_writing_utils.cpp b/native/jni/src/suggest/policyimpl/dictionary/utils/dict_file_writing_utils.cpp
index 87fa599..7bc7b0a 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/utils/dict_file_writing_utils.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/utils/dict_file_writing_utils.cpp
@@ -34,9 +34,12 @@
         const int dictVersion, const std::vector<int> localeAsCodePointVector,
         const DictionaryHeaderStructurePolicy::AttributeMap *const attributeMap) {
     TimeKeeper::setCurrentTime();
-    switch (dictVersion) {
+    const FormatUtils::FORMAT_VERSION formatVersion = FormatUtils::getFormatVersion(dictVersion);
+    switch (formatVersion) {
+        case FormatUtils::VERSION_4_ONLY_FOR_TESTING:
         case FormatUtils::VERSION_4:
-            return createEmptyV4DictFile(filePath, localeAsCodePointVector, attributeMap);
+            return createEmptyV4DictFile(filePath, localeAsCodePointVector, attributeMap,
+                    formatVersion);
         default:
             AKLOGE("Cannot create dictionary %s because format version %d is not supported.",
                     filePath, dictVersion);
@@ -46,8 +49,9 @@
 
 /* static */ bool DictFileWritingUtils::createEmptyV4DictFile(const char *const dirPath,
         const std::vector<int> localeAsCodePointVector,
-        const DictionaryHeaderStructurePolicy::AttributeMap *const attributeMap) {
-    HeaderPolicy headerPolicy(FormatUtils::VERSION_4, localeAsCodePointVector, attributeMap);
+        const DictionaryHeaderStructurePolicy::AttributeMap *const attributeMap,
+        const FormatUtils::FORMAT_VERSION formatVersion) {
+    HeaderPolicy headerPolicy(formatVersion, localeAsCodePointVector, attributeMap);
     Ver4DictBuffers::Ver4DictBuffersPtr dictBuffers(
             Ver4DictBuffers::createVer4DictBuffers(&headerPolicy,
                     Ver4DictConstants::MAX_DICT_EXTENDED_REGION_SIZE));
diff --git a/native/jni/src/suggest/policyimpl/dictionary/utils/dict_file_writing_utils.h b/native/jni/src/suggest/policyimpl/dictionary/utils/dict_file_writing_utils.h
index 54ec651..a822989 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/utils/dict_file_writing_utils.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/utils/dict_file_writing_utils.h
@@ -21,6 +21,7 @@
 
 #include "defines.h"
 #include "suggest/policyimpl/dictionary/header/header_read_write_utils.h"
+#include "suggest/policyimpl/dictionary/utils/format_utils.h"
 
 namespace latinime {
 
@@ -46,7 +47,8 @@
 
     static bool createEmptyV4DictFile(const char *const filePath,
             const std::vector<int> localeAsCodePointVector,
-            const DictionaryHeaderStructurePolicy::AttributeMap *const attributeMap);
+            const DictionaryHeaderStructurePolicy::AttributeMap *const attributeMap,
+            const FormatUtils::FORMAT_VERSION formatVersion);
 
     static bool flushBufferToFile(const char *const filePath,
             const BufferWithExtendableBuffer *const buffer);
diff --git a/native/jni/src/suggest/policyimpl/dictionary/utils/forgetting_curve_utils.cpp b/native/jni/src/suggest/policyimpl/dictionary/utils/forgetting_curve_utils.cpp
index c7d3df9..fed0ae7 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/utils/forgetting_curve_utils.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/utils/forgetting_curve_utils.cpp
@@ -30,7 +30,7 @@
 const int ForgettingCurveUtils::DECAY_INTERVAL_SECONDS = 2 * 60 * 60;
 
 const int ForgettingCurveUtils::MAX_LEVEL = 3;
-const int ForgettingCurveUtils::MIN_VALID_LEVEL = 1;
+const int ForgettingCurveUtils::MIN_VISIBLE_LEVEL = 1;
 const int ForgettingCurveUtils::MAX_ELAPSED_TIME_STEP_COUNT = 15;
 const int ForgettingCurveUtils::DISCARD_LEVEL_ZERO_ENTRY_TIME_STEP_COUNT_THRESHOLD = 14;
 
@@ -41,25 +41,34 @@
 
 // TODO: Revise the logic to decide the initial probability depending on the given probability.
 /* static */ const HistoricalInfo ForgettingCurveUtils::createUpdatedHistoricalInfo(
-        const HistoricalInfo *const originalHistoricalInfo,
-        const int newProbability, const int timestamp, const HeaderPolicy *const headerPolicy) {
+        const HistoricalInfo *const originalHistoricalInfo, const int newProbability,
+        const HistoricalInfo *const newHistoricalInfo, const HeaderPolicy *const headerPolicy) {
+    const int timestamp = newHistoricalInfo->getTimeStamp();
     if (newProbability != NOT_A_PROBABILITY && originalHistoricalInfo->getLevel() == 0) {
-        return HistoricalInfo(timestamp, MIN_VALID_LEVEL /* level */, 0 /* count */);
-    } else if (!originalHistoricalInfo->isValid()) {
+        // Add entry as a valid word.
+        const int level = clampToVisibleEntryLevelRange(newHistoricalInfo->getLevel());
+        const int count = clampToValidCountRange(newHistoricalInfo->getCount(), headerPolicy);
+        return HistoricalInfo(timestamp, level, count);
+    } else if (!originalHistoricalInfo->isValid()
+            || originalHistoricalInfo->getLevel() < newHistoricalInfo->getLevel()
+            || (originalHistoricalInfo->getLevel() == newHistoricalInfo->getLevel()
+                    && originalHistoricalInfo->getCount() < newHistoricalInfo->getCount())) {
         // Initial information.
-        return HistoricalInfo(timestamp, 0 /* level */, 1 /* count */);
+        const int level = clampToValidLevelRange(newHistoricalInfo->getLevel());
+        const int count = clampToValidCountRange(newHistoricalInfo->getCount(), headerPolicy);
+        return HistoricalInfo(timestamp, level, count);
     } else {
         const int updatedCount = originalHistoricalInfo->getCount() + 1;
         if (updatedCount >= headerPolicy->getForgettingCurveOccurrencesToLevelUp()) {
             // The count exceeds the max value the level can be incremented.
             if (originalHistoricalInfo->getLevel() >= MAX_LEVEL) {
                 // The level is already max.
-                return HistoricalInfo(timestamp, originalHistoricalInfo->getLevel(),
-                        originalHistoricalInfo->getCount());
+                return HistoricalInfo(timestamp,
+                        originalHistoricalInfo->getLevel(), originalHistoricalInfo->getCount());
             } else {
                 // Level up.
-                return HistoricalInfo(timestamp, originalHistoricalInfo->getLevel() + 1,
-                        0 /* count */);
+                return HistoricalInfo(timestamp,
+                        originalHistoricalInfo->getLevel() + 1, 0 /* count */);
             }
         } else {
             return HistoricalInfo(timestamp, originalHistoricalInfo->getLevel(), updatedCount);
@@ -73,8 +82,8 @@
             headerPolicy->getForgettingCurveDurationToLevelDown());
     return sProbabilityTable.getProbability(
             headerPolicy->getForgettingCurveProbabilityValuesTableId(),
-            std::min(std::max(historicalInfo->getLevel(), 0), MAX_LEVEL),
-            std::min(std::max(elapsedTimeStepCount, 0), MAX_ELAPSED_TIME_STEP_COUNT));
+            clampToValidLevelRange(historicalInfo->getLevel()),
+            clampToValidTimeStepCountRange(elapsedTimeStepCount));
 }
 
 /* static */ int ForgettingCurveUtils::getProbability(const int unigramProbability,
@@ -155,6 +164,23 @@
     return elapsedTimeInSeconds / timeStepDurationInSeconds;
 }
 
+/* static */ int ForgettingCurveUtils::clampToVisibleEntryLevelRange(const int level) {
+    return std::min(std::max(level, MIN_VISIBLE_LEVEL), MAX_LEVEL);
+}
+
+/* static */ int ForgettingCurveUtils::clampToValidCountRange(const int count,
+        const HeaderPolicy *const headerPolicy) {
+    return std::min(std::max(count, 0), headerPolicy->getForgettingCurveOccurrencesToLevelUp() - 1);
+}
+
+/* static */ int ForgettingCurveUtils::clampToValidLevelRange(const int level) {
+    return std::min(std::max(level, 0), MAX_LEVEL);
+}
+
+/* static */ int ForgettingCurveUtils::clampToValidTimeStepCountRange(const int timeStepCount) {
+    return std::min(std::max(timeStepCount, 0), MAX_ELAPSED_TIME_STEP_COUNT);
+}
+
 const int ForgettingCurveUtils::ProbabilityTable::PROBABILITY_TABLE_COUNT = 4;
 const int ForgettingCurveUtils::ProbabilityTable::WEAK_PROBABILITY_TABLE_ID = 0;
 const int ForgettingCurveUtils::ProbabilityTable::MODEST_PROBABILITY_TABLE_ID = 1;
diff --git a/native/jni/src/suggest/policyimpl/dictionary/utils/forgetting_curve_utils.h b/native/jni/src/suggest/policyimpl/dictionary/utils/forgetting_curve_utils.h
index bb86909..3ff80ae 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/utils/forgetting_curve_utils.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/utils/forgetting_curve_utils.h
@@ -30,7 +30,7 @@
  public:
     static const HistoricalInfo createUpdatedHistoricalInfo(
             const HistoricalInfo *const originalHistoricalInfo, const int newProbability,
-            const int timestamp, const HeaderPolicy *const headerPolicy);
+            const HistoricalInfo *const newHistoricalInfo, const HeaderPolicy *const headerPolicy);
 
     static const HistoricalInfo createHistoricalInfoToSave(
             const HistoricalInfo *const originalHistoricalInfo,
@@ -93,7 +93,7 @@
     static const int DECAY_INTERVAL_SECONDS;
 
     static const int MAX_LEVEL;
-    static const int MIN_VALID_LEVEL;
+    static const int MIN_VISIBLE_LEVEL;
     static const int MAX_ELAPSED_TIME_STEP_COUNT;
     static const int DISCARD_LEVEL_ZERO_ENTRY_TIME_STEP_COUNT_THRESHOLD;
 
@@ -103,8 +103,11 @@
     static const ProbabilityTable sProbabilityTable;
 
     static int backoff(const int unigramProbability);
-
     static int getElapsedTimeStepCount(const int timestamp, const int durationToLevelDown);
+    static int clampToVisibleEntryLevelRange(const int level);
+    static int clampToValidLevelRange(const int level);
+    static int clampToValidCountRange(const int count, const HeaderPolicy *const headerPolicy);
+    static int clampToValidTimeStepCountRange(const int timeStepCount);
 };
 } // namespace latinime
 #endif /* LATINIME_FORGETTING_CURVE_UTILS_H */
diff --git a/native/jni/src/suggest/policyimpl/dictionary/utils/format_utils.cpp b/native/jni/src/suggest/policyimpl/dictionary/utils/format_utils.cpp
index cd3c403..a8518cd 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/utils/format_utils.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/utils/format_utils.cpp
@@ -25,6 +25,18 @@
 // Magic number (4 bytes), version (2 bytes), flags (2 bytes), header size (4 bytes) = 12
 const int FormatUtils::DICTIONARY_MINIMUM_SIZE = 12;
 
+/* static */ FormatUtils::FORMAT_VERSION FormatUtils::getFormatVersion(const int formatVersion) {
+    switch (formatVersion) {
+        case VERSION_2:
+            return VERSION_2;
+        case VERSION_4_ONLY_FOR_TESTING:
+            return VERSION_4_ONLY_FOR_TESTING;
+        case VERSION_4:
+            return VERSION_4;
+        default:
+            return UNKNOWN_VERSION;
+    }
+}
 /* static */ FormatUtils::FORMAT_VERSION FormatUtils::detectFormatVersion(
         const uint8_t *const dict, const int dictSize) {
     // The magic number is stored big-endian.
@@ -46,6 +58,8 @@
             // same so we use them for both here.
             if (ByteArrayUtils::readUint16(dict, 4) == VERSION_2) {
                 return VERSION_2;
+            } else if (ByteArrayUtils::readUint16(dict, 4) == VERSION_4_ONLY_FOR_TESTING) {
+                return VERSION_4_ONLY_FOR_TESTING;
             } else if (ByteArrayUtils::readUint16(dict, 4) == VERSION_4) {
                 return VERSION_4;
             } else {
diff --git a/native/jni/src/suggest/policyimpl/dictionary/utils/format_utils.h b/native/jni/src/suggest/policyimpl/dictionary/utils/format_utils.h
index 759b1c9..20dfb9d 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/utils/format_utils.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/utils/format_utils.h
@@ -31,6 +31,7 @@
     enum FORMAT_VERSION {
         // These MUST have the same values as the relevant constants in FormatSpec.java.
         VERSION_2 = 2,
+        VERSION_4_ONLY_FOR_TESTING = 399,
         VERSION_4 = 401,
         UNKNOWN_VERSION = -1
     };
@@ -39,6 +40,7 @@
     // unsupported or obsolete dictionary formats.
     static const uint32_t MAGIC_NUMBER;
 
+    static FORMAT_VERSION getFormatVersion(const int formatVersion);
     static FORMAT_VERSION detectFormatVersion(const uint8_t *const dict, const int dictSize);
 
  private:
diff --git a/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetTestsBase.java b/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetTestsBase.java
index 0fb6ff2..75bd609 100644
--- a/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetTestsBase.java
+++ b/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetTestsBase.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 import android.content.res.Resources;
+import android.preference.PreferenceManager;
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.view.ContextThemeWrapper;
@@ -40,9 +41,6 @@
 
 @SmallTest
 public class KeyboardLayoutSetTestsBase extends AndroidTestCase {
-    private static final KeyboardTheme DEFAULT_KEYBOARD_THEME =
-            KeyboardTheme.getDefaultKeyboardTheme();
-
     // All input method subtypes of LatinIME.
     private final ArrayList<InputMethodSubtype> mAllSubtypesList = CollectionUtils.newArrayList();
     private final ArrayList<InputMethodSubtype> mAsciiCapableSubtypesList =
@@ -58,7 +56,9 @@
         super.setUp();
         mScreenMetrics = mContext.getResources().getInteger(R.integer.config_screen_metrics);
 
-        mThemeContext = new ContextThemeWrapper(mContext, DEFAULT_KEYBOARD_THEME.mStyleId);
+        final KeyboardTheme keyboardTheme = KeyboardTheme.getKeyboardTheme(
+                PreferenceManager.getDefaultSharedPreferences(mContext));
+        mThemeContext = new ContextThemeWrapper(mContext, keyboardTheme.mStyleId);
         RichInputMethodManager.init(mThemeContext);
         final RichInputMethodManager richImm = RichInputMethodManager.getInstance();
 
diff --git a/tests/src/com/android/inputmethod/keyboard/KeyboardThemeTests.java b/tests/src/com/android/inputmethod/keyboard/KeyboardThemeTests.java
new file mode 100644
index 0000000..9b532fe
--- /dev/null
+++ b/tests/src/com/android/inputmethod/keyboard/KeyboardThemeTests.java
@@ -0,0 +1,93 @@
+/*
+ * 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;
+
+import android.content.SharedPreferences;
+import android.os.Build.VERSION_CODES;
+import android.preference.PreferenceManager;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+@SmallTest
+public class KeyboardThemeTests extends AndroidTestCase {
+    private SharedPreferences mPrefs;
+
+    private static final int THEME_ID_NULL = -1;
+    private static final int THEME_ID_ICS = KeyboardTheme.THEME_ID_ICS;
+    private static final int THEME_ID_KLP = KeyboardTheme.THEME_ID_KLP;
+    private static final int THEME_ID_LMP = KeyboardTheme.THEME_ID_LMP;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mPrefs = PreferenceManager.getDefaultSharedPreferences(getContext());
+    }
+
+    private void assertDefaultKeyboardTheme(final int sdkVersion, final int oldThemeId,
+            final int expectedThemeId) {
+        if (oldThemeId == THEME_ID_NULL) {
+            mPrefs.edit().remove(KeyboardTheme.KITKAT_KEYBOARD_THEME_KEY).apply();
+        } else {
+            final String themeIdString = Integer.toString(oldThemeId);
+            mPrefs.edit().putString(KeyboardTheme.KITKAT_KEYBOARD_THEME_KEY, themeIdString).apply();
+        }
+        final KeyboardTheme defaultTheme =
+                KeyboardTheme.getDefaultKeyboardTheme(mPrefs, sdkVersion);
+        assertNotNull(defaultTheme);
+        assertEquals(expectedThemeId, defaultTheme.mThemeId);
+        assertFalse(mPrefs.contains(KeyboardTheme.KITKAT_KEYBOARD_THEME_KEY));
+    }
+
+    private void assertDefaultKeyboardThemeICS(final int sdkVersion) {
+        assertDefaultKeyboardTheme(sdkVersion, THEME_ID_NULL, THEME_ID_ICS);
+        assertDefaultKeyboardTheme(sdkVersion, THEME_ID_ICS, THEME_ID_ICS);
+        assertDefaultKeyboardTheme(sdkVersion, THEME_ID_KLP, THEME_ID_KLP);
+    }
+
+    private void assertDefaultKeyboardThemeKLP(final int sdkVersion) {
+        assertDefaultKeyboardTheme(sdkVersion, THEME_ID_NULL, THEME_ID_KLP);
+        assertDefaultKeyboardTheme(sdkVersion, THEME_ID_ICS, THEME_ID_ICS);
+        assertDefaultKeyboardTheme(sdkVersion, THEME_ID_KLP, THEME_ID_KLP);
+    }
+
+    private void assertDefaultKeyboardThemeLMP(final int sdkVersion) {
+        // Forced to switch to LMP theme.
+        assertDefaultKeyboardTheme(sdkVersion, THEME_ID_NULL, THEME_ID_LMP);
+        assertDefaultKeyboardTheme(sdkVersion, THEME_ID_ICS, THEME_ID_LMP);
+        assertDefaultKeyboardTheme(sdkVersion, THEME_ID_KLP, THEME_ID_LMP);
+    }
+
+    public void testDefaultKeyboardThemeICS() {
+        assertDefaultKeyboardThemeICS(VERSION_CODES.ICE_CREAM_SANDWICH);
+        assertDefaultKeyboardThemeICS(VERSION_CODES.ICE_CREAM_SANDWICH_MR1);
+    }
+
+    public void testDefaultKeyboardThemeJB() {
+        assertDefaultKeyboardThemeICS(VERSION_CODES.JELLY_BEAN);
+        assertDefaultKeyboardThemeICS(VERSION_CODES.JELLY_BEAN_MR1);
+        assertDefaultKeyboardThemeICS(VERSION_CODES.JELLY_BEAN_MR2);
+    }
+
+    public void testDefaultKeyboardThemeKLP() {
+        assertDefaultKeyboardThemeKLP(VERSION_CODES.KITKAT);
+    }
+
+    public void testDefaultKeyboardThemeLMP() {
+        // TODO: Update this constant once the *next* version becomes available.
+        assertDefaultKeyboardThemeLMP(VERSION_CODES.CUR_DEVELOPMENT);
+    }
+}
diff --git a/tests/src/com/android/inputmethod/keyboard/layout/Dvorak.java b/tests/src/com/android/inputmethod/keyboard/layout/Dvorak.java
index 99cf6e5..7ba1d78 100644
--- a/tests/src/com/android/inputmethod/keyboard/layout/Dvorak.java
+++ b/tests/src/com/android/inputmethod/keyboard/layout/Dvorak.java
@@ -51,7 +51,7 @@
 
         @Override
         public ExpectedKey[] getKeysLeftToSpacebar(final boolean isPhone) {
-            return isPhone ? joinKeys(key("q", SHORTCUT_KEY, SETTINGS_KEY)) : joinKeys(key("/"));
+            return isPhone ? joinKeys(key("q", SETTINGS_KEY)) : joinKeys(key("/"));
         }
 
         @Override
diff --git a/tests/src/com/android/inputmethod/keyboard/layout/Farsi.java b/tests/src/com/android/inputmethod/keyboard/layout/Farsi.java
index a007089..16d2f86 100644
--- a/tests/src/com/android/inputmethod/keyboard/layout/Farsi.java
+++ b/tests/src/com/android/inputmethod/keyboard/layout/Farsi.java
@@ -91,7 +91,7 @@
 
         @Override
         public ExpectedKey[] getSpaceKeys(final boolean isPhone) {
-            return joinKeys(SPACE_KEY, key(ZWNJ_KEY, ZWJ_KEY));
+            return joinKeys(LANGUAGE_SWITCH_KEY, SPACE_KEY, key(ZWNJ_KEY, ZWJ_KEY));
         }
 
         @Override
diff --git a/tests/src/com/android/inputmethod/keyboard/layout/HindiCompact.java b/tests/src/com/android/inputmethod/keyboard/layout/HindiCompact.java
index afd26e4..cf20149 100644
--- a/tests/src/com/android/inputmethod/keyboard/layout/HindiCompact.java
+++ b/tests/src/com/android/inputmethod/keyboard/layout/HindiCompact.java
@@ -16,29 +16,12 @@
 
 package com.android.inputmethod.keyboard.layout;
 
-import static com.android.inputmethod.keyboard.layout.DevanagariLetterConstants.SIGN_ANUSVARA;
-import static com.android.inputmethod.keyboard.layout.DevanagariLetterConstants.SIGN_CANDRABINDU;
-import static com.android.inputmethod.keyboard.layout.DevanagariLetterConstants.SIGN_NUKTA;
-import static com.android.inputmethod.keyboard.layout.DevanagariLetterConstants.SIGN_VIRAMA;
-import static com.android.inputmethod.keyboard.layout.DevanagariLetterConstants.SIGN_VISARGA;
-import static com.android.inputmethod.keyboard.layout.DevanagariLetterConstants.VOWEL_SIGN_AA;
-import static com.android.inputmethod.keyboard.layout.DevanagariLetterConstants.VOWEL_SIGN_AI;
-import static com.android.inputmethod.keyboard.layout.DevanagariLetterConstants.VOWEL_SIGN_AU;
-import static com.android.inputmethod.keyboard.layout.DevanagariLetterConstants.VOWEL_SIGN_CANDRA_E;
-import static com.android.inputmethod.keyboard.layout.DevanagariLetterConstants.VOWEL_SIGN_CANDRA_O;
-import static com.android.inputmethod.keyboard.layout.DevanagariLetterConstants.VOWEL_SIGN_E;
-import static com.android.inputmethod.keyboard.layout.DevanagariLetterConstants.VOWEL_SIGN_I;
-import static com.android.inputmethod.keyboard.layout.DevanagariLetterConstants.VOWEL_SIGN_II;
-import static com.android.inputmethod.keyboard.layout.DevanagariLetterConstants.VOWEL_SIGN_O;
-import static com.android.inputmethod.keyboard.layout.DevanagariLetterConstants.VOWEL_SIGN_U;
-import static com.android.inputmethod.keyboard.layout.DevanagariLetterConstants.VOWEL_SIGN_UU;
-import static com.android.inputmethod.keyboard.layout.DevanagariLetterConstants.VOWEL_SIGN_VOCALIC_R;
+import static com.android.inputmethod.keyboard.layout.DevanagariLetterConstants.*;
 
 import com.android.inputmethod.keyboard.layout.Hindi.HindiCustomizer;
 import com.android.inputmethod.keyboard.layout.Hindi.HindiSymbols;
 import com.android.inputmethod.keyboard.layout.expected.ExpectedKey;
 import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder;
-import com.android.inputmethod.latin.Constants;
 
 import java.util.Locale;
 
diff --git a/tests/src/com/android/inputmethod/keyboard/layout/LayoutBase.java b/tests/src/com/android/inputmethod/keyboard/layout/LayoutBase.java
index 4123a22..e4e9554 100644
--- a/tests/src/com/android/inputmethod/keyboard/layout/LayoutBase.java
+++ b/tests/src/com/android/inputmethod/keyboard/layout/LayoutBase.java
@@ -29,7 +29,6 @@
  * The base class of keyboard layout.
  */
 public abstract class LayoutBase extends AbstractLayoutBase {
-
     /**
      * This class is used to customize common keyboard layout to language specific layout.
      */
@@ -152,7 +151,7 @@
          *         keyboard.
          */
         public ExpectedKey[] getSpaceKeys(final boolean isPhone) {
-            return joinKeys(SPACE_KEY);
+            return joinKeys(LANGUAGE_SWITCH_KEY, SPACE_KEY);
         }
 
         /**
@@ -161,7 +160,8 @@
          * @return the array of {@link ExpectedKey} that should be placed at left of the spacebar.
          */
         public ExpectedKey[] getKeysLeftToSpacebar(final boolean isPhone) {
-            return isPhone ? joinKeys(key(",", SETTINGS_KEY)) : joinKeys("/");
+            // U+002C: "," COMMA
+            return isPhone ? joinKeys(key("\u002C", SETTINGS_KEY)) : joinKeys("/");
         }
 
         /**
@@ -306,6 +306,10 @@
 
     /**
      * Get common alphabet layout. This layout doesn't contain any special keys.
+     *
+     * A keyboard layout is an array of rows, and a row consists of an array of
+     * {@link ExpectedKey}s. Each row may have different number of {@link ExpectedKey}s.
+     *
      * @param isPhone true if requesting phone's layout.
      * @return the common alphabet keyboard layout.
      */
@@ -313,6 +317,10 @@
 
     /**
      * Get common alphabet shifted layout. This layout doesn't contain any special keys.
+     *
+     * A keyboard layout is an array of rows, and a row consists of an array of
+     * {@link ExpectedKey}s. Each row may have different number of {@link ExpectedKey}s.
+     *
      * @param isPhone true if requesting phone's layout.
      * @param elementId the element id of the requesting shifted mode.
      * @return the common alphabet shifted keyboard layout.
@@ -327,9 +335,13 @@
 
     /**
      * Get the complete expected keyboard layout.
+     *
+     * A keyboard layout is an array of rows, and a row consists of an array of
+     * {@link ExpectedKey}s. Each row may have different number of {@link ExpectedKey}s.
+     *
      * @param isPhone true if requesting phone's layout.
      * @param elementId the element id of the requesting keyboard mode.
-     * @return
+     * @return the keyboard layout of the <code>elementId</code>.
      */
     public ExpectedKey[][] getLayout(final boolean isPhone, final int elementId) {
         if (elementId == KeyboardId.ELEMENT_SYMBOLS) {
diff --git a/tests/src/com/android/inputmethod/keyboard/layout/NepaliRomanized.java b/tests/src/com/android/inputmethod/keyboard/layout/NepaliRomanized.java
index 7048dbb..7933d07 100644
--- a/tests/src/com/android/inputmethod/keyboard/layout/NepaliRomanized.java
+++ b/tests/src/com/android/inputmethod/keyboard/layout/NepaliRomanized.java
@@ -47,7 +47,7 @@
 
         @Override
         public ExpectedKey[] getSpaceKeys(final boolean isPhone) {
-            return joinKeys(SPACE_KEY, key(ZWNJ_KEY, ZWJ_KEY));
+            return joinKeys(LANGUAGE_SWITCH_KEY, SPACE_KEY, key(ZWNJ_KEY, ZWJ_KEY));
         }
 
         // U+0930/U+0941/U+002E "रु." NEPALESE RUPEE SIGN
diff --git a/tests/src/com/android/inputmethod/keyboard/layout/expected/AbstractKeyboardBuilder.java b/tests/src/com/android/inputmethod/keyboard/layout/expected/AbstractKeyboardBuilder.java
index 3365b92..6e72104 100644
--- a/tests/src/com/android/inputmethod/keyboard/layout/expected/AbstractKeyboardBuilder.java
+++ b/tests/src/com/android/inputmethod/keyboard/layout/expected/AbstractKeyboardBuilder.java
@@ -21,9 +21,9 @@
 /**
  * This class builds a keyboard that is a two dimensional array of elements <code>E</code>.
  *
- * A keyboard consists of array of rows, and a row consists of array of elements. Each row may have
- * different number of elements. A element of a keyboard can be specified by a row number and a
- * column number, both numbers starts from 1.
+ * A keyboard consists of an array of rows, and a row consists of an array of elements. Each row
+ * may have different number of elements. A element of a keyboard can be specified by a row number
+ * and a column number, both numbers starts from 1.
  *
  * @param <E> the type of a keyboard element. A keyboard element must be an immutable object.
  */
@@ -39,8 +39,7 @@
     abstract E[][] newArrayOfArray(final int size);
 
     /**
-     * Construct a builder filled with the default element.
-     * @param dimensions the integer array of each row's size.
+     * Construct an empty builder.
      */
     AbstractKeyboardBuilder() {
         mRows = newArrayOfArray(0);
@@ -80,7 +79,7 @@
      * Get the current contents of the specified row.
      * @param row the row number to get the contents.
      * @return the array of elements at row number <code>row</code>.
-     * @throws {@link RuntimeException} if <code>row</code> is illegal.
+     * @throws RuntimeException if <code>row</code> is illegal.
      */
     E[] getRowAt(final int row) {
         final int rowIndex = row - 1;
@@ -94,7 +93,7 @@
      * Set an array of elements to the specified row.
      * @param row the row number to set <code>elements</code>.
      * @param elements the array of elements to set at row number <code>row</code>.
-     * @throws {@link RuntimeException} if <code>row</code> is illegal.
+     * @throws RuntimeException if <code>row</code> is illegal.
      */
     void setRowAt(final int row, final E[] elements) {
         final int rowIndex = row - 1;
@@ -114,7 +113,7 @@
      * @param element the element to set or insert at <code>row,column</code>.
      * @param insert if true, the <code>element</code> is inserted at <code>row,column</code>.
      *        Otherwise the <code>element</code> replace the element at <code>row,column</code>.
-     * @throws {@link RuntimeException} if <code>row</code> or <code>column</code> is illegal.
+     * @throws RuntimeException if <code>row</code> or <code>column</code> is illegal.
      */
     void setElementAt(final int row, final int column, final E element, final boolean insert) {
         final E[] elements = getRowAt(row);
diff --git a/tests/src/com/android/inputmethod/keyboard/layout/expected/AbstractLayoutBase.java b/tests/src/com/android/inputmethod/keyboard/layout/expected/AbstractLayoutBase.java
index 6176f6a..9e0039d 100644
--- a/tests/src/com/android/inputmethod/keyboard/layout/expected/AbstractLayoutBase.java
+++ b/tests/src/com/android/inputmethod/keyboard/layout/expected/AbstractLayoutBase.java
@@ -19,7 +19,6 @@
 import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
 import com.android.inputmethod.keyboard.layout.expected.ExpectedKey.ExpectedAdditionalMoreKey;
 import com.android.inputmethod.latin.Constants;
-import com.android.inputmethod.latin.utils.StringUtils;
 
 /**
  * Base class to create an expected keyboard for unit test.
@@ -109,6 +108,8 @@
     // Icon ids.
     private static final int ICON_DELETE = KeyboardIconsSet.getIconId(
             KeyboardIconsSet.NAME_DELETE_KEY);
+    private static final int ICON_SPACE = KeyboardIconsSet.getIconId(
+            KeyboardIconsSet.NAME_SPACE_KEY);
     private static final int ICON_TAB = KeyboardIconsSet.getIconId(
             KeyboardIconsSet.NAME_TAB_KEY);
     private static final int ICON_SHORTCUT = KeyboardIconsSet.getIconId(
@@ -131,6 +132,5 @@
             ICON_LANGUAGE_SWITCH, Constants.CODE_LANGUAGE_SWITCH);
     public static final ExpectedKey ENTER_KEY = key(ICON_ENTER, Constants.CODE_ENTER);
     public static final ExpectedKey EMOJI_KEY = key(ICON_EMOJI, Constants.CODE_EMOJI);
-    public static final ExpectedKey SPACE_KEY = key(
-            StringUtils.newSingleCodePointString(Constants.CODE_SPACE));
+    public static final ExpectedKey SPACE_KEY = key(ICON_SPACE, Constants.CODE_SPACE);
 }
diff --git a/tests/src/com/android/inputmethod/keyboard/layout/expected/ActualKeyboardBuilder.java b/tests/src/com/android/inputmethod/keyboard/layout/expected/ActualKeyboardBuilder.java
index 26d2e2a..d0fea58 100644
--- a/tests/src/com/android/inputmethod/keyboard/layout/expected/ActualKeyboardBuilder.java
+++ b/tests/src/com/android/inputmethod/keyboard/layout/expected/ActualKeyboardBuilder.java
@@ -28,6 +28,9 @@
 
 /**
  * This class builds an actual keyboard for unit test.
+ *
+ * An actual keyboard is an array of rows, and a row consists of an array of {@link Key}s.
+ * Each row may have different number of {@link Key}s.
  */
 public final class ActualKeyboardBuilder extends AbstractKeyboardBuilder<Key> {
     private static ArrayList<Key> filterOutSpacer(final List<Key> keys) {
@@ -43,7 +46,8 @@
 
     /**
      * Create the keyboard that consists of the array of rows of the actual keyboard's keys.
-     * @param sortedKeys the sorted list of keys of the actual keyboard.
+     * @param sortedKeys keys list of the actual keyboard that is sorted from top-left to
+     * bottom-right.
      * @return the actual keyboard grouped with rows.
      */
     public static Key[][] buildKeyboard(final List<Key> sortedKeys) {
diff --git a/tests/src/com/android/inputmethod/keyboard/layout/expected/ExpectedKeyboardBuilder.java b/tests/src/com/android/inputmethod/keyboard/layout/expected/ExpectedKeyboardBuilder.java
index f068ad1..e06d34d 100644
--- a/tests/src/com/android/inputmethod/keyboard/layout/expected/ExpectedKeyboardBuilder.java
+++ b/tests/src/com/android/inputmethod/keyboard/layout/expected/ExpectedKeyboardBuilder.java
@@ -24,6 +24,11 @@
 
 /**
  * This class builds an expected keyboard for unit test.
+ *
+ * An expected keyboard is an array of rows, and a row consists of an array of {@link ExpectedKey}s.
+ * Each row may have different number of {@link ExpectedKey}s. While building an expected keyboard,
+ * an {@link ExpectedKey} can be specified by a row number and a column number, both numbers starts
+ * from 1.
  */
 public final class ExpectedKeyboardBuilder extends AbstractKeyboardBuilder<ExpectedKey> {
     public ExpectedKeyboardBuilder() {
@@ -212,7 +217,7 @@
      * @param keys the array of keys to insert at <code>row,column</code>. Each key can be
      *        {@link ExpectedKey}, {@link ExpectedKey} array, and {@link String}.
      * @return this builder.
-     * @throws {@link RuntimeException} if <code>row</code> or <code>column</code> is illegal.
+     * @throws RuntimeException if <code>row</code> or <code>column</code> is illegal.
      */
     public ExpectedKeyboardBuilder insertKeysAtRow(final int row, final int column,
             final Object ... keys) {
@@ -229,7 +234,7 @@
      * @param keys the array of keys to add on the left most of the row. Each key can be
      *        {@link ExpectedKey}, {@link ExpectedKey} array, and {@link String}.
      * @return this builder.
-     * @throws {@link RuntimeException} if <code>row</code> is illegal.
+     * @throws RuntimeException if <code>row</code> is illegal.
      */
     public ExpectedKeyboardBuilder addKeysOnTheLeftOfRow(final int row,
             final Object ... keys) {
@@ -247,7 +252,7 @@
      * @param keys the array of keys to add on the right most of the row. Each key can be
      *        {@link ExpectedKey}, {@link ExpectedKey} array, and {@link String}.
      * @return this builder.
-     * @throws {@link RuntimeException} if <code>row</code> is illegal.
+     * @throws RuntimeException if <code>row</code> is illegal.
      */
     public ExpectedKeyboardBuilder addKeysOnTheRightOfRow(final int row,
             final Object ... keys) {
diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/EnglishCustomizer.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/EnglishCustomizer.java
index 29264ff..3e82f65 100644
--- a/tests/src/com/android/inputmethod/keyboard/layout/tests/EnglishCustomizer.java
+++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/EnglishCustomizer.java
@@ -27,34 +27,34 @@
     @Override
     public ExpectedKeyboardBuilder setAccentedLetters(final ExpectedKeyboardBuilder builder) {
         return builder
-                // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
                 // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
+                // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
                 // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
                 // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
                 // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON
-                .setMoreKeysOf("e", "\u00E8", "\u00E9", "\u00EA", "\u00EB", "\u0113")
+                .setMoreKeysOf("e", "\u00E9", "\u00E8", "\u00EA", "\u00EB", "\u0113")
+                // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
                 // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
                 // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
                 // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
-                // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
                 // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
-                .setMoreKeysOf("u", "\u00FB", "\u00FC", "\u00F9", "\u00FA", "\u016B")
+                .setMoreKeysOf("u", "\u00FA", "\u00FB", "\u00FC", "\u00F9", "\u016B")
+                // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
                 // U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
                 // U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
-                // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
                 // U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
                 // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
-                .setMoreKeysOf("i", "\u00EE", "\u00EF", "\u00ED", "\u012B", "\u00EC")
+                .setMoreKeysOf("i", "\u00ED", "\u00EE", "\u00EF", "\u012B", "\u00EC")
+                // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
                 // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
                 // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
                 // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE
-                // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
                 // U+0153: "œ" LATIN SMALL LIGATURE OE
                 // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
                 // U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
                 // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
                 .setMoreKeysOf("o",
-                        "\u00F4", "\u00F6", "\u00F2", "\u00F3", "\u0153", "\u00F8", "\u014D",
+                        "\u00F3", "\u00F4", "\u00F6", "\u00F2", "\u0153", "\u00F8", "\u014D",
                         "\u00F5")
                 // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE
                 // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX
diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/LayoutTestsBase.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/LayoutTestsBase.java
index 4002c49..d4e8fb6 100644
--- a/tests/src/com/android/inputmethod/keyboard/layout/tests/LayoutTestsBase.java
+++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/LayoutTestsBase.java
@@ -51,7 +51,10 @@
         mSubtype = getSubtype(mLayout.getLocale(), mLayout.getName());
         mLogTag = SubtypeLocaleUtils.getSubtypeNameForLogging(mSubtype) + "/"
                 + (isPhone() ? "phone" : "tablet");
-        mKeyboardLayoutSet = createKeyboardLayoutSet(mSubtype, null /* editorInfo */);
+        // TODO: Test with language switch key enabled and disabled.
+        mKeyboardLayoutSet = createKeyboardLayoutSet(mSubtype, null /* editorInfo */,
+                true /* isShortcutImeEnabled */, true /* showsVoiceInputKey */,
+                true /* isLanguageSwitchKeyEnabled */);
     }
 
     // Those helper methods have a lower case name to be readable when defining expected keyboard
diff --git a/tests/src/com/android/inputmethod/latin/BinaryDictionaryDecayingTests.java b/tests/src/com/android/inputmethod/latin/BinaryDictionaryDecayingTests.java
index ae2205b..aed24c5 100644
--- a/tests/src/com/android/inputmethod/latin/BinaryDictionaryDecayingTests.java
+++ b/tests/src/com/android/inputmethod/latin/BinaryDictionaryDecayingTests.java
@@ -93,15 +93,17 @@
 
     private File createEmptyDictionaryAndGetFile(final String dictId,
             final int formatVersion) throws IOException {
-        if (formatVersion == FormatSpec.VERSION4) {
-            return createEmptyVer4DictionaryAndGetFile(dictId);
+        if (formatVersion == FormatSpec.VERSION4
+                || formatVersion == FormatSpec.VERSION4_ONLY_FOR_TESTING) {
+            return createEmptyVer4DictionaryAndGetFile(dictId, formatVersion);
         } else {
             throw new IOException("Dictionary format version " + formatVersion
                     + " is not supported.");
         }
     }
 
-    private File createEmptyVer4DictionaryAndGetFile(final String dictId) throws IOException {
+    private File createEmptyVer4DictionaryAndGetFile(final String dictId, final int formatVersion)
+            throws IOException {
         final File file = File.createTempFile(dictId, TEST_DICT_FILE_EXTENSION,
                 getContext().getCacheDir());
         FileUtils.deleteRecursively(file);
@@ -113,7 +115,7 @@
                 DictionaryHeader.ATTRIBUTE_VALUE_TRUE);
         attributeMap.put(DictionaryHeader.HAS_HISTORICAL_INFO_KEY,
                 DictionaryHeader.ATTRIBUTE_VALUE_TRUE);
-        if (BinaryDictionaryUtils.createEmptyDictFile(file.getAbsolutePath(), FormatSpec.VERSION4,
+        if (BinaryDictionaryUtils.createEmptyDictFile(file.getAbsolutePath(), formatVersion,
                 LocaleUtils.constructLocaleFromString(TEST_LOCALE), attributeMap)) {
             return file;
         } else {
@@ -562,4 +564,43 @@
             }
         }
     }
+
+    public void testDictMigration() {
+        testDictMigration(FormatSpec.VERSION4_ONLY_FOR_TESTING, FormatSpec.VERSION4);
+    }
+
+    private void testDictMigration(final int fromFormatVersion, final int toFormatVersion) {
+        setCurrentTimeForTestMode(mCurrentTime);
+        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 */);
+        // 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);
+        assertFalse(binaryDictionary.isValidWord("bbb"));
+        addUnigramWord(binaryDictionary, "ccc", DUMMY_PROBABILITY);
+        addUnigramWord(binaryDictionary, "ccc", DUMMY_PROBABILITY);
+        addUnigramWord(binaryDictionary, "ccc", DUMMY_PROBABILITY);
+        addUnigramWord(binaryDictionary, "ccc", DUMMY_PROBABILITY);
+        addUnigramWord(binaryDictionary, "ccc", DUMMY_PROBABILITY);
+
+        assertEquals(fromFormatVersion, binaryDictionary.getFormatVersion());
+        assertTrue(binaryDictionary.migrateTo(toFormatVersion));
+        assertTrue(binaryDictionary.isValidDictionary());
+        assertEquals(toFormatVersion, binaryDictionary.getFormatVersion());
+        assertTrue(binaryDictionary.isValidWord("aaa"));
+        assertFalse(binaryDictionary.isValidWord("bbb"));
+        assertTrue(binaryDictionary.getFrequency("aaa") < binaryDictionary.getFrequency("ccc"));
+        addUnigramWord(binaryDictionary, "bbb", Dictionary.NOT_A_PROBABILITY);
+        assertTrue(binaryDictionary.isValidWord("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 0fb0fa5..9ceafa7 100644
--- a/tests/src/com/android/inputmethod/latin/BinaryDictionaryTests.java
+++ b/tests/src/com/android/inputmethod/latin/BinaryDictionaryTests.java
@@ -46,21 +46,23 @@
 
     private File createEmptyDictionaryAndGetFile(final String dictId,
             final int formatVersion) throws IOException {
-       if (formatVersion == FormatSpec.VERSION4) {
-            return createEmptyVer4DictionaryAndGetFile(dictId);
+        if (formatVersion == FormatSpec.VERSION4
+                || formatVersion == FormatSpec.VERSION4_ONLY_FOR_TESTING) {
+            return createEmptyVer4DictionaryAndGetFile(dictId, formatVersion);
         } else {
             throw new IOException("Dictionary format version " + formatVersion
                     + " is not supported.");
         }
     }
 
-    private File createEmptyVer4DictionaryAndGetFile(final String dictId) throws IOException {
+    private File createEmptyVer4DictionaryAndGetFile(final String dictId,
+            final int formatVersion) throws IOException {
         final File file = File.createTempFile(dictId, TEST_DICT_FILE_EXTENSION,
                 getContext().getCacheDir());
         file.delete();
         file.mkdir();
         Map<String, String> attributeMap = new HashMap<String, String>();
-        if (BinaryDictionaryUtils.createEmptyDictFile(file.getAbsolutePath(), FormatSpec.VERSION4,
+        if (BinaryDictionaryUtils.createEmptyDictFile(file.getAbsolutePath(), formatVersion,
                 Locale.ENGLISH, attributeMap)) {
             return file;
         } else {
@@ -1223,4 +1225,36 @@
             }
         }
     }
+
+    public void testDictMigration() {
+        testDictMigration(FormatSpec.VERSION4_ONLY_FOR_TESTING, FormatSpec.VERSION4);
+    }
+
+    private void testDictMigration(final int fromFormatVersion, final int toFormatVersion) {
+        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 int unigramProbability = 100;
+        addUnigramWord(binaryDictionary, "aaa", unigramProbability);
+        addUnigramWord(binaryDictionary, "bbb", unigramProbability);
+        final int bigramProbability = 10;
+        addBigramWords(binaryDictionary, "aaa", "bbb", bigramProbability);
+        assertEquals(unigramProbability, binaryDictionary.getFrequency("aaa"));
+        assertEquals(unigramProbability, binaryDictionary.getFrequency("bbb"));
+        assertTrue(binaryDictionary.isValidBigram("aaa", "bbb"));
+        assertEquals(fromFormatVersion, binaryDictionary.getFormatVersion());
+        assertTrue(binaryDictionary.migrateTo(toFormatVersion));
+        assertTrue(binaryDictionary.isValidDictionary());
+        assertEquals(toFormatVersion, binaryDictionary.getFormatVersion());
+        assertEquals(unigramProbability, binaryDictionary.getFrequency("aaa"));
+        assertEquals(unigramProbability, binaryDictionary.getFrequency("bbb"));
+        // TODO: Add tests for bigram frequency when the implementation gets ready.
+        assertTrue(binaryDictionary.isValidBigram("aaa", "bbb"));
+    }
 }
diff --git a/tools/dicttool/compat/com/android/inputmethod/event/CombinerChain.java b/tools/dicttool/compat/com/android/inputmethod/event/CombinerChain.java
index b68df1c..47f781e 100644
--- a/tools/dicttool/compat/com/android/inputmethod/event/CombinerChain.java
+++ b/tools/dicttool/compat/com/android/inputmethod/event/CombinerChain.java
@@ -20,6 +20,13 @@
 
 import java.util.ArrayList;
 
+/**
+ * Compatibility class that stands in for the combiner chain in LatinIME.
+ *
+ * This is not used by dicttool, it's just needed by the dependency chain.
+ */
+// TODO: there should not be a dependency to this in dicttool, so there
+// should be a sensible way to separate them cleanly.
 public class CombinerChain {
     private StringBuilder mComposingWord = new StringBuilder();
     public CombinerChain(final Combiner... combinerList) {}
@@ -35,4 +42,9 @@
     public void reset() {
         mComposingWord.setLength(0);
     }
+
+    public static Combiner[] createCombiners(final String spec) {
+        // Dicttool never uses a combiner at all, so we just return a zero-sized array.
+        return new Combiner[0];
+    }
 }
diff --git a/tools/make-keyboard-text/res/values-en/donottranslate-more-keys.xml b/tools/make-keyboard-text/res/values-en/donottranslate-more-keys.xml
index 7998bf1..c22edba 100644
--- a/tools/make-keyboard-text/res/values-en/donottranslate-more-keys.xml
+++ b/tools/make-keyboard-text/res/values-en/donottranslate-more-keys.xml
@@ -27,33 +27,33 @@
          U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
          U+0101: "ā" LATIN SMALL LETTER A WITH MACRON -->
     <string name="morekeys_a">&#x00E0;,&#x00E1;,&#x00E2;,&#x00E4;,&#x00E6;,&#x00E3;,&#x00E5;,&#x0101;</string>
-    <!-- U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
-         U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
+    <!-- U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
+         U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
          U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
          U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
          U+0113: "ē" LATIN SMALL LETTER E WITH MACRON -->
-    <string name="morekeys_e">&#x00E8;,&#x00E9;,&#x00EA;,&#x00EB;,&#x0113;</string>
-    <!-- U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
+    <string name="morekeys_e">&#x00E9;,&#x00E8;,&#x00EA;,&#x00EB;,&#x0113;</string>
+    <!-- U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
+         U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
          U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
-         U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
          U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
          U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE -->
-    <string name="morekeys_i">&#x00EE;,&#x00EF;,&#x00ED;,&#x012B;,&#x00EC;</string>
-    <!-- U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
+    <string name="morekeys_i">&#x00ED;,&#x00EE;,&#x00EF;,&#x012B;,&#x00EC;</string>
+    <!-- U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
+         U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
          U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
          U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE
-         U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
          U+0153: "œ" LATIN SMALL LIGATURE OE
          U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
          U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
          U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE -->
-    <string name="morekeys_o">&#x00F4;,&#x00F6;,&#x00F2;,&#x00F3;,&#x0153;,&#x00F8;,&#x014D;,&#x00F5;</string>
-    <!-- U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
+    <string name="morekeys_o">&#x00F3;,&#x00F4;,&#x00F6;,&#x00F2;,&#x0153;,&#x00F8;,&#x014D;,&#x00F5;</string>
+    <!-- U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
+         U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
          U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
          U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
-         U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
          U+016B: "ū" LATIN SMALL LETTER U WITH MACRON -->
-    <string name="morekeys_u">&#x00FB;,&#x00FC;,&#x00F9;,&#x00FA;,&#x016B;</string>
+    <string name="morekeys_u">&#x00FA;,&#x00FB;,&#x00FC;,&#x00F9;,&#x016B;</string>
     <!-- U+00DF: "ß" LATIN SMALL LETTER SHARP S -->
     <string name="morekeys_s">&#x00DF;</string>
     <!-- U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE -->
diff --git a/tools/make-keyboard-text/res/values-zu/donottranslate-more-keys.xml b/tools/make-keyboard-text/res/values-zu/donottranslate-more-keys.xml
index f9150f3..2c5df0c 100644
--- a/tools/make-keyboard-text/res/values-zu/donottranslate-more-keys.xml
+++ b/tools/make-keyboard-text/res/values-zu/donottranslate-more-keys.xml
@@ -28,33 +28,33 @@
          U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
          U+0101: "ā" LATIN SMALL LETTER A WITH MACRON -->
     <string name="morekeys_a">&#x00E0;,&#x00E1;,&#x00E2;,&#x00E4;,&#x00E6;,&#x00E3;,&#x00E5;,&#x0101;</string>
-    <!-- U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
-         U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
+    <!-- U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
+         U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
          U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
          U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
          U+0113: "ē" LATIN SMALL LETTER E WITH MACRON -->
-    <string name="morekeys_e">&#x00E8;,&#x00E9;,&#x00EA;,&#x00EB;,&#x0113;</string>
-    <!-- U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
+    <string name="morekeys_e">&#x00E9;,&#x00E8;,&#x00EA;,&#x00EB;,&#x0113;</string>
+    <!-- U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
+         U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
          U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
-         U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
          U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
          U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE -->
-    <string name="morekeys_i">&#x00EE;,&#x00EF;,&#x00ED;,&#x012B;,&#x00EC;</string>
-    <!-- U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
+    <string name="morekeys_i">&#x00ED;,&#x00EE;,&#x00EF;,&#x012B;,&#x00EC;</string>
+    <!-- U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
+         U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
          U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
          U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE
-         U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
          U+0153: "œ" LATIN SMALL LIGATURE OE
          U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
          U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
          U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE -->
-    <string name="morekeys_o">&#x00F4;,&#x00F6;,&#x00F2;,&#x00F3;,&#x0153;,&#x00F8;,&#x014D;,&#x00F5;</string>
-    <!-- U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
+    <string name="morekeys_o">&#x00F3;,&#x00F4;,&#x00F6;,&#x00F2;,&#x0153;,&#x00F8;,&#x014D;,&#x00F5;</string>
+    <!-- U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
+         U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
          U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
          U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
-         U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
          U+016B: "ū" LATIN SMALL LETTER U WITH MACRON -->
-    <string name="morekeys_u">&#x00FB;,&#x00FC;,&#x00F9;,&#x00FA;,&#x016B;</string>
+    <string name="morekeys_u">&#x00FA;,&#x00FB;,&#x00FC;,&#x00F9;,&#x016B;</string>
     <!-- U+00DF: "ß" LATIN SMALL LETTER SHARP S -->
     <string name="morekeys_s">&#x00DF;</string>
     <!-- U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE -->