am aad3cb80: (-s ours) am ade16493: Merge "Include horizontal and vertical gap in the count of Key.isOnKey" into honeycomb-mr2

* commit 'aad3cb8059a295f15c45680e3fb0eba96b4436c5':
  Include horizontal and vertical gap in the count of Key.isOnKey
diff --git a/java/AndroidManifest.xml b/java/AndroidManifest.xml
index e0eecfc..2fbf4c2 100644
--- a/java/AndroidManifest.xml
+++ b/java/AndroidManifest.xml
@@ -33,7 +33,7 @@
             </intent-filter>
         </activity>
 
-        <activity android:name="InputLanguageSelection"
+        <activity android:name="com.android.inputmethod.deprecated.languageswitcher.InputLanguageSelection"
                 android:label="@string/language_selection_title">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
diff --git a/java/proguard.flags b/java/proguard.flags
index 729f4ad..914bd75 100644
--- a/java/proguard.flags
+++ b/java/proguard.flags
@@ -18,3 +18,7 @@
 -keep class com.android.inputmethod.latin.AutoCorrection {
   java.lang.CharSequence getAutoCorrectionWord();
 }
+
+-keep class com.android.inputmethod.latin.Utils {
+  boolean equalsIgnoreCase(...);
+}
diff --git a/java/res/anim/key_preview_fadein.xml b/java/res/anim/key_preview_fadein.xml
deleted file mode 100644
index 9fad7b9..0000000
--- a/java/res/anim/key_preview_fadein.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2010, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<set
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:interpolator="@android:anim/decelerate_interpolator"
->
-    <alpha
-        android:fromAlpha="0.5"
-        android:toAlpha="1.0"
-        android:duration="@integer/config_preview_fadein_anim_time" />
-</set>
diff --git a/java/res/anim/key_preview_fadeout.xml b/java/res/anim/key_preview_fadeout.xml
deleted file mode 100644
index 7de5123..0000000
--- a/java/res/anim/key_preview_fadeout.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2010, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<set
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:interpolator="@android:anim/accelerate_interpolator"
->
-    <alpha
-        android:fromAlpha="1.0"
-        android:toAlpha="0.0"
-        android:duration="@integer/config_preview_fadeout_anim_time" />
-</set>
diff --git a/java/res/anim/mini_keyboard_fadein.xml b/java/res/anim/mini_keyboard_fadein.xml
index 9fad7b9..f80e8b8 100644
--- a/java/res/anim/mini_keyboard_fadein.xml
+++ b/java/res/anim/mini_keyboard_fadein.xml
@@ -25,5 +25,5 @@
     <alpha
         android:fromAlpha="0.5"
         android:toAlpha="1.0"
-        android:duration="@integer/config_preview_fadein_anim_time" />
+        android:duration="@integer/config_mini_keyboard_fadein_anim_time" />
 </set>
diff --git a/java/res/anim/mini_keyboard_fadeout.xml b/java/res/anim/mini_keyboard_fadeout.xml
index 7de5123..535b100 100644
--- a/java/res/anim/mini_keyboard_fadeout.xml
+++ b/java/res/anim/mini_keyboard_fadeout.xml
@@ -25,5 +25,5 @@
     <alpha
         android:fromAlpha="1.0"
         android:toAlpha="0.0"
-        android:duration="@integer/config_preview_fadeout_anim_time" />
+        android:duration="@integer/config_mini_keyboard_fadeout_anim_time" />
 </set>
diff --git a/java/res/drawable-hdpi/btn_candidate_normal.9.png b/java/res/drawable-hdpi/btn_candidate_normal.9.png
deleted file mode 100644
index 7cab5a8..0000000
--- a/java/res/drawable-hdpi/btn_candidate_normal.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-hdpi/btn_keyboard_key_dark_normal.9.png b/java/res/drawable-hdpi/btn_keyboard_key_dark_normal.9.png
index 50cc49f..01fc8ca 100644
--- a/java/res/drawable-hdpi/btn_keyboard_key_dark_normal.9.png
+++ b/java/res/drawable-hdpi/btn_keyboard_key_dark_normal.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/btn_keyboard_key_dark_normal_off.9.png b/java/res/drawable-hdpi/btn_keyboard_key_dark_normal_off.9.png
index dabf77e..af4017e 100644
--- a/java/res/drawable-hdpi/btn_keyboard_key_dark_normal_off.9.png
+++ b/java/res/drawable-hdpi/btn_keyboard_key_dark_normal_off.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/btn_keyboard_key_dark_normal_on.9.png b/java/res/drawable-hdpi/btn_keyboard_key_dark_normal_on.9.png
index 6e7d74c..4c35aca 100644
--- a/java/res/drawable-hdpi/btn_keyboard_key_dark_normal_on.9.png
+++ b/java/res/drawable-hdpi/btn_keyboard_key_dark_normal_on.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/btn_keyboard_key_dark_pressed.9.png b/java/res/drawable-hdpi/btn_keyboard_key_dark_pressed.9.png
index ddb77c2..174f345 100644
--- a/java/res/drawable-hdpi/btn_keyboard_key_dark_pressed.9.png
+++ b/java/res/drawable-hdpi/btn_keyboard_key_dark_pressed.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/btn_keyboard_key_dark_pressed_off.9.png b/java/res/drawable-hdpi/btn_keyboard_key_dark_pressed_off.9.png
index 1e9227e..1fcbd9a 100644
--- a/java/res/drawable-hdpi/btn_keyboard_key_dark_pressed_off.9.png
+++ b/java/res/drawable-hdpi/btn_keyboard_key_dark_pressed_off.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/btn_keyboard_key_dark_pressed_on.9.png b/java/res/drawable-hdpi/btn_keyboard_key_dark_pressed_on.9.png
index 7207b2e..072753f 100644
--- a/java/res/drawable-hdpi/btn_keyboard_key_dark_pressed_on.9.png
+++ b/java/res/drawable-hdpi/btn_keyboard_key_dark_pressed_on.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/btn_keyboard_key_fulltrans_normal.9.png b/java/res/drawable-hdpi/btn_keyboard_key_fulltrans_normal.9.png
index a524168..b6c234c 100644
--- a/java/res/drawable-hdpi/btn_keyboard_key_fulltrans_normal.9.png
+++ b/java/res/drawable-hdpi/btn_keyboard_key_fulltrans_normal.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/btn_keyboard_key_fulltrans_pressed.9.png b/java/res/drawable-hdpi/btn_keyboard_key_fulltrans_pressed.9.png
index 4395e97..73a8cd1 100644
--- a/java/res/drawable-hdpi/btn_keyboard_key_fulltrans_pressed.9.png
+++ b/java/res/drawable-hdpi/btn_keyboard_key_fulltrans_pressed.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/btn_keyboard_key_light_normal.9.png b/java/res/drawable-hdpi/btn_keyboard_key_light_normal.9.png
index 9d85c7b..1ad7460 100644
--- a/java/res/drawable-hdpi/btn_keyboard_key_light_normal.9.png
+++ b/java/res/drawable-hdpi/btn_keyboard_key_light_normal.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/btn_keyboard_key_light_popup_normal.9.png b/java/res/drawable-hdpi/btn_keyboard_key_light_popup_normal.9.png
index 2ed1b34..e3a77d6 100644
--- a/java/res/drawable-hdpi/btn_keyboard_key_light_popup_normal.9.png
+++ b/java/res/drawable-hdpi/btn_keyboard_key_light_popup_normal.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/btn_keyboard_key_light_popup_selected.9.png b/java/res/drawable-hdpi/btn_keyboard_key_light_popup_selected.9.png
index 77e17db..431c449 100644
--- a/java/res/drawable-hdpi/btn_keyboard_key_light_popup_selected.9.png
+++ b/java/res/drawable-hdpi/btn_keyboard_key_light_popup_selected.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/btn_keyboard_key_light_pressed.9.png b/java/res/drawable-hdpi/btn_keyboard_key_light_pressed.9.png
index a409639..ccd59d5 100644
--- a/java/res/drawable-hdpi/btn_keyboard_key_light_pressed.9.png
+++ b/java/res/drawable-hdpi/btn_keyboard_key_light_pressed.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/btn_keyboard_key_normal.9.png b/java/res/drawable-hdpi/btn_keyboard_key_normal.9.png
index 6ec7e65..42c7c14 100644
--- a/java/res/drawable-hdpi/btn_keyboard_key_normal.9.png
+++ b/java/res/drawable-hdpi/btn_keyboard_key_normal.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/btn_keyboard_key_normal_off.9.png b/java/res/drawable-hdpi/btn_keyboard_key_normal_off.9.png
index 995780c..01e2506 100644
--- a/java/res/drawable-hdpi/btn_keyboard_key_normal_off.9.png
+++ b/java/res/drawable-hdpi/btn_keyboard_key_normal_off.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/btn_keyboard_key_normal_off_stone.9.png b/java/res/drawable-hdpi/btn_keyboard_key_normal_off_stone.9.png
index 1388b66..fad0ec4 100644
--- a/java/res/drawable-hdpi/btn_keyboard_key_normal_off_stone.9.png
+++ b/java/res/drawable-hdpi/btn_keyboard_key_normal_off_stone.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/btn_keyboard_key_normal_on.9.png b/java/res/drawable-hdpi/btn_keyboard_key_normal_on.9.png
index 7215782..83c6eb3 100644
--- a/java/res/drawable-hdpi/btn_keyboard_key_normal_on.9.png
+++ b/java/res/drawable-hdpi/btn_keyboard_key_normal_on.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/btn_keyboard_key_normal_on_stone.9.png b/java/res/drawable-hdpi/btn_keyboard_key_normal_on_stone.9.png
index 5a94cb6..215f815 100644
--- a/java/res/drawable-hdpi/btn_keyboard_key_normal_on_stone.9.png
+++ b/java/res/drawable-hdpi/btn_keyboard_key_normal_on_stone.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/btn_keyboard_key_normal_stone.9.png b/java/res/drawable-hdpi/btn_keyboard_key_normal_stone.9.png
index c6373a8..88acdd7 100644
--- a/java/res/drawable-hdpi/btn_keyboard_key_normal_stone.9.png
+++ b/java/res/drawable-hdpi/btn_keyboard_key_normal_stone.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/btn_keyboard_key_pressed.9.png b/java/res/drawable-hdpi/btn_keyboard_key_pressed.9.png
index 0bd49a0..e047eaf 100644
--- a/java/res/drawable-hdpi/btn_keyboard_key_pressed.9.png
+++ b/java/res/drawable-hdpi/btn_keyboard_key_pressed.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/btn_keyboard_key_pressed_off.9.png b/java/res/drawable-hdpi/btn_keyboard_key_pressed_off.9.png
index 634419f..218a2d2 100644
--- a/java/res/drawable-hdpi/btn_keyboard_key_pressed_off.9.png
+++ b/java/res/drawable-hdpi/btn_keyboard_key_pressed_off.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/btn_keyboard_key_pressed_on.9.png b/java/res/drawable-hdpi/btn_keyboard_key_pressed_on.9.png
index 8474f9f..afe4951 100644
--- a/java/res/drawable-hdpi/btn_keyboard_key_pressed_on.9.png
+++ b/java/res/drawable-hdpi/btn_keyboard_key_pressed_on.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/cancel.png b/java/res/drawable-hdpi/cancel.png
index fdf01db..506cf99 100644
--- a/java/res/drawable-hdpi/cancel.png
+++ b/java/res/drawable-hdpi/cancel.png
Binary files differ
diff --git a/java/res/drawable-hdpi/candidate_feedback_background.9.png b/java/res/drawable-hdpi/candidate_feedback_background.9.png
index 1649900..203c4e6 100644
--- a/java/res/drawable-hdpi/candidate_feedback_background.9.png
+++ b/java/res/drawable-hdpi/candidate_feedback_background.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/caution.png b/java/res/drawable-hdpi/caution.png
index caed941..5cb6c54 100644
--- a/java/res/drawable-hdpi/caution.png
+++ b/java/res/drawable-hdpi/caution.png
Binary files differ
diff --git a/java/res/drawable-hdpi/dialog_bubble_step02.9.png b/java/res/drawable-hdpi/dialog_bubble_step02.9.png
index 2a3ac18..b338364 100644
--- a/java/res/drawable-hdpi/dialog_bubble_step02.9.png
+++ b/java/res/drawable-hdpi/dialog_bubble_step02.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/dialog_bubble_step07.9.png b/java/res/drawable-hdpi/dialog_bubble_step07.9.png
index 0a5046b..94b9154 100644
--- a/java/res/drawable-hdpi/dialog_bubble_step07.9.png
+++ b/java/res/drawable-hdpi/dialog_bubble_step07.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/highlight_pressed.png b/java/res/drawable-hdpi/highlight_pressed.png
deleted file mode 100644
index 638df19..0000000
--- a/java/res/drawable-hdpi/highlight_pressed.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-hdpi/hint_popup.9.png b/java/res/drawable-hdpi/hint_popup.9.png
index 5b2ad53..b5ec003 100644
--- a/java/res/drawable-hdpi/hint_popup.9.png
+++ b/java/res/drawable-hdpi/hint_popup.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/ic_dialog_keyboard.png b/java/res/drawable-hdpi/ic_dialog_keyboard.png
index fb6d898..c772956 100644
--- a/java/res/drawable-hdpi/ic_dialog_keyboard.png
+++ b/java/res/drawable-hdpi/ic_dialog_keyboard.png
Binary files differ
diff --git a/java/res/drawable-hdpi/ic_mic_dialog.png b/java/res/drawable-hdpi/ic_mic_dialog.png
index 6498cd5..349dc4b 100644
--- a/java/res/drawable-hdpi/ic_mic_dialog.png
+++ b/java/res/drawable-hdpi/ic_mic_dialog.png
Binary files differ
diff --git a/java/res/drawable-hdpi/ic_subtype_keyboard.png b/java/res/drawable-hdpi/ic_subtype_keyboard.png
index b5a9fa8..7015e26 100644
--- a/java/res/drawable-hdpi/ic_subtype_keyboard.png
+++ b/java/res/drawable-hdpi/ic_subtype_keyboard.png
Binary files differ
diff --git a/java/res/drawable-hdpi/ic_subtype_mic.png b/java/res/drawable-hdpi/ic_subtype_mic.png
index 5d68e85..cb86a55 100644
--- a/java/res/drawable-hdpi/ic_subtype_mic.png
+++ b/java/res/drawable-hdpi/ic_subtype_mic.png
Binary files differ
diff --git a/java/res/drawable-hdpi/ic_suggest_strip_microphone.png b/java/res/drawable-hdpi/ic_suggest_strip_microphone.png
index 0462bdd..c00b4aa 100644
--- a/java/res/drawable-hdpi/ic_suggest_strip_microphone.png
+++ b/java/res/drawable-hdpi/ic_suggest_strip_microphone.png
Binary files differ
diff --git a/java/res/drawable-hdpi/ic_suggest_strip_microphone_swipe.png b/java/res/drawable-hdpi/ic_suggest_strip_microphone_swipe.png
index 80c20f69..256dc3d 100644
--- a/java/res/drawable-hdpi/ic_suggest_strip_microphone_swipe.png
+++ b/java/res/drawable-hdpi/ic_suggest_strip_microphone_swipe.png
Binary files differ
diff --git a/java/res/drawable-hdpi/keyboard_background.9.png b/java/res/drawable-hdpi/keyboard_background.9.png
index d57463f..edffac5 100644
--- a/java/res/drawable-hdpi/keyboard_background.9.png
+++ b/java/res/drawable-hdpi/keyboard_background.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/keyboard_dark_background.9.png b/java/res/drawable-hdpi/keyboard_dark_background.9.png
index fa3d449..f315cbd 100644
--- a/java/res/drawable-hdpi/keyboard_dark_background.9.png
+++ b/java/res/drawable-hdpi/keyboard_dark_background.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/keyboard_hint_0.9.png b/java/res/drawable-hdpi/keyboard_hint_0.9.png
index da52e0f..271264e 100644
--- a/java/res/drawable-hdpi/keyboard_hint_0.9.png
+++ b/java/res/drawable-hdpi/keyboard_hint_0.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/keyboard_hint_1.9.png b/java/res/drawable-hdpi/keyboard_hint_1.9.png
index 7325c4c..eaf3742 100644
--- a/java/res/drawable-hdpi/keyboard_hint_1.9.png
+++ b/java/res/drawable-hdpi/keyboard_hint_1.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/keyboard_hint_2.9.png b/java/res/drawable-hdpi/keyboard_hint_2.9.png
index 35b7f25..8a16571 100644
--- a/java/res/drawable-hdpi/keyboard_hint_2.9.png
+++ b/java/res/drawable-hdpi/keyboard_hint_2.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/keyboard_hint_3.9.png b/java/res/drawable-hdpi/keyboard_hint_3.9.png
index 1ae2848..34b5011 100644
--- a/java/res/drawable-hdpi/keyboard_hint_3.9.png
+++ b/java/res/drawable-hdpi/keyboard_hint_3.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/keyboard_hint_4.9.png b/java/res/drawable-hdpi/keyboard_hint_4.9.png
index b67d6dd..d4cc250 100644
--- a/java/res/drawable-hdpi/keyboard_hint_4.9.png
+++ b/java/res/drawable-hdpi/keyboard_hint_4.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/keyboard_hint_5.9.png b/java/res/drawable-hdpi/keyboard_hint_5.9.png
index ec52198..6a054b4 100644
--- a/java/res/drawable-hdpi/keyboard_hint_5.9.png
+++ b/java/res/drawable-hdpi/keyboard_hint_5.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/keyboard_hint_6.9.png b/java/res/drawable-hdpi/keyboard_hint_6.9.png
index 66dcf67..66e9140 100644
--- a/java/res/drawable-hdpi/keyboard_hint_6.9.png
+++ b/java/res/drawable-hdpi/keyboard_hint_6.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/keyboard_hint_7.9.png b/java/res/drawable-hdpi/keyboard_hint_7.9.png
index 9d54992..5eae24f 100644
--- a/java/res/drawable-hdpi/keyboard_hint_7.9.png
+++ b/java/res/drawable-hdpi/keyboard_hint_7.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/keyboard_hint_8.9.png b/java/res/drawable-hdpi/keyboard_hint_8.9.png
index beba162..ea7f512 100644
--- a/java/res/drawable-hdpi/keyboard_hint_8.9.png
+++ b/java/res/drawable-hdpi/keyboard_hint_8.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/keyboard_hint_9.9.png b/java/res/drawable-hdpi/keyboard_hint_9.9.png
index 31ea54f..0bf85de 100644
--- a/java/res/drawable-hdpi/keyboard_hint_9.9.png
+++ b/java/res/drawable-hdpi/keyboard_hint_9.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/keyboard_key_feedback_background.9.png b/java/res/drawable-hdpi/keyboard_key_feedback_background.9.png
index 27d9923..762a257 100644
--- a/java/res/drawable-hdpi/keyboard_key_feedback_background.9.png
+++ b/java/res/drawable-hdpi/keyboard_key_feedback_background.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/keyboard_key_feedback_background_holo.9.png b/java/res/drawable-hdpi/keyboard_key_feedback_background_holo.9.png
index 943f9e4..8d6acac 100644
--- a/java/res/drawable-hdpi/keyboard_key_feedback_background_holo.9.png
+++ b/java/res/drawable-hdpi/keyboard_key_feedback_background_holo.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/keyboard_key_feedback_more_background.9.png b/java/res/drawable-hdpi/keyboard_key_feedback_more_background.9.png
index 33263b9..141d2d6 100644
--- a/java/res/drawable-hdpi/keyboard_key_feedback_more_background.9.png
+++ b/java/res/drawable-hdpi/keyboard_key_feedback_more_background.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/keyboard_popup_panel_background.9.png b/java/res/drawable-hdpi/keyboard_popup_panel_background.9.png
index baff809..d6b2c79 100644
--- a/java/res/drawable-hdpi/keyboard_popup_panel_background.9.png
+++ b/java/res/drawable-hdpi/keyboard_popup_panel_background.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/keyboard_suggest_strip.9.png b/java/res/drawable-hdpi/keyboard_suggest_strip.9.png
index 7cab5a8..0ccdb6a 100644
--- a/java/res/drawable-hdpi/keyboard_suggest_strip.9.png
+++ b/java/res/drawable-hdpi/keyboard_suggest_strip.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/keyboard_suggest_strip_divider.png b/java/res/drawable-hdpi/keyboard_suggest_strip_divider.png
index 7fca8c6..7ca3e61 100644
--- a/java/res/drawable-hdpi/keyboard_suggest_strip_divider.png
+++ b/java/res/drawable-hdpi/keyboard_suggest_strip_divider.png
Binary files differ
diff --git a/java/res/drawable-hdpi/mic_slash.png b/java/res/drawable-hdpi/mic_slash.png
index 71f4dc5..dc8da62 100644
--- a/java/res/drawable-hdpi/mic_slash.png
+++ b/java/res/drawable-hdpi/mic_slash.png
Binary files differ
diff --git a/java/res/drawable-hdpi/ok_cancel.png b/java/res/drawable-hdpi/ok_cancel.png
index 48c00f0..f11e57a 100644
--- a/java/res/drawable-hdpi/ok_cancel.png
+++ b/java/res/drawable-hdpi/ok_cancel.png
Binary files differ
diff --git a/java/res/drawable-hdpi/speak_now_level0.png b/java/res/drawable-hdpi/speak_now_level0.png
index 31571f7..342849c 100644
--- a/java/res/drawable-hdpi/speak_now_level0.png
+++ b/java/res/drawable-hdpi/speak_now_level0.png
Binary files differ
diff --git a/java/res/drawable-hdpi/speak_now_level1.png b/java/res/drawable-hdpi/speak_now_level1.png
index c8d0aae..8947a43 100644
--- a/java/res/drawable-hdpi/speak_now_level1.png
+++ b/java/res/drawable-hdpi/speak_now_level1.png
Binary files differ
diff --git a/java/res/drawable-hdpi/speak_now_level2.png b/java/res/drawable-hdpi/speak_now_level2.png
index 123eea6..44fc58c 100644
--- a/java/res/drawable-hdpi/speak_now_level2.png
+++ b/java/res/drawable-hdpi/speak_now_level2.png
Binary files differ
diff --git a/java/res/drawable-hdpi/speak_now_level3.png b/java/res/drawable-hdpi/speak_now_level3.png
index a8a2c5c..cfa5c1b 100644
--- a/java/res/drawable-hdpi/speak_now_level3.png
+++ b/java/res/drawable-hdpi/speak_now_level3.png
Binary files differ
diff --git a/java/res/drawable-hdpi/speak_now_level4.png b/java/res/drawable-hdpi/speak_now_level4.png
index b84d7b0..a050d88 100644
--- a/java/res/drawable-hdpi/speak_now_level4.png
+++ b/java/res/drawable-hdpi/speak_now_level4.png
Binary files differ
diff --git a/java/res/drawable-hdpi/speak_now_level5.png b/java/res/drawable-hdpi/speak_now_level5.png
index 8dd2b60..8cd5ae7 100644
--- a/java/res/drawable-hdpi/speak_now_level5.png
+++ b/java/res/drawable-hdpi/speak_now_level5.png
Binary files differ
diff --git a/java/res/drawable-hdpi/speak_now_level6.png b/java/res/drawable-hdpi/speak_now_level6.png
index 888d0e5..9f4481e 100644
--- a/java/res/drawable-hdpi/speak_now_level6.png
+++ b/java/res/drawable-hdpi/speak_now_level6.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_bkeyboard_123_mic.png b/java/res/drawable-hdpi/sym_bkeyboard_123_mic.png
index 24edfaa..3e4eff6 100644
--- a/java/res/drawable-hdpi/sym_bkeyboard_123_mic.png
+++ b/java/res/drawable-hdpi/sym_bkeyboard_123_mic.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_bkeyboard_delete.png b/java/res/drawable-hdpi/sym_bkeyboard_delete.png
index 4ccd218..1d24cc8 100644
--- a/java/res/drawable-hdpi/sym_bkeyboard_delete.png
+++ b/java/res/drawable-hdpi/sym_bkeyboard_delete.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_bkeyboard_done.png b/java/res/drawable-hdpi/sym_bkeyboard_done.png
index 6959aee..b77803d 100644
--- a/java/res/drawable-hdpi/sym_bkeyboard_done.png
+++ b/java/res/drawable-hdpi/sym_bkeyboard_done.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_bkeyboard_mic.png b/java/res/drawable-hdpi/sym_bkeyboard_mic.png
index 6876fb6..512f460 100644
--- a/java/res/drawable-hdpi/sym_bkeyboard_mic.png
+++ b/java/res/drawable-hdpi/sym_bkeyboard_mic.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_bkeyboard_num0.png b/java/res/drawable-hdpi/sym_bkeyboard_num0.png
index 08df3f3..678a790 100644
--- a/java/res/drawable-hdpi/sym_bkeyboard_num0.png
+++ b/java/res/drawable-hdpi/sym_bkeyboard_num0.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_bkeyboard_num1.png b/java/res/drawable-hdpi/sym_bkeyboard_num1.png
index 36d8e56..4e68e35 100644
--- a/java/res/drawable-hdpi/sym_bkeyboard_num1.png
+++ b/java/res/drawable-hdpi/sym_bkeyboard_num1.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_bkeyboard_num2.png b/java/res/drawable-hdpi/sym_bkeyboard_num2.png
index c67fe2e..546663f 100644
--- a/java/res/drawable-hdpi/sym_bkeyboard_num2.png
+++ b/java/res/drawable-hdpi/sym_bkeyboard_num2.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_bkeyboard_num3.png b/java/res/drawable-hdpi/sym_bkeyboard_num3.png
index cf80b27..57f9a8d 100644
--- a/java/res/drawable-hdpi/sym_bkeyboard_num3.png
+++ b/java/res/drawable-hdpi/sym_bkeyboard_num3.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_bkeyboard_num4.png b/java/res/drawable-hdpi/sym_bkeyboard_num4.png
index bfbb55a..de50438 100644
--- a/java/res/drawable-hdpi/sym_bkeyboard_num4.png
+++ b/java/res/drawable-hdpi/sym_bkeyboard_num4.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_bkeyboard_num5.png b/java/res/drawable-hdpi/sym_bkeyboard_num5.png
index 9f121ec..1d2e1ef 100644
--- a/java/res/drawable-hdpi/sym_bkeyboard_num5.png
+++ b/java/res/drawable-hdpi/sym_bkeyboard_num5.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_bkeyboard_num6.png b/java/res/drawable-hdpi/sym_bkeyboard_num6.png
index 256186f..39788b7 100644
--- a/java/res/drawable-hdpi/sym_bkeyboard_num6.png
+++ b/java/res/drawable-hdpi/sym_bkeyboard_num6.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_bkeyboard_num7.png b/java/res/drawable-hdpi/sym_bkeyboard_num7.png
index 7c8ce20..fff6f27 100644
--- a/java/res/drawable-hdpi/sym_bkeyboard_num7.png
+++ b/java/res/drawable-hdpi/sym_bkeyboard_num7.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_bkeyboard_num8.png b/java/res/drawable-hdpi/sym_bkeyboard_num8.png
index 4cfe7b1..8cc1a95 100644
--- a/java/res/drawable-hdpi/sym_bkeyboard_num8.png
+++ b/java/res/drawable-hdpi/sym_bkeyboard_num8.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_bkeyboard_num9.png b/java/res/drawable-hdpi/sym_bkeyboard_num9.png
index d19c15c..0217425 100644
--- a/java/res/drawable-hdpi/sym_bkeyboard_num9.png
+++ b/java/res/drawable-hdpi/sym_bkeyboard_num9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_bkeyboard_numalt.png b/java/res/drawable-hdpi/sym_bkeyboard_numalt.png
index 762fd8c..200714f 100644
--- a/java/res/drawable-hdpi/sym_bkeyboard_numalt.png
+++ b/java/res/drawable-hdpi/sym_bkeyboard_numalt.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_bkeyboard_numpound.png b/java/res/drawable-hdpi/sym_bkeyboard_numpound.png
index 2bd800d..0a46122 100644
--- a/java/res/drawable-hdpi/sym_bkeyboard_numpound.png
+++ b/java/res/drawable-hdpi/sym_bkeyboard_numpound.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_bkeyboard_numstar.png b/java/res/drawable-hdpi/sym_bkeyboard_numstar.png
index b574f83..ca22bd5 100644
--- a/java/res/drawable-hdpi/sym_bkeyboard_numstar.png
+++ b/java/res/drawable-hdpi/sym_bkeyboard_numstar.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_bkeyboard_return.png b/java/res/drawable-hdpi/sym_bkeyboard_return.png
index 2f9631a..426e159 100644
--- a/java/res/drawable-hdpi/sym_bkeyboard_return.png
+++ b/java/res/drawable-hdpi/sym_bkeyboard_return.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_bkeyboard_search.png b/java/res/drawable-hdpi/sym_bkeyboard_search.png
index 7a5a0aa..1b6f884 100644
--- a/java/res/drawable-hdpi/sym_bkeyboard_search.png
+++ b/java/res/drawable-hdpi/sym_bkeyboard_search.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_bkeyboard_settings.png b/java/res/drawable-hdpi/sym_bkeyboard_settings.png
index 8a8caa8..08ba18f 100644
--- a/java/res/drawable-hdpi/sym_bkeyboard_settings.png
+++ b/java/res/drawable-hdpi/sym_bkeyboard_settings.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_bkeyboard_shift.png b/java/res/drawable-hdpi/sym_bkeyboard_shift.png
index 1e3d5ec..5a22dd3 100644
--- a/java/res/drawable-hdpi/sym_bkeyboard_shift.png
+++ b/java/res/drawable-hdpi/sym_bkeyboard_shift.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_bkeyboard_shift_locked.png b/java/res/drawable-hdpi/sym_bkeyboard_shift_locked.png
index e8a4d64..5664491 100644
--- a/java/res/drawable-hdpi/sym_bkeyboard_shift_locked.png
+++ b/java/res/drawable-hdpi/sym_bkeyboard_shift_locked.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_bkeyboard_space.png b/java/res/drawable-hdpi/sym_bkeyboard_space.png
index 9937a62..cd0ebe2 100644
--- a/java/res/drawable-hdpi/sym_bkeyboard_space.png
+++ b/java/res/drawable-hdpi/sym_bkeyboard_space.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_bkeyboard_tab.png b/java/res/drawable-hdpi/sym_bkeyboard_tab.png
index 8dee747..3466e12 100644
--- a/java/res/drawable-hdpi/sym_bkeyboard_tab.png
+++ b/java/res/drawable-hdpi/sym_bkeyboard_tab.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_123_mic.png b/java/res/drawable-hdpi/sym_keyboard_123_mic.png
index 6f82929..6266980 100644
--- a/java/res/drawable-hdpi/sym_keyboard_123_mic.png
+++ b/java/res/drawable-hdpi/sym_keyboard_123_mic.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_delete.png b/java/res/drawable-hdpi/sym_keyboard_delete.png
index 8db099a..459ebcf 100644
--- a/java/res/drawable-hdpi/sym_keyboard_delete.png
+++ b/java/res/drawable-hdpi/sym_keyboard_delete.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_done.png b/java/res/drawable-hdpi/sym_keyboard_done.png
index 6ba51d5..471c502 100644
--- a/java/res/drawable-hdpi/sym_keyboard_done.png
+++ b/java/res/drawable-hdpi/sym_keyboard_done.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_feedback_123_mic.png b/java/res/drawable-hdpi/sym_keyboard_feedback_123_mic.png
index 4867298..eef7896 100644
--- a/java/res/drawable-hdpi/sym_keyboard_feedback_123_mic.png
+++ b/java/res/drawable-hdpi/sym_keyboard_feedback_123_mic.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_feedback_delete.png b/java/res/drawable-hdpi/sym_keyboard_feedback_delete.png
index 7c12f79..8322e8e 100644
--- a/java/res/drawable-hdpi/sym_keyboard_feedback_delete.png
+++ b/java/res/drawable-hdpi/sym_keyboard_feedback_delete.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_feedback_done.png b/java/res/drawable-hdpi/sym_keyboard_feedback_done.png
index e79bbb3..7015e26 100644
--- a/java/res/drawable-hdpi/sym_keyboard_feedback_done.png
+++ b/java/res/drawable-hdpi/sym_keyboard_feedback_done.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_feedback_language_arrows_left.png b/java/res/drawable-hdpi/sym_keyboard_feedback_language_arrows_left.png
index 4f4923b..889477c 100644
--- a/java/res/drawable-hdpi/sym_keyboard_feedback_language_arrows_left.png
+++ b/java/res/drawable-hdpi/sym_keyboard_feedback_language_arrows_left.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_feedback_language_arrows_right.png b/java/res/drawable-hdpi/sym_keyboard_feedback_language_arrows_right.png
index ed2ebe6..b0f6d7f 100644
--- a/java/res/drawable-hdpi/sym_keyboard_feedback_language_arrows_right.png
+++ b/java/res/drawable-hdpi/sym_keyboard_feedback_language_arrows_right.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_feedback_mic.png b/java/res/drawable-hdpi/sym_keyboard_feedback_mic.png
index f228910..f82c33a 100644
--- a/java/res/drawable-hdpi/sym_keyboard_feedback_mic.png
+++ b/java/res/drawable-hdpi/sym_keyboard_feedback_mic.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_feedback_numalt.png b/java/res/drawable-hdpi/sym_keyboard_feedback_numalt.png
index bb69300..819236c 100644
--- a/java/res/drawable-hdpi/sym_keyboard_feedback_numalt.png
+++ b/java/res/drawable-hdpi/sym_keyboard_feedback_numalt.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_feedback_return.png b/java/res/drawable-hdpi/sym_keyboard_feedback_return.png
index 99fa13c..f038d3a 100644
--- a/java/res/drawable-hdpi/sym_keyboard_feedback_return.png
+++ b/java/res/drawable-hdpi/sym_keyboard_feedback_return.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_feedback_search.png b/java/res/drawable-hdpi/sym_keyboard_feedback_search.png
index c006866..337f9e4 100644
--- a/java/res/drawable-hdpi/sym_keyboard_feedback_search.png
+++ b/java/res/drawable-hdpi/sym_keyboard_feedback_search.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_feedback_settings.png b/java/res/drawable-hdpi/sym_keyboard_feedback_settings.png
index 5c685f9..8a02be0 100644
--- a/java/res/drawable-hdpi/sym_keyboard_feedback_settings.png
+++ b/java/res/drawable-hdpi/sym_keyboard_feedback_settings.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_feedback_shift.png b/java/res/drawable-hdpi/sym_keyboard_feedback_shift.png
index 5b91afb..abf15f8 100644
--- a/java/res/drawable-hdpi/sym_keyboard_feedback_shift.png
+++ b/java/res/drawable-hdpi/sym_keyboard_feedback_shift.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_feedback_shift_locked.png b/java/res/drawable-hdpi/sym_keyboard_feedback_shift_locked.png
index 77e6a5f..1fd822e 100644
--- a/java/res/drawable-hdpi/sym_keyboard_feedback_shift_locked.png
+++ b/java/res/drawable-hdpi/sym_keyboard_feedback_shift_locked.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_feedback_space.png b/java/res/drawable-hdpi/sym_keyboard_feedback_space.png
index 2d1b4a4..70debca 100644
--- a/java/res/drawable-hdpi/sym_keyboard_feedback_space.png
+++ b/java/res/drawable-hdpi/sym_keyboard_feedback_space.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_feedback_tab.png b/java/res/drawable-hdpi/sym_keyboard_feedback_tab.png
index 82280c6..d2efb16 100644
--- a/java/res/drawable-hdpi/sym_keyboard_feedback_tab.png
+++ b/java/res/drawable-hdpi/sym_keyboard_feedback_tab.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_language_arrows_left.png b/java/res/drawable-hdpi/sym_keyboard_language_arrows_left.png
index 34b8e93..dcc4bd5 100644
--- a/java/res/drawable-hdpi/sym_keyboard_language_arrows_left.png
+++ b/java/res/drawable-hdpi/sym_keyboard_language_arrows_left.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_language_arrows_right.png b/java/res/drawable-hdpi/sym_keyboard_language_arrows_right.png
index b6ea336..ecf61a9 100644
--- a/java/res/drawable-hdpi/sym_keyboard_language_arrows_right.png
+++ b/java/res/drawable-hdpi/sym_keyboard_language_arrows_right.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_mic.png b/java/res/drawable-hdpi/sym_keyboard_mic.png
index 7207f8a..c8dca62 100644
--- a/java/res/drawable-hdpi/sym_keyboard_mic.png
+++ b/java/res/drawable-hdpi/sym_keyboard_mic.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_num0.png b/java/res/drawable-hdpi/sym_keyboard_num0.png
index 169efe2..10ac70b 100644
--- a/java/res/drawable-hdpi/sym_keyboard_num0.png
+++ b/java/res/drawable-hdpi/sym_keyboard_num0.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_num1.png b/java/res/drawable-hdpi/sym_keyboard_num1.png
index 5b86848..0fc03ef 100644
--- a/java/res/drawable-hdpi/sym_keyboard_num1.png
+++ b/java/res/drawable-hdpi/sym_keyboard_num1.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_num2.png b/java/res/drawable-hdpi/sym_keyboard_num2.png
index ddbe219..283560b 100644
--- a/java/res/drawable-hdpi/sym_keyboard_num2.png
+++ b/java/res/drawable-hdpi/sym_keyboard_num2.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_num3.png b/java/res/drawable-hdpi/sym_keyboard_num3.png
index 1de90f3..9a3b329 100644
--- a/java/res/drawable-hdpi/sym_keyboard_num3.png
+++ b/java/res/drawable-hdpi/sym_keyboard_num3.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_num4.png b/java/res/drawable-hdpi/sym_keyboard_num4.png
index c67ba52..f13ff1a 100644
--- a/java/res/drawable-hdpi/sym_keyboard_num4.png
+++ b/java/res/drawable-hdpi/sym_keyboard_num4.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_num5.png b/java/res/drawable-hdpi/sym_keyboard_num5.png
index 8410f25..c251329 100644
--- a/java/res/drawable-hdpi/sym_keyboard_num5.png
+++ b/java/res/drawable-hdpi/sym_keyboard_num5.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_num6.png b/java/res/drawable-hdpi/sym_keyboard_num6.png
index 22fa29d..4acba4c 100644
--- a/java/res/drawable-hdpi/sym_keyboard_num6.png
+++ b/java/res/drawable-hdpi/sym_keyboard_num6.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_num7.png b/java/res/drawable-hdpi/sym_keyboard_num7.png
index a3798ea..14931c1 100644
--- a/java/res/drawable-hdpi/sym_keyboard_num7.png
+++ b/java/res/drawable-hdpi/sym_keyboard_num7.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_num8.png b/java/res/drawable-hdpi/sym_keyboard_num8.png
index 7e963ad..d4973fd 100644
--- a/java/res/drawable-hdpi/sym_keyboard_num8.png
+++ b/java/res/drawable-hdpi/sym_keyboard_num8.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_num9.png b/java/res/drawable-hdpi/sym_keyboard_num9.png
index 1160d85..49cec66 100644
--- a/java/res/drawable-hdpi/sym_keyboard_num9.png
+++ b/java/res/drawable-hdpi/sym_keyboard_num9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_numalt.png b/java/res/drawable-hdpi/sym_keyboard_numalt.png
index f3a73de..3cc5311 100644
--- a/java/res/drawable-hdpi/sym_keyboard_numalt.png
+++ b/java/res/drawable-hdpi/sym_keyboard_numalt.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_numpound.png b/java/res/drawable-hdpi/sym_keyboard_numpound.png
index 471f4fd..d091339 100644
--- a/java/res/drawable-hdpi/sym_keyboard_numpound.png
+++ b/java/res/drawable-hdpi/sym_keyboard_numpound.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_numstar.png b/java/res/drawable-hdpi/sym_keyboard_numstar.png
index 017c0f4..e838e16 100644
--- a/java/res/drawable-hdpi/sym_keyboard_numstar.png
+++ b/java/res/drawable-hdpi/sym_keyboard_numstar.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_return.png b/java/res/drawable-hdpi/sym_keyboard_return.png
index 984db42..9d97e1e 100644
--- a/java/res/drawable-hdpi/sym_keyboard_return.png
+++ b/java/res/drawable-hdpi/sym_keyboard_return.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_search.png b/java/res/drawable-hdpi/sym_keyboard_search.png
index 179e725..1aa22d7 100644
--- a/java/res/drawable-hdpi/sym_keyboard_search.png
+++ b/java/res/drawable-hdpi/sym_keyboard_search.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_settings.png b/java/res/drawable-hdpi/sym_keyboard_settings.png
index 1641178..35d1ed6 100644
--- a/java/res/drawable-hdpi/sym_keyboard_settings.png
+++ b/java/res/drawable-hdpi/sym_keyboard_settings.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_shift.png b/java/res/drawable-hdpi/sym_keyboard_shift.png
index 2b3bd66..bf217d1 100644
--- a/java/res/drawable-hdpi/sym_keyboard_shift.png
+++ b/java/res/drawable-hdpi/sym_keyboard_shift.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_shift_locked.png b/java/res/drawable-hdpi/sym_keyboard_shift_locked.png
index 8a34a98..d11b397 100644
--- a/java/res/drawable-hdpi/sym_keyboard_shift_locked.png
+++ b/java/res/drawable-hdpi/sym_keyboard_shift_locked.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_space.png b/java/res/drawable-hdpi/sym_keyboard_space.png
index dacc97d..fcd20de 100644
--- a/java/res/drawable-hdpi/sym_keyboard_space.png
+++ b/java/res/drawable-hdpi/sym_keyboard_space.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_space_led.9.png b/java/res/drawable-hdpi/sym_keyboard_space_led.9.png
index c76f64b..2c6f4a9 100644
--- a/java/res/drawable-hdpi/sym_keyboard_space_led.9.png
+++ b/java/res/drawable-hdpi/sym_keyboard_space_led.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_tab.png b/java/res/drawable-hdpi/sym_keyboard_tab.png
index efd740b..51d17d9 100644
--- a/java/res/drawable-hdpi/sym_keyboard_tab.png
+++ b/java/res/drawable-hdpi/sym_keyboard_tab.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_voice_off_holo.png b/java/res/drawable-hdpi/sym_keyboard_voice_off_holo.png
index 8a445eb..6e6279a 100644
--- a/java/res/drawable-hdpi/sym_keyboard_voice_off_holo.png
+++ b/java/res/drawable-hdpi/sym_keyboard_voice_off_holo.png
Binary files differ
diff --git a/java/res/drawable-hdpi/voice_ime_background.9.png b/java/res/drawable-hdpi/voice_ime_background.9.png
index a604f49..4286852 100644
--- a/java/res/drawable-hdpi/voice_ime_background.9.png
+++ b/java/res/drawable-hdpi/voice_ime_background.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/voice_swipe_hint.png b/java/res/drawable-hdpi/voice_swipe_hint.png
index 976fd56..130f83a 100644
--- a/java/res/drawable-hdpi/voice_swipe_hint.png
+++ b/java/res/drawable-hdpi/voice_swipe_hint.png
Binary files differ
diff --git a/java/res/drawable-hdpi/working.png b/java/res/drawable-hdpi/working.png
old mode 100644
new mode 100755
index c43439e..5ea7023
--- a/java/res/drawable-hdpi/working.png
+++ b/java/res/drawable-hdpi/working.png
Binary files differ
diff --git a/java/res/drawable-mdpi/btn_candidate_normal.9.png b/java/res/drawable-mdpi/btn_candidate_normal.9.png
deleted file mode 100644
index fa6c0fe..0000000
--- a/java/res/drawable-mdpi/btn_candidate_normal.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-mdpi/highlight_pressed.png b/java/res/drawable-mdpi/highlight_pressed.png
deleted file mode 100644
index d27f106..0000000
--- a/java/res/drawable-mdpi/highlight_pressed.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-mdpi/keyboard_key_feedback_background_holo.9.png b/java/res/drawable-mdpi/keyboard_key_feedback_background_holo.9.png
index a7acb4a..286cf84 100644
--- a/java/res/drawable-mdpi/keyboard_key_feedback_background_holo.9.png
+++ b/java/res/drawable-mdpi/keyboard_key_feedback_background_holo.9.png
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_keyboard_voice_off_holo.png b/java/res/drawable-mdpi/sym_keyboard_voice_off_holo.png
index 081a130..44919df 100644
--- a/java/res/drawable-mdpi/sym_keyboard_voice_off_holo.png
+++ b/java/res/drawable-mdpi/sym_keyboard_voice_off_holo.png
Binary files differ
diff --git a/java/res/drawable-mdpi/top_suggest_line_holo.9.png b/java/res/drawable-mdpi/top_suggest_line_holo.9.png
deleted file mode 100644
index 8fdffd3..0000000
--- a/java/res/drawable-mdpi/top_suggest_line_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable/btn_candidate.xml b/java/res/drawable/btn_candidate.xml
index b0c1c30..efcee9f 100644
--- a/java/res/drawable/btn_candidate.xml
+++ b/java/res/drawable/btn_candidate.xml
@@ -24,6 +24,4 @@
     <item
         android:state_pressed="true"
         android:drawable="@drawable/btn_candidate_pressed" />
-    <item
-        android:drawable="@drawable/btn_candidate_normal" />
 </selector>
diff --git a/java/res/drawable/ic_suggest_scroll_background.xml b/java/res/drawable/ic_suggest_scroll_background.xml
deleted file mode 100644
index 9d246e4..0000000
--- a/java/res/drawable/ic_suggest_scroll_background.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2008 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-  
-          http://www.apache.org/licenses/LICENSE-2.0
-  
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-
-    <item android:state_pressed="false"
-        android:drawable="@android:color/transparent" />
-
-    <item android:state_pressed="true"
-        android:drawable="@drawable/highlight_pressed" />
-
-</selector>
\ No newline at end of file
diff --git a/java/res/drawable/keyboard_key_feedback_honeycomb.xml b/java/res/drawable/keyboard_key_feedback_honeycomb.xml
index dd9b53e..a3ea140 100644
--- a/java/res/drawable/keyboard_key_feedback_honeycomb.xml
+++ b/java/res/drawable/keyboard_key_feedback_honeycomb.xml
@@ -16,6 +16,6 @@
 
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
     <item android:state_long_pressable="true"
-            android:drawable="@drawable/keyboard_key_feedback_more_background" />
+            android:drawable="@drawable/keyboard_key_feedback_more_background_holo" />
     <item android:drawable="@drawable/keyboard_key_feedback_background_holo" />
 </selector>
diff --git a/java/res/layout-xlarge/candidate.xml b/java/res/layout-xlarge/candidate.xml
index 74532a1..582e642 100644
--- a/java/res/layout-xlarge/candidate.xml
+++ b/java/res/layout-xlarge/candidate.xml
@@ -20,41 +20,40 @@
 
 <LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="@dimen/candidate_strip_height"
+    android:layout_width="wrap_content"
+    android:layout_height="match_parent"
     android:orientation="horizontal"
-    android:paddingRight="@dimen/candidate_padding"
 >
     <ImageView
         android:id="@+id/candidate_divider"
         android:layout_width="wrap_content"
-        android:layout_height="@dimen/candidate_strip_height"
-        android:visibility="gone"
+        android:layout_height="match_parent"
+        android:src="@drawable/keyboard_suggest_strip_divider"
+        android:paddingRight="@dimen/candidate_padding"
+        android:paddingLeft="@dimen/candidate_padding"
+        android:visibility="invisible"
         android:focusable="false"
         android:clickable="false"
-        android:src="@drawable/keyboard_suggest_strip_divider"
         android:gravity="center_vertical|center_horizontal" />
     <Button
         android:id="@+id/candidate_word"
         android:layout_width="wrap_content"
-        android:layout_height="@dimen/candidate_strip_height"
+        android:layout_height="match_parent"
         android:minWidth="@dimen/candidate_min_width"
         android:textSize="@dimen/candidate_text_size"
         android:textColor="@color/candidate_normal"
         android:background="@drawable/btn_candidate_holo"
         android:focusable="true"
         android:clickable="true"
-        android:gravity="center_vertical|center_horizontal"
-        android:paddingLeft="@dimen/candidate_padding" />
+        android:gravity="center_vertical|center_horizontal" />
     <TextView
         android:id="@+id/candidate_debug_info"
         android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
+        android:layout_height="match_parent"
         android:visibility="gone"
         android:textSize="10dip"
         android:textColor="#ff808080"
         android:focusable="false"
         android:clickable="false"
-        android:gravity="bottom"
-        android:paddingLeft="4dip" />
+        android:gravity="bottom" />
 </LinearLayout>
diff --git a/java/res/layout-xlarge/candidates.xml b/java/res/layout-xlarge/candidates.xml
index e2ddb84..096a0ad 100644
--- a/java/res/layout-xlarge/candidates.xml
+++ b/java/res/layout-xlarge/candidates.xml
@@ -21,25 +21,34 @@
 <LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:orientation="horizontal"
+    android:gravity="bottom"
     android:layout_width="match_parent"
-    android:layout_height="@dimen/candidate_strip_height"
-    android:background="@drawable/keyboard_suggest_strip_holo"
-    android:paddingRight="@dimen/candidate_strip_padding"
-    android:paddingLeft="@dimen/candidate_strip_padding"
+    android:layout_height="wrap_content"
+    android:minHeight="@dimen/candidate_strip_minimum_height"
 >
-    <HorizontalScrollView
-        android:id="@+id/candidates_scroll_view"
-        android:layout_width="wrap_content"
-        android:layout_height="@dimen/candidate_strip_height"
-        android:fadingEdge="horizontal"
-        android:fadingEdgeLength="@dimen/candidate_strip_fading_edge_length"
-        android:scrollbars="none"
+    <!-- On tablets, the candidate strip is centered with horizontal paddings on both sides because
+         width of the landscape mode is too long for the candidate strip. This LinearLayout is
+         required to hold the paddings. -->
+    <LinearLayout
+        android:orientation="horizontal"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:background="@drawable/keyboard_suggest_strip_holo"
+        android:paddingRight="@dimen/candidate_strip_padding"
+        android:paddingLeft="@dimen/candidate_strip_padding"
     >
-        <com.android.inputmethod.latin.CandidateView
-            android:id="@+id/candidates"
-            android:orientation="horizontal"
+        <HorizontalScrollView
             android:layout_width="match_parent"
-            android:layout_height="@dimen/candidate_strip_height"
-            android:background="@drawable/keyboard_suggest_strip_holo" />
-    </HorizontalScrollView>
+            android:layout_height="wrap_content"
+            android:fadingEdge="horizontal"
+            android:fadingEdgeLength="@dimen/candidate_strip_fading_edge_length"
+            android:scrollbars="none"
+        >
+            <com.android.inputmethod.latin.CandidateView
+                android:id="@+id/candidates"
+                android:layout_width="match_parent"
+                android:layout_height="@dimen/candidate_strip_height"
+                android:gravity="center_vertical" />
+        </HorizontalScrollView>
+    </LinearLayout>
 </LinearLayout>
diff --git a/java/res/layout-xlarge/keyboard_popup_honeycomb.xml b/java/res/layout-xlarge/keyboard_popup_honeycomb.xml
deleted file mode 100644
index 0b8229c..0000000
--- a/java/res/layout-xlarge/keyboard_popup_honeycomb.xml
+++ /dev/null
@@ -1,41 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2010, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-<LinearLayout
-        xmlns:android="http://schemas.android.com/apk/res/android"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:orientation="horizontal"
-        android:background="@drawable/keyboard_popup_panel_background_holo"
-        android:paddingLeft="40dip"
-        android:paddingRight="40dip"
-        >
-    <com.android.inputmethod.keyboard.KeyboardView
-            xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-            android:id="@+id/KeyboardView"
-            android:layout_alignParentBottom="true"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:background="@color/latinkeyboard_transparent"
-
-            latin:keyBackground="@drawable/btn_keyboard_key_honeycomb_popup"
-            latin:keyHysteresisDistance="0dip"
-            latin:verticalCorrection="@dimen/mini_keyboard_vertical_correction"
-            />
-</LinearLayout>
diff --git a/java/res/layout-xlarge/recognition_status.xml b/java/res/layout-xlarge/recognition_status.xml
new file mode 100644
index 0000000..40bc098
--- /dev/null
+++ b/java/res/layout-xlarge/recognition_status.xml
@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<RelativeLayout
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_height="wrap_content"
+        android:layout_width="match_parent"
+        android:background="@drawable/background_voice">
+    <LinearLayout
+            xmlns:android="http://schemas.android.com/apk/res/android"
+            android:id="@+id/popup_layout"
+            android:orientation="vertical"
+            android:layout_height="371dip"
+            android:layout_width="500dip"
+            android:layout_centerInParent="true"
+            android:background="@drawable/vs_dialog_red">
+        <TextView
+                android:id="@+id/text"
+                android:text="@string/voice_error"
+                android:layout_height="wrap_content"
+                android:layout_width="wrap_content"
+                android:singleLine="true"
+                android:layout_marginTop="10dip"
+                android:textSize="28sp"
+                android:textColor="#ffffff"
+                android:layout_gravity="center"
+                android:visibility="invisible"/>
+        <RelativeLayout
+                android:layout_height="0dip"
+                android:layout_width="match_parent"
+                android:layout_weight="1.0">
+            <com.android.inputmethod.deprecated.voice.SoundIndicator
+                    android:id="@+id/sound_indicator"
+                    android:src="@drawable/mic_full"
+                    android:background="@drawable/mic_base"
+                    android:adjustViewBounds="true"
+                    android:layout_height="wrap_content"
+                    android:layout_width="wrap_content"
+                    android:layout_centerInParent="true"
+                    android:visibility="gone"/>
+            <ImageView
+                    android:id="@+id/image"
+                    android:src="@drawable/mic_slash"
+                    android:layout_height="wrap_content"
+                    android:layout_width="wrap_content"
+                    android:layout_centerInParent="true"
+                    android:visibility="visible"/>
+            <ProgressBar
+                    android:id="@+id/progress"
+                    android:indeterminate="true"
+                    android:indeterminateOnly="false"
+                    android:layout_height="60dip"
+                    android:layout_width="60dip"
+                    android:layout_centerInParent="true"
+                    android:visibility="gone"/>
+        </RelativeLayout>
+        <!--
+        The text is set by the code. We specify a random text (voice_error), so the
+        text view does not have a zero height. This is necessary to keep the slash
+        mic and the recording mic is the same position
+        -->
+        <TextView
+                android:id="@+id/language"
+                android:text="@string/voice_error"
+                android:layout_height="wrap_content"
+                android:layout_width="wrap_content"
+                android:singleLine="true"
+                android:textSize="14sp"
+                android:layout_marginBottom="3dip"
+                android:layout_gravity="center"
+                android:textColor="#ffffff"
+                android:visibility="invisible"/>
+        <Button
+                android:id="@+id/button"
+                android:layout_width="match_parent"
+                android:layout_height="54dip"
+                android:singleLine="true"
+                android:focusable="true"
+                android:text="@string/cancel"
+                android:layout_gravity="center_horizontal"
+                android:background="@drawable/btn_center"
+                android:textColor="#ffffff"
+                android:textSize="19sp" />
+    </LinearLayout>
+</RelativeLayout>
diff --git a/java/res/layout/candidate.xml b/java/res/layout/candidate.xml
index f2c4126..5472a1d 100644
--- a/java/res/layout/candidate.xml
+++ b/java/res/layout/candidate.xml
@@ -20,41 +20,40 @@
 
 <LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="@dimen/candidate_strip_height"
+    android:layout_width="wrap_content"
+    android:layout_height="match_parent"
     android:orientation="horizontal"
-    android:paddingRight="@dimen/candidate_padding"
 >
     <ImageView
         android:id="@+id/candidate_divider"
         android:layout_width="wrap_content"
-        android:layout_height="@dimen/candidate_strip_height"
-        android:visibility="gone"
+        android:layout_height="match_parent"
+        android:src="@drawable/keyboard_suggest_strip_divider"
+        android:paddingRight="@dimen/candidate_padding"
+        android:paddingLeft="@dimen/candidate_padding"
+        android:visibility="invisible"
         android:focusable="false"
         android:clickable="false"
-        android:src="@drawable/keyboard_suggest_strip_divider"
         android:gravity="center_vertical|center_horizontal" />
     <Button
         android:id="@+id/candidate_word"
         android:layout_width="wrap_content"
-        android:layout_height="@dimen/candidate_strip_height"
+        android:layout_height="match_parent"
         android:minWidth="@dimen/candidate_min_width"
         android:textSize="@dimen/candidate_text_size"
         android:textColor="@color/candidate_normal"
         android:background="@drawable/btn_candidate"
         android:focusable="true"
         android:clickable="true"
-        android:gravity="center_vertical|center_horizontal"
-        android:paddingLeft="@dimen/candidate_padding" />
+        android:gravity="center_vertical|center_horizontal" />
     <TextView
         android:id="@+id/candidate_debug_info"
         android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
+        android:layout_height="match_parent"
         android:visibility="gone"
         android:textSize="10dip"
         android:textColor="#ff808080"
         android:focusable="false"
         android:clickable="false"
-        android:gravity="bottom"
-        android:paddingLeft="4dip" />
+        android:gravity="bottom" />
 </LinearLayout>
diff --git a/java/res/layout/candidates.xml b/java/res/layout/candidates.xml
index 1b8d041..3d91c1d 100644
--- a/java/res/layout/candidates.xml
+++ b/java/res/layout/candidates.xml
@@ -21,25 +21,25 @@
 <LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:orientation="horizontal"
+    android:gravity="bottom"
     android:layout_width="match_parent"
-    android:layout_height="@dimen/candidate_strip_height"
-    android:background="@drawable/keyboard_suggest_strip"
+    android:layout_height="wrap_content"
+    android:minHeight="@dimen/candidate_strip_minimum_height"
     android:paddingRight="@dimen/candidate_strip_padding"
     android:paddingLeft="@dimen/candidate_strip_padding"
 >
     <HorizontalScrollView
-        android:id="@+id/candidates_scroll_view"
-        android:layout_width="wrap_content"
-        android:layout_height="@dimen/candidate_strip_height"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:background="@drawable/keyboard_suggest_strip"
         android:fadingEdge="horizontal"
         android:fadingEdgeLength="@dimen/candidate_strip_fading_edge_length"
         android:scrollbars="none"
     >
         <com.android.inputmethod.latin.CandidateView
             android:id="@+id/candidates"
-            android:orientation="horizontal"
             android:layout_width="match_parent"
             android:layout_height="@dimen/candidate_strip_height"
-            android:background="@drawable/keyboard_suggest_strip" />
+            android:gravity="center_vertical" />
     </HorizontalScrollView>
 </LinearLayout>
diff --git a/java/res/layout/input_basic.xml b/java/res/layout/input_basic.xml
index 7b85bae..8666dae 100644
--- a/java/res/layout/input_basic.xml
+++ b/java/res/layout/input_basic.xml
@@ -21,10 +21,11 @@
 <com.android.inputmethod.keyboard.LatinKeyboardView
         xmlns:android="http://schemas.android.com/apk/res/android"
         xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-        android:id="@+id/LatinkeyboardBaseView"
+        android:id="@+id/latin_keyboard_view"
         android:layout_alignParentBottom="true"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
+        android:padding="0dip"
         android:background="@drawable/keyboard_background"
 
         latin:keyBackground="@drawable/btn_keyboard_key"
diff --git a/java/res/layout/input_basic_highcontrast.xml b/java/res/layout/input_basic_highcontrast.xml
index d9200fd..4829c7d 100644
--- a/java/res/layout/input_basic_highcontrast.xml
+++ b/java/res/layout/input_basic_highcontrast.xml
@@ -21,11 +21,11 @@
 <com.android.inputmethod.keyboard.LatinKeyboardView
         xmlns:android="http://schemas.android.com/apk/res/android"
         xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-
-        android:id="@+id/LatinkeyboardBaseView"
+        android:id="@+id/latin_keyboard_view"
         android:layout_alignParentBottom="true"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
+        android:padding="0dip"
         android:background="@android:color/black"
 
         latin:keyBackground="@drawable/btn_keyboard_key3"
diff --git a/java/res/layout/input_gingerbread.xml b/java/res/layout/input_gingerbread.xml
index 6233e6d..ccca501 100644
--- a/java/res/layout/input_gingerbread.xml
+++ b/java/res/layout/input_gingerbread.xml
@@ -21,12 +21,11 @@
 <com.android.inputmethod.keyboard.LatinKeyboardView
         xmlns:android="http://schemas.android.com/apk/res/android"
         xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-        android:id="@+id/LatinkeyboardBaseView"
+        android:id="@+id/latin_keyboard_view"
         android:layout_alignParentBottom="true"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:paddingTop="@dimen/keyboard_top_padding"
-        android:paddingBottom="@dimen/keyboard_bottom_padding"
+        android:padding="0dip"
         android:background="@drawable/keyboard_dark_background"
 
         latin:keyBackground="@drawable/btn_keyboard_key_gingerbread"
diff --git a/java/res/layout/input_honeycomb.xml b/java/res/layout/input_honeycomb.xml
index 6ccc63c..8dadafd 100644
--- a/java/res/layout/input_honeycomb.xml
+++ b/java/res/layout/input_honeycomb.xml
@@ -21,16 +21,17 @@
 <com.android.inputmethod.keyboard.LatinKeyboardView
         xmlns:android="http://schemas.android.com/apk/res/android"
         xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-        android:id="@+id/LatinkeyboardBaseView"
+        android:id="@+id/latin_keyboard_view"
         android:layout_alignParentBottom="true"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:paddingTop="@dimen/keyboard_top_padding"
-        android:paddingBottom="@dimen/keyboard_bottom_padding"
+        android:padding="0dip"
         android:background="@drawable/keyboard_background_holo"
 
         latin:keyBackground="@drawable/btn_keyboard_key_honeycomb"
         latin:keyPreviewLayout="@layout/key_preview_honeycomb"
+        latin:keyPreviewHeight="@dimen/key_preview_height_holo"
+        latin:keyPreviewOffset="@dimen/key_preview_offset_holo"
         latin:popupLayout="@layout/keyboard_popup_honeycomb"
         latin:keyTextColorDisabled="#FF63666D"
         latin:keyLetterStyle="bold"
diff --git a/java/res/layout/input_stone_bold.xml b/java/res/layout/input_stone_bold.xml
index 6fdc938..a0b4068 100644
--- a/java/res/layout/input_stone_bold.xml
+++ b/java/res/layout/input_stone_bold.xml
@@ -21,10 +21,11 @@
 <com.android.inputmethod.keyboard.LatinKeyboardView
         xmlns:android="http://schemas.android.com/apk/res/android"
         xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-        android:id="@+id/LatinkeyboardBaseView"
+        android:id="@+id/latin_keyboard_view"
         android:layout_alignParentBottom="true"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
+        android:padding="0dip"
         android:background="@drawable/keyboard_background"
 
         latin:keyBackground="@drawable/btn_keyboard_key_stone"
@@ -33,5 +34,5 @@
         latin:shadowColor="@color/latinkeyboard_key_color_white"
         latin:keyLetterStyle="bold"
         latin:colorScheme="black"
-        latin:popupLayout="@layout/input_stone_popup"
+        latin:popupLayout="@layout/keyboard_popup_stone"
         />
diff --git a/java/res/layout/input_stone_normal.xml b/java/res/layout/input_stone_normal.xml
index 6ae9aed..41cbc16 100644
--- a/java/res/layout/input_stone_normal.xml
+++ b/java/res/layout/input_stone_normal.xml
@@ -21,10 +21,11 @@
 <com.android.inputmethod.keyboard.LatinKeyboardView
         xmlns:android="http://schemas.android.com/apk/res/android"
         xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-        android:id="@+id/LatinkeyboardBaseView"
+        android:id="@+id/latin_keyboard_view"
         android:layout_alignParentBottom="true"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
+        android:padding="0dip"
         android:background="@drawable/keyboard_background"
 
         latin:keyBackground="@drawable/btn_keyboard_key_stone"
@@ -32,5 +33,5 @@
         latin:keyTextColorDisabled="#FF808080"
         latin:shadowColor="@color/latinkeyboard_key_color_white"
         latin:colorScheme="black"
-        latin:popupLayout="@layout/input_stone_popup"
+        latin:popupLayout="@layout/keyboard_popup_stone"
         />
diff --git a/java/res/layout/key_preview_honeycomb.xml b/java/res/layout/key_preview_honeycomb.xml
index a90fe55..2fbfbb5 100644
--- a/java/res/layout/key_preview_honeycomb.xml
+++ b/java/res/layout/key_preview_honeycomb.xml
@@ -23,7 +23,7 @@
     android:layout_height="80sp"
     android:textSize="40sp"
     android:textColor="@color/latinkeyboard_key_color_white"
-    android:minWidth="24dip"
+    android:minWidth="32dip"
     android:gravity="center"
     android:background="@drawable/keyboard_key_feedback_honeycomb"
     />
diff --git a/java/res/layout/keyboard_popup.xml b/java/res/layout/keyboard_popup.xml
index ac8134b..0317d8d 100644
--- a/java/res/layout/keyboard_popup.xml
+++ b/java/res/layout/keyboard_popup.xml
@@ -19,20 +19,19 @@
 -->
 <LinearLayout
         xmlns:android="http://schemas.android.com/apk/res/android"
-        android:layout_width="match_parent"
+        android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:orientation="horizontal"
         android:background="@drawable/keyboard_popup_panel_background"
-        android:paddingLeft="16dip"
-        android:paddingRight="16dip"
+        android:paddingLeft="@dimen/mini_keyboard_horizontal_padding"
+        android:paddingRight="@dimen/mini_keyboard_horizontal_padding"
         >
-    <com.android.inputmethod.keyboard.KeyboardView
+    <com.android.inputmethod.keyboard.PopupMiniKeyboardView
             xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-            android:id="@+id/KeyboardView"
+            android:id="@+id/mini_keyboard_view"
             android:layout_alignParentBottom="true"
-            android:layout_width="match_parent"
+            android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:background="@color/latinkeyboard_transparent"
 
             latin:keyBackground="@drawable/btn_keyboard_key_gingerbread_popup"
             latin:keyHysteresisDistance="0dip"
diff --git a/java/res/layout/keyboard_popup_honeycomb.xml b/java/res/layout/keyboard_popup_honeycomb.xml
index e5fcbd4..2ddcbdc 100644
--- a/java/res/layout/keyboard_popup_honeycomb.xml
+++ b/java/res/layout/keyboard_popup_honeycomb.xml
@@ -19,20 +19,19 @@
 -->
 <LinearLayout
         xmlns:android="http://schemas.android.com/apk/res/android"
-        android:layout_width="match_parent"
+        android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:orientation="horizontal"
         android:background="@drawable/keyboard_popup_panel_background_holo"
-        android:paddingLeft="24dip"
-        android:paddingRight="24dip"
+        android:paddingLeft="@dimen/mini_keyboard_horizontal_padding_holo"
+        android:paddingRight="@dimen/mini_keyboard_horizontal_padding_holo"
         >
-    <com.android.inputmethod.keyboard.KeyboardView
+    <com.android.inputmethod.keyboard.PopupMiniKeyboardView
             xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-            android:id="@+id/KeyboardView"
+            android:id="@+id/mini_keyboard_view"
             android:layout_alignParentBottom="true"
-            android:layout_width="match_parent"
+            android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:background="@color/latinkeyboard_transparent"
 
             latin:keyBackground="@drawable/btn_keyboard_key_honeycomb_popup"
             latin:keyHysteresisDistance="0dip"
diff --git a/java/res/layout/input_stone_popup.xml b/java/res/layout/keyboard_popup_stone.xml
similarity index 90%
rename from java/res/layout/input_stone_popup.xml
rename to java/res/layout/keyboard_popup_stone.xml
index b4da045..94176b2 100644
--- a/java/res/layout/input_stone_popup.xml
+++ b/java/res/layout/keyboard_popup_stone.xml
@@ -25,9 +25,9 @@
         android:orientation="horizontal"
         android:background="@drawable/keyboard_popup_panel_background"
         >
-    <com.android.inputmethod.keyboard.KeyboardView
+    <com.android.inputmethod.keyboard.PopupMiniKeyboardView
             xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-            android:id="@+id/KeyboardView"
+            android:id="@+id/mini_keyboard_view"
             android:layout_alignParentBottom="true"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
@@ -36,6 +36,5 @@
             latin:keyBackground="@drawable/btn_keyboard_key_stone"
             latin:keyTextColor="@color/latinkeyboard_key_color_black"
             latin:shadowColor="@color/latinkeyboard_key_color_white"
-            latin:popupLayout="@layout/input_stone_popup"
         />
 </LinearLayout>
diff --git a/java/res/layout/recognition_status.xml b/java/res/layout/recognition_status.xml
index 9474d6f..a2ddb7c 100644
--- a/java/res/layout/recognition_status.xml
+++ b/java/res/layout/recognition_status.xml
@@ -1,19 +1,19 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-/* 
+/*
 **
 ** Copyright 2009, The Android Open Source Project
 **
-** Licensed under the Apache License, Version 2.0 (the "License"); 
-** you may not use this file except in compliance with the License. 
-** You may obtain a copy of the License at 
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
 **
-**     http://www.apache.org/licenses/LICENSE-2.0 
+**     http://www.apache.org/licenses/LICENSE-2.0
 **
-** Unless required by applicable law or agreed to in writing, software 
-** distributed under the License is distributed on an "AS IS" BASIS, 
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
-** See the License for the specific language governing permissions and 
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
 ** limitations under the License.
 */
 -->
@@ -37,7 +37,7 @@
                 android:layout_width="wrap_content"
                 android:singleLine="true"
                 android:layout_marginTop="10dip"
-                android:textSize="28sp"
+                android:textSize="20sp"
                 android:textColor="#ffffff"
                 android:layout_gravity="center"
                 android:visibility="invisible"/>
@@ -45,7 +45,7 @@
                 android:layout_height="0dip"
                 android:layout_width="match_parent"
                 android:layout_weight="1.0">
-            <com.android.inputmethod.voice.SoundIndicator
+            <com.android.inputmethod.deprecated.voice.SoundIndicator
                     android:id="@+id/sound_indicator"
                     android:src="@drawable/mic_full"
                     android:background="@drawable/mic_base"
@@ -81,7 +81,8 @@
                 android:layout_height="wrap_content"
                 android:layout_width="wrap_content"
                 android:singleLine="true"
-                android:textSize="14sp"
+                android:textSize="15sp"
+                android:layout_marginTop="3dip"
                 android:layout_marginBottom="3dip"
                 android:layout_gravity="center"
                 android:textColor="#ffffff"
@@ -89,13 +90,13 @@
         <Button
                 android:id="@+id/button"
                 android:layout_width="match_parent"
-                android:layout_height="54dip"
+                android:layout_height="30dip"
                 android:singleLine="true"
                 android:focusable="true"
                 android:text="@string/cancel"
                 android:layout_gravity="center_horizontal"
                 android:background="@drawable/btn_center"
                 android:textColor="#ffffff"
-                android:textSize="19sp" />
+                android:textSize="15sp" />
     </LinearLayout>
 </RelativeLayout>
diff --git a/java/res/values-ar/strings.xml b/java/res/values-ar/strings.xml
index d1d70ff..df5b87a 100644
--- a/java/res/values-ar/strings.xml
+++ b/java/res/values-ar/strings.xml
@@ -27,7 +27,9 @@
     <string name="sound_on_keypress" msgid="6093592297198243644">"صوت عند الضغط على مفتاح"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"انبثاق عند الضغط على المفاتيح"</string>
     <string name="general_category" msgid="1859088467017573195">"عام"</string>
-    <string name="prediction_category" msgid="6361242011806282176">"تصحيح النص"</string>
+    <string name="correction_category" msgid="2236750915056607613">"تصحيح النص"</string>
+    <string name="ngram_category" msgid="5337109164339320257">"الاقتراحات بناءً على الكلمات السابقة"</string>
+    <string name="misc_category" msgid="6894192814868233453">"خيارات أخرى"</string>
     <string name="auto_cap" msgid="1719746674854628252">"استخدام الأحرف الكبيرة تلقائيًا"</string>
     <string name="quick_fixes" msgid="5353213327680897927">"إصلاحات سريعة"</string>
     <string name="quick_fixes_summary" msgid="3405028402510332373">"تصحيح الأخطاء المكتوبة الشائعة"</string>
@@ -36,6 +38,7 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"عرض دومًا"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"عرض في وضع رأسي"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"إخفاء دومًا"</string>
+    <string name="prefs_use_spacebar_language_switch" msgid="8828538114550634449">"استخدام مفتاح المسافة لتبديل اللغة"</string>
     <string name="prefs_settings_key" msgid="4623341240804046498">"عرض مفتاح الإعدادات"</string>
     <string name="settings_key_mode_auto_name" msgid="2993460277873684680">"تلقائي"</string>
     <string name="settings_key_mode_always_show_name" msgid="3047567041784760575">"إظهار بشكل دائم"</string>
@@ -45,8 +48,10 @@
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"إيقاف"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"معتدل"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"حاد"</string>
-    <string name="bigram_suggestion" msgid="1323347224043514969">"اقتراحات ثنائية"</string>
+    <string name="bigram_suggestion" msgid="2636414079905220518">"اقتراحات ثنائية"</string>
     <string name="bigram_suggestion_summary" msgid="4383845146070101531">"استخدام الكلمة السابقة لتحسين الاقتراح"</string>
+    <string name="bigram_prediction" msgid="8914273444762259739">"التنبؤ الثنائي"</string>
+    <string name="bigram_prediction_summary" msgid="1747261921174300098">"استخدام الكلمة السابقة أيضًا للتنبؤ"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : تم الحفظ"</string>
     <string name="label_go_key" msgid="1635148082137219148">"تنفيذ"</string>
     <string name="label_next_key" msgid="362972844525672568">"التالي"</string>
@@ -56,18 +61,6 @@
     <string name="label_more_key" msgid="3760239494604948502">"المزيد"</string>
     <string name="label_pause_key" msgid="181098308428035340">"توقف مؤقت"</string>
     <string name="label_wait_key" msgid="6402152600878093134">"انتظار"</string>
-    <string name="description_delete_key" msgid="5586406298531883960">"حذف"</string>
-    <string name="description_return_key" msgid="8750044000806461678">"رجوع"</string>
-    <string name="description_settings_key" msgid="7484527796782969219">"الإعدادات"</string>
-    <string name="description_shift_key" msgid="346906866277787836">"العالي"</string>
-    <string name="description_space_key" msgid="8512130111575878517">"مسافة"</string>
-    <string name="description_switch_alpha_symbol_key" msgid="4537975384274405537">"الرموز"</string>
-    <string name="description_tab_key" msgid="828186583738307137">"Tab"</string>
-    <string name="description_voice_key" msgid="3057731675774652754">"الإدخال الصوتي"</string>
-    <string name="description_symbols_on" msgid="2994366855822840559">"تشغيل الرموز"</string>
-    <string name="description_symbols_off" msgid="3209578267079515136">"إيقاف الرموز"</string>
-    <string name="description_shift_on" msgid="6983188949895971587">"تشغيل العالي"</string>
-    <string name="description_shift_off" msgid="8553265474523069034">"إيقاف العالي"</string>
     <string name="voice_warning_title" msgid="4419354150908395008">"الإدخال الصوتي"</string>
     <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"الإدخال الصوتي غير معتمد حاليًا للغتك، ولكنه يعمل باللغة الإنجليزية."</string>
     <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"يستخدم الإدخال الصوتي خاصية التعرف على الكلام من Google. تنطبق "<a href="http://m.google.com/privacy">"سياسة خصوصية الجوال"</a>"."</string>
@@ -106,6 +99,7 @@
     <string name="prefs_enable_recorrection_summary" msgid="5082041365862396329">"المس الكلمات التي تم إدخالها لتصحيحها، وذلك فقط عندما تكون الاقتراحات مرئية."</string>
     <string name="keyboard_layout" msgid="437433231038683666">"مظهر لوحة المفاتيح"</string>
     <string name="subtype_mode_cs_keyboard" msgid="1141718931112377586">"لوحة مفاتيح تشيكية"</string>
+    <string name="subtype_mode_ar_keyboard" msgid="2655338636329774995">"لوحة المفاتيح العربية"</string>
     <string name="subtype_mode_da_keyboard" msgid="1243570804427922104">"لوحة مفاتيح دانماركية"</string>
     <string name="subtype_mode_de_keyboard" msgid="1990979135959462145">"لوحة مفاتيح ألمانية"</string>
     <string name="subtype_mode_en_GB_keyboard" msgid="7945856548410373708">"لوحة مفاتيح إنجليزية (بريطانيا)"</string>
@@ -115,6 +109,7 @@
     <string name="subtype_mode_fr_keyboard" msgid="8016515336759761014">"لوحة مفاتيح فرنسية"</string>
     <string name="subtype_mode_fr_CA_keyboard" msgid="2628517247158376263">"لوحة مفاتيح فرنسية (كندا)"</string>
     <string name="subtype_mode_fr_CH_keyboard" msgid="6742806653181621228">"لوحة مفاتيح فرنسية (سويسرا)"</string>
+    <string name="subtype_mode_iw_keyboard" msgid="1787536828253289950">"لوحة المفاتيح العبرية"</string>
     <string name="subtype_mode_it_keyboard" msgid="4934199655425394484">"لوحة مفاتيح إيطالية"</string>
     <string name="subtype_mode_nb_keyboard" msgid="1175783216100212360">"لوحة مفاتيح نرويجية"</string>
     <string name="subtype_mode_nl_keyboard" msgid="5090278083256037936">"لوحة مفاتيح بولندية"</string>
diff --git a/java/res/values-bg/strings.xml b/java/res/values-bg/strings.xml
index 418bb18..7f59a13 100644
--- a/java/res/values-bg/strings.xml
+++ b/java/res/values-bg/strings.xml
@@ -27,7 +27,9 @@
     <string name="sound_on_keypress" msgid="6093592297198243644">"Звук при натискане на клавиш"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"Изскачащ прозорец при натискане на клавиш"</string>
     <string name="general_category" msgid="1859088467017573195">"Общи"</string>
-    <string name="prediction_category" msgid="6361242011806282176">"Корекция на текста"</string>
+    <string name="correction_category" msgid="2236750915056607613">"Корекция на текста"</string>
+    <string name="ngram_category" msgid="5337109164339320257">"Предложения въз на основа на предишни думи"</string>
+    <string name="misc_category" msgid="6894192814868233453">"Други опции"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Автоматично поставяне на главни букви"</string>
     <string name="quick_fixes" msgid="5353213327680897927">"Бързи корекции"</string>
     <string name="quick_fixes_summary" msgid="3405028402510332373">"Коригира най-честите грешки при въвеждане"</string>
@@ -36,6 +38,7 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Винаги да се показва"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"Показване с вертикална ориентация"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Винаги да се скрива"</string>
+    <string name="prefs_use_spacebar_language_switch" msgid="8828538114550634449">"Клавишът интервал да превкл. езика"</string>
     <string name="prefs_settings_key" msgid="4623341240804046498">"Показване на клавиша за настройки"</string>
     <string name="settings_key_mode_auto_name" msgid="2993460277873684680">"Автоматично"</string>
     <string name="settings_key_mode_always_show_name" msgid="3047567041784760575">"Да се показва винаги"</string>
@@ -45,8 +48,10 @@
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Изкл."</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Умерено"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"Агресивно"</string>
-    <string name="bigram_suggestion" msgid="1323347224043514969">"Предложения за биграми"</string>
+    <string name="bigram_suggestion" msgid="2636414079905220518">"Предложения за биграми"</string>
     <string name="bigram_suggestion_summary" msgid="4383845146070101531">"Използване на предишната дума за подобряване на предложението"</string>
+    <string name="bigram_prediction" msgid="8914273444762259739">"Предвиждане за биграми"</string>
+    <string name="bigram_prediction_summary" msgid="1747261921174300098">"Използване на предишната дума и за предвиждане"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : Запазено"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Старт"</string>
     <string name="label_next_key" msgid="362972844525672568">"Напред"</string>
@@ -56,18 +61,6 @@
     <string name="label_more_key" msgid="3760239494604948502">"Още"</string>
     <string name="label_pause_key" msgid="181098308428035340">"Пауза"</string>
     <string name="label_wait_key" msgid="6402152600878093134">"Чака"</string>
-    <string name="description_delete_key" msgid="5586406298531883960">"Изтриване"</string>
-    <string name="description_return_key" msgid="8750044000806461678">"Return"</string>
-    <string name="description_settings_key" msgid="7484527796782969219">"Настройки"</string>
-    <string name="description_shift_key" msgid="346906866277787836">"Shift"</string>
-    <string name="description_space_key" msgid="8512130111575878517">"Интервал"</string>
-    <string name="description_switch_alpha_symbol_key" msgid="4537975384274405537">"Символи"</string>
-    <string name="description_tab_key" msgid="828186583738307137">"Tab"</string>
-    <string name="description_voice_key" msgid="3057731675774652754">"Гласово въвеждане"</string>
-    <string name="description_symbols_on" msgid="2994366855822840559">"Символите са включени"</string>
-    <string name="description_symbols_off" msgid="3209578267079515136">"Символите са изключени"</string>
-    <string name="description_shift_on" msgid="6983188949895971587">"Shift е включен"</string>
-    <string name="description_shift_off" msgid="8553265474523069034">"Shift е изключен"</string>
     <string name="voice_warning_title" msgid="4419354150908395008">"Гласово въвеждане"</string>
     <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"За вашия език понастоящем не се поддържа гласово въвеждане, но можете да го използвате на английски."</string>
     <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"Гласовото въвеждане използва функцията на Google за разпознаване на говор. В сила е "<a href="http://m.google.com/privacy">"Декларацията за поверителност за мобилни устройства"</a>"."</string>
@@ -106,6 +99,7 @@
     <string name="prefs_enable_recorrection_summary" msgid="5082041365862396329">"Докоснете въведените думи, за да ги поправите – само когато предложенията са видими"</string>
     <string name="keyboard_layout" msgid="437433231038683666">"Тема на клавиатурата"</string>
     <string name="subtype_mode_cs_keyboard" msgid="1141718931112377586">"чешка клавиатура"</string>
+    <string name="subtype_mode_ar_keyboard" msgid="2655338636329774995">"клавиатура на арабски"</string>
     <string name="subtype_mode_da_keyboard" msgid="1243570804427922104">"датска клавиатура"</string>
     <string name="subtype_mode_de_keyboard" msgid="1990979135959462145">"немска клавиатура"</string>
     <string name="subtype_mode_en_GB_keyboard" msgid="7945856548410373708">"английска (Великобрит.) клавиатура"</string>
@@ -115,6 +109,7 @@
     <string name="subtype_mode_fr_keyboard" msgid="8016515336759761014">"френска клавиатура"</string>
     <string name="subtype_mode_fr_CA_keyboard" msgid="2628517247158376263">"френска (Канада) клавиатура"</string>
     <string name="subtype_mode_fr_CH_keyboard" msgid="6742806653181621228">"френска (Швейцария) клавиатура"</string>
+    <string name="subtype_mode_iw_keyboard" msgid="1787536828253289950">"клавиатура на иврит"</string>
     <string name="subtype_mode_it_keyboard" msgid="4934199655425394484">"италианска клавиатура"</string>
     <string name="subtype_mode_nb_keyboard" msgid="1175783216100212360">"норвежка клавиатура"</string>
     <string name="subtype_mode_nl_keyboard" msgid="5090278083256037936">"нидерландска клавиатура"</string>
diff --git a/java/res/values-ca/strings.xml b/java/res/values-ca/strings.xml
index 1bfa3a9..36939fe 100644
--- a/java/res/values-ca/strings.xml
+++ b/java/res/values-ca/strings.xml
@@ -27,7 +27,9 @@
     <string name="sound_on_keypress" msgid="6093592297198243644">"So en prémer una tecla"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"Finestra emergent en prémer un botó"</string>
     <string name="general_category" msgid="1859088467017573195">"General"</string>
-    <string name="prediction_category" msgid="6361242011806282176">"Correcció de text"</string>
+    <string name="correction_category" msgid="2236750915056607613">"Correcció de text"</string>
+    <string name="ngram_category" msgid="5337109164339320257">"Suggeriments basats en paraules anteriors"</string>
+    <string name="misc_category" msgid="6894192814868233453">"Altres opcions"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Majúscules automàtiques"</string>
     <string name="quick_fixes" msgid="5353213327680897927">"Correccions ràpides"</string>
     <string name="quick_fixes_summary" msgid="3405028402510332373">"Corregeix els errors d\'ortografia habituals"</string>
@@ -36,6 +38,7 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Mostra sempre"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"Mostra en mode vertical"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Amaga sempre"</string>
+    <string name="prefs_use_spacebar_language_switch" msgid="8828538114550634449">"Utilitza el canvi d\'idioma amb la barra espaiadora"</string>
     <string name="prefs_settings_key" msgid="4623341240804046498">"Mostra la tecla de configuració"</string>
     <string name="settings_key_mode_auto_name" msgid="2993460277873684680">"Automàtic"</string>
     <string name="settings_key_mode_always_show_name" msgid="3047567041784760575">"Mostra sempre"</string>
@@ -45,8 +48,10 @@
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Desactiva"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Moderada"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"Total"</string>
-    <string name="bigram_suggestion" msgid="1323347224043514969">"Suggeriments Bigram"</string>
+    <string name="bigram_suggestion" msgid="2636414079905220518">"Suggeriments Bigram"</string>
     <string name="bigram_suggestion_summary" msgid="4383845146070101531">"Utilitza la paraula anterior per millorar el suggeriment"</string>
+    <string name="bigram_prediction" msgid="8914273444762259739">"Predicció Bigram"</string>
+    <string name="bigram_prediction_summary" msgid="1747261921174300098">"Utilitza la paraula anterior per a la predicció també"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g>: desada"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Vés"</string>
     <string name="label_next_key" msgid="362972844525672568">"Següent"</string>
@@ -56,18 +61,6 @@
     <string name="label_more_key" msgid="3760239494604948502">"Més"</string>
     <string name="label_pause_key" msgid="181098308428035340">"Pausa"</string>
     <string name="label_wait_key" msgid="6402152600878093134">"Espera"</string>
-    <string name="description_delete_key" msgid="5586406298531883960">"Suprimeix"</string>
-    <string name="description_return_key" msgid="8750044000806461678">"Retorn"</string>
-    <string name="description_settings_key" msgid="7484527796782969219">"Configuració"</string>
-    <string name="description_shift_key" msgid="346906866277787836">"Majúscules"</string>
-    <string name="description_space_key" msgid="8512130111575878517">"Espai"</string>
-    <string name="description_switch_alpha_symbol_key" msgid="4537975384274405537">"Símbols"</string>
-    <string name="description_tab_key" msgid="828186583738307137">"Tabulador"</string>
-    <string name="description_voice_key" msgid="3057731675774652754">"Entrada de veu"</string>
-    <string name="description_symbols_on" msgid="2994366855822840559">"Símbols activats"</string>
-    <string name="description_symbols_off" msgid="3209578267079515136">"Símbols desactivats"</string>
-    <string name="description_shift_on" msgid="6983188949895971587">"Majúscules activades"</string>
-    <string name="description_shift_off" msgid="8553265474523069034">"Majúscules desactivades"</string>
     <string name="voice_warning_title" msgid="4419354150908395008">"Entrada de veu"</string>
     <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"Actualment, l\'entrada de veu no és compatible amb el vostre idioma, però funciona en anglès."</string>
     <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"L\'entrada de veu utilitza el reconeixement de veu de Google. S\'hi aplica la "<a href="http://m.google.com/privacy">"Política de privadesa de Google Mobile"</a>"."</string>
@@ -106,6 +99,7 @@
     <string name="prefs_enable_recorrection_summary" msgid="5082041365862396329">"Toca les paraules introduïdes per corregir-les, només quan els suggeriments siguin visibles"</string>
     <string name="keyboard_layout" msgid="437433231038683666">"Tema del teclat"</string>
     <string name="subtype_mode_cs_keyboard" msgid="1141718931112377586">"Teclat txec"</string>
+    <string name="subtype_mode_ar_keyboard" msgid="2655338636329774995">"Teclat àrab"</string>
     <string name="subtype_mode_da_keyboard" msgid="1243570804427922104">"Teclat danès"</string>
     <string name="subtype_mode_de_keyboard" msgid="1990979135959462145">"Teclat alemany"</string>
     <string name="subtype_mode_en_GB_keyboard" msgid="7945856548410373708">"Teclat anglès (Regne Unit)"</string>
@@ -115,6 +109,7 @@
     <string name="subtype_mode_fr_keyboard" msgid="8016515336759761014">"Teclat francès"</string>
     <string name="subtype_mode_fr_CA_keyboard" msgid="2628517247158376263">"Teclat francès (Canadà)"</string>
     <string name="subtype_mode_fr_CH_keyboard" msgid="6742806653181621228">"Teclat francès (Suïssa)"</string>
+    <string name="subtype_mode_iw_keyboard" msgid="1787536828253289950">"Teclat hebreu"</string>
     <string name="subtype_mode_it_keyboard" msgid="4934199655425394484">"Teclat italià"</string>
     <string name="subtype_mode_nb_keyboard" msgid="1175783216100212360">"Teclat noruec"</string>
     <string name="subtype_mode_nl_keyboard" msgid="5090278083256037936">"Teclat holandès"</string>
diff --git a/java/res/values-cs/strings.xml b/java/res/values-cs/strings.xml
index b0cb7da..132bc1c 100644
--- a/java/res/values-cs/strings.xml
+++ b/java/res/values-cs/strings.xml
@@ -27,7 +27,9 @@
     <string name="sound_on_keypress" msgid="6093592297198243644">"Zvuk při stisku klávesy"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"Zobrazit znaky při stisku klávesy"</string>
     <string name="general_category" msgid="1859088467017573195">"Obecné"</string>
-    <string name="prediction_category" msgid="6361242011806282176">"Oprava textu"</string>
+    <string name="correction_category" msgid="2236750915056607613">"Oprava textu"</string>
+    <string name="ngram_category" msgid="5337109164339320257">"Návrhy na základě předchozích slov"</string>
+    <string name="misc_category" msgid="6894192814868233453">"Další možnosti"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Velká písmena automaticky"</string>
     <string name="quick_fixes" msgid="5353213327680897927">"Rychlé opravy"</string>
     <string name="quick_fixes_summary" msgid="3405028402510332373">"Opravuje nejčastější chyby při psaní"</string>
@@ -36,6 +38,7 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Vždy zobrazovat"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"Zobrazit v režimu na výšku"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Vždy skrývat"</string>
+    <string name="prefs_use_spacebar_language_switch" msgid="8828538114550634449">"Přepínání jazyků mezerníkem"</string>
     <string name="prefs_settings_key" msgid="4623341240804046498">"Zobrazit klávesu Nastavení"</string>
     <string name="settings_key_mode_auto_name" msgid="2993460277873684680">"Automaticky"</string>
     <string name="settings_key_mode_always_show_name" msgid="3047567041784760575">"Vždy zobrazovat"</string>
@@ -45,8 +48,10 @@
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Vypnuto"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Mírné"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"Agresivní"</string>
-    <string name="bigram_suggestion" msgid="1323347224043514969">"Návrh Bigram"</string>
+    <string name="bigram_suggestion" msgid="2636414079905220518">"Návrhy Bigram"</string>
     <string name="bigram_suggestion_summary" msgid="4383845146070101531">"Použít předchozí slovo ke zlepšení návrhu"</string>
+    <string name="bigram_prediction" msgid="8914273444762259739">"Odhady Bigram"</string>
+    <string name="bigram_prediction_summary" msgid="1747261921174300098">"Použít předchozí slovo také pro odhad"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g>: Uloženo"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Přejít"</string>
     <string name="label_next_key" msgid="362972844525672568">"Další"</string>
@@ -56,18 +61,6 @@
     <string name="label_more_key" msgid="3760239494604948502">"Další"</string>
     <string name="label_pause_key" msgid="181098308428035340">"Pauza"</string>
     <string name="label_wait_key" msgid="6402152600878093134">"Čekat"</string>
-    <string name="description_delete_key" msgid="5586406298531883960">"Smazat"</string>
-    <string name="description_return_key" msgid="8750044000806461678">"Enter"</string>
-    <string name="description_settings_key" msgid="7484527796782969219">"Nastavení"</string>
-    <string name="description_shift_key" msgid="346906866277787836">"Shift"</string>
-    <string name="description_space_key" msgid="8512130111575878517">"mezera"</string>
-    <string name="description_switch_alpha_symbol_key" msgid="4537975384274405537">"Symboly"</string>
-    <string name="description_tab_key" msgid="828186583738307137">"Karta"</string>
-    <string name="description_voice_key" msgid="3057731675774652754">"Hlasový vstup"</string>
-    <string name="description_symbols_on" msgid="2994366855822840559">"Symboly jsou zapnuty"</string>
-    <string name="description_symbols_off" msgid="3209578267079515136">"Symboly jsou vypnuty"</string>
-    <string name="description_shift_on" msgid="6983188949895971587">"Režim Shift je zapnutý"</string>
-    <string name="description_shift_off" msgid="8553265474523069034">"Režim Shift je vypnutý"</string>
     <string name="voice_warning_title" msgid="4419354150908395008">"Hlasový vstup"</string>
     <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"Pro váš jazyk aktuálně není hlasový vstup podporován, ale funguje v angličtině."</string>
     <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"Hlasový vstup používá rozpoznávání hlasu Google a vztahují se na něj "<a href="http://m.google.com/privacy">"Zásady ochrany osobních údajů pro mobilní služby"</a>"."</string>
@@ -106,6 +99,7 @@
     <string name="prefs_enable_recorrection_summary" msgid="5082041365862396329">"Klepnutím na zadaná slova tato slova opravíte, musí však být viditelné návrhy."</string>
     <string name="keyboard_layout" msgid="437433231038683666">"Motiv klávesnice"</string>
     <string name="subtype_mode_cs_keyboard" msgid="1141718931112377586">"Klávesnice – čeština"</string>
+    <string name="subtype_mode_ar_keyboard" msgid="2655338636329774995">"Klávesnice – arabština"</string>
     <string name="subtype_mode_da_keyboard" msgid="1243570804427922104">"Klávesnice – dánština"</string>
     <string name="subtype_mode_de_keyboard" msgid="1990979135959462145">"Klávesnice – němčina"</string>
     <string name="subtype_mode_en_GB_keyboard" msgid="7945856548410373708">"Klávesnice – angličtina (VB)"</string>
@@ -115,6 +109,7 @@
     <string name="subtype_mode_fr_keyboard" msgid="8016515336759761014">"Klávesnice – francouzština"</string>
     <string name="subtype_mode_fr_CA_keyboard" msgid="2628517247158376263">"Klávesnice – francouzština (Kanada)"</string>
     <string name="subtype_mode_fr_CH_keyboard" msgid="6742806653181621228">"Klávesnice – francouzština (Švýc.)"</string>
+    <string name="subtype_mode_iw_keyboard" msgid="1787536828253289950">"Klávesnice – hebrejština"</string>
     <string name="subtype_mode_it_keyboard" msgid="4934199655425394484">"Klávesnice – italština"</string>
     <string name="subtype_mode_nb_keyboard" msgid="1175783216100212360">"Klávesnice – norština"</string>
     <string name="subtype_mode_nl_keyboard" msgid="5090278083256037936">"Klávesnice – holandština"</string>
diff --git a/java/res/values-da/strings.xml b/java/res/values-da/strings.xml
index eb79888..ad92b24 100644
--- a/java/res/values-da/strings.xml
+++ b/java/res/values-da/strings.xml
@@ -27,7 +27,9 @@
     <string name="sound_on_keypress" msgid="6093592297198243644">"Lyd ved tastetryk"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"Popup ved tastetryk"</string>
     <string name="general_category" msgid="1859088467017573195">"Generelt"</string>
-    <string name="prediction_category" msgid="6361242011806282176">"Tekstkorrigering"</string>
+    <string name="correction_category" msgid="2236750915056607613">"Tekstkorrigering"</string>
+    <string name="ngram_category" msgid="5337109164339320257">"Forslag baseret på tidligere ord"</string>
+    <string name="misc_category" msgid="6894192814868233453">"Andre valgmuligheder"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Skriv aut. med stort"</string>
     <string name="quick_fixes" msgid="5353213327680897927">"Hurtige løsninger"</string>
     <string name="quick_fixes_summary" msgid="3405028402510332373">"Retter almindelige stavefejl"</string>
@@ -36,6 +38,7 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Vis altid"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"Vis i portrættilstand"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Skjul altid"</string>
+    <string name="prefs_use_spacebar_language_switch" msgid="8828538114550634449">"Brug mellemrumst. som sprogskifter"</string>
     <string name="prefs_settings_key" msgid="4623341240804046498">"Vis indstillingsnøgle"</string>
     <string name="settings_key_mode_auto_name" msgid="2993460277873684680">"Automatisk"</string>
     <string name="settings_key_mode_always_show_name" msgid="3047567041784760575">"Vis altid"</string>
@@ -45,8 +48,10 @@
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Fra"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Beskeden"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"Aggressiv"</string>
-    <string name="bigram_suggestion" msgid="1323347224043514969">"Bigram-forslag"</string>
+    <string name="bigram_suggestion" msgid="2636414079905220518">"Bigram-forslag"</string>
     <string name="bigram_suggestion_summary" msgid="4383845146070101531">"Brug forrige ord for at forbedre forslag"</string>
+    <string name="bigram_prediction" msgid="8914273444762259739">"Bigram-forudsigelse"</string>
+    <string name="bigram_prediction_summary" msgid="1747261921174300098">"Brug også tidligere ord til forudsigelse"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g>: Gemt"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Gå"</string>
     <string name="label_next_key" msgid="362972844525672568">"Næste"</string>
@@ -56,18 +61,6 @@
     <string name="label_more_key" msgid="3760239494604948502">"Mere"</string>
     <string name="label_pause_key" msgid="181098308428035340">"Pause"</string>
     <string name="label_wait_key" msgid="6402152600878093134">"Vent"</string>
-    <string name="description_delete_key" msgid="5586406298531883960">"Slet"</string>
-    <string name="description_return_key" msgid="8750044000806461678">"Tilbage"</string>
-    <string name="description_settings_key" msgid="7484527796782969219">"Indstillinger"</string>
-    <string name="description_shift_key" msgid="346906866277787836">"Shift"</string>
-    <string name="description_space_key" msgid="8512130111575878517">"Mellemrum"</string>
-    <string name="description_switch_alpha_symbol_key" msgid="4537975384274405537">"Symboler"</string>
-    <string name="description_tab_key" msgid="828186583738307137">"Fane"</string>
-    <string name="description_voice_key" msgid="3057731675774652754">"Stemmeinput"</string>
-    <string name="description_symbols_on" msgid="2994366855822840559">"Symboler: Til"</string>
-    <string name="description_symbols_off" msgid="3209578267079515136">"Symboler: Fra"</string>
-    <string name="description_shift_on" msgid="6983188949895971587">"Shift: Til"</string>
-    <string name="description_shift_off" msgid="8553265474523069034">"Shift: Fra"</string>
     <string name="voice_warning_title" msgid="4419354150908395008">"Stemmeinput"</string>
     <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"Stemmeinput understøttes i øjeblikket ikke for dit sprog, men fungerer på engelsk."</string>
     <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"Stemmeinput anvender Googles stemmegenkendelse. "<a href="http://m.google.com/privacy">"Fortrolighedspolitikken for mobilenheder"</a>" gælder."</string>
@@ -106,6 +99,7 @@
     <string name="prefs_enable_recorrection_summary" msgid="5082041365862396329">"Tryk på de indtastede ord for at rette dem. Kun når der er synlige forslag."</string>
     <string name="keyboard_layout" msgid="437433231038683666">"Tastaturtema"</string>
     <string name="subtype_mode_cs_keyboard" msgid="1141718931112377586">"Tjekkisk tastatur"</string>
+    <string name="subtype_mode_ar_keyboard" msgid="2655338636329774995">"Arabisk tastatur"</string>
     <string name="subtype_mode_da_keyboard" msgid="1243570804427922104">"Dansk tastatur"</string>
     <string name="subtype_mode_de_keyboard" msgid="1990979135959462145">"Tysk tastatur"</string>
     <string name="subtype_mode_en_GB_keyboard" msgid="7945856548410373708">"Engelsk tastatur (Storbritannien)"</string>
@@ -115,6 +109,7 @@
     <string name="subtype_mode_fr_keyboard" msgid="8016515336759761014">"Fransk tastatur"</string>
     <string name="subtype_mode_fr_CA_keyboard" msgid="2628517247158376263">"Fransk tastatur (Canada)"</string>
     <string name="subtype_mode_fr_CH_keyboard" msgid="6742806653181621228">"Fransk tastatur (Schweiz)"</string>
+    <string name="subtype_mode_iw_keyboard" msgid="1787536828253289950">"Hebraisk tastatur"</string>
     <string name="subtype_mode_it_keyboard" msgid="4934199655425394484">"Italiensk tastatur"</string>
     <string name="subtype_mode_nb_keyboard" msgid="1175783216100212360">"Norsk tastatur"</string>
     <string name="subtype_mode_nl_keyboard" msgid="5090278083256037936">"Hollandsk tastatur"</string>
diff --git a/java/res/xml/kbd_popup_narrow_template.xml b/java/res/values-de/config.xml
similarity index 67%
rename from java/res/xml/kbd_popup_narrow_template.xml
rename to java/res/values-de/config.xml
index 36caf1c..272ff32 100644
--- a/java/res/xml/kbd_popup_narrow_template.xml
+++ b/java/res/values-de/config.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2010, The Android Open Source Project
+** Copyright 2011, The Android Open Source Project
 **
 ** Licensed under the Apache License, Version 2.0 (the "License");
 ** you may not use this file except in compliance with the License.
@@ -18,10 +18,6 @@
 */
 -->
 
-<Keyboard xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyWidth="9.45%p"
-    latin:horizontalGap="0px"
-    latin:verticalGap="0px"
-    latin:rowHeight="@dimen/popup_key_height"
-    >
-</Keyboard>
+<resources>
+    <bool name="config_require_umlaut_processing">true</bool>
+</resources>
diff --git a/java/res/values-de/strings.xml b/java/res/values-de/strings.xml
index e79d2c8..65f0faa 100644
--- a/java/res/values-de/strings.xml
+++ b/java/res/values-de/strings.xml
@@ -27,7 +27,9 @@
     <string name="sound_on_keypress" msgid="6093592297198243644">"Ton bei Tastendruck"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"Pop-up bei Tastendruck"</string>
     <string name="general_category" msgid="1859088467017573195">"Allgemein"</string>
-    <string name="prediction_category" msgid="6361242011806282176">"Textkorrektur"</string>
+    <string name="correction_category" msgid="2236750915056607613">"Textkorrektur"</string>
+    <string name="ngram_category" msgid="5337109164339320257">"Vorschläge basieren auf bisherigen Wörtern"</string>
+    <string name="misc_category" msgid="6894192814868233453">"Sonstige Optionen"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Autom. Groß-/Kleinschr."</string>
     <string name="quick_fixes" msgid="5353213327680897927">"Quick Fixes"</string>
     <string name="quick_fixes_summary" msgid="3405028402510332373">"Korrigiert gängige Tippfehler"</string>
@@ -36,6 +38,7 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Immer anzeigen"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"Im Hochformat anzeigen"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Immer ausblenden"</string>
+    <string name="prefs_use_spacebar_language_switch" msgid="8828538114550634449">"Sprache mit Leertaste ändern"</string>
     <string name="prefs_settings_key" msgid="4623341240804046498">"Einstellungstaste anz."</string>
     <string name="settings_key_mode_auto_name" msgid="2993460277873684680">"Automatisch"</string>
     <string name="settings_key_mode_always_show_name" msgid="3047567041784760575">"Immer anzeigen"</string>
@@ -45,8 +48,10 @@
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Aus"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Mäßig"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"Stark"</string>
-    <string name="bigram_suggestion" msgid="1323347224043514969">"Bigramm-Vorschläge"</string>
+    <string name="bigram_suggestion" msgid="2636414079905220518">"Bigramm-Vorschläge"</string>
     <string name="bigram_suggestion_summary" msgid="4383845146070101531">"Zur Verbesserung des Vorschlags vorheriges Wort verwenden"</string>
+    <string name="bigram_prediction" msgid="8914273444762259739">"Bigramm-Vervollständigung"</string>
+    <string name="bigram_prediction_summary" msgid="1747261921174300098">"Vorheriges Wort auch für Vervollständigung verwenden"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g>: gespeichert"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Los"</string>
     <string name="label_next_key" msgid="362972844525672568">"Weiter"</string>
@@ -56,18 +61,6 @@
     <string name="label_more_key" msgid="3760239494604948502">"Mehr"</string>
     <string name="label_pause_key" msgid="181098308428035340">"Pause"</string>
     <string name="label_wait_key" msgid="6402152600878093134">"Warten"</string>
-    <string name="description_delete_key" msgid="5586406298531883960">"Löschen"</string>
-    <string name="description_return_key" msgid="8750044000806461678">"Eingabe"</string>
-    <string name="description_settings_key" msgid="7484527796782969219">"Einstellungen"</string>
-    <string name="description_shift_key" msgid="346906866277787836">"Umschalt"</string>
-    <string name="description_space_key" msgid="8512130111575878517">"Leerzeichen"</string>
-    <string name="description_switch_alpha_symbol_key" msgid="4537975384274405537">"Symbole"</string>
-    <string name="description_tab_key" msgid="828186583738307137">"Tab"</string>
-    <string name="description_voice_key" msgid="3057731675774652754">"Spracheingabe"</string>
-    <string name="description_symbols_on" msgid="2994366855822840559">"Symbole an"</string>
-    <string name="description_symbols_off" msgid="3209578267079515136">"Symbole aus"</string>
-    <string name="description_shift_on" msgid="6983188949895971587">"Umschalt an"</string>
-    <string name="description_shift_off" msgid="8553265474523069034">"Umschalt aus"</string>
     <string name="voice_warning_title" msgid="4419354150908395008">"Spracheingabe"</string>
     <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"Spracheingaben werden derzeit nicht für Ihre Sprache unterstützt, funktionieren jedoch in Englisch."</string>
     <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"Die Spracheingabe verwendet die Spracherkennung von Google. Es gelten die "<a href="http://m.google.com/privacy">"Google Mobile-Datenschutzbestimmungen"</a>"."</string>
@@ -106,6 +99,7 @@
     <string name="prefs_enable_recorrection_summary" msgid="5082041365862396329">"Tippen Sie zum Korrigieren auf eingegebene Wörter (nur, wenn Vorschläge angezeigt werden)."</string>
     <string name="keyboard_layout" msgid="437433231038683666">"Tastaturdesign"</string>
     <string name="subtype_mode_cs_keyboard" msgid="1141718931112377586">"Tschechische Tastatur"</string>
+    <string name="subtype_mode_ar_keyboard" msgid="2655338636329774995">"Arabische Tastatur"</string>
     <string name="subtype_mode_da_keyboard" msgid="1243570804427922104">"Dänische Tastatur"</string>
     <string name="subtype_mode_de_keyboard" msgid="1990979135959462145">"Deutsche Tastatur"</string>
     <string name="subtype_mode_en_GB_keyboard" msgid="7945856548410373708">"Englische Tastatur (GB)"</string>
@@ -115,6 +109,7 @@
     <string name="subtype_mode_fr_keyboard" msgid="8016515336759761014">"Französische Tastatur"</string>
     <string name="subtype_mode_fr_CA_keyboard" msgid="2628517247158376263">"Französische Tastatur (Kanada)"</string>
     <string name="subtype_mode_fr_CH_keyboard" msgid="6742806653181621228">"Französische Tastatur (Schweiz)"</string>
+    <string name="subtype_mode_iw_keyboard" msgid="1787536828253289950">"Hebräische Tastatur"</string>
     <string name="subtype_mode_it_keyboard" msgid="4934199655425394484">"Italienische Tastatur"</string>
     <string name="subtype_mode_nb_keyboard" msgid="1175783216100212360">"Norwegische Tastatur"</string>
     <string name="subtype_mode_nl_keyboard" msgid="5090278083256037936">"Niederländische Tastatur"</string>
diff --git a/java/res/values-el/strings.xml b/java/res/values-el/strings.xml
index 4412fa4..5cee959 100644
--- a/java/res/values-el/strings.xml
+++ b/java/res/values-el/strings.xml
@@ -27,7 +27,9 @@
     <string name="sound_on_keypress" msgid="6093592297198243644">"Ήχος κατά το πάτημα πλήκτρων"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"Εμφάνιση με το πάτημα πλήκτρου"</string>
     <string name="general_category" msgid="1859088467017573195">"Γενικά"</string>
-    <string name="prediction_category" msgid="6361242011806282176">"Διόρθωση κειμένου"</string>
+    <string name="correction_category" msgid="2236750915056607613">"Διόρθωση κειμένου"</string>
+    <string name="ngram_category" msgid="5337109164339320257">"Προτάσεις που βασίζονται σε προηγούμενες λέξεις"</string>
+    <string name="misc_category" msgid="6894192814868233453">"Άλλες επιλογές"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Αυτόματη χρήση κεφαλαίων"</string>
     <string name="quick_fixes" msgid="5353213327680897927">"Γρήγορες διορθώσεις"</string>
     <string name="quick_fixes_summary" msgid="3405028402510332373">"Διορθώνει συνηθισμένα λάθη πληκτρολόγησης"</string>
@@ -36,6 +38,7 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Να εμφανίζεται πάντα"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"Εμφάνιση σε λειτουργία κατακόρυφης προβολής"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Πάντα απόκρυψη"</string>
+    <string name="prefs_use_spacebar_language_switch" msgid="8828538114550634449">"Χρησιμοποιήστε τη δυνατότητα εναλλαγής γλώσσας του πλήκτρου διαστήματος"</string>
     <string name="prefs_settings_key" msgid="4623341240804046498">"Εμφάνιση πλήκτρου ρυθμίσεων"</string>
     <string name="settings_key_mode_auto_name" msgid="2993460277873684680">"Αυτόματο"</string>
     <string name="settings_key_mode_always_show_name" msgid="3047567041784760575">"Να εμφανίζεται πάντα"</string>
@@ -45,8 +48,10 @@
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Απενεργοποίηση"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Μέτρια"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"Υψηλή"</string>
-    <string name="bigram_suggestion" msgid="1323347224043514969">"Προτάσεις bigram"</string>
+    <string name="bigram_suggestion" msgid="2636414079905220518">"Προτάσεις bigram"</string>
     <string name="bigram_suggestion_summary" msgid="4383845146070101531">"Χρήση προηγούμενης λέξης για τη βελτίωση πρότασης"</string>
+    <string name="bigram_prediction" msgid="8914273444762259739">"Πρόβλεψη bigram"</string>
+    <string name="bigram_prediction_summary" msgid="1747261921174300098">"Χρησιμοποιήστε, επίσης, την προηγούμενη λέξη για πρόβλεψη"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : Αποθηκεύτηκε"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Μετ."</string>
     <string name="label_next_key" msgid="362972844525672568">"Επόμενο"</string>
@@ -56,18 +61,6 @@
     <string name="label_more_key" msgid="3760239494604948502">"Περισσότερα"</string>
     <string name="label_pause_key" msgid="181098308428035340">"Παύση"</string>
     <string name="label_wait_key" msgid="6402152600878093134">"Αναμ."</string>
-    <string name="description_delete_key" msgid="5586406298531883960">"Delete"</string>
-    <string name="description_return_key" msgid="8750044000806461678">"Return"</string>
-    <string name="description_settings_key" msgid="7484527796782969219">"Ρυθμίσεις"</string>
-    <string name="description_shift_key" msgid="346906866277787836">"Shift"</string>
-    <string name="description_space_key" msgid="8512130111575878517">"Κενό"</string>
-    <string name="description_switch_alpha_symbol_key" msgid="4537975384274405537">"Σύμβολα"</string>
-    <string name="description_tab_key" msgid="828186583738307137">"Tab"</string>
-    <string name="description_voice_key" msgid="3057731675774652754">"Φωνητική εντολή"</string>
-    <string name="description_symbols_on" msgid="2994366855822840559">"Σύμβολα ενεργά"</string>
-    <string name="description_symbols_off" msgid="3209578267079515136">"Σύμβολα ανενεργά"</string>
-    <string name="description_shift_on" msgid="6983188949895971587">"Shift ενεργό"</string>
-    <string name="description_shift_off" msgid="8553265474523069034">"Shift ανενεργό"</string>
     <string name="voice_warning_title" msgid="4419354150908395008">"Φωνητική είσοδος"</string>
     <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"Η φωνητική είσοδος δεν υποστηρίζεται αυτή τη στιγμή για τη γλώσσα σας, ωστόσο λειτουργεί στα Αγγλικά."</string>
     <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"Οι φωνητικές εντολές χρησιμοποιούν την τεχνολογία αναγνώρισης φωνής της Google. Ισχύει "<a href="http://m.google.com/privacy">"η Πολιτική Απορρήτου για κινητά"</a>"."</string>
@@ -106,6 +99,7 @@
     <string name="prefs_enable_recorrection_summary" msgid="5082041365862396329">"Αγγίξτε τις λέξες για να τις διορθώσετε, μόνο όταν οι προτάσεις είναι ορατές"</string>
     <string name="keyboard_layout" msgid="437433231038683666">"Θέμα πληκτρολογίου"</string>
     <string name="subtype_mode_cs_keyboard" msgid="1141718931112377586">"Τσεχικό πληκτρολόγιο"</string>
+    <string name="subtype_mode_ar_keyboard" msgid="2655338636329774995">"Πληκτρολόγιο με αραβική γραφή"</string>
     <string name="subtype_mode_da_keyboard" msgid="1243570804427922104">"Δανικό πληκτρολόγιο"</string>
     <string name="subtype_mode_de_keyboard" msgid="1990979135959462145">"Γερμανικό πληκτρολόγιο"</string>
     <string name="subtype_mode_en_GB_keyboard" msgid="7945856548410373708">"Αγγλικό (ΗΒ) πληκτρολόγιο"</string>
@@ -115,6 +109,7 @@
     <string name="subtype_mode_fr_keyboard" msgid="8016515336759761014">"Γαλλικό πληκτρολόγιο"</string>
     <string name="subtype_mode_fr_CA_keyboard" msgid="2628517247158376263">"Γαλλικό (Καναδάς) πληκτρολόγιο"</string>
     <string name="subtype_mode_fr_CH_keyboard" msgid="6742806653181621228">"Γαλλικό (Ελβετία) πληκτρολόγιο"</string>
+    <string name="subtype_mode_iw_keyboard" msgid="1787536828253289950">"Πληκτρολόγιο με εβραϊκή γραφή"</string>
     <string name="subtype_mode_it_keyboard" msgid="4934199655425394484">"Ιταλικό πληκτρολόγιο"</string>
     <string name="subtype_mode_nb_keyboard" msgid="1175783216100212360">"Νορβηγικό πληκτρολόγιο"</string>
     <string name="subtype_mode_nl_keyboard" msgid="5090278083256037936">"Ολλανδικό πληκτρολόγιο"</string>
diff --git a/java/res/values-en-rGB/strings.xml b/java/res/values-en-rGB/strings.xml
index b2e62b0..7d5d501 100644
--- a/java/res/values-en-rGB/strings.xml
+++ b/java/res/values-en-rGB/strings.xml
@@ -27,7 +27,9 @@
     <string name="sound_on_keypress" msgid="6093592297198243644">"Sound on key-press"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"Pop-up on key press"</string>
     <string name="general_category" msgid="1859088467017573195">"General"</string>
-    <string name="prediction_category" msgid="6361242011806282176">"Text correction"</string>
+    <string name="correction_category" msgid="2236750915056607613">"Text correction"</string>
+    <string name="ngram_category" msgid="5337109164339320257">"Suggestions based on previous words"</string>
+    <string name="misc_category" msgid="6894192814868233453">"Other Options"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Auto-capitalisation"</string>
     <string name="quick_fixes" msgid="5353213327680897927">"Quick fixes"</string>
     <string name="quick_fixes_summary" msgid="3405028402510332373">"Corrects commonly typed mistakes"</string>
@@ -36,6 +38,7 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Always show"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"Show on portrait mode"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Always hide"</string>
+    <string name="prefs_use_spacebar_language_switch" msgid="8828538114550634449">"Use the spacebar language switcher"</string>
     <string name="prefs_settings_key" msgid="4623341240804046498">"Show settings key"</string>
     <string name="settings_key_mode_auto_name" msgid="2993460277873684680">"Automatic"</string>
     <string name="settings_key_mode_always_show_name" msgid="3047567041784760575">"Always show"</string>
@@ -45,8 +48,10 @@
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Off"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Modest"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"Aggressive"</string>
-    <string name="bigram_suggestion" msgid="1323347224043514969">"Bigram Suggestions"</string>
+    <string name="bigram_suggestion" msgid="2636414079905220518">"Bigram Suggestions"</string>
     <string name="bigram_suggestion_summary" msgid="4383845146070101531">"Use previous word to improve suggestion"</string>
+    <string name="bigram_prediction" msgid="8914273444762259739">"Bigram prediction"</string>
+    <string name="bigram_prediction_summary" msgid="1747261921174300098">"Use previous word also for prediction"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : Saved"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Go"</string>
     <string name="label_next_key" msgid="362972844525672568">"Next"</string>
@@ -56,18 +61,6 @@
     <string name="label_more_key" msgid="3760239494604948502">"More"</string>
     <string name="label_pause_key" msgid="181098308428035340">"Pause"</string>
     <string name="label_wait_key" msgid="6402152600878093134">"Wait"</string>
-    <string name="description_delete_key" msgid="5586406298531883960">"Delete"</string>
-    <string name="description_return_key" msgid="8750044000806461678">"Return"</string>
-    <string name="description_settings_key" msgid="7484527796782969219">"Settings"</string>
-    <string name="description_shift_key" msgid="346906866277787836">"Shift"</string>
-    <string name="description_space_key" msgid="8512130111575878517">"Space"</string>
-    <string name="description_switch_alpha_symbol_key" msgid="4537975384274405537">"Symbols"</string>
-    <string name="description_tab_key" msgid="828186583738307137">"Tab"</string>
-    <string name="description_voice_key" msgid="3057731675774652754">"Voice Input"</string>
-    <string name="description_symbols_on" msgid="2994366855822840559">"Symbols on"</string>
-    <string name="description_symbols_off" msgid="3209578267079515136">"Symbols off"</string>
-    <string name="description_shift_on" msgid="6983188949895971587">"Shift on"</string>
-    <string name="description_shift_off" msgid="8553265474523069034">"Shift off"</string>
     <string name="voice_warning_title" msgid="4419354150908395008">"Voice input"</string>
     <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"Voice input is not currently supported for your language, but does work in English."</string>
     <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"Voice input uses Google\'s speech recognition. "<a href="http://m.google.com/privacy">"The Mobile Privacy Policy"</a>" applies."</string>
@@ -106,6 +99,7 @@
     <string name="prefs_enable_recorrection_summary" msgid="5082041365862396329">"Touch words entered to correct them, only when suggestions are visible"</string>
     <string name="keyboard_layout" msgid="437433231038683666">"Keyboard Theme"</string>
     <string name="subtype_mode_cs_keyboard" msgid="1141718931112377586">"Czech Keyboard"</string>
+    <string name="subtype_mode_ar_keyboard" msgid="2655338636329774995">"Arabic Keyboard"</string>
     <string name="subtype_mode_da_keyboard" msgid="1243570804427922104">"Danish Keyboard"</string>
     <string name="subtype_mode_de_keyboard" msgid="1990979135959462145">"German Keyboard"</string>
     <string name="subtype_mode_en_GB_keyboard" msgid="7945856548410373708">"English (UK) Keyboard"</string>
@@ -115,6 +109,7 @@
     <string name="subtype_mode_fr_keyboard" msgid="8016515336759761014">"French Keyboard"</string>
     <string name="subtype_mode_fr_CA_keyboard" msgid="2628517247158376263">"French (Canada) Keyboard"</string>
     <string name="subtype_mode_fr_CH_keyboard" msgid="6742806653181621228">"French (Switzerland) Keyboard"</string>
+    <string name="subtype_mode_iw_keyboard" msgid="1787536828253289950">"Hebrew Keyboard"</string>
     <string name="subtype_mode_it_keyboard" msgid="4934199655425394484">"Italian Keyboard"</string>
     <string name="subtype_mode_nb_keyboard" msgid="1175783216100212360">"Norwegian Keyboard"</string>
     <string name="subtype_mode_nl_keyboard" msgid="5090278083256037936">"Dutch Keyboard"</string>
diff --git a/java/res/values-es-rUS/strings.xml b/java/res/values-es-rUS/strings.xml
index 02f36ff..e40f98b 100644
--- a/java/res/values-es-rUS/strings.xml
+++ b/java/res/values-es-rUS/strings.xml
@@ -27,7 +27,9 @@
     <string name="sound_on_keypress" msgid="6093592297198243644">"Sonar al pulsar las teclas"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"Aviso emergente al pulsar tecla"</string>
     <string name="general_category" msgid="1859088467017573195">"General"</string>
-    <string name="prediction_category" msgid="6361242011806282176">"Corrección de texto"</string>
+    <string name="correction_category" msgid="2236750915056607613">"Corrección de texto"</string>
+    <string name="ngram_category" msgid="5337109164339320257">"Sugerencias sobre la base de palabras anteriores"</string>
+    <string name="misc_category" msgid="6894192814868233453">"Otras opciones"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Mayúsculas automáticas"</string>
     <string name="quick_fixes" msgid="5353213327680897927">"Arreglos rápidos"</string>
     <string name="quick_fixes_summary" msgid="3405028402510332373">"Corrige errores de escritura comunes"</string>
@@ -36,6 +38,7 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Mostrar siempre"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"Mostrar en modo retrato"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Ocultar siempre"</string>
+    <string name="prefs_use_spacebar_language_switch" msgid="8828538114550634449">"Usa select. de id. de barra espac."</string>
     <string name="prefs_settings_key" msgid="4623341240804046498">"Mostrar tecla de configuración"</string>
     <string name="settings_key_mode_auto_name" msgid="2993460277873684680">"Automático"</string>
     <string name="settings_key_mode_always_show_name" msgid="3047567041784760575">"Mostrar siempre"</string>
@@ -45,8 +48,10 @@
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Desactivado"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Moderado"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"Total"</string>
-    <string name="bigram_suggestion" msgid="1323347224043514969">"Sugerencias de Vigoran"</string>
+    <string name="bigram_suggestion" msgid="2636414079905220518">"Sugerencias de bigramas"</string>
     <string name="bigram_suggestion_summary" msgid="4383845146070101531">"Utiliza la palabra anterior para mejorar la sugerencia"</string>
+    <string name="bigram_prediction" msgid="8914273444762259739">"Predicción de biagramas"</string>
+    <string name="bigram_prediction_summary" msgid="1747261921174300098">"Usar la palabra anterior también para predicción."</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g>: guardada"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Ir"</string>
     <string name="label_next_key" msgid="362972844525672568">"Siguiente"</string>
@@ -56,18 +61,6 @@
     <string name="label_more_key" msgid="3760239494604948502">"Más"</string>
     <string name="label_pause_key" msgid="181098308428035340">"Pausa"</string>
     <string name="label_wait_key" msgid="6402152600878093134">"Espera"</string>
-    <string name="description_delete_key" msgid="5586406298531883960">"Eliminar"</string>
-    <string name="description_return_key" msgid="8750044000806461678">"Volver"</string>
-    <string name="description_settings_key" msgid="7484527796782969219">"Configuración"</string>
-    <string name="description_shift_key" msgid="346906866277787836">"Mayús"</string>
-    <string name="description_space_key" msgid="8512130111575878517">"Espacio"</string>
-    <string name="description_switch_alpha_symbol_key" msgid="4537975384274405537">"Símbolos"</string>
-    <string name="description_tab_key" msgid="828186583738307137">"Tab"</string>
-    <string name="description_voice_key" msgid="3057731675774652754">"Entrada de voz"</string>
-    <string name="description_symbols_on" msgid="2994366855822840559">"Símbolos activados"</string>
-    <string name="description_symbols_off" msgid="3209578267079515136">"Símbolos desactivados"</string>
-    <string name="description_shift_on" msgid="6983188949895971587">"Mayús activado"</string>
-    <string name="description_shift_off" msgid="8553265474523069034">"Mayús desactivado"</string>
     <string name="voice_warning_title" msgid="4419354150908395008">"Entrada por voz"</string>
     <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"La entrada por voz no está admitida en tu idioma, pero sí funciona en inglés."</string>
     <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"La entrada de voz usa el reconocimiento de voz de Google. "<a href="http://m.google.com/privacy">"Se aplica la política de privacidad para"</a>" celulares."</string>
@@ -106,6 +99,7 @@
     <string name="prefs_enable_recorrection_summary" msgid="5082041365862396329">"Toca las palabras ingresadas que desees corregir solo cuando las sugerencias estén visibles."</string>
     <string name="keyboard_layout" msgid="437433231038683666">"Tema del teclado"</string>
     <string name="subtype_mode_cs_keyboard" msgid="1141718931112377586">"Teclado en checo"</string>
+    <string name="subtype_mode_ar_keyboard" msgid="2655338636329774995">"Teclado árabe"</string>
     <string name="subtype_mode_da_keyboard" msgid="1243570804427922104">"Teclado en danés"</string>
     <string name="subtype_mode_de_keyboard" msgid="1990979135959462145">"Teclado en alemán"</string>
     <string name="subtype_mode_en_GB_keyboard" msgid="7945856548410373708">"Teclado en inglés (Reino Unido)"</string>
@@ -115,6 +109,7 @@
     <string name="subtype_mode_fr_keyboard" msgid="8016515336759761014">"Teclado en francés"</string>
     <string name="subtype_mode_fr_CA_keyboard" msgid="2628517247158376263">"Teclado en francés (Canadá)"</string>
     <string name="subtype_mode_fr_CH_keyboard" msgid="6742806653181621228">"Teclado en francés (Suiza)"</string>
+    <string name="subtype_mode_iw_keyboard" msgid="1787536828253289950">"Teclado hebreo"</string>
     <string name="subtype_mode_it_keyboard" msgid="4934199655425394484">"Teclado en italiano"</string>
     <string name="subtype_mode_nb_keyboard" msgid="1175783216100212360">"Teclado en noruego"</string>
     <string name="subtype_mode_nl_keyboard" msgid="5090278083256037936">"Teclado en holandés"</string>
diff --git a/java/res/values-es/strings.xml b/java/res/values-es/strings.xml
index 24e975b..e153242 100644
--- a/java/res/values-es/strings.xml
+++ b/java/res/values-es/strings.xml
@@ -27,7 +27,12 @@
     <string name="sound_on_keypress" msgid="6093592297198243644">"Sonido al pulsar tecla"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"Popup al pulsar tecla"</string>
     <string name="general_category" msgid="1859088467017573195">"General"</string>
-    <string name="prediction_category" msgid="6361242011806282176">"Corrección ortográfica"</string>
+    <!-- no translation found for correction_category (2236750915056607613) -->
+    <skip />
+    <!-- no translation found for ngram_category (5337109164339320257) -->
+    <skip />
+    <!-- no translation found for misc_category (6894192814868233453) -->
+    <skip />
     <string name="auto_cap" msgid="1719746674854628252">"Mayúsculas automáticas"</string>
     <string name="quick_fixes" msgid="5353213327680897927">"Correcciones rápidas"</string>
     <string name="quick_fixes_summary" msgid="3405028402510332373">"Corrige los errores tipográficos que se cometen con más frecuencia."</string>
@@ -36,6 +41,8 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Mostrar siempre"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"Mostrar en modo vertical"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Ocultar siempre"</string>
+    <!-- no translation found for prefs_use_spacebar_language_switch (8828538114550634449) -->
+    <skip />
     <string name="prefs_settings_key" msgid="4623341240804046498">"Mostrar tecla de ajustes"</string>
     <string name="settings_key_mode_auto_name" msgid="2993460277873684680">"Automáticamente"</string>
     <string name="settings_key_mode_always_show_name" msgid="3047567041784760575">"Mostrar siempre"</string>
@@ -45,8 +52,12 @@
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Desactivada"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Parcial"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"Total"</string>
-    <string name="bigram_suggestion" msgid="1323347224043514969">"Sugerencias de bigramas"</string>
+    <!-- outdated translation 7146707435859263625 -->     <string name="bigram_suggestion" msgid="2636414079905220518">"Sugerencias de bigramas"</string>
     <string name="bigram_suggestion_summary" msgid="4383845146070101531">"Usar palabra anterior para mejorar sugerencias"</string>
+    <!-- no translation found for bigram_prediction (8914273444762259739) -->
+    <skip />
+    <!-- no translation found for bigram_prediction_summary (1747261921174300098) -->
+    <skip />
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g>: guardada"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Ir"</string>
     <string name="label_next_key" msgid="362972844525672568">"Sig."</string>
@@ -56,18 +67,6 @@
     <string name="label_more_key" msgid="3760239494604948502">"Más"</string>
     <string name="label_pause_key" msgid="181098308428035340">"Pausa"</string>
     <string name="label_wait_key" msgid="6402152600878093134">"Espera"</string>
-    <string name="description_delete_key" msgid="5586406298531883960">"Eliminar"</string>
-    <string name="description_return_key" msgid="8750044000806461678">"Retroceso"</string>
-    <string name="description_settings_key" msgid="7484527796782969219">"Ajustes"</string>
-    <string name="description_shift_key" msgid="346906866277787836">"Mayús"</string>
-    <string name="description_space_key" msgid="8512130111575878517">"Espacio"</string>
-    <string name="description_switch_alpha_symbol_key" msgid="4537975384274405537">"Símbolos"</string>
-    <string name="description_tab_key" msgid="828186583738307137">"Tabulador"</string>
-    <string name="description_voice_key" msgid="3057731675774652754">"Entrada de voz"</string>
-    <string name="description_symbols_on" msgid="2994366855822840559">"Símbolos activados"</string>
-    <string name="description_symbols_off" msgid="3209578267079515136">"Símbolos desactivados"</string>
-    <string name="description_shift_on" msgid="6983188949895971587">"Mayús activadas"</string>
-    <string name="description_shift_off" msgid="8553265474523069034">"Mayús desactivadas"</string>
     <string name="voice_warning_title" msgid="4419354150908395008">"Introducción de voz"</string>
     <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"Actualmente la introducción de voz no está disponible en tu idioma, pero se puede utilizar en inglés."</string>
     <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"La entrada de voz utiliza el reconocimiento de voz de Google. Se aplica la "<a href="http://m.google.com/privacy">"Política de privacidad de Google para móviles"</a>"."</string>
@@ -106,6 +105,7 @@
     <string name="prefs_enable_recorrection_summary" msgid="5082041365862396329">"Toca las palabras introducidas para corregirlas, solo cuando las sugerencias sean visibles."</string>
     <string name="keyboard_layout" msgid="437433231038683666">"Tema de teclado"</string>
     <string name="subtype_mode_cs_keyboard" msgid="1141718931112377586">"Teclado checo"</string>
+    <string name="subtype_mode_ar_keyboard" msgid="2655338636329774995">"Teclado árabe"</string>
     <string name="subtype_mode_da_keyboard" msgid="1243570804427922104">"Teclado danés"</string>
     <string name="subtype_mode_de_keyboard" msgid="1990979135959462145">"Teclado alemán"</string>
     <string name="subtype_mode_en_GB_keyboard" msgid="7945856548410373708">"Teclado inglés (Reino Unido)"</string>
@@ -115,6 +115,7 @@
     <string name="subtype_mode_fr_keyboard" msgid="8016515336759761014">"Teclado francés"</string>
     <string name="subtype_mode_fr_CA_keyboard" msgid="2628517247158376263">"Teclado francés (Canadá)"</string>
     <string name="subtype_mode_fr_CH_keyboard" msgid="6742806653181621228">"Teclado francés (Suiza)"</string>
+    <string name="subtype_mode_iw_keyboard" msgid="1787536828253289950">"Teclado hebreo"</string>
     <string name="subtype_mode_it_keyboard" msgid="4934199655425394484">"Teclado italiano"</string>
     <string name="subtype_mode_nb_keyboard" msgid="1175783216100212360">"Teclado noruego"</string>
     <string name="subtype_mode_nl_keyboard" msgid="5090278083256037936">"Teclado holandés"</string>
diff --git a/java/res/values-fa/strings.xml b/java/res/values-fa/strings.xml
index b594411..25ee9df 100644
--- a/java/res/values-fa/strings.xml
+++ b/java/res/values-fa/strings.xml
@@ -27,7 +27,9 @@
     <string name="sound_on_keypress" msgid="6093592297198243644">"صدا با فشار کلید"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"بازشو با فشار کلید"</string>
     <string name="general_category" msgid="1859088467017573195">"کلی"</string>
-    <string name="prediction_category" msgid="6361242011806282176">"تصحیح متن"</string>
+    <string name="correction_category" msgid="2236750915056607613">"تصحیح متن"</string>
+    <string name="ngram_category" msgid="5337109164339320257">"پیشنهادهایی بر اساس کلمه های قبلی"</string>
+    <string name="misc_category" msgid="6894192814868233453">"سایر گزینه ها"</string>
     <string name="auto_cap" msgid="1719746674854628252">"نوشتن با حروف بزرگ خودکار"</string>
     <string name="quick_fixes" msgid="5353213327680897927">"راه حل های سریع"</string>
     <string name="quick_fixes_summary" msgid="3405028402510332373">"تصحیح خطاهای تایپی رایج"</string>
@@ -36,6 +38,7 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"همیشه نمایش داده شود"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"نمایش در حالت عمودی"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"همیشه پنهان شود"</string>
+    <string name="prefs_use_spacebar_language_switch" msgid="8828538114550634449">"از ویژگی تعویض زبان کلید فاصله استفاده شود"</string>
     <string name="prefs_settings_key" msgid="4623341240804046498">"نمایش کلید تنظیمات"</string>
     <string name="settings_key_mode_auto_name" msgid="2993460277873684680">"خودکار"</string>
     <string name="settings_key_mode_always_show_name" msgid="3047567041784760575">"همیشه نمایش"</string>
@@ -45,8 +48,10 @@
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"خاموش"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"متوسط"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"فعال"</string>
-    <string name="bigram_suggestion" msgid="1323347224043514969">"توضیحات بیگرام"</string>
+    <string name="bigram_suggestion" msgid="2636414079905220518">"پیشنهادهای Bigram"</string>
     <string name="bigram_suggestion_summary" msgid="4383845146070101531">"برای بهبود پیشنهاد از کلمه قبلی استفاده شود"</string>
+    <string name="bigram_prediction" msgid="8914273444762259739">"پیش بینی Bigram"</string>
+    <string name="bigram_prediction_summary" msgid="1747261921174300098">"استفاده از کلمه قبلی برای پیش بینی"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : ذخیره شد"</string>
     <string name="label_go_key" msgid="1635148082137219148">"برو"</string>
     <string name="label_next_key" msgid="362972844525672568">"بعدی"</string>
@@ -56,18 +61,6 @@
     <string name="label_more_key" msgid="3760239494604948502">"بیشتر"</string>
     <string name="label_pause_key" msgid="181098308428035340">"توقف موقت"</string>
     <string name="label_wait_key" msgid="6402152600878093134">"منتظر بمانید"</string>
-    <string name="description_delete_key" msgid="5586406298531883960">"Delete"</string>
-    <string name="description_return_key" msgid="8750044000806461678">"Return"</string>
-    <string name="description_settings_key" msgid="7484527796782969219">"تنظیمات"</string>
-    <string name="description_shift_key" msgid="346906866277787836">"Shift"</string>
-    <string name="description_space_key" msgid="8512130111575878517">"فاصله"</string>
-    <string name="description_switch_alpha_symbol_key" msgid="4537975384274405537">"نمادها"</string>
-    <string name="description_tab_key" msgid="828186583738307137">"Tab"</string>
-    <string name="description_voice_key" msgid="3057731675774652754">"ورودی صوتی"</string>
-    <string name="description_symbols_on" msgid="2994366855822840559">"نمادها روشن"</string>
-    <string name="description_symbols_off" msgid="3209578267079515136">"نمادها خاموش"</string>
-    <string name="description_shift_on" msgid="6983188949895971587">"Shift روشن"</string>
-    <string name="description_shift_off" msgid="8553265474523069034">"Shift خاموش"</string>
     <string name="voice_warning_title" msgid="4419354150908395008">"ورودی صوتی"</string>
     <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"ورودی صوتی در حال حاضر برای زبان شما پشتیبانی نمی شود اما برای زبان انگلیسی فعال است."</string>
     <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"ورودی صوتی از تشخیص صدای Google استفاده می کند. "<a href="http://m.google.com/privacy">"خط مشی رازداری Mobile "</a>" اعمال می شود."</string>
@@ -106,6 +99,7 @@
     <string name="prefs_enable_recorrection_summary" msgid="5082041365862396329">"فقط هنگامی که پیشنهادات قابل مشاهده هستند، برای تصحیح کلمات وارد شده آنها را لمس کنید"</string>
     <string name="keyboard_layout" msgid="437433231038683666">"طرح زمینه صفحه کلید"</string>
     <string name="subtype_mode_cs_keyboard" msgid="1141718931112377586">"صفحه کلید چک"</string>
+    <string name="subtype_mode_ar_keyboard" msgid="2655338636329774995">"صفحه کلید عربی"</string>
     <string name="subtype_mode_da_keyboard" msgid="1243570804427922104">"صفحه کلید دانمارکی"</string>
     <string name="subtype_mode_de_keyboard" msgid="1990979135959462145">"صفحه کلید آلمانی"</string>
     <string name="subtype_mode_en_GB_keyboard" msgid="7945856548410373708">"صفحه کلید انگلیسی (بریتانیایی)"</string>
@@ -115,6 +109,7 @@
     <string name="subtype_mode_fr_keyboard" msgid="8016515336759761014">"صفحه کلید فرانسوی"</string>
     <string name="subtype_mode_fr_CA_keyboard" msgid="2628517247158376263">"صفحه کلید فرانسوی (کانادایی)"</string>
     <string name="subtype_mode_fr_CH_keyboard" msgid="6742806653181621228">"صفحه کلید فرانسوی (سوئیس)"</string>
+    <string name="subtype_mode_iw_keyboard" msgid="1787536828253289950">"صفحه کلید عبری"</string>
     <string name="subtype_mode_it_keyboard" msgid="4934199655425394484">"صفحه کلید ایتالیایی"</string>
     <string name="subtype_mode_nb_keyboard" msgid="1175783216100212360">"صفحه کلید نروژی"</string>
     <string name="subtype_mode_nl_keyboard" msgid="5090278083256037936">"صفحه کلید هلندی"</string>
diff --git a/java/res/values-fi/strings.xml b/java/res/values-fi/strings.xml
index 0bf3855..f02e821 100644
--- a/java/res/values-fi/strings.xml
+++ b/java/res/values-fi/strings.xml
@@ -27,7 +27,9 @@
     <string name="sound_on_keypress" msgid="6093592297198243644">"Toista ääni näppäimiä painettaessa"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"Ponnahdusikkuna painalluksella"</string>
     <string name="general_category" msgid="1859088467017573195">"Yleinen"</string>
-    <string name="prediction_category" msgid="6361242011806282176">"Tekstin korjaus"</string>
+    <string name="correction_category" msgid="2236750915056607613">"Tekstin korjaus"</string>
+    <string name="ngram_category" msgid="5337109164339320257">"Aiempiin sanoihin perustuvat ehdotukset"</string>
+    <string name="misc_category" msgid="6894192814868233453">"Muut vaihtoehdot"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Automaattiset isot kirjaimet"</string>
     <string name="quick_fixes" msgid="5353213327680897927">"Pikakorjaukset"</string>
     <string name="quick_fixes_summary" msgid="3405028402510332373">"Korjaa yleiset kirjoitusvirheet"</string>
@@ -36,6 +38,7 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Näytä aina"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"Näytä pystysuunnassa"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Piilota aina"</string>
+    <string name="prefs_use_spacebar_language_switch" msgid="8828538114550634449">"Vaihda kieli välil."</string>
     <string name="prefs_settings_key" msgid="4623341240804046498">"Näytä asetukset-näppäin"</string>
     <string name="settings_key_mode_auto_name" msgid="2993460277873684680">"Automaattinen"</string>
     <string name="settings_key_mode_always_show_name" msgid="3047567041784760575">"Näytä aina"</string>
@@ -45,8 +48,10 @@
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Älä käytä"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Osittainen"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"Täysi"</string>
-    <string name="bigram_suggestion" msgid="1323347224043514969">"Bigram-ehdotukset"</string>
+    <string name="bigram_suggestion" msgid="2636414079905220518">"Bigram-ehdotukset"</string>
     <string name="bigram_suggestion_summary" msgid="4383845146070101531">"Paranna ehdotusta aiemman sanan avulla"</string>
+    <string name="bigram_prediction" msgid="8914273444762259739">"Bigram-ennakointi"</string>
+    <string name="bigram_prediction_summary" msgid="1747261921174300098">"Käytä edellistä sanaa myös ennakointiin"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : Tallennettu"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Siirry"</string>
     <string name="label_next_key" msgid="362972844525672568">"Seuraava"</string>
@@ -56,18 +61,6 @@
     <string name="label_more_key" msgid="3760239494604948502">"Lisää"</string>
     <string name="label_pause_key" msgid="181098308428035340">"Tauko"</string>
     <string name="label_wait_key" msgid="6402152600878093134">"Odota"</string>
-    <string name="description_delete_key" msgid="5586406298531883960">"Poista"</string>
-    <string name="description_return_key" msgid="8750044000806461678">"Rivinvaihto"</string>
-    <string name="description_settings_key" msgid="7484527796782969219">"Asetukset"</string>
-    <string name="description_shift_key" msgid="346906866277787836">"Shift"</string>
-    <string name="description_space_key" msgid="8512130111575878517">"Välilyönti"</string>
-    <string name="description_switch_alpha_symbol_key" msgid="4537975384274405537">"Symbolit"</string>
-    <string name="description_tab_key" msgid="828186583738307137">"Sarkain"</string>
-    <string name="description_voice_key" msgid="3057731675774652754">"Äänisyöte"</string>
-    <string name="description_symbols_on" msgid="2994366855822840559">"Symbolit käytössä"</string>
-    <string name="description_symbols_off" msgid="3209578267079515136">"Symbolit pois käytöstä"</string>
-    <string name="description_shift_on" msgid="6983188949895971587">"Shift käytössä"</string>
-    <string name="description_shift_off" msgid="8553265474523069034">"Shift pois käytöstä"</string>
     <string name="voice_warning_title" msgid="4419354150908395008">"Äänisyöte"</string>
     <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"Äänisyötettä ei vielä tueta kielelläsi, mutta voit käyttää sitä englanniksi."</string>
     <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"Äänisyöte käyttää Googlen puheentunnistusta. "<a href="http://m.google.com/privacy">"Mobile-tietosuojakäytäntö"</a>" on voimassa."</string>
@@ -106,6 +99,7 @@
     <string name="prefs_enable_recorrection_summary" msgid="5082041365862396329">"Korjaa annetut sanat napauttamalla. (Vain, kun ehdotuksia on näkyvillä.)"</string>
     <string name="keyboard_layout" msgid="437433231038683666">"Näppäimistön teema"</string>
     <string name="subtype_mode_cs_keyboard" msgid="1141718931112377586">"Näppäimistö: tšekki"</string>
+    <string name="subtype_mode_ar_keyboard" msgid="2655338636329774995">"Arabiankielinen näppäimistö"</string>
     <string name="subtype_mode_da_keyboard" msgid="1243570804427922104">"Näppäimistö: tanska"</string>
     <string name="subtype_mode_de_keyboard" msgid="1990979135959462145">"Näppäimistö: saksa"</string>
     <string name="subtype_mode_en_GB_keyboard" msgid="7945856548410373708">"Näppäimistö: englanti (UK)"</string>
@@ -115,6 +109,7 @@
     <string name="subtype_mode_fr_keyboard" msgid="8016515336759761014">"Näppäimistö: ranska"</string>
     <string name="subtype_mode_fr_CA_keyboard" msgid="2628517247158376263">"Näppäimistö: ranska (Kanada)"</string>
     <string name="subtype_mode_fr_CH_keyboard" msgid="6742806653181621228">"Näppäimistö: ranska (Sveitsi)"</string>
+    <string name="subtype_mode_iw_keyboard" msgid="1787536828253289950">"Hepreankielinen näppäimistö"</string>
     <string name="subtype_mode_it_keyboard" msgid="4934199655425394484">"Näppäimistö: italia"</string>
     <string name="subtype_mode_nb_keyboard" msgid="1175783216100212360">"Näppäimistö: norja"</string>
     <string name="subtype_mode_nl_keyboard" msgid="5090278083256037936">"Näppäimistö: hollanti"</string>
diff --git a/java/res/values-fr/donottranslate-altchars.xml b/java/res/values-fr/donottranslate-altchars.xml
index e01f63f..ae9292f 100644
--- a/java/res/values-fr/donottranslate-altchars.xml
+++ b/java/res/values-fr/donottranslate-altchars.xml
@@ -18,11 +18,11 @@
 */
 -->
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="alternates_for_a">1,à,â,æ,á,ä,ã,å,ā,ª</string>
-    <string name="alternates_for_e">3,é,è,ê,ë,ę,ė,ē</string>
-    <string name="alternates_for_i">8,î,ï,ì,í,į,ī</string>
-    <string name="alternates_for_o">9,ô,œ,ö,ò,ó,õ,ø,ō,º</string>
-    <string name="alternates_for_u">7,û,ù,ü,ú,ū</string>
+    <string name="alternates_for_a">à,â,1,æ,á,ä,ã,å,ā,ª</string>
+    <string name="alternates_for_e">é,è,ê,ë,3,ę,ė,ē</string>
+    <string name="alternates_for_i">î,8,ï,ì,í,į,ī</string>
+    <string name="alternates_for_o">ô,œ,9,ö,ò,ó,õ,ø,ō,º</string>
+    <string name="alternates_for_u">ù,û,7,ü,ú,ū</string>
     <string name="alternates_for_c">ç,ć,č</string>
     <string name="alternates_for_y">6,ÿ</string>
     <string name="alternates_for_q"></string>
diff --git a/java/res/values-fr/donottranslate.xml b/java/res/values-fr/donottranslate.xml
index 6c35362..09c37e3 100644
--- a/java/res/values-fr/donottranslate.xml
+++ b/java/res/values-fr/donottranslate.xml
@@ -18,8 +18,12 @@
 */
 -->
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- Symbols that are commonly considered word separators in this language -->
-    <string name="word_separators">.\u0009\u0020,;:!?\n()[]*&amp;@{}/&lt;&gt;_+=|\u0022</string>
-    <!-- Symbols that are sentence separators, for purposes of making it hug the last sentence. -->
-    <string name="sentence_separators">.,</string>
+    <!-- Symbols that should be swapped with a magic space -->
+    <string name="magic_space_swapping_symbols">.,\u0022)]}</string>
+    <!-- Symbols that should strip a magic space -->
+    <string name="magic_space_stripping_symbols">\u0009\u0020\u0027\n-/_</string>
+    <!-- Symbols that should promote magic spaces into real space -->
+    <string name="magic_space_promoting_symbols">;:!?([*&amp;@{&lt;&gt;+=|</string>
+    <!-- Symbols that do NOT separate words -->
+    <string name="non_word_separator_symbols">\u0027</string>
 </resources>
diff --git a/java/res/values-fr/strings.xml b/java/res/values-fr/strings.xml
index 65e88c1..276aed2 100644
--- a/java/res/values-fr/strings.xml
+++ b/java/res/values-fr/strings.xml
@@ -27,7 +27,9 @@
     <string name="sound_on_keypress" msgid="6093592297198243644">"Son à chaque touche"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"Agrandir les caractères"</string>
     <string name="general_category" msgid="1859088467017573195">"Général"</string>
-    <string name="prediction_category" msgid="6361242011806282176">"Correction du texte"</string>
+    <string name="correction_category" msgid="2236750915056607613">"Correction du texte"</string>
+    <string name="ngram_category" msgid="5337109164339320257">"Suggestions basées sur les mots précédents"</string>
+    <string name="misc_category" msgid="6894192814868233453">"Autres options"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Majuscules auto"</string>
     <string name="quick_fixes" msgid="5353213327680897927">"Corrections rapides"</string>
     <string name="quick_fixes_summary" msgid="3405028402510332373">"Corrige les fautes de frappe courantes"</string>
@@ -36,6 +38,7 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Toujours afficher"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"Afficher en mode Portrait"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Toujours masquer"</string>
+    <string name="prefs_use_spacebar_language_switch" msgid="8828538114550634449">"Sélecteur langue barre d\'espace"</string>
     <string name="prefs_settings_key" msgid="4623341240804046498">"Afficher touche param."</string>
     <string name="settings_key_mode_auto_name" msgid="2993460277873684680">"Automatique"</string>
     <string name="settings_key_mode_always_show_name" msgid="3047567041784760575">"Toujours afficher"</string>
@@ -45,8 +48,10 @@
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Désactiver"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Simple"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"Proactive"</string>
-    <string name="bigram_suggestion" msgid="1323347224043514969">"Suggestions de type bigramme"</string>
+    <string name="bigram_suggestion" msgid="2636414079905220518">"Suggestions de type bigramme"</string>
     <string name="bigram_suggestion_summary" msgid="4383845146070101531">"Améliorer la suggestion en fonction du mot précédent"</string>
+    <string name="bigram_prediction" msgid="8914273444762259739">"Prédiction bigramme"</string>
+    <string name="bigram_prediction_summary" msgid="1747261921174300098">"Utiliser le mot précédent pour la prédiction"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : enregistré"</string>
     <string name="label_go_key" msgid="1635148082137219148">"OK"</string>
     <string name="label_next_key" msgid="362972844525672568">"Suivant"</string>
@@ -56,18 +61,6 @@
     <string name="label_more_key" msgid="3760239494604948502">"Plus"</string>
     <string name="label_pause_key" msgid="181098308428035340">"Pause"</string>
     <string name="label_wait_key" msgid="6402152600878093134">"Attente"</string>
-    <string name="description_delete_key" msgid="5586406298531883960">"Supprimer"</string>
-    <string name="description_return_key" msgid="8750044000806461678">"Entrée"</string>
-    <string name="description_settings_key" msgid="7484527796782969219">"Paramètres"</string>
-    <string name="description_shift_key" msgid="346906866277787836">"Maj"</string>
-    <string name="description_space_key" msgid="8512130111575878517">"Espace"</string>
-    <string name="description_switch_alpha_symbol_key" msgid="4537975384274405537">"Symboles"</string>
-    <string name="description_tab_key" msgid="828186583738307137">"Tabulation"</string>
-    <string name="description_voice_key" msgid="3057731675774652754">"Saisie vocale"</string>
-    <string name="description_symbols_on" msgid="2994366855822840559">"Symboles activés"</string>
-    <string name="description_symbols_off" msgid="3209578267079515136">"Symboles désactivés"</string>
-    <string name="description_shift_on" msgid="6983188949895971587">"Maj activée"</string>
-    <string name="description_shift_off" msgid="8553265474523069034">"Maj désactivée"</string>
     <string name="voice_warning_title" msgid="4419354150908395008">"Saisie vocale"</string>
     <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"La saisie vocale n\'est pas encore prise en charge pour votre langue, mais elle fonctionne en anglais."</string>
     <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"La saisie vocale fait appel à la reconnaissance vocale de Google. Les "<a href="http://m.google.com/privacy">"Règles de confidentialité Google Mobile"</a>" s\'appliquent."</string>
@@ -106,6 +99,7 @@
     <string name="prefs_enable_recorrection_summary" msgid="5082041365862396329">"Appuyer sur les mots saisis pour les corriger, uniquement lorsque des suggestions sont visibles"</string>
     <string name="keyboard_layout" msgid="437433231038683666">"Thème du clavier"</string>
     <string name="subtype_mode_cs_keyboard" msgid="1141718931112377586">"Clavier tchèque"</string>
+    <string name="subtype_mode_ar_keyboard" msgid="2655338636329774995">"Clavier arabe"</string>
     <string name="subtype_mode_da_keyboard" msgid="1243570804427922104">"Clavier danois"</string>
     <string name="subtype_mode_de_keyboard" msgid="1990979135959462145">"Clavier allemand"</string>
     <string name="subtype_mode_en_GB_keyboard" msgid="7945856548410373708">"Clavier anglais (Royaume-Uni)"</string>
@@ -115,6 +109,7 @@
     <string name="subtype_mode_fr_keyboard" msgid="8016515336759761014">"Clavier français"</string>
     <string name="subtype_mode_fr_CA_keyboard" msgid="2628517247158376263">"Clavier français (Canada)"</string>
     <string name="subtype_mode_fr_CH_keyboard" msgid="6742806653181621228">"Clavier français (Suisse)"</string>
+    <string name="subtype_mode_iw_keyboard" msgid="1787536828253289950">"Clavier hébreu"</string>
     <string name="subtype_mode_it_keyboard" msgid="4934199655425394484">"Clavier italien"</string>
     <string name="subtype_mode_nb_keyboard" msgid="1175783216100212360">"Clavier norvégien"</string>
     <string name="subtype_mode_nl_keyboard" msgid="5090278083256037936">"Clavier néerlandais"</string>
diff --git a/java/res/xml/kbd_popup_narrow_template.xml b/java/res/values-hdpi/config.xml
similarity index 67%
copy from java/res/xml/kbd_popup_narrow_template.xml
copy to java/res/values-hdpi/config.xml
index 36caf1c..7333e94 100644
--- a/java/res/xml/kbd_popup_narrow_template.xml
+++ b/java/res/values-hdpi/config.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2010, The Android Open Source Project
+** Copyright 2011, The Android Open Source Project
 **
 ** Licensed under the Apache License, Version 2.0 (the "License");
 ** you may not use this file except in compliance with the License.
@@ -18,10 +18,7 @@
 */
 -->
 
-<Keyboard xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-    latin:keyWidth="9.45%p"
-    latin:horizontalGap="0px"
-    latin:verticalGap="0px"
-    latin:rowHeight="@dimen/popup_key_height"
-    >
-</Keyboard>
+<resources>
+    <!--  Screen metrics for logging. 0 = "mdpi", 1 = "hdpi", 2 = "xlarge" -->
+    <integer name="log_screen_metrics">1</integer>
+</resources>
diff --git a/java/res/values-hr/strings.xml b/java/res/values-hr/strings.xml
index e6879a6..6117821 100644
--- a/java/res/values-hr/strings.xml
+++ b/java/res/values-hr/strings.xml
@@ -27,7 +27,9 @@
     <string name="sound_on_keypress" msgid="6093592297198243644">"Zvuk pri pritisku tipke"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"Povećanja na pritisak tipke"</string>
     <string name="general_category" msgid="1859088467017573195">"Općenito"</string>
-    <string name="prediction_category" msgid="6361242011806282176">"Ispravak teksta"</string>
+    <string name="correction_category" msgid="2236750915056607613">"Ispravak teksta"</string>
+    <string name="ngram_category" msgid="5337109164339320257">"Prijedlozi na temelju prethodnih riječi"</string>
+    <string name="misc_category" msgid="6894192814868233453">"Ostale opcije"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Automatsko pisanje velikih slova"</string>
     <string name="quick_fixes" msgid="5353213327680897927">"Brzi popravci"</string>
     <string name="quick_fixes_summary" msgid="3405028402510332373">"Ispravlja uobičajene pogreške u pisanju"</string>
@@ -36,6 +38,7 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Uvijek prikaži"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"Prikaži u portretnom načinu"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Uvijek sakrij"</string>
+    <string name="prefs_use_spacebar_language_switch" msgid="8828538114550634449">"Koristite razmaknicu za prebacivanje jezika"</string>
     <string name="prefs_settings_key" msgid="4623341240804046498">"Prikaži tipku postavki"</string>
     <string name="settings_key_mode_auto_name" msgid="2993460277873684680">"Automatski"</string>
     <string name="settings_key_mode_always_show_name" msgid="3047567041784760575">"Uvijek prikaži"</string>
@@ -45,8 +48,10 @@
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Isključeno"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Skromno"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"Agresivno"</string>
-    <string name="bigram_suggestion" msgid="1323347224043514969">"Bigram prijedlozi"</string>
+    <string name="bigram_suggestion" msgid="2636414079905220518">"Bigram prijedlozi"</string>
     <string name="bigram_suggestion_summary" msgid="4383845146070101531">"Upotrijebi prethodnu riječ radi poboljšanja prijedloga"</string>
+    <string name="bigram_prediction" msgid="8914273444762259739">"Bigram predviđanje"</string>
+    <string name="bigram_prediction_summary" msgid="1747261921174300098">"Koristite prethodnu riječ i za predviđanje"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : Spremljeno"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Idi"</string>
     <string name="label_next_key" msgid="362972844525672568">"Dalje"</string>
@@ -56,18 +61,6 @@
     <string name="label_more_key" msgid="3760239494604948502">"Više"</string>
     <string name="label_pause_key" msgid="181098308428035340">"Pauza"</string>
     <string name="label_wait_key" msgid="6402152600878093134">"Pričekaj"</string>
-    <string name="description_delete_key" msgid="5586406298531883960">"Delete"</string>
-    <string name="description_return_key" msgid="8750044000806461678">"Enter"</string>
-    <string name="description_settings_key" msgid="7484527796782969219">"Postavke"</string>
-    <string name="description_shift_key" msgid="346906866277787836">"Shift"</string>
-    <string name="description_space_key" msgid="8512130111575878517">"Razmaknica"</string>
-    <string name="description_switch_alpha_symbol_key" msgid="4537975384274405537">"Simboli"</string>
-    <string name="description_tab_key" msgid="828186583738307137">"Tabulator"</string>
-    <string name="description_voice_key" msgid="3057731675774652754">"Glasovni unos"</string>
-    <string name="description_symbols_on" msgid="2994366855822840559">"Simboli uključeni"</string>
-    <string name="description_symbols_off" msgid="3209578267079515136">"Simboli isključeni"</string>
-    <string name="description_shift_on" msgid="6983188949895971587">"Shift uključen"</string>
-    <string name="description_shift_off" msgid="8553265474523069034">"Shift isključen"</string>
     <string name="voice_warning_title" msgid="4419354150908395008">"Glasovni unos"</string>
     <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"Vaš jezik trenutno nije podržan za glasovni unos, ali radi za engleski."</string>
     <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"Glasovni unos upotrebljava Googleovo prepoznavanje govora. Primjenjuju se "<a href="http://m.google.com/privacy">"Pravila o privatnosti za uslugu Mobile"</a>"."</string>
@@ -106,6 +99,7 @@
     <string name="prefs_enable_recorrection_summary" msgid="5082041365862396329">"Dodirnite unesene riječi da biste ih ispravili samo kada su prijedlozi vidljivi"</string>
     <string name="keyboard_layout" msgid="437433231038683666">"Tema tipkovnice"</string>
     <string name="subtype_mode_cs_keyboard" msgid="1141718931112377586">"Češka tipkovnica"</string>
+    <string name="subtype_mode_ar_keyboard" msgid="2655338636329774995">"Arapska tipkovnica"</string>
     <string name="subtype_mode_da_keyboard" msgid="1243570804427922104">"Danska tipkovnica"</string>
     <string name="subtype_mode_de_keyboard" msgid="1990979135959462145">"Njemačka tipkovnica"</string>
     <string name="subtype_mode_en_GB_keyboard" msgid="7945856548410373708">"Engleska (UK) tipkovnica"</string>
@@ -115,6 +109,7 @@
     <string name="subtype_mode_fr_keyboard" msgid="8016515336759761014">"Francuska tipkovnica"</string>
     <string name="subtype_mode_fr_CA_keyboard" msgid="2628517247158376263">"Francuska (Kanada) tipkovnica"</string>
     <string name="subtype_mode_fr_CH_keyboard" msgid="6742806653181621228">"Francuska (Švicarska) tipkovnica"</string>
+    <string name="subtype_mode_iw_keyboard" msgid="1787536828253289950">"Hebrejska tipkovnica"</string>
     <string name="subtype_mode_it_keyboard" msgid="4934199655425394484">"Talijanska tipkovnica"</string>
     <string name="subtype_mode_nb_keyboard" msgid="1175783216100212360">"Norveška tipkovnica"</string>
     <string name="subtype_mode_nl_keyboard" msgid="5090278083256037936">"Nizozemska tipkovnica"</string>
diff --git a/java/res/values-hu/strings.xml b/java/res/values-hu/strings.xml
index a8bf983..81255d7 100644
--- a/java/res/values-hu/strings.xml
+++ b/java/res/values-hu/strings.xml
@@ -27,7 +27,9 @@
     <string name="sound_on_keypress" msgid="6093592297198243644">"Hangjelzés billentyű megnyomása esetén"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"Legyen nagyobb billentyű lenyomásakor"</string>
     <string name="general_category" msgid="1859088467017573195">"Általános"</string>
-    <string name="prediction_category" msgid="6361242011806282176">"Szövegjavítás"</string>
+    <string name="correction_category" msgid="2236750915056607613">"Szövegjavítás"</string>
+    <string name="ngram_category" msgid="5337109164339320257">"Javaslatok korábbi szavak alapján"</string>
+    <string name="misc_category" msgid="6894192814868233453">"Egyéb beállítások"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Automatikusan nagy kezdőbetű"</string>
     <string name="quick_fixes" msgid="5353213327680897927">"Gyorsjavítások"</string>
     <string name="quick_fixes_summary" msgid="3405028402510332373">"Kijavítja a gyakori gépelési hibákat"</string>
@@ -36,6 +38,7 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Mindig látszik"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"Megjelenítés álló tájolásban"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Mindig rejtve"</string>
+    <string name="prefs_use_spacebar_language_switch" msgid="8828538114550634449">"Nyelvváltó: szóköz"</string>
     <string name="prefs_settings_key" msgid="4623341240804046498">"Beállítások billentyű megjelenítése"</string>
     <string name="settings_key_mode_auto_name" msgid="2993460277873684680">"Automatikus"</string>
     <string name="settings_key_mode_always_show_name" msgid="3047567041784760575">"Mindig látszik"</string>
@@ -45,8 +48,10 @@
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Ki"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Mérsékelt"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"Agresszív"</string>
-    <string name="bigram_suggestion" msgid="1323347224043514969">"Bigram javaslatok"</string>
+    <string name="bigram_suggestion" msgid="2636414079905220518">"Bigram javaslatok"</string>
     <string name="bigram_suggestion_summary" msgid="4383845146070101531">"Előző szó használata a javaslatok javításához"</string>
+    <string name="bigram_prediction" msgid="8914273444762259739">"Bigram előrejelzés"</string>
+    <string name="bigram_prediction_summary" msgid="1747261921174300098">"Az előző szó használata a prediktív bevitelhez is"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : mentve"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Ugrás"</string>
     <string name="label_next_key" msgid="362972844525672568">"Tovább"</string>
@@ -56,18 +61,6 @@
     <string name="label_more_key" msgid="3760239494604948502">"Egyebek"</string>
     <string name="label_pause_key" msgid="181098308428035340">"Szün."</string>
     <string name="label_wait_key" msgid="6402152600878093134">"Vár"</string>
-    <string name="description_delete_key" msgid="5586406298531883960">"Törlés"</string>
-    <string name="description_return_key" msgid="8750044000806461678">"Vissza"</string>
-    <string name="description_settings_key" msgid="7484527796782969219">"Beállítások"</string>
-    <string name="description_shift_key" msgid="346906866277787836">"Shift"</string>
-    <string name="description_space_key" msgid="8512130111575878517">"Szóköz"</string>
-    <string name="description_switch_alpha_symbol_key" msgid="4537975384274405537">"Szimbólumok"</string>
-    <string name="description_tab_key" msgid="828186583738307137">"Tab"</string>
-    <string name="description_voice_key" msgid="3057731675774652754">"Hangbevitel"</string>
-    <string name="description_symbols_on" msgid="2994366855822840559">"Szimbólumok be"</string>
-    <string name="description_symbols_off" msgid="3209578267079515136">"Szimbólumok ki"</string>
-    <string name="description_shift_on" msgid="6983188949895971587">"Shift be"</string>
-    <string name="description_shift_off" msgid="8553265474523069034">"Shift ki"</string>
     <string name="voice_warning_title" msgid="4419354150908395008">"Hangbevitel"</string>
     <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"A hangbevitel szolgáltatás jelenleg nem támogatja az Ön nyelvét, ám angolul működik."</string>
     <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"A hangbevitel a Google beszédfelismerő technológiáját használja, amelyre a "<a href="http://m.google.com/privacy">"Mobil adatvédelmi irányelvek"</a>" érvényesek."</string>
@@ -106,6 +99,7 @@
     <string name="prefs_enable_recorrection_summary" msgid="5082041365862396329">"A beírt szavakat csak akkor javíthatja ki megérintve, ha látszanak javaslatok"</string>
     <string name="keyboard_layout" msgid="437433231038683666">"Billentyűzettéma"</string>
     <string name="subtype_mode_cs_keyboard" msgid="1141718931112377586">"Cseh billentyűzet"</string>
+    <string name="subtype_mode_ar_keyboard" msgid="2655338636329774995">"Arab billentyűzet"</string>
     <string name="subtype_mode_da_keyboard" msgid="1243570804427922104">"Dán billentyűzet"</string>
     <string name="subtype_mode_de_keyboard" msgid="1990979135959462145">"Német billentyűzet"</string>
     <string name="subtype_mode_en_GB_keyboard" msgid="7945856548410373708">"Angol (UK) billentyűzet"</string>
@@ -115,6 +109,7 @@
     <string name="subtype_mode_fr_keyboard" msgid="8016515336759761014">"Francia billentyűzet"</string>
     <string name="subtype_mode_fr_CA_keyboard" msgid="2628517247158376263">"Francia (kanadai) billentyűzet"</string>
     <string name="subtype_mode_fr_CH_keyboard" msgid="6742806653181621228">"Francia (svájci) billentyűzet"</string>
+    <string name="subtype_mode_iw_keyboard" msgid="1787536828253289950">"Héber billentyűzet"</string>
     <string name="subtype_mode_it_keyboard" msgid="4934199655425394484">"Olasz billentyűzet"</string>
     <string name="subtype_mode_nb_keyboard" msgid="1175783216100212360">"Norvég billentyűzet"</string>
     <string name="subtype_mode_nl_keyboard" msgid="5090278083256037936">"Holland billentyűzet"</string>
diff --git a/java/res/values-in/strings.xml b/java/res/values-in/strings.xml
index 4df7f1e..32ef38d 100644
--- a/java/res/values-in/strings.xml
+++ b/java/res/values-in/strings.xml
@@ -27,7 +27,9 @@
     <string name="sound_on_keypress" msgid="6093592297198243644">"Berbunyi jika tombol ditekan"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"Muncul saat tombol ditekan"</string>
     <string name="general_category" msgid="1859088467017573195">"Umum"</string>
-    <string name="prediction_category" msgid="6361242011806282176">"Koreksi teks"</string>
+    <string name="correction_category" msgid="2236750915056607613">"Koreksi teks"</string>
+    <string name="ngram_category" msgid="5337109164339320257">"Saran berdasarkan kata sebelumnya"</string>
+    <string name="misc_category" msgid="6894192814868233453">"Opsi lain"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Kapitalisasi otomatis"</string>
     <string name="quick_fixes" msgid="5353213327680897927">"Perbaikan cepat"</string>
     <string name="quick_fixes_summary" msgid="3405028402510332373">"Memperbaiki kesalahan ketik umum"</string>
@@ -36,6 +38,7 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Selalu tampilkan"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"Tampilkan pada mode potret"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Selalu sembunyikan"</string>
+    <string name="prefs_use_spacebar_language_switch" msgid="8828538114550634449">"Gunakan pengalih bahasa bilah spasi"</string>
     <string name="prefs_settings_key" msgid="4623341240804046498">"Lihat tombol setelan"</string>
     <string name="settings_key_mode_auto_name" msgid="2993460277873684680">"Otomatis"</string>
     <string name="settings_key_mode_always_show_name" msgid="3047567041784760575">"Selalu tampilkan"</string>
@@ -45,8 +48,10 @@
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Mati"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Sederhana"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"Agresif"</string>
-    <string name="bigram_suggestion" msgid="1323347224043514969">"Saran Bigram"</string>
+    <string name="bigram_suggestion" msgid="2636414079905220518">"Saran bigram"</string>
     <string name="bigram_suggestion_summary" msgid="4383845146070101531">"Gunakan kata sebelumnya untuk meningkatkan sara"</string>
+    <string name="bigram_prediction" msgid="8914273444762259739">"Prediksi bigram"</string>
+    <string name="bigram_prediction_summary" msgid="1747261921174300098">"Gunakan kata sebelumnya juga untuk prediksi"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : Telah disimpan"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Buka"</string>
     <string name="label_next_key" msgid="362972844525672568">"Berikutnya"</string>
@@ -56,18 +61,6 @@
     <string name="label_more_key" msgid="3760239494604948502">"Lainnya"</string>
     <string name="label_pause_key" msgid="181098308428035340">"Jeda"</string>
     <string name="label_wait_key" msgid="6402152600878093134">"Tunggu"</string>
-    <string name="description_delete_key" msgid="5586406298531883960">"Hapus"</string>
-    <string name="description_return_key" msgid="8750044000806461678">"Enter"</string>
-    <string name="description_settings_key" msgid="7484527796782969219">"Setelan"</string>
-    <string name="description_shift_key" msgid="346906866277787836">"Shift"</string>
-    <string name="description_space_key" msgid="8512130111575878517">"Spasi"</string>
-    <string name="description_switch_alpha_symbol_key" msgid="4537975384274405537">"Simbol"</string>
-    <string name="description_tab_key" msgid="828186583738307137">"Tab"</string>
-    <string name="description_voice_key" msgid="3057731675774652754">"Masukan Suara"</string>
-    <string name="description_symbols_on" msgid="2994366855822840559">"Simbol hidup"</string>
-    <string name="description_symbols_off" msgid="3209578267079515136">"Simbol mati"</string>
-    <string name="description_shift_on" msgid="6983188949895971587">"Shift hidup"</string>
-    <string name="description_shift_off" msgid="8553265474523069034">"Shift mati"</string>
     <string name="voice_warning_title" msgid="4419354150908395008">"Masukan suara"</string>
     <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"Masukan suara saat ini tidak didukung untuk bahasa Anda, tetapi bekerja dalam Bahasa Inggris."</string>
     <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"Masukan suara menggunakan pengenalan ucapan Google. "<a href="http://m.google.com/privacy">"Kebijakan Privasi Seluler"</a>" berlaku."</string>
@@ -106,6 +99,7 @@
     <string name="prefs_enable_recorrection_summary" msgid="5082041365862396329">"Sentuh kata yang dimasukkan untuk memperbaikinya, hanya saat saran dapat dilihat"</string>
     <string name="keyboard_layout" msgid="437433231038683666">"Tema Keyboard"</string>
     <string name="subtype_mode_cs_keyboard" msgid="1141718931112377586">"Keyboard Cheska"</string>
+    <string name="subtype_mode_ar_keyboard" msgid="2655338636329774995">"Papan Tombol Arab"</string>
     <string name="subtype_mode_da_keyboard" msgid="1243570804427922104">"Keyboard Denmark"</string>
     <string name="subtype_mode_de_keyboard" msgid="1990979135959462145">"Keyboard Jerman"</string>
     <string name="subtype_mode_en_GB_keyboard" msgid="7945856548410373708">"Keyboard Inggris (Britania Raya)"</string>
@@ -115,6 +109,7 @@
     <string name="subtype_mode_fr_keyboard" msgid="8016515336759761014">"Keyboard Prancis"</string>
     <string name="subtype_mode_fr_CA_keyboard" msgid="2628517247158376263">"Keyboard Prancis (Kanada)"</string>
     <string name="subtype_mode_fr_CH_keyboard" msgid="6742806653181621228">"Keyboard Prancis (Swiss)"</string>
+    <string name="subtype_mode_iw_keyboard" msgid="1787536828253289950">"Papan tombol Ibrani"</string>
     <string name="subtype_mode_it_keyboard" msgid="4934199655425394484">"Keyboard Italia"</string>
     <string name="subtype_mode_nb_keyboard" msgid="1175783216100212360">"Keyboard Norwegia"</string>
     <string name="subtype_mode_nl_keyboard" msgid="5090278083256037936">"Keyboard Belanda"</string>
diff --git a/java/res/values-it/donottranslate.xml b/java/res/values-it/donottranslate.xml
index 3e3f3ef..adb2a9a 100644
--- a/java/res/values-it/donottranslate.xml
+++ b/java/res/values-it/donottranslate.xml
@@ -18,6 +18,6 @@
 */
 -->
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- Symbols that are commonly considered word separators in this language -->
-    <string name="word_separators">.\u0009\u0020,;:!?\'\n()[]*&amp;@{}/&lt;&gt;_+=|\u0022</string>
+    <!-- Symbols that do NOT separate words -->
+    <string name="non_word_separator_symbols"></string>
 </resources>
diff --git a/java/res/values-it/strings.xml b/java/res/values-it/strings.xml
index 9bf4f9e..3a896fc 100644
--- a/java/res/values-it/strings.xml
+++ b/java/res/values-it/strings.xml
@@ -27,7 +27,9 @@
     <string name="sound_on_keypress" msgid="6093592297198243644">"Suono tasti"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"Popup sui tasti"</string>
     <string name="general_category" msgid="1859088467017573195">"Generali"</string>
-    <string name="prediction_category" msgid="6361242011806282176">"Correzione testo"</string>
+    <string name="correction_category" msgid="2236750915056607613">"Correzione testo"</string>
+    <string name="ngram_category" msgid="5337109164339320257">"Suggerimenti in base alle parole precedenti"</string>
+    <string name="misc_category" msgid="6894192814868233453">"Altre opzioni"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Maiuscole automatiche"</string>
     <string name="quick_fixes" msgid="5353213327680897927">"Correzioni veloci"</string>
     <string name="quick_fixes_summary" msgid="3405028402510332373">"Corregge gli errori di digitazione più comuni"</string>
@@ -36,6 +38,7 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Mostra sempre"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"Mostra in modalità verticale"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Nascondi sempre"</string>
+    <string name="prefs_use_spacebar_language_switch" msgid="8828538114550634449">"Selettore lingua da barra spaziatrice"</string>
     <string name="prefs_settings_key" msgid="4623341240804046498">"Mostra tasto impostaz."</string>
     <string name="settings_key_mode_auto_name" msgid="2993460277873684680">"Automatico"</string>
     <string name="settings_key_mode_always_show_name" msgid="3047567041784760575">"Mostra sempre"</string>
@@ -45,8 +48,10 @@
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Off"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Media"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"Massima"</string>
-    <string name="bigram_suggestion" msgid="1323347224043514969">"Suggerimenti sui bigrammi"</string>
+    <string name="bigram_suggestion" msgid="2636414079905220518">"Suggerimenti sui bigrammi"</string>
     <string name="bigram_suggestion_summary" msgid="4383845146070101531">"Utilizza parola precedente per migliorare il suggerimento"</string>
+    <string name="bigram_prediction" msgid="8914273444762259739">"Previsione bigramma"</string>
+    <string name="bigram_prediction_summary" msgid="1747261921174300098">"Usa anche la parola precedente per la previsione"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : parola salvata"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Vai"</string>
     <string name="label_next_key" msgid="362972844525672568">"Avanti"</string>
@@ -56,18 +61,6 @@
     <string name="label_more_key" msgid="3760239494604948502">"Altro"</string>
     <string name="label_pause_key" msgid="181098308428035340">"Pausa"</string>
     <string name="label_wait_key" msgid="6402152600878093134">"Attesa"</string>
-    <string name="description_delete_key" msgid="5586406298531883960">"Cancella"</string>
-    <string name="description_return_key" msgid="8750044000806461678">"Invio"</string>
-    <string name="description_settings_key" msgid="7484527796782969219">"Impostazioni"</string>
-    <string name="description_shift_key" msgid="346906866277787836">"Maiuscolo"</string>
-    <string name="description_space_key" msgid="8512130111575878517">"Spazio"</string>
-    <string name="description_switch_alpha_symbol_key" msgid="4537975384274405537">"Simboli"</string>
-    <string name="description_tab_key" msgid="828186583738307137">"Tabulazione"</string>
-    <string name="description_voice_key" msgid="3057731675774652754">"Input vocale"</string>
-    <string name="description_symbols_on" msgid="2994366855822840559">"Simboli attivati"</string>
-    <string name="description_symbols_off" msgid="3209578267079515136">"Simboli disattivati"</string>
-    <string name="description_shift_on" msgid="6983188949895971587">"Maiuscole attivate"</string>
-    <string name="description_shift_off" msgid="8553265474523069034">"Maiuscole disattivate"</string>
     <string name="voice_warning_title" msgid="4419354150908395008">"Comandi vocali"</string>
     <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"I comandi vocali non sono attualmente supportati per la tua lingua ma funzionano in inglese."</string>
     <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"L\'input vocale utilizza il riconoscimento vocale di Google. Sono valide le "<a href="http://m.google.com/privacy">"norme sulla privacy di Google Mobile"</a>"."</string>
@@ -106,6 +99,7 @@
     <string name="prefs_enable_recorrection_summary" msgid="5082041365862396329">"Tocca le parole inserite per correggerle, solo quando sono visibili i suggerimenti"</string>
     <string name="keyboard_layout" msgid="437433231038683666">"Tema della tastiera"</string>
     <string name="subtype_mode_cs_keyboard" msgid="1141718931112377586">"Tastiera ceca"</string>
+    <string name="subtype_mode_ar_keyboard" msgid="2655338636329774995">"Tastiera araba"</string>
     <string name="subtype_mode_da_keyboard" msgid="1243570804427922104">"Tastiera danese"</string>
     <string name="subtype_mode_de_keyboard" msgid="1990979135959462145">"Tastiera tedesca"</string>
     <string name="subtype_mode_en_GB_keyboard" msgid="7945856548410373708">"Tastiera inglese (Regno Unito)"</string>
@@ -115,6 +109,7 @@
     <string name="subtype_mode_fr_keyboard" msgid="8016515336759761014">"Tastiera francese"</string>
     <string name="subtype_mode_fr_CA_keyboard" msgid="2628517247158376263">"Tastiera francese (Canada)"</string>
     <string name="subtype_mode_fr_CH_keyboard" msgid="6742806653181621228">"Tastiera francese (Svizzera)"</string>
+    <string name="subtype_mode_iw_keyboard" msgid="1787536828253289950">"Tastiera ebraica"</string>
     <string name="subtype_mode_it_keyboard" msgid="4934199655425394484">"Tastiera italiana"</string>
     <string name="subtype_mode_nb_keyboard" msgid="1175783216100212360">"Tastiera norvegese"</string>
     <string name="subtype_mode_nl_keyboard" msgid="5090278083256037936">"Tastiera olandese"</string>
diff --git a/java/res/values-iw/strings.xml b/java/res/values-iw/strings.xml
index af0854c..0b26ed9 100644
--- a/java/res/values-iw/strings.xml
+++ b/java/res/values-iw/strings.xml
@@ -27,7 +27,9 @@
     <string name="sound_on_keypress" msgid="6093592297198243644">"צלילים עם לחיצה על מקשים"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"חלון קופץ עם לחיצה על מקשים"</string>
     <string name="general_category" msgid="1859088467017573195">"כללי"</string>
-    <string name="prediction_category" msgid="6361242011806282176">"תיקון טקסט"</string>
+    <string name="correction_category" msgid="2236750915056607613">"תיקון טקסט"</string>
+    <string name="ngram_category" msgid="5337109164339320257">"הצעות המבוססות על מילים קודמות"</string>
+    <string name="misc_category" msgid="6894192814868233453">"אפשרויות אחרות"</string>
     <string name="auto_cap" msgid="1719746674854628252">"הפיכה אוטומטית של אותיות לרישיות"</string>
     <string name="quick_fixes" msgid="5353213327680897927">"תיקונים מהירים"</string>
     <string name="quick_fixes_summary" msgid="3405028402510332373">"מתקן שגיאות הקלדה נפוצות"</string>
@@ -36,6 +38,7 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"הצג תמיד"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"הצג בפריסה לאורך"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"הסתר תמיד"</string>
+    <string name="prefs_use_spacebar_language_switch" msgid="8828538114550634449">"השתמש במחליף השפה שבמקש הרווח"</string>
     <string name="prefs_settings_key" msgid="4623341240804046498">"הצג מקש הגדרות"</string>
     <string name="settings_key_mode_auto_name" msgid="2993460277873684680">"אוטומטי"</string>
     <string name="settings_key_mode_always_show_name" msgid="3047567041784760575">"הצג תמיד"</string>
@@ -45,8 +48,10 @@
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"כבוי"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"מצומצם"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"מחמיר"</string>
-    <string name="bigram_suggestion" msgid="1323347224043514969">"הצעות של צמדי אותיות (Bigram)"</string>
+    <string name="bigram_suggestion" msgid="2636414079905220518">"הצעות של צמדי אותיות (Bigram)"</string>
     <string name="bigram_suggestion_summary" msgid="4383845146070101531">"השתמש במילה הקודמת כדי לשפר את ההצעה"</string>
+    <string name="bigram_prediction" msgid="8914273444762259739">"חיזוי צמדי אותיות (Bigram)"</string>
+    <string name="bigram_prediction_summary" msgid="1747261921174300098">"השתמש במילה הקודמת גם עבור חיזוי"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : נשמרה"</string>
     <string name="label_go_key" msgid="1635148082137219148">"בצע"</string>
     <string name="label_next_key" msgid="362972844525672568">"הבא"</string>
@@ -56,18 +61,6 @@
     <string name="label_more_key" msgid="3760239494604948502">"עוד"</string>
     <string name="label_pause_key" msgid="181098308428035340">"השהה"</string>
     <string name="label_wait_key" msgid="6402152600878093134">"המתן"</string>
-    <string name="description_delete_key" msgid="5586406298531883960">"מחק"</string>
-    <string name="description_return_key" msgid="8750044000806461678">"חזור"</string>
-    <string name="description_settings_key" msgid="7484527796782969219">"הגדרות"</string>
-    <string name="description_shift_key" msgid="346906866277787836">"Shift"</string>
-    <string name="description_space_key" msgid="8512130111575878517">"רווח"</string>
-    <string name="description_switch_alpha_symbol_key" msgid="4537975384274405537">"סמלים"</string>
-    <string name="description_tab_key" msgid="828186583738307137">"כרטיסייה"</string>
-    <string name="description_voice_key" msgid="3057731675774652754">"קלט קולי"</string>
-    <string name="description_symbols_on" msgid="2994366855822840559">"מצב סמלים פועל"</string>
-    <string name="description_symbols_off" msgid="3209578267079515136">"מצב סמלים כבוי"</string>
-    <string name="description_shift_on" msgid="6983188949895971587">"Shift פועל"</string>
-    <string name="description_shift_off" msgid="8553265474523069034">"Shift כבוי"</string>
     <string name="voice_warning_title" msgid="4419354150908395008">"קלט קולי"</string>
     <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"קלט קולי אינו נתמך בשלב זה בשפתך, אך הוא פועל באנגלית."</string>
     <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"קלט קולי משתמש בזיהוי דיבור של Google.‏ "<a href="http://m.google.com/privacy">"מדיניות הפרטיות של \'Google לנייד\'"</a>" חלה במקרה זה."</string>
@@ -106,6 +99,7 @@
     <string name="prefs_enable_recorrection_summary" msgid="5082041365862396329">"גע במילים שהוזנו כדי לתקן אותן, רק כאשר הצעות מוצגות"</string>
     <string name="keyboard_layout" msgid="437433231038683666">"עיצוב מקלדת"</string>
     <string name="subtype_mode_cs_keyboard" msgid="1141718931112377586">"מקלדת צ\'כית"</string>
+    <string name="subtype_mode_ar_keyboard" msgid="2655338636329774995">"מקלדת בשפה הערבית"</string>
     <string name="subtype_mode_da_keyboard" msgid="1243570804427922104">"מקלדת דנית"</string>
     <string name="subtype_mode_de_keyboard" msgid="1990979135959462145">"מקלדת גרמנית "</string>
     <string name="subtype_mode_en_GB_keyboard" msgid="7945856548410373708">"מקלדת אנגלית (בריטניה)"</string>
@@ -115,6 +109,7 @@
     <string name="subtype_mode_fr_keyboard" msgid="8016515336759761014">"מקלדת צרפתית"</string>
     <string name="subtype_mode_fr_CA_keyboard" msgid="2628517247158376263">"מקלדת צרפתית (קנדה)"</string>
     <string name="subtype_mode_fr_CH_keyboard" msgid="6742806653181621228">"מקלדת צרפתית (שוויץ)"</string>
+    <string name="subtype_mode_iw_keyboard" msgid="1787536828253289950">"מקלדת בשפה העברית"</string>
     <string name="subtype_mode_it_keyboard" msgid="4934199655425394484">"מקלדת איטלקית"</string>
     <string name="subtype_mode_nb_keyboard" msgid="1175783216100212360">"מקלדת נורווגית"</string>
     <string name="subtype_mode_nl_keyboard" msgid="5090278083256037936">"מקלדת הולנדית"</string>
diff --git a/java/res/values-ja/strings.xml b/java/res/values-ja/strings.xml
index cd2cf80..402c356 100644
--- a/java/res/values-ja/strings.xml
+++ b/java/res/values-ja/strings.xml
@@ -27,7 +27,9 @@
     <string name="sound_on_keypress" msgid="6093592297198243644">"キー操作音"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"キー押下時ポップアップ"</string>
     <string name="general_category" msgid="1859088467017573195">"全般"</string>
-    <string name="prediction_category" msgid="6361242011806282176">"テキストの修正"</string>
+    <string name="correction_category" msgid="2236750915056607613">"テキストの修正"</string>
+    <string name="ngram_category" msgid="5337109164339320257">"前の語句に基づいた入力候補表示"</string>
+    <string name="misc_category" msgid="6894192814868233453">"他のオプション"</string>
     <string name="auto_cap" msgid="1719746674854628252">"自動大文字変換"</string>
     <string name="quick_fixes" msgid="5353213327680897927">"クイックフィックス"</string>
     <string name="quick_fixes_summary" msgid="3405028402510332373">"よくある誤字・脱字を修正します"</string>
@@ -36,6 +38,7 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"常に表示"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"縦向きで表示"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"常に非表示"</string>
+    <string name="prefs_use_spacebar_language_switch" msgid="8828538114550634449">"スペースバーで切替"</string>
     <string name="prefs_settings_key" msgid="4623341240804046498">"設定キーを表示"</string>
     <string name="settings_key_mode_auto_name" msgid="2993460277873684680">"自動"</string>
     <string name="settings_key_mode_always_show_name" msgid="3047567041784760575">"常に表示"</string>
@@ -45,8 +48,10 @@
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"OFF"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"中"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"強"</string>
-    <string name="bigram_suggestion" msgid="1323347224043514969">"バイグラム入力候補表示"</string>
+    <string name="bigram_suggestion" msgid="2636414079905220518">"バイグラム入力候補表示"</string>
     <string name="bigram_suggestion_summary" msgid="4383845146070101531">"直前の単語から入力候補を予測します"</string>
+    <string name="bigram_prediction" msgid="8914273444762259739">"バイグラム予測"</string>
+    <string name="bigram_prediction_summary" msgid="1747261921174300098">"前の語句も予測に使用"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g>:保存しました"</string>
     <string name="label_go_key" msgid="1635148082137219148">"実行"</string>
     <string name="label_next_key" msgid="362972844525672568">"次へ"</string>
@@ -56,18 +61,6 @@
     <string name="label_more_key" msgid="3760239494604948502">"Shift"</string>
     <string name="label_pause_key" msgid="181098308428035340">"停止"</string>
     <string name="label_wait_key" msgid="6402152600878093134">"待機"</string>
-    <string name="description_delete_key" msgid="5586406298531883960">"Del"</string>
-    <string name="description_return_key" msgid="8750044000806461678">"Enter"</string>
-    <string name="description_settings_key" msgid="7484527796782969219">"設定"</string>
-    <string name="description_shift_key" msgid="346906866277787836">"Shift"</string>
-    <string name="description_space_key" msgid="8512130111575878517">"Space"</string>
-    <string name="description_switch_alpha_symbol_key" msgid="4537975384274405537">"記号"</string>
-    <string name="description_tab_key" msgid="828186583738307137">"Tab"</string>
-    <string name="description_voice_key" msgid="3057731675774652754">"音声入力"</string>
-    <string name="description_symbols_on" msgid="2994366855822840559">"記号ON"</string>
-    <string name="description_symbols_off" msgid="3209578267079515136">"記号OFF"</string>
-    <string name="description_shift_on" msgid="6983188949895971587">"Shift ON"</string>
-    <string name="description_shift_off" msgid="8553265474523069034">"Shift OFF"</string>
     <string name="voice_warning_title" msgid="4419354150908395008">"音声入力"</string>
     <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"音声入力は現在英語には対応していますが、日本語には対応していません。"</string>
     <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"音声入力ではGoogleの音声認識技術を利用します。"<a href="http://m.google.com/privacy">"モバイルプライバシーポリシー"</a>"が適用されます。"</string>
@@ -106,6 +99,7 @@
     <string name="prefs_enable_recorrection_summary" msgid="5082041365862396329">"候補が表示されているときのみ、入力した語句をタップして修正する"</string>
     <string name="keyboard_layout" msgid="437433231038683666">"キーボードテーマ"</string>
     <string name="subtype_mode_cs_keyboard" msgid="1141718931112377586">"チェコ語のキーボード"</string>
+    <string name="subtype_mode_ar_keyboard" msgid="2655338636329774995">"アラビア語のキーボード"</string>
     <string name="subtype_mode_da_keyboard" msgid="1243570804427922104">"デンマーク語のキーボード"</string>
     <string name="subtype_mode_de_keyboard" msgid="1990979135959462145">"ドイツ語のキーボード"</string>
     <string name="subtype_mode_en_GB_keyboard" msgid="7945856548410373708">"英語(英国)のキーボード"</string>
@@ -115,6 +109,7 @@
     <string name="subtype_mode_fr_keyboard" msgid="8016515336759761014">"フランス語のキーボード"</string>
     <string name="subtype_mode_fr_CA_keyboard" msgid="2628517247158376263">"フランス語(カナダ)のキーボード"</string>
     <string name="subtype_mode_fr_CH_keyboard" msgid="6742806653181621228">"フランス語(スイス)のキーボード"</string>
+    <string name="subtype_mode_iw_keyboard" msgid="1787536828253289950">"ヘブライ語のキーボード"</string>
     <string name="subtype_mode_it_keyboard" msgid="4934199655425394484">"イタリア語のキーボード"</string>
     <string name="subtype_mode_nb_keyboard" msgid="1175783216100212360">"ノルウェー語のキーボード"</string>
     <string name="subtype_mode_nl_keyboard" msgid="5090278083256037936">"オランダ語のキーボード"</string>
diff --git a/java/res/values-ko/strings.xml b/java/res/values-ko/strings.xml
index 7a09da8..ca2f570 100644
--- a/java/res/values-ko/strings.xml
+++ b/java/res/values-ko/strings.xml
@@ -27,7 +27,9 @@
     <string name="sound_on_keypress" msgid="6093592297198243644">"키를 누를 때 소리 발생"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"키를 누를 때 팝업"</string>
     <string name="general_category" msgid="1859088467017573195">"일반"</string>
-    <string name="prediction_category" msgid="6361242011806282176">"텍스트 수정"</string>
+    <string name="correction_category" msgid="2236750915056607613">"텍스트 수정"</string>
+    <string name="ngram_category" msgid="5337109164339320257">"이전 단어에 기반한 추천"</string>
+    <string name="misc_category" msgid="6894192814868233453">"기타 옵션"</string>
     <string name="auto_cap" msgid="1719746674854628252">"자동 대문자화"</string>
     <string name="quick_fixes" msgid="5353213327680897927">"빠른 수정"</string>
     <string name="quick_fixes_summary" msgid="3405028402510332373">"자주 발생하는 오타를 수정합니다."</string>
@@ -36,6 +38,7 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"항상 표시"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"세로 모드로 표시"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"항상 숨기기"</string>
+    <string name="prefs_use_spacebar_language_switch" msgid="8828538114550634449">"스페이스 바 언어 교환기 사용"</string>
     <string name="prefs_settings_key" msgid="4623341240804046498">"설정 키 표시"</string>
     <string name="settings_key_mode_auto_name" msgid="2993460277873684680">"자동"</string>
     <string name="settings_key_mode_always_show_name" msgid="3047567041784760575">"항상 표시"</string>
@@ -45,8 +48,10 @@
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"사용 안함"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"보통"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"적극적"</string>
-    <string name="bigram_suggestion" msgid="1323347224043514969">"Bigram 추천"</string>
+    <string name="bigram_suggestion" msgid="2636414079905220518">"Bigram 추천"</string>
     <string name="bigram_suggestion_summary" msgid="4383845146070101531">"이전 단어를 사용하여 추천 기능 개선"</string>
+    <string name="bigram_prediction" msgid="8914273444762259739">"Bigram 예측"</string>
+    <string name="bigram_prediction_summary" msgid="1747261921174300098">"이전 단어를 사용하여 예상 검색어를 표시합니다."</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g>: 저장됨"</string>
     <string name="label_go_key" msgid="1635148082137219148">"이동"</string>
     <string name="label_next_key" msgid="362972844525672568">"다음"</string>
@@ -56,18 +61,6 @@
     <string name="label_more_key" msgid="3760239494604948502">"더보기"</string>
     <string name="label_pause_key" msgid="181098308428035340">"일시 중지"</string>
     <string name="label_wait_key" msgid="6402152600878093134">"대기"</string>
-    <string name="description_delete_key" msgid="5586406298531883960">"삭제"</string>
-    <string name="description_return_key" msgid="8750044000806461678">"리턴"</string>
-    <string name="description_settings_key" msgid="7484527796782969219">"설정"</string>
-    <string name="description_shift_key" msgid="346906866277787836">"시프트"</string>
-    <string name="description_space_key" msgid="8512130111575878517">"스페이스"</string>
-    <string name="description_switch_alpha_symbol_key" msgid="4537975384274405537">"기호"</string>
-    <string name="description_tab_key" msgid="828186583738307137">"탭"</string>
-    <string name="description_voice_key" msgid="3057731675774652754">"음성 입력"</string>
-    <string name="description_symbols_on" msgid="2994366855822840559">"기호 사용"</string>
-    <string name="description_symbols_off" msgid="3209578267079515136">"기호 사용 안함"</string>
-    <string name="description_shift_on" msgid="6983188949895971587">"시프트 사용"</string>
-    <string name="description_shift_off" msgid="8553265474523069034">"시프트 사용 안함"</string>
     <string name="voice_warning_title" msgid="4419354150908395008">"음성 입력"</string>
     <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"음성 입력은 현재 자국어로 지원되지 않으며 영어로 작동됩니다."</string>
     <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"음성 입력에서는 Google의 음성 인식 기능을 사용합니다. "<a href="http://m.google.com/privacy">"모바일 개인정보취급방침"</a>"이 적용됩니다."</string>
@@ -106,6 +99,7 @@
     <string name="prefs_enable_recorrection_summary" msgid="5082041365862396329">"입력한 단어를 터치하여 수정(추천 단어가 표시되는 경우에만)"</string>
     <string name="keyboard_layout" msgid="437433231038683666">"키보드 테마"</string>
     <string name="subtype_mode_cs_keyboard" msgid="1141718931112377586">"체코어 키보드"</string>
+    <string name="subtype_mode_ar_keyboard" msgid="2655338636329774995">"아랍어 키보드"</string>
     <string name="subtype_mode_da_keyboard" msgid="1243570804427922104">"덴마크어 키보드"</string>
     <string name="subtype_mode_de_keyboard" msgid="1990979135959462145">"독일어 키보드"</string>
     <string name="subtype_mode_en_GB_keyboard" msgid="7945856548410373708">"영어(영국) 키보드"</string>
@@ -115,6 +109,7 @@
     <string name="subtype_mode_fr_keyboard" msgid="8016515336759761014">"프랑스어 키보드"</string>
     <string name="subtype_mode_fr_CA_keyboard" msgid="2628517247158376263">"프랑스어(캐나다) 키보드"</string>
     <string name="subtype_mode_fr_CH_keyboard" msgid="6742806653181621228">"프랑스어(스위스) 키보드"</string>
+    <string name="subtype_mode_iw_keyboard" msgid="1787536828253289950">"히브리어 키보드"</string>
     <string name="subtype_mode_it_keyboard" msgid="4934199655425394484">"이탈리아어 키보드"</string>
     <string name="subtype_mode_nb_keyboard" msgid="1175783216100212360">"노르웨이어 키보드"</string>
     <string name="subtype_mode_nl_keyboard" msgid="5090278083256037936">"네덜란드어 키보드"</string>
diff --git a/java/res/values-land/dimens.xml b/java/res/values-land/dimens.xml
index 7df124b..c46a517 100644
--- a/java/res/values-land/dimens.xml
+++ b/java/res/values-land/dimens.xml
@@ -19,20 +19,21 @@
 -->
 
 <resources>
-    <!-- keyboardHeight = key_height*4 + key_bottom_gap*3 -->
-    <dimen name="keyboardHeight">1.060in</dimen>
+    <!-- keyboardHeight = key_height*4 + key_bottom_gap*3, key_height=0.260in -->
+    <dimen name="keyboardHeight">1.100in</dimen>
     <!-- key_height + key_bottom_gap = popup_key_height -->
-<!--    <dimen name="key_height">0.250in</dimen>-->
+<!--    <dimen name="key_height">0.260in</dimen>-->
     <dimen name="key_bottom_gap">0.020in</dimen>
-    <dimen name="popup_key_height">0.270in</dimen>
+    <dimen name="popup_key_height">0.280in</dimen>
     <dimen name="keyboard_top_padding">0.0in</dimen>
     <dimen name="keyboard_bottom_padding">0.0in</dimen>
+    <dimen name="keyboard_horizontal_edges_padding">0.0in</dimen>
     <dimen name="candidate_strip_height">38dip</dimen>
     <dimen name="candidate_strip_fading_edge_length">63dip</dimen>
     <dimen name="spacebar_vertical_correction">2dip</dimen>
     <!-- Amount of allowance for selecting keys in a mini popup keyboard by sliding finger. -->
     <!-- popup_key_height x 1.2 -->
-    <dimen name="mini_keyboard_slide_allowance">0.324in</dimen>
+    <dimen name="mini_keyboard_slide_allowance">0.336in</dimen>
     <!-- popup_key_height x -1.0 -->
-    <dimen name="mini_keyboard_vertical_correction">-0.270in</dimen>
+    <dimen name="mini_keyboard_vertical_correction">-0.280in</dimen>
 </resources>
diff --git a/java/res/values-lt/strings.xml b/java/res/values-lt/strings.xml
index c12c62a..fbe50e0 100644
--- a/java/res/values-lt/strings.xml
+++ b/java/res/values-lt/strings.xml
@@ -27,7 +27,9 @@
     <string name="sound_on_keypress" msgid="6093592297198243644">"Klavišo paspaudimo garsas"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"Iššoka paspaudus klavišą"</string>
     <string name="general_category" msgid="1859088467017573195">"Bendra"</string>
-    <string name="prediction_category" msgid="6361242011806282176">"Teksto taisymas"</string>
+    <string name="correction_category" msgid="2236750915056607613">"Teksto taisymas"</string>
+    <string name="ngram_category" msgid="5337109164339320257">"Pasiūlymai pagal ankstesnius žodžius"</string>
+    <string name="misc_category" msgid="6894192814868233453">"Kitos parinktys"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Automatinis didžiųjų raidžių rašymas"</string>
     <string name="quick_fixes" msgid="5353213327680897927">"Greiti pataisymai"</string>
     <string name="quick_fixes_summary" msgid="3405028402510332373">"Taiso dažnai padarytas rašybos klaidas"</string>
@@ -36,6 +38,7 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Visada rodyti"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"Rodyti stačiuoju režimu"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Visada slėpti"</string>
+    <string name="prefs_use_spacebar_language_switch" msgid="8828538114550634449">"Naud. tarpo kl. k. jung."</string>
     <string name="prefs_settings_key" msgid="4623341240804046498">"Rodyti nustatymų raktą"</string>
     <string name="settings_key_mode_auto_name" msgid="2993460277873684680">"Automatinis"</string>
     <string name="settings_key_mode_always_show_name" msgid="3047567041784760575">"Visada rodyti"</string>
@@ -45,8 +48,10 @@
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Išjungta"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Vidutinis"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"Atkaklus"</string>
-    <string name="bigram_suggestion" msgid="1323347224043514969">"Digramų pasiūlymai"</string>
+    <string name="bigram_suggestion" msgid="2636414079905220518">"Bigramų pasiūlymai"</string>
     <string name="bigram_suggestion_summary" msgid="4383845146070101531">"Naudoti ankstesnį žodį pasiūlymui patobulinti"</string>
+    <string name="bigram_prediction" msgid="8914273444762259739">"Bigramų numatymas"</string>
+    <string name="bigram_prediction_summary" msgid="1747261921174300098">"Numatant naudoti ir ankstesnį žodį"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g>: išsaugota"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Pradėti"</string>
     <string name="label_next_key" msgid="362972844525672568">"Kitas"</string>
@@ -56,18 +61,6 @@
     <string name="label_more_key" msgid="3760239494604948502">"Daugiau"</string>
     <string name="label_pause_key" msgid="181098308428035340">"Prist."</string>
     <string name="label_wait_key" msgid="6402152600878093134">"Lauk."</string>
-    <string name="description_delete_key" msgid="5586406298531883960">"Ištrinti"</string>
-    <string name="description_return_key" msgid="8750044000806461678">"Grįžti"</string>
-    <string name="description_settings_key" msgid="7484527796782969219">"Nustatymai"</string>
-    <string name="description_shift_key" msgid="346906866277787836">"Keitimas"</string>
-    <string name="description_space_key" msgid="8512130111575878517">"Tarpas"</string>
-    <string name="description_switch_alpha_symbol_key" msgid="4537975384274405537">"Simboliai"</string>
-    <string name="description_tab_key" msgid="828186583738307137">"Skirtukas"</string>
-    <string name="description_voice_key" msgid="3057731675774652754">"Balso įvestis"</string>
-    <string name="description_symbols_on" msgid="2994366855822840559">"Simboliai įjungti"</string>
-    <string name="description_symbols_off" msgid="3209578267079515136">"Simboliai išjungti"</string>
-    <string name="description_shift_on" msgid="6983188949895971587">"Keitimas įjungtas"</string>
-    <string name="description_shift_off" msgid="8553265474523069034">"Keitimas išjungtas"</string>
     <string name="voice_warning_title" msgid="4419354150908395008">"Balso įvestis"</string>
     <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"Šiuo metu balso įvestis jūsų kompiuteryje nepalaikoma, bet ji veikia anglų k."</string>
     <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"Balso įvesčiai naudojamas „Google“ kalbos atpažinimas. Taikoma "<a href="http://m.google.com/privacy">"privatumo politika mobiliesiems"</a>"."</string>
@@ -106,6 +99,7 @@
     <string name="prefs_enable_recorrection_summary" msgid="5082041365862396329">"Jei norite ištaisyti įvestus žodžius, palieskite juos tik tada, kai matomi pasiūlymai"</string>
     <string name="keyboard_layout" msgid="437433231038683666">"Klaviatūros tema"</string>
     <string name="subtype_mode_cs_keyboard" msgid="1141718931112377586">"Čekiška klaviatūra"</string>
+    <string name="subtype_mode_ar_keyboard" msgid="2655338636329774995">"Arabiška klaviatūra"</string>
     <string name="subtype_mode_da_keyboard" msgid="1243570804427922104">"Daniška klaviatūra"</string>
     <string name="subtype_mode_de_keyboard" msgid="1990979135959462145">"Vokiška klaviatūra"</string>
     <string name="subtype_mode_en_GB_keyboard" msgid="7945856548410373708">"Angliška (JK) klaviatūra"</string>
@@ -115,6 +109,7 @@
     <string name="subtype_mode_fr_keyboard" msgid="8016515336759761014">"Prancūziška klaviatūra"</string>
     <string name="subtype_mode_fr_CA_keyboard" msgid="2628517247158376263">"Prancūziška (Kanada) klaviatūra"</string>
     <string name="subtype_mode_fr_CH_keyboard" msgid="6742806653181621228">"Prancūziška (Šveicarija) klaviatūra"</string>
+    <string name="subtype_mode_iw_keyboard" msgid="1787536828253289950">"Hebrajiška klaviatūra"</string>
     <string name="subtype_mode_it_keyboard" msgid="4934199655425394484">"Itališka klaviatūra"</string>
     <string name="subtype_mode_nb_keyboard" msgid="1175783216100212360">"Norvegiška klaviatūra"</string>
     <string name="subtype_mode_nl_keyboard" msgid="5090278083256037936">"Olandiška klaviatūra"</string>
diff --git a/java/res/values-lv/strings.xml b/java/res/values-lv/strings.xml
index 8b975b0..84bee53 100644
--- a/java/res/values-lv/strings.xml
+++ b/java/res/values-lv/strings.xml
@@ -27,7 +27,9 @@
     <string name="sound_on_keypress" msgid="6093592297198243644">"Skaņa, nospiežot taustiņu"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"Nospiežot taustiņu, parādīt uznirstošo izvēlni"</string>
     <string name="general_category" msgid="1859088467017573195">"Vispārīgi"</string>
-    <string name="prediction_category" msgid="6361242011806282176">"Teksta korekcija"</string>
+    <string name="correction_category" msgid="2236750915056607613">"Teksta korekcija"</string>
+    <string name="ngram_category" msgid="5337109164339320257">"Ieteikumi, pamatojoties uz iepriekšējiem vārdiem"</string>
+    <string name="misc_category" msgid="6894192814868233453">"Citas opcijas"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Automātiska lielo burtu lietošana"</string>
     <string name="quick_fixes" msgid="5353213327680897927">"Ātrie labojumi"</string>
     <string name="quick_fixes_summary" msgid="3405028402510332373">"Nodrošina izplatītu drukas kļūdu labošanu."</string>
@@ -36,6 +38,7 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Vienmēr rādīt"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"Rādīt portreta režīmā"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Vienmēr slēpt"</string>
+    <string name="prefs_use_spacebar_language_switch" msgid="8828538114550634449">"Izmantot atstarpēšanas taustiņu, lai pārslēgtu valodu"</string>
     <string name="prefs_settings_key" msgid="4623341240804046498">"Rādīt iestatījumu taustiņu"</string>
     <string name="settings_key_mode_auto_name" msgid="2993460277873684680">"Automātiski"</string>
     <string name="settings_key_mode_always_show_name" msgid="3047567041784760575">"Vienmēr rādīt"</string>
@@ -45,8 +48,10 @@
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Izslēgta"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Mērena"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"Agresīva"</string>
-    <string name="bigram_suggestion" msgid="1323347224043514969">"Bigram ieteikumi"</string>
+    <string name="bigram_suggestion" msgid="2636414079905220518">"Bigrammu ieteikumi"</string>
     <string name="bigram_suggestion_summary" msgid="4383845146070101531">"Ieteikuma uzlabošanai izmantot iepriekšējo vārdu"</string>
+    <string name="bigram_prediction" msgid="8914273444762259739">"Bigrammu prognozes"</string>
+    <string name="bigram_prediction_summary" msgid="1747261921174300098">"Izmantot iepriekšējo vārdu arī prognozēm"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g>: saglabāts"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Sākt"</string>
     <string name="label_next_key" msgid="362972844525672568">"Tālāk"</string>
@@ -56,18 +61,6 @@
     <string name="label_more_key" msgid="3760239494604948502">"Vairāk"</string>
     <string name="label_pause_key" msgid="181098308428035340">"Pauze"</string>
     <string name="label_wait_key" msgid="6402152600878093134">"Gaidīt"</string>
-    <string name="description_delete_key" msgid="5586406298531883960">"Dzēšanas taustiņš"</string>
-    <string name="description_return_key" msgid="8750044000806461678">"Atgriešanās taustiņš"</string>
-    <string name="description_settings_key" msgid="7484527796782969219">"Iestatījumu taustiņš"</string>
-    <string name="description_shift_key" msgid="346906866277787836">"Pārslēgšanas taustiņš"</string>
-    <string name="description_space_key" msgid="8512130111575878517">"Atstarpes taustiņš"</string>
-    <string name="description_switch_alpha_symbol_key" msgid="4537975384274405537">"Simbolu taustiņš"</string>
-    <string name="description_tab_key" msgid="828186583738307137">"Tabulēšanas taustiņš"</string>
-    <string name="description_voice_key" msgid="3057731675774652754">"Runas ievades taustiņš"</string>
-    <string name="description_symbols_on" msgid="2994366855822840559">"Simbolu režīms ir ieslēgts."</string>
-    <string name="description_symbols_off" msgid="3209578267079515136">"Simbolu režīms ir izslēgts."</string>
-    <string name="description_shift_on" msgid="6983188949895971587">"Pārslēgšanas režīms ir ieslēgts."</string>
-    <string name="description_shift_off" msgid="8553265474523069034">"Pārslēgšanas režīms ir izslēgts."</string>
     <string name="voice_warning_title" msgid="4419354150908395008">"Balss ievade"</string>
     <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"Balss ievade jūsu valodā pašlaik netiek atbalstīta, taču tā ir pieejama angļu valodā."</string>
     <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"Balss ievadei tiek izmantota Google runas atpazīšanas funkcija. Uz šīs funkcijas lietošanu attiecas "<a href="http://m.google.com/privacy">"mobilo sakaru ierīču lietošanas konfidencialitātes politika"</a>"."</string>
@@ -106,6 +99,7 @@
     <string name="prefs_enable_recorrection_summary" msgid="5082041365862396329">"Pieskarties ievadītajiem vārdiem, lai tos labotu (tikai tad, ja tiek rādīti ieteikumi)."</string>
     <string name="keyboard_layout" msgid="437433231038683666">"Tastatūras motīvs"</string>
     <string name="subtype_mode_cs_keyboard" msgid="1141718931112377586">"Čehu tastatūra"</string>
+    <string name="subtype_mode_ar_keyboard" msgid="2655338636329774995">"Arābu tastatūra"</string>
     <string name="subtype_mode_da_keyboard" msgid="1243570804427922104">"Dāņu tastatūra"</string>
     <string name="subtype_mode_de_keyboard" msgid="1990979135959462145">"Vācu tastatūra"</string>
     <string name="subtype_mode_en_GB_keyboard" msgid="7945856548410373708">"Angļu (Lielbritānija) tastatūra"</string>
@@ -115,6 +109,7 @@
     <string name="subtype_mode_fr_keyboard" msgid="8016515336759761014">"Franču tastatūra"</string>
     <string name="subtype_mode_fr_CA_keyboard" msgid="2628517247158376263">"Franču (Kanāda) tastatūra"</string>
     <string name="subtype_mode_fr_CH_keyboard" msgid="6742806653181621228">"Franču (Šveices) tastatūra"</string>
+    <string name="subtype_mode_iw_keyboard" msgid="1787536828253289950">"Ebreju tastatūra"</string>
     <string name="subtype_mode_it_keyboard" msgid="4934199655425394484">"Itāļu tastatūra"</string>
     <string name="subtype_mode_nb_keyboard" msgid="1175783216100212360">"Norvēģu tastatūra"</string>
     <string name="subtype_mode_nl_keyboard" msgid="5090278083256037936">"Holandiešu tastatūra"</string>
diff --git a/java/res/values-nb/strings.xml b/java/res/values-nb/strings.xml
index 0471e74..e20c951 100644
--- a/java/res/values-nb/strings.xml
+++ b/java/res/values-nb/strings.xml
@@ -27,7 +27,9 @@
     <string name="sound_on_keypress" msgid="6093592297198243644">"Lyd ved tastetrykk"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"Hurtigvindu ved tastetrykk"</string>
     <string name="general_category" msgid="1859088467017573195">"Generelt"</string>
-    <string name="prediction_category" msgid="6361242011806282176">"Tekstkorrigering"</string>
+    <string name="correction_category" msgid="2236750915056607613">"Tekstkorrigering"</string>
+    <string name="ngram_category" msgid="5337109164339320257">"Forslag basert på tidligere ord"</string>
+    <string name="misc_category" msgid="6894192814868233453">"Andre alternativer"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Stor forbokstav"</string>
     <string name="quick_fixes" msgid="5353213327680897927">"Autokorrektur"</string>
     <string name="quick_fixes_summary" msgid="3405028402510332373">"Retter vanlige stavefeil"</string>
@@ -36,6 +38,7 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Vis alltid"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"Vis i stående modus"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Skjul alltid"</string>
+    <string name="prefs_use_spacebar_language_switch" msgid="8828538114550634449">"Skift språk med mellomromstasten"</string>
     <string name="prefs_settings_key" msgid="4623341240804046498">"Vis innstillingsnøkkel"</string>
     <string name="settings_key_mode_auto_name" msgid="2993460277873684680">"Automatisk"</string>
     <string name="settings_key_mode_always_show_name" msgid="3047567041784760575">"Vis alltid"</string>
@@ -45,8 +48,10 @@
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Av"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Moderat"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"Omfattende"</string>
-    <string name="bigram_suggestion" msgid="1323347224043514969">"Bigram-forslag"</string>
+    <string name="bigram_suggestion" msgid="2636414079905220518">"Bigram-forslag"</string>
     <string name="bigram_suggestion_summary" msgid="4383845146070101531">"Bruk forrige ord til å forbedre forslaget"</string>
+    <string name="bigram_prediction" msgid="8914273444762259739">"Bigram-prediksjon"</string>
+    <string name="bigram_prediction_summary" msgid="1747261921174300098">"Bruk forrige ord også for forslag"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g>: Lagret"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Gå"</string>
     <string name="label_next_key" msgid="362972844525672568">"Neste"</string>
@@ -56,18 +61,6 @@
     <string name="label_more_key" msgid="3760239494604948502">"Mer"</string>
     <string name="label_pause_key" msgid="181098308428035340">"Pause"</string>
     <string name="label_wait_key" msgid="6402152600878093134">"Vent"</string>
-    <string name="description_delete_key" msgid="5586406298531883960">"Delete"</string>
-    <string name="description_return_key" msgid="8750044000806461678">"Enter"</string>
-    <string name="description_settings_key" msgid="7484527796782969219">"Innstillinger"</string>
-    <string name="description_shift_key" msgid="346906866277787836">"Shift"</string>
-    <string name="description_space_key" msgid="8512130111575878517">"Mellomrom"</string>
-    <string name="description_switch_alpha_symbol_key" msgid="4537975384274405537">"Symboler"</string>
-    <string name="description_tab_key" msgid="828186583738307137">"Tab"</string>
-    <string name="description_voice_key" msgid="3057731675774652754">"Taleinndata"</string>
-    <string name="description_symbols_on" msgid="2994366855822840559">"Symboler er slått på"</string>
-    <string name="description_symbols_off" msgid="3209578267079515136">"Symboler er slått av"</string>
-    <string name="description_shift_on" msgid="6983188949895971587">"Shift på"</string>
-    <string name="description_shift_off" msgid="8553265474523069034">"Shift av"</string>
     <string name="voice_warning_title" msgid="4419354150908395008">"Stemmedata"</string>
     <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"Stemmedata håndteres foreløpig ikke på ditt språk, men fungerer på engelsk."</string>
     <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"Google Voice bruker Googles talegjenkjenning. "<a href="http://m.google.com/privacy">"Personvernreglene for mobil"</a>" gjelder."</string>
@@ -106,6 +99,7 @@
     <string name="prefs_enable_recorrection_summary" msgid="5082041365862396329">"Når forslag er synlige, kan du trykke på ord du har skrevet inn, for å endre dem"</string>
     <string name="keyboard_layout" msgid="437433231038683666">"Tastaturtema"</string>
     <string name="subtype_mode_cs_keyboard" msgid="1141718931112377586">"Tsjekkisk tastatur"</string>
+    <string name="subtype_mode_ar_keyboard" msgid="2655338636329774995">"Arabisk tastatur"</string>
     <string name="subtype_mode_da_keyboard" msgid="1243570804427922104">"Dansk tastatur"</string>
     <string name="subtype_mode_de_keyboard" msgid="1990979135959462145">"Tysk tastatur"</string>
     <string name="subtype_mode_en_GB_keyboard" msgid="7945856548410373708">"Engelsk tastatur (Storbritannia)"</string>
@@ -115,6 +109,7 @@
     <string name="subtype_mode_fr_keyboard" msgid="8016515336759761014">"Fransk tastatur"</string>
     <string name="subtype_mode_fr_CA_keyboard" msgid="2628517247158376263">"Fransk tastatur (Canada)"</string>
     <string name="subtype_mode_fr_CH_keyboard" msgid="6742806653181621228">"Fransk tastatur (Sveits)"</string>
+    <string name="subtype_mode_iw_keyboard" msgid="1787536828253289950">"Hebraisk tastatur"</string>
     <string name="subtype_mode_it_keyboard" msgid="4934199655425394484">"Italiensk tastatur"</string>
     <string name="subtype_mode_nb_keyboard" msgid="1175783216100212360">"Norsk tastatur"</string>
     <string name="subtype_mode_nl_keyboard" msgid="5090278083256037936">"Nederlandsk tastatur"</string>
diff --git a/java/res/values-nl/strings.xml b/java/res/values-nl/strings.xml
index a1213ec..89b714b 100644
--- a/java/res/values-nl/strings.xml
+++ b/java/res/values-nl/strings.xml
@@ -27,7 +27,9 @@
     <string name="sound_on_keypress" msgid="6093592297198243644">"Geluid bij toetsaanslag"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"Pop-up bij toetsaanslag"</string>
     <string name="general_category" msgid="1859088467017573195">"Algemeen"</string>
-    <string name="prediction_category" msgid="6361242011806282176">"Tekstcorrectie"</string>
+    <string name="correction_category" msgid="2236750915056607613">"Tekstcorrectie"</string>
+    <string name="ngram_category" msgid="5337109164339320257">"Suggesties op basis van eerdere woorden"</string>
+    <string name="misc_category" msgid="6894192814868233453">"Andere opties"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Auto-hoofdlettergebruik"</string>
     <string name="quick_fixes" msgid="5353213327680897927">"Snelle oplossingen"</string>
     <string name="quick_fixes_summary" msgid="3405028402510332373">"Hiermee worden veelvoorkomende typefouten gecorrigeerd"</string>
@@ -36,6 +38,7 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Altijd weergeven"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"Weergeven in staande modus"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Altijd verbergen"</string>
+    <string name="prefs_use_spacebar_language_switch" msgid="8828538114550634449">"Taal schakelen via spatiebalk"</string>
     <string name="prefs_settings_key" msgid="4623341240804046498">"Instellingscode weergeven"</string>
     <string name="settings_key_mode_auto_name" msgid="2993460277873684680">"Automatisch"</string>
     <string name="settings_key_mode_always_show_name" msgid="3047567041784760575">"Altijd weergeven"</string>
@@ -45,8 +48,10 @@
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Uitgeschakeld"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Normaal"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"Agressief"</string>
-    <string name="bigram_suggestion" msgid="1323347224043514969">"Digram-suggesties"</string>
+    <string name="bigram_suggestion" msgid="2636414079905220518">"Digram-suggesties"</string>
     <string name="bigram_suggestion_summary" msgid="4383845146070101531">"Vorig woord gebruiken om suggestie te verbeteren"</string>
+    <string name="bigram_prediction" msgid="8914273444762259739">"Digram-voorspelling"</string>
+    <string name="bigram_prediction_summary" msgid="1747261921174300098">"Het voorgaande woord ook voor voorspelling gebruiken"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g>: opgeslagen"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Start"</string>
     <string name="label_next_key" msgid="362972844525672568">"Volgende"</string>
@@ -56,18 +61,6 @@
     <string name="label_more_key" msgid="3760239494604948502">"Meer"</string>
     <string name="label_pause_key" msgid="181098308428035340">"Onderbr."</string>
     <string name="label_wait_key" msgid="6402152600878093134">"Wacht"</string>
-    <string name="description_delete_key" msgid="5586406298531883960">"Delete"</string>
-    <string name="description_return_key" msgid="8750044000806461678">"Return"</string>
-    <string name="description_settings_key" msgid="7484527796782969219">"Instellingen"</string>
-    <string name="description_shift_key" msgid="346906866277787836">"Shift"</string>
-    <string name="description_space_key" msgid="8512130111575878517">"Spatie"</string>
-    <string name="description_switch_alpha_symbol_key" msgid="4537975384274405537">"Symbolen"</string>
-    <string name="description_tab_key" msgid="828186583738307137">"Tab"</string>
-    <string name="description_voice_key" msgid="3057731675774652754">"Spraakinvoer"</string>
-    <string name="description_symbols_on" msgid="2994366855822840559">"Symbolen aan"</string>
-    <string name="description_symbols_off" msgid="3209578267079515136">"Symbolen uit"</string>
-    <string name="description_shift_on" msgid="6983188949895971587">"Shift aan"</string>
-    <string name="description_shift_off" msgid="8553265474523069034">"Shift uit"</string>
     <string name="voice_warning_title" msgid="4419354150908395008">"Spraakinvoer"</string>
     <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"Spraakinvoer wordt momenteel niet ondersteund in uw taal, maar is wel beschikbaar in het Engels."</string>
     <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"Spraakinvoer maakt gebruik van de spraakherkenning van Google. Het "<a href="http://m.google.com/privacy">"Privacybeleid van Google Mobile"</a>" is van toepassing."</string>
@@ -106,6 +99,7 @@
     <string name="prefs_enable_recorrection_summary" msgid="5082041365862396329">"Ingevoerde woorden aanraken om ze te verbeteren, alleen mogelijk wanneer suggesties zichtbaar zijn"</string>
     <string name="keyboard_layout" msgid="437433231038683666">"Toetsenbordthema"</string>
     <string name="subtype_mode_cs_keyboard" msgid="1141718931112377586">"Tsjechisch toetsenbord"</string>
+    <string name="subtype_mode_ar_keyboard" msgid="2655338636329774995">"Arabisch toetsenbord"</string>
     <string name="subtype_mode_da_keyboard" msgid="1243570804427922104">"Deens toetsenbord"</string>
     <string name="subtype_mode_de_keyboard" msgid="1990979135959462145">"Duits toetsenbord"</string>
     <string name="subtype_mode_en_GB_keyboard" msgid="7945856548410373708">"Engels toetsenbord (VK)"</string>
@@ -115,6 +109,7 @@
     <string name="subtype_mode_fr_keyboard" msgid="8016515336759761014">"Frans toetsenbord"</string>
     <string name="subtype_mode_fr_CA_keyboard" msgid="2628517247158376263">"Frans toetsenbord (Canada)"</string>
     <string name="subtype_mode_fr_CH_keyboard" msgid="6742806653181621228">"Frans toetsenbord (Zwitserland)"</string>
+    <string name="subtype_mode_iw_keyboard" msgid="1787536828253289950">"Hebreeuws toetsenbord"</string>
     <string name="subtype_mode_it_keyboard" msgid="4934199655425394484">"Italiaans toetsenbord"</string>
     <string name="subtype_mode_nb_keyboard" msgid="1175783216100212360">"Noors toetsenbord"</string>
     <string name="subtype_mode_nl_keyboard" msgid="5090278083256037936">"Nederlands toetsenbord"</string>
diff --git a/java/res/values-pl/strings.xml b/java/res/values-pl/strings.xml
index 633b159..65240d7 100644
--- a/java/res/values-pl/strings.xml
+++ b/java/res/values-pl/strings.xml
@@ -27,7 +27,9 @@
     <string name="sound_on_keypress" msgid="6093592297198243644">"Dźwięk przy naciśnięciu"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"Powiększ po naciśnięciu"</string>
     <string name="general_category" msgid="1859088467017573195">"Ogólne"</string>
-    <string name="prediction_category" msgid="6361242011806282176">"Korekta tekstu"</string>
+    <string name="correction_category" msgid="2236750915056607613">"Poprawianie tekstu"</string>
+    <string name="ngram_category" msgid="5337109164339320257">"Podpowiedzi na podstawie wcześniejszych słów"</string>
+    <string name="misc_category" msgid="6894192814868233453">"Inne opcje"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Wstawiaj wielkie litery"</string>
     <string name="quick_fixes" msgid="5353213327680897927">"Szybkie poprawki"</string>
     <string name="quick_fixes_summary" msgid="3405028402510332373">"Poprawia częste błędy wpisywania"</string>
@@ -36,6 +38,7 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Zawsze pokazuj"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"Pokaż w trybie pionowym"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Zawsze ukrywaj"</string>
+    <string name="prefs_use_spacebar_language_switch" msgid="8828538114550634449">"Spacja przełącza język"</string>
     <string name="prefs_settings_key" msgid="4623341240804046498">"Pokaż klawisz ustawień"</string>
     <string name="settings_key_mode_auto_name" msgid="2993460277873684680">"Automatycznie"</string>
     <string name="settings_key_mode_always_show_name" msgid="3047567041784760575">"Zawsze pokazuj"</string>
@@ -45,8 +48,10 @@
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Wyłącz"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Umiarkowana"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"Agresywna"</string>
-    <string name="bigram_suggestion" msgid="1323347224043514969">"Sugestie dla bigramów"</string>
+    <string name="bigram_suggestion" msgid="2636414079905220518">"Podpowiadanie dwuznaków"</string>
     <string name="bigram_suggestion_summary" msgid="4383845146070101531">"Używaj poprzedniego wyrazu, aby polepszyć sugestię"</string>
+    <string name="bigram_prediction" msgid="8914273444762259739">"Przewidywanie dwuznaków"</string>
+    <string name="bigram_prediction_summary" msgid="1747261921174300098">"Przewiduj również na podstawie poprzedniego słowa"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : Zapisano"</string>
     <string name="label_go_key" msgid="1635148082137219148">"OK"</string>
     <string name="label_next_key" msgid="362972844525672568">"Dalej"</string>
@@ -56,18 +61,6 @@
     <string name="label_more_key" msgid="3760239494604948502">"Więcej"</string>
     <string name="label_pause_key" msgid="181098308428035340">"Pauza"</string>
     <string name="label_wait_key" msgid="6402152600878093134">"Czekaj"</string>
-    <string name="description_delete_key" msgid="5586406298531883960">"Delete"</string>
-    <string name="description_return_key" msgid="8750044000806461678">"Enter"</string>
-    <string name="description_settings_key" msgid="7484527796782969219">"Ustawienia"</string>
-    <string name="description_shift_key" msgid="346906866277787836">"Shift"</string>
-    <string name="description_space_key" msgid="8512130111575878517">"Spacja"</string>
-    <string name="description_switch_alpha_symbol_key" msgid="4537975384274405537">"Symbole"</string>
-    <string name="description_tab_key" msgid="828186583738307137">"Tab"</string>
-    <string name="description_voice_key" msgid="3057731675774652754">"Wprowadzanie głosowe"</string>
-    <string name="description_symbols_on" msgid="2994366855822840559">"Symbole włączone"</string>
-    <string name="description_symbols_off" msgid="3209578267079515136">"Symbole wyłączone"</string>
-    <string name="description_shift_on" msgid="6983188949895971587">"Shift włączony"</string>
-    <string name="description_shift_off" msgid="8553265474523069034">"Shift wyłączony"</string>
     <string name="voice_warning_title" msgid="4419354150908395008">"Wprowadzanie głosowe"</string>
     <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"Wprowadzanie głosowe obecnie nie jest obsługiwane w Twoim języku, ale działa w języku angielskim."</string>
     <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"Funkcja wprowadzania głosowego wykorzystuje mechanizm rozpoznawania mowy. Obowiązuje "<a href="http://m.google.com/privacy">"Polityka prywatności Google Mobile"</a>"."</string>
@@ -106,6 +99,7 @@
     <string name="prefs_enable_recorrection_summary" msgid="5082041365862396329">"Dotykaj wprowadzonych słów, aby je poprawiać tylko wówczas, gdy widoczne są sugestie."</string>
     <string name="keyboard_layout" msgid="437433231038683666">"Motyw klawiatury"</string>
     <string name="subtype_mode_cs_keyboard" msgid="1141718931112377586">"Klawiatura czeska"</string>
+    <string name="subtype_mode_ar_keyboard" msgid="2655338636329774995">"Klawiatura arabska"</string>
     <string name="subtype_mode_da_keyboard" msgid="1243570804427922104">"Klawiatura duńska"</string>
     <string name="subtype_mode_de_keyboard" msgid="1990979135959462145">"Klawiatura niemiecka"</string>
     <string name="subtype_mode_en_GB_keyboard" msgid="7945856548410373708">"Klawiatura angielska (UK)"</string>
@@ -115,6 +109,7 @@
     <string name="subtype_mode_fr_keyboard" msgid="8016515336759761014">"Klawiatura francuska"</string>
     <string name="subtype_mode_fr_CA_keyboard" msgid="2628517247158376263">"Klawiatura francuska (Kanada)"</string>
     <string name="subtype_mode_fr_CH_keyboard" msgid="6742806653181621228">"Klawiatura francuska (Szwajcaria)"</string>
+    <string name="subtype_mode_iw_keyboard" msgid="1787536828253289950">"Klawiatura hebrajska"</string>
     <string name="subtype_mode_it_keyboard" msgid="4934199655425394484">"Klawiatura włoska"</string>
     <string name="subtype_mode_nb_keyboard" msgid="1175783216100212360">"Klawiatura norweska"</string>
     <string name="subtype_mode_nl_keyboard" msgid="5090278083256037936">"Klawiatura holenderska"</string>
diff --git a/java/res/values-pt-rPT/strings.xml b/java/res/values-pt-rPT/strings.xml
index f06d64c..b972ce0 100644
--- a/java/res/values-pt-rPT/strings.xml
+++ b/java/res/values-pt-rPT/strings.xml
@@ -27,7 +27,9 @@
     <string name="sound_on_keypress" msgid="6093592297198243644">"Som ao premir as teclas"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"Mostrar popup ao premir tecla"</string>
     <string name="general_category" msgid="1859088467017573195">"Geral"</string>
-    <string name="prediction_category" msgid="6361242011806282176">"Correcção de texto"</string>
+    <string name="correction_category" msgid="2236750915056607613">"Correção de texto"</string>
+    <string name="ngram_category" msgid="5337109164339320257">"Sugestões baseadas em palavras anteriores"</string>
+    <string name="misc_category" msgid="6894192814868233453">"Outras opções"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Letras maiúsculas automáticas"</string>
     <string name="quick_fixes" msgid="5353213327680897927">"Correcções rápidas"</string>
     <string name="quick_fixes_summary" msgid="3405028402510332373">"Corrige os erros de escrita comuns"</string>
@@ -36,6 +38,7 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Mostrar sempre"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"Mostrar no modo de retrato"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Ocultar sempre"</string>
+    <string name="prefs_use_spacebar_language_switch" msgid="8828538114550634449">"Utilizar barra esp. alt. idioma"</string>
     <string name="prefs_settings_key" msgid="4623341240804046498">"Mostrar tecla das definições"</string>
     <string name="settings_key_mode_auto_name" msgid="2993460277873684680">"Automático"</string>
     <string name="settings_key_mode_always_show_name" msgid="3047567041784760575">"Mostrar sempre"</string>
@@ -45,8 +48,10 @@
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Desligar"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Moderada"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"Agressiva"</string>
-    <string name="bigram_suggestion" msgid="1323347224043514969">"Sugestões Bigram"</string>
+    <string name="bigram_suggestion" msgid="2636414079905220518">"Sugestões Bigram"</string>
     <string name="bigram_suggestion_summary" msgid="4383845146070101531">"Utilizar a palavra anterior para melhorar a sugestão"</string>
+    <string name="bigram_prediction" msgid="8914273444762259739">"Predição Bigram"</string>
+    <string name="bigram_prediction_summary" msgid="1747261921174300098">"Utilizar a palavra anterior também para predição"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g>: guardada"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Ir"</string>
     <string name="label_next_key" msgid="362972844525672568">"Seguinte"</string>
@@ -56,18 +61,6 @@
     <string name="label_more_key" msgid="3760239494604948502">"Mais"</string>
     <string name="label_pause_key" msgid="181098308428035340">"Pausa"</string>
     <string name="label_wait_key" msgid="6402152600878093134">"Esp."</string>
-    <string name="description_delete_key" msgid="5586406298531883960">"Delete"</string>
-    <string name="description_return_key" msgid="8750044000806461678">"Enter"</string>
-    <string name="description_settings_key" msgid="7484527796782969219">"Definições"</string>
-    <string name="description_shift_key" msgid="346906866277787836">"Shift"</string>
-    <string name="description_space_key" msgid="8512130111575878517">"Espaço"</string>
-    <string name="description_switch_alpha_symbol_key" msgid="4537975384274405537">"Símbolos"</string>
-    <string name="description_tab_key" msgid="828186583738307137">"Tab"</string>
-    <string name="description_voice_key" msgid="3057731675774652754">"Entrada de voz"</string>
-    <string name="description_symbols_on" msgid="2994366855822840559">"Símbolos ativados"</string>
-    <string name="description_symbols_off" msgid="3209578267079515136">"Símbolos desativados"</string>
-    <string name="description_shift_on" msgid="6983188949895971587">"Shift ativado"</string>
-    <string name="description_shift_off" msgid="8553265474523069034">"Shift desativado"</string>
     <string name="voice_warning_title" msgid="4419354150908395008">"Entrada de voz"</string>
     <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"Actualmente, a entrada de voz não é suportada para o seu idioma, mas funciona em inglês."</string>
     <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"A entrada de voz utiliza o reconhecimento de voz da Google. É aplicável a "<a href="http://m.google.com/privacy">"Política de privacidade do Google Mobile"</a>"."</string>
@@ -106,6 +99,7 @@
     <string name="prefs_enable_recorrection_summary" msgid="5082041365862396329">"Tocar nas palavras introduzidas para as corrigir, apenas quando as sugestões estiverem visíveis"</string>
     <string name="keyboard_layout" msgid="437433231038683666">"Tema do teclado"</string>
     <string name="subtype_mode_cs_keyboard" msgid="1141718931112377586">"Teclado checo"</string>
+    <string name="subtype_mode_ar_keyboard" msgid="2655338636329774995">"Teclado árabe"</string>
     <string name="subtype_mode_da_keyboard" msgid="1243570804427922104">"Teclado dinamarquês"</string>
     <string name="subtype_mode_de_keyboard" msgid="1990979135959462145">"Teclado alemão"</string>
     <string name="subtype_mode_en_GB_keyboard" msgid="7945856548410373708">"Teclado inglês (Reino Unido)"</string>
@@ -115,6 +109,7 @@
     <string name="subtype_mode_fr_keyboard" msgid="8016515336759761014">"Teclado francês"</string>
     <string name="subtype_mode_fr_CA_keyboard" msgid="2628517247158376263">"Teclado francês (Canadá)"</string>
     <string name="subtype_mode_fr_CH_keyboard" msgid="6742806653181621228">"Teclado francês (Suíça)"</string>
+    <string name="subtype_mode_iw_keyboard" msgid="1787536828253289950">"Teclado hebraico"</string>
     <string name="subtype_mode_it_keyboard" msgid="4934199655425394484">"Teclado italiano"</string>
     <string name="subtype_mode_nb_keyboard" msgid="1175783216100212360">"Teclado norueguês"</string>
     <string name="subtype_mode_nl_keyboard" msgid="5090278083256037936">"Teclado holandês"</string>
diff --git a/java/res/values-pt/strings.xml b/java/res/values-pt/strings.xml
index 9fc1a97..a999dc6 100644
--- a/java/res/values-pt/strings.xml
+++ b/java/res/values-pt/strings.xml
@@ -27,7 +27,9 @@
     <string name="sound_on_keypress" msgid="6093592297198243644">"Som ao tocar a tecla"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"Exibir pop-up ao digitar"</string>
     <string name="general_category" msgid="1859088467017573195">"Geral"</string>
-    <string name="prediction_category" msgid="6361242011806282176">"Correção de texto"</string>
+    <string name="correction_category" msgid="2236750915056607613">"Correção de texto"</string>
+    <string name="ngram_category" msgid="5337109164339320257">"Sugestões baseadas em palavras anteriores"</string>
+    <string name="misc_category" msgid="6894192814868233453">"Outras opções"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Capitaliz. automática"</string>
     <string name="quick_fixes" msgid="5353213327680897927">"Reparos rápidos"</string>
     <string name="quick_fixes_summary" msgid="3405028402510332373">"Corrige erros comuns de digitação"</string>
@@ -36,6 +38,7 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Mostrar sempre"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"Mostrar em modo retrato"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Sempre ocultar"</string>
+    <string name="prefs_use_spacebar_language_switch" msgid="8828538114550634449">"Alt. idiomas c/ a barra"</string>
     <string name="prefs_settings_key" msgid="4623341240804046498">"Mostrar tecla de config."</string>
     <string name="settings_key_mode_auto_name" msgid="2993460277873684680">"Automático"</string>
     <string name="settings_key_mode_always_show_name" msgid="3047567041784760575">"Mostrar sempre"</string>
@@ -45,8 +48,10 @@
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Desativado"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Moderado"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"Agressivo"</string>
-    <string name="bigram_suggestion" msgid="1323347224043514969">"Sugestões de bigrama"</string>
+    <string name="bigram_suggestion" msgid="2636414079905220518">"Sugestões de bigrama"</string>
     <string name="bigram_suggestion_summary" msgid="4383845146070101531">"Usar palavra anterior para melhorar a sugestão"</string>
+    <string name="bigram_prediction" msgid="8914273444762259739">"Previsão de bigrama"</string>
+    <string name="bigram_prediction_summary" msgid="1747261921174300098">"Use também a palavra anterior para prever"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : Salvo"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Ir"</string>
     <string name="label_next_key" msgid="362972844525672568">"Avançar"</string>
@@ -56,18 +61,6 @@
     <string name="label_more_key" msgid="3760239494604948502">"Mais"</string>
     <string name="label_pause_key" msgid="181098308428035340">"Pausa"</string>
     <string name="label_wait_key" msgid="6402152600878093134">"Esp."</string>
-    <string name="description_delete_key" msgid="5586406298531883960">"Excluir"</string>
-    <string name="description_return_key" msgid="8750044000806461678">"Voltar"</string>
-    <string name="description_settings_key" msgid="7484527796782969219">"Configurações"</string>
-    <string name="description_shift_key" msgid="346906866277787836">"Shift"</string>
-    <string name="description_space_key" msgid="8512130111575878517">"Espaço"</string>
-    <string name="description_switch_alpha_symbol_key" msgid="4537975384274405537">"Símbolos"</string>
-    <string name="description_tab_key" msgid="828186583738307137">"Tab"</string>
-    <string name="description_voice_key" msgid="3057731675774652754">"Entrada de texto por voz"</string>
-    <string name="description_symbols_on" msgid="2994366855822840559">"Símbolos ativados"</string>
-    <string name="description_symbols_off" msgid="3209578267079515136">"Símbolos desativados"</string>
-    <string name="description_shift_on" msgid="6983188949895971587">"Shift ativado"</string>
-    <string name="description_shift_off" msgid="8553265474523069034">"Shift desativado"</string>
     <string name="voice_warning_title" msgid="4419354150908395008">"Entrada de voz"</string>
     <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"A entrada de voz não é suportada no momento para o seu idioma, mas funciona em inglês."</string>
     <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"A entrada de texto por voz usa o reconhecimento de voz do Google. "<a href="http://m.google.com/privacy">"A política de privacidade para celulares"</a>" é aplicada."</string>
@@ -106,6 +99,7 @@
     <string name="prefs_enable_recorrection_summary" msgid="5082041365862396329">"Toque nas palavras digitadas para corrigi-las apenas quando as sugestões estiverem visíveis"</string>
     <string name="keyboard_layout" msgid="437433231038683666">"Tema do teclado"</string>
     <string name="subtype_mode_cs_keyboard" msgid="1141718931112377586">"Teclado em tcheco"</string>
+    <string name="subtype_mode_ar_keyboard" msgid="2655338636329774995">"Teclado árabe"</string>
     <string name="subtype_mode_da_keyboard" msgid="1243570804427922104">"Teclado para dinamarquês"</string>
     <string name="subtype_mode_de_keyboard" msgid="1990979135959462145">"Teclado para alemão"</string>
     <string name="subtype_mode_en_GB_keyboard" msgid="7945856548410373708">"Teclado para inglês (Reino Unido)"</string>
@@ -115,6 +109,7 @@
     <string name="subtype_mode_fr_keyboard" msgid="8016515336759761014">"Teclado para francês"</string>
     <string name="subtype_mode_fr_CA_keyboard" msgid="2628517247158376263">"Teclado para francês (Canadá)"</string>
     <string name="subtype_mode_fr_CH_keyboard" msgid="6742806653181621228">"Teclado para francês (Suíça)"</string>
+    <string name="subtype_mode_iw_keyboard" msgid="1787536828253289950">"Teclado hebraico"</string>
     <string name="subtype_mode_it_keyboard" msgid="4934199655425394484">"Teclado para italiano"</string>
     <string name="subtype_mode_nb_keyboard" msgid="1175783216100212360">"Teclado para norueguês"</string>
     <string name="subtype_mode_nl_keyboard" msgid="5090278083256037936">"Teclado para holandês"</string>
diff --git a/java/res/values-rm/strings.xml b/java/res/values-rm/strings.xml
index 57548b5..b0c1bea 100644
--- a/java/res/values-rm/strings.xml
+++ b/java/res/values-rm/strings.xml
@@ -29,7 +29,12 @@
     <string name="popup_on_keypress" msgid="123894815723512944">"Pop-up cun smatgar ina tasta"</string>
     <!-- no translation found for general_category (1859088467017573195) -->
     <skip />
-    <!-- outdated translation 7027100625580696660 -->     <string name="prediction_category" msgid="6361242011806282176">"Parameters da las propostas per pleds"</string>
+    <!-- no translation found for correction_category (2236750915056607613) -->
+    <skip />
+    <!-- no translation found for ngram_category (5337109164339320257) -->
+    <skip />
+    <!-- no translation found for misc_category (6894192814868233453) -->
+    <skip />
     <string name="auto_cap" msgid="1719746674854628252">"Maiusclas automaticas"</string>
     <string name="quick_fixes" msgid="5353213327680897927">"Correcturas sveltas"</string>
     <string name="quick_fixes_summary" msgid="3405028402510332373">"Curregia sbagls da tippar currents"</string>
@@ -43,6 +48,8 @@
     <skip />
     <!-- no translation found for prefs_suggestion_visibility_hide_name (6309143926422234673) -->
     <skip />
+    <!-- no translation found for prefs_use_spacebar_language_switch (8828538114550634449) -->
+    <skip />
     <!-- no translation found for prefs_settings_key (4623341240804046498) -->
     <skip />
     <!-- no translation found for settings_key_mode_auto_name (2993460277873684680) -->
@@ -59,8 +66,12 @@
     <skip />
     <!-- no translation found for auto_correction_threshold_mode_aggeressive (3524029103734923819) -->
     <skip />
-    <string name="bigram_suggestion" msgid="1323347224043514969">"Propostas da tip bigram"</string>
+    <!-- outdated translation 1323347224043514969 -->     <string name="bigram_suggestion" msgid="2636414079905220518">"Propostas da tip bigram"</string>
     <string name="bigram_suggestion_summary" msgid="4383845146070101531">"Meglierar la proposta cun agid dal pled precedent"</string>
+    <!-- no translation found for bigram_prediction (8914273444762259739) -->
+    <skip />
+    <!-- no translation found for bigram_prediction_summary (1747261921174300098) -->
+    <skip />
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : Memorisà"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Dai"</string>
     <string name="label_next_key" msgid="362972844525672568">"Vinavant"</string>
@@ -74,30 +85,6 @@
     <skip />
     <!-- no translation found for label_wait_key (6402152600878093134) -->
     <skip />
-    <!-- no translation found for description_delete_key (5586406298531883960) -->
-    <skip />
-    <!-- no translation found for description_return_key (8750044000806461678) -->
-    <skip />
-    <!-- no translation found for description_settings_key (7484527796782969219) -->
-    <skip />
-    <!-- no translation found for description_shift_key (346906866277787836) -->
-    <skip />
-    <!-- no translation found for description_space_key (8512130111575878517) -->
-    <skip />
-    <!-- no translation found for description_switch_alpha_symbol_key (4537975384274405537) -->
-    <skip />
-    <!-- no translation found for description_tab_key (828186583738307137) -->
-    <skip />
-    <!-- no translation found for description_voice_key (3057731675774652754) -->
-    <skip />
-    <!-- no translation found for description_symbols_on (2994366855822840559) -->
-    <skip />
-    <!-- no translation found for description_symbols_off (3209578267079515136) -->
-    <skip />
-    <!-- no translation found for description_shift_on (6983188949895971587) -->
-    <skip />
-    <!-- no translation found for description_shift_off (8553265474523069034) -->
-    <skip />
     <string name="voice_warning_title" msgid="4419354150908395008">"Cumonds vocals"</string>
     <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"\"Cumonds vocals en Vossa lingua na vegnan actualmain betg sustegnids, ma la funcziun è disponibla per englais.\""</string>
     <!-- outdated translation 4611518823070986445 -->     <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"Ils cumonds vocals èn ina funcziunalitad experimentala che utilisescha la renconuschientscha vocala da rait da Google."</string>
@@ -146,6 +133,8 @@
     <string name="keyboard_layout" msgid="437433231038683666">"Design da la tastatura"</string>
     <!-- no translation found for subtype_mode_cs_keyboard (1141718931112377586) -->
     <skip />
+    <!-- no translation found for subtype_mode_ar_keyboard (2655338636329774995) -->
+    <skip />
     <!-- no translation found for subtype_mode_da_keyboard (1243570804427922104) -->
     <skip />
     <!-- no translation found for subtype_mode_de_keyboard (1990979135959462145) -->
@@ -164,6 +153,8 @@
     <skip />
     <!-- no translation found for subtype_mode_fr_CH_keyboard (6742806653181621228) -->
     <skip />
+    <!-- no translation found for subtype_mode_iw_keyboard (1787536828253289950) -->
+    <skip />
     <!-- no translation found for subtype_mode_it_keyboard (4934199655425394484) -->
     <skip />
     <!-- no translation found for subtype_mode_nb_keyboard (1175783216100212360) -->
diff --git a/java/res/values-ro/strings.xml b/java/res/values-ro/strings.xml
index 9c97392..0fcefd1 100644
--- a/java/res/values-ro/strings.xml
+++ b/java/res/values-ro/strings.xml
@@ -27,7 +27,9 @@
     <string name="sound_on_keypress" msgid="6093592297198243644">"Sunet la apăsarea tastei"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"Fereastră pop-up la apăsarea tastei"</string>
     <string name="general_category" msgid="1859088467017573195">"General"</string>
-    <string name="prediction_category" msgid="6361242011806282176">"Corectare text"</string>
+    <string name="correction_category" msgid="2236750915056607613">"Corectare text"</string>
+    <string name="ngram_category" msgid="5337109164339320257">"Sugestii bazate pe cuvinte anterioare"</string>
+    <string name="misc_category" msgid="6894192814868233453">"Alte opţiuni"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Auto-capitalizare"</string>
     <string name="quick_fixes" msgid="5353213327680897927">"Remedieri rapide"</string>
     <string name="quick_fixes_summary" msgid="3405028402510332373">"Corectează greşelile introduse frecvent"</string>
@@ -36,6 +38,7 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Afişaţi întotdeauna"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"Afişaţi în modul Portret"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Ascundeţi întotdeauna"</string>
+    <string name="prefs_use_spacebar_language_switch" msgid="8828538114550634449">"Sp. pt. comut. lb."</string>
     <string name="prefs_settings_key" msgid="4623341240804046498">"Afişaţi tasta setări"</string>
     <string name="settings_key_mode_auto_name" msgid="2993460277873684680">"Automat"</string>
     <string name="settings_key_mode_always_show_name" msgid="3047567041784760575">"Afişaţi întotdeauna"</string>
@@ -45,8 +48,10 @@
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Dezactivată"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Moderată"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"Agresivă"</string>
-    <string name="bigram_suggestion" msgid="1323347224043514969">"Sugestii pentru cuvinte de două litere"</string>
+    <string name="bigram_suggestion" msgid="2636414079905220518">"Sugestii pentru cuvinte de două litere"</string>
     <string name="bigram_suggestion_summary" msgid="4383845146070101531">"Utilizaţi cuvântul anterior pentru a îmbunătăţi sugestia"</string>
+    <string name="bigram_prediction" msgid="8914273444762259739">"Sugestii pentru cuvinte de două litere"</string>
+    <string name="bigram_prediction_summary" msgid="1747261921174300098">"De asemenea, utilizaţi pentru sugestii cuvântul precedent"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g>: salvat"</string>
     <string name="label_go_key" msgid="1635148082137219148">"OK"</string>
     <string name="label_next_key" msgid="362972844525672568">"Înainte"</string>
@@ -56,18 +61,6 @@
     <string name="label_more_key" msgid="3760239494604948502">"Mai multe"</string>
     <string name="label_pause_key" msgid="181098308428035340">"Pauză"</string>
     <string name="label_wait_key" msgid="6402152600878093134">"Aşt."</string>
-    <string name="description_delete_key" msgid="5586406298531883960">"Ştergeţi"</string>
-    <string name="description_return_key" msgid="8750044000806461678">"Tasta Enter"</string>
-    <string name="description_settings_key" msgid="7484527796782969219">"Setări"</string>
-    <string name="description_shift_key" msgid="346906866277787836">"Shift"</string>
-    <string name="description_space_key" msgid="8512130111575878517">"Tasta Space"</string>
-    <string name="description_switch_alpha_symbol_key" msgid="4537975384274405537">"Simboluri"</string>
-    <string name="description_tab_key" msgid="828186583738307137">"Tasta Tab"</string>
-    <string name="description_voice_key" msgid="3057731675774652754">"Intrare vocală"</string>
-    <string name="description_symbols_on" msgid="2994366855822840559">"Simbolurile sunt activate"</string>
-    <string name="description_symbols_off" msgid="3209578267079515136">"Simbolurile sunt dezactivate"</string>
-    <string name="description_shift_on" msgid="6983188949895971587">"Tasta Shift este activată"</string>
-    <string name="description_shift_off" msgid="8553265474523069034">"Tasta Shift este dezactivată"</string>
     <string name="voice_warning_title" msgid="4419354150908395008">"Intrare voce"</string>
     <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"Intrarea vocală nu este acceptată în prezent pentru limba dvs., însă funcţionează în limba engleză."</string>
     <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"Intrarea vocală utilizează funcţia Google de recunoaştere vocală. Se aplică "<a href="http://m.google.com/privacy">"Politica de confidenţialitate Google Mobil"</a>"."</string>
@@ -106,6 +99,7 @@
     <string name="prefs_enable_recorrection_summary" msgid="5082041365862396329">"Atingeţi cuvintele introduse pentru a le corecta, numai când pot fi văzute sugestii"</string>
     <string name="keyboard_layout" msgid="437433231038683666">"Temă pentru tastatură"</string>
     <string name="subtype_mode_cs_keyboard" msgid="1141718931112377586">"Tastatură cehă"</string>
+    <string name="subtype_mode_ar_keyboard" msgid="2655338636329774995">"Tastatură arabă"</string>
     <string name="subtype_mode_da_keyboard" msgid="1243570804427922104">"Tastatură daneză"</string>
     <string name="subtype_mode_de_keyboard" msgid="1990979135959462145">"Tastatură germană"</string>
     <string name="subtype_mode_en_GB_keyboard" msgid="7945856548410373708">"Tastatură engleză (Marea Britanie)"</string>
@@ -115,6 +109,7 @@
     <string name="subtype_mode_fr_keyboard" msgid="8016515336759761014">"Tastatură franceză"</string>
     <string name="subtype_mode_fr_CA_keyboard" msgid="2628517247158376263">"Tastatură franceză (Canada)"</string>
     <string name="subtype_mode_fr_CH_keyboard" msgid="6742806653181621228">"Tastatură franceză (Elveţia)"</string>
+    <string name="subtype_mode_iw_keyboard" msgid="1787536828253289950">"Tastatură ebraică"</string>
     <string name="subtype_mode_it_keyboard" msgid="4934199655425394484">"Tastatură italiană"</string>
     <string name="subtype_mode_nb_keyboard" msgid="1175783216100212360">"Tastatură norvegiană"</string>
     <string name="subtype_mode_nl_keyboard" msgid="5090278083256037936">"Tastatură olandeză"</string>
diff --git a/java/res/values-ru/strings.xml b/java/res/values-ru/strings.xml
index f8fdab0..2e8577e 100644
--- a/java/res/values-ru/strings.xml
+++ b/java/res/values-ru/strings.xml
@@ -27,7 +27,9 @@
     <string name="sound_on_keypress" msgid="6093592297198243644">"Звук клавиш"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"Увеличение нажатых"</string>
     <string name="general_category" msgid="1859088467017573195">"Общие"</string>
-    <string name="prediction_category" msgid="6361242011806282176">"Коррекция текста"</string>
+    <string name="correction_category" msgid="2236750915056607613">"Исправление текста"</string>
+    <string name="ngram_category" msgid="5337109164339320257">"Подсказки, основанные на предыдущих словах"</string>
+    <string name="misc_category" msgid="6894192814868233453">"Другие варианты"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Заглавные автоматически"</string>
     <string name="quick_fixes" msgid="5353213327680897927">"Быстрое исправление"</string>
     <string name="quick_fixes_summary" msgid="3405028402510332373">"Исправлять распространенные опечатки"</string>
@@ -36,6 +38,7 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Всегда показывать"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"Показать вертикально"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Всегда скрывать"</string>
+    <string name="prefs_use_spacebar_language_switch" msgid="8828538114550634449">"Пробел меняет язык"</string>
     <string name="prefs_settings_key" msgid="4623341240804046498">"Кнопка настроек"</string>
     <string name="settings_key_mode_auto_name" msgid="2993460277873684680">"Автоматически"</string>
     <string name="settings_key_mode_always_show_name" msgid="3047567041784760575">"Всегда показывать"</string>
@@ -45,8 +48,10 @@
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Откл."</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Умеренное"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"Активное"</string>
-    <string name="bigram_suggestion" msgid="1323347224043514969">"Биграммные подсказки"</string>
+    <string name="bigram_suggestion" msgid="2636414079905220518">"Биграммные подсказки"</string>
     <string name="bigram_suggestion_summary" msgid="4383845146070101531">"Используйте предыдущее слово, чтобы исправить подсказку"</string>
+    <string name="bigram_prediction" msgid="8914273444762259739">"Биграммный прогноз"</string>
+    <string name="bigram_prediction_summary" msgid="1747261921174300098">"Использовать предыдущее слово для прогнозирования"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g>: сохранено"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Поиск"</string>
     <string name="label_next_key" msgid="362972844525672568">"Далее"</string>
@@ -56,18 +61,6 @@
     <string name="label_more_key" msgid="3760239494604948502">"Ещё"</string>
     <string name="label_pause_key" msgid="181098308428035340">"Приостановить"</string>
     <string name="label_wait_key" msgid="6402152600878093134">"Подождите"</string>
-    <string name="description_delete_key" msgid="5586406298531883960">"Клавиша удаления"</string>
-    <string name="description_return_key" msgid="8750044000806461678">"Клавиша \"Ввод\""</string>
-    <string name="description_settings_key" msgid="7484527796782969219">"Клавиша настроек"</string>
-    <string name="description_shift_key" msgid="346906866277787836">"Клавиша верхнего регистра"</string>
-    <string name="description_space_key" msgid="8512130111575878517">"Клавиша \"Пробел\""</string>
-    <string name="description_switch_alpha_symbol_key" msgid="4537975384274405537">"Клавиша символов"</string>
-    <string name="description_tab_key" msgid="828186583738307137">"Клавиша табуляции"</string>
-    <string name="description_voice_key" msgid="3057731675774652754">"Клавиша голосового ввода"</string>
-    <string name="description_symbols_on" msgid="2994366855822840559">"Клавиши символов выключены"</string>
-    <string name="description_symbols_off" msgid="3209578267079515136">"Клавиши символов включены"</string>
-    <string name="description_shift_on" msgid="6983188949895971587">"Верхний регистр включен"</string>
-    <string name="description_shift_off" msgid="8553265474523069034">"Верхний регистр выключен"</string>
     <string name="voice_warning_title" msgid="4419354150908395008">"Голосовой ввод"</string>
     <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"В настоящее время функция голосового ввода не поддерживает ваш язык, но вы можете пользоваться ей на английском."</string>
     <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"Голосовой ввод использует алгоритмы распознавания речи Google. Действует "<a href="http://m.google.com/privacy">"политика конфиденциальности для мобильных устройств"</a>"."</string>
@@ -106,6 +99,7 @@
     <string name="prefs_enable_recorrection_summary" msgid="5082041365862396329">"Нажмите на слово, чтобы исправить его (при наличии подсказок)"</string>
     <string name="keyboard_layout" msgid="437433231038683666">"Вид клавиатуры"</string>
     <string name="subtype_mode_cs_keyboard" msgid="1141718931112377586">"Клавиатура: чешская"</string>
+    <string name="subtype_mode_ar_keyboard" msgid="2655338636329774995">"Арабская клавиатура"</string>
     <string name="subtype_mode_da_keyboard" msgid="1243570804427922104">"Клавиатура: датская"</string>
     <string name="subtype_mode_de_keyboard" msgid="1990979135959462145">"Клавиатура: немецкая"</string>
     <string name="subtype_mode_en_GB_keyboard" msgid="7945856548410373708">"Клавиатура: английская (Великобритания)"</string>
@@ -115,6 +109,7 @@
     <string name="subtype_mode_fr_keyboard" msgid="8016515336759761014">"Клавиатура: французская"</string>
     <string name="subtype_mode_fr_CA_keyboard" msgid="2628517247158376263">"Клавиатура: французская"</string>
     <string name="subtype_mode_fr_CH_keyboard" msgid="6742806653181621228">"Клавиатура: французская (Швейцария)"</string>
+    <string name="subtype_mode_iw_keyboard" msgid="1787536828253289950">"Клавиатура на иврите"</string>
     <string name="subtype_mode_it_keyboard" msgid="4934199655425394484">"Клавиатура: итальянская"</string>
     <string name="subtype_mode_nb_keyboard" msgid="1175783216100212360">"Клавиатура: норвежская"</string>
     <string name="subtype_mode_nl_keyboard" msgid="5090278083256037936">"Клавиатура: голландская"</string>
diff --git a/java/res/values-sk/strings.xml b/java/res/values-sk/strings.xml
index 569f273..eeefa82 100644
--- a/java/res/values-sk/strings.xml
+++ b/java/res/values-sk/strings.xml
@@ -27,7 +27,9 @@
     <string name="sound_on_keypress" msgid="6093592297198243644">"Zvuk pri stlačení klávesu"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"Zobraziť znaky pri stlačení klávesu"</string>
     <string name="general_category" msgid="1859088467017573195">"Všeobecné"</string>
-    <string name="prediction_category" msgid="6361242011806282176">"Oprava textu"</string>
+    <string name="correction_category" msgid="2236750915056607613">"Oprava textu"</string>
+    <string name="ngram_category" msgid="5337109164339320257">"Návrhy na základe predchádzajúcich slov"</string>
+    <string name="misc_category" msgid="6894192814868233453">"Ďalšie možnosti"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Veľké písmená automaticky"</string>
     <string name="quick_fixes" msgid="5353213327680897927">"Rýchle opravy"</string>
     <string name="quick_fixes_summary" msgid="3405028402510332373">"Opravuje najčastejšie chyby pri písaní"</string>
@@ -36,6 +38,7 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Vždy zobrazovať"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"Zobraziť v režime na výšku"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Vždy skrývať"</string>
+    <string name="prefs_use_spacebar_language_switch" msgid="8828538114550634449">"Použite medzerník na prepínanie medzi jazykmi"</string>
     <string name="prefs_settings_key" msgid="4623341240804046498">"Zobraziť kláves Nastavenia"</string>
     <string name="settings_key_mode_auto_name" msgid="2993460277873684680">"Automaticky"</string>
     <string name="settings_key_mode_always_show_name" msgid="3047567041784760575">"Vždy zobrazovať"</string>
@@ -45,8 +48,10 @@
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Vypnuté"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Mierne"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"Agresívne"</string>
-    <string name="bigram_suggestion" msgid="1323347224043514969">"Návrh Bigram"</string>
+    <string name="bigram_suggestion" msgid="2636414079905220518">"Návrhy Bigram"</string>
     <string name="bigram_suggestion_summary" msgid="4383845146070101531">"Na zlepšenie návrhu použiť predchádzajúce slovo"</string>
+    <string name="bigram_prediction" msgid="8914273444762259739">"Odhady Bigram"</string>
+    <string name="bigram_prediction_summary" msgid="1747261921174300098">"Použiť predchádzajúce slovo aj pre predpoveď"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : Uložené"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Hľadať"</string>
     <string name="label_next_key" msgid="362972844525672568">"Ďalej"</string>
@@ -56,18 +61,6 @@
     <string name="label_more_key" msgid="3760239494604948502">"Viac"</string>
     <string name="label_pause_key" msgid="181098308428035340">"Pozastaviť"</string>
     <string name="label_wait_key" msgid="6402152600878093134">"Čakajte"</string>
-    <string name="description_delete_key" msgid="5586406298531883960">"Delete"</string>
-    <string name="description_return_key" msgid="8750044000806461678">"Return"</string>
-    <string name="description_settings_key" msgid="7484527796782969219">"Nastavenia"</string>
-    <string name="description_shift_key" msgid="346906866277787836">"Shift"</string>
-    <string name="description_space_key" msgid="8512130111575878517">"Medzera"</string>
-    <string name="description_switch_alpha_symbol_key" msgid="4537975384274405537">"Symboly"</string>
-    <string name="description_tab_key" msgid="828186583738307137">"Tab"</string>
-    <string name="description_voice_key" msgid="3057731675774652754">"Hlasový vstup"</string>
-    <string name="description_symbols_on" msgid="2994366855822840559">"Symboly zapnuté"</string>
-    <string name="description_symbols_off" msgid="3209578267079515136">"Symboly vypnuté"</string>
-    <string name="description_shift_on" msgid="6983188949895971587">"Shift zapnutý"</string>
-    <string name="description_shift_off" msgid="8553265474523069034">"Shift vypnutý"</string>
     <string name="voice_warning_title" msgid="4419354150908395008">"Hlasový vstup"</string>
     <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"Pre váš jazyk aktuálne nie je hlasový vstup podporovaný, ale funguje v angličtine."</string>
     <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"Hlasový vstup používa rozpoznávanie hlasu Google. Na používanie hlasového vstupu sa vzťahujú "<a href="http://m.google.com/privacy">"Pravidlá ochrany osobných údajov pre mobilné služby"</a>"."</string>
@@ -106,6 +99,7 @@
     <string name="prefs_enable_recorrection_summary" msgid="5082041365862396329">"Dotykom zadaných slov tieto slová opravíte, musia však byť viditeľné návrhy"</string>
     <string name="keyboard_layout" msgid="437433231038683666">"Motív klávesnice"</string>
     <string name="subtype_mode_cs_keyboard" msgid="1141718931112377586">"klávesnica – čeština"</string>
+    <string name="subtype_mode_ar_keyboard" msgid="2655338636329774995">"klávesnica – arabčina"</string>
     <string name="subtype_mode_da_keyboard" msgid="1243570804427922104">"klávesnica – dánčina"</string>
     <string name="subtype_mode_de_keyboard" msgid="1990979135959462145">"klávesnica – nemčina"</string>
     <string name="subtype_mode_en_GB_keyboard" msgid="7945856548410373708">"klávesnica – angličtina (br.)"</string>
@@ -115,6 +109,7 @@
     <string name="subtype_mode_fr_keyboard" msgid="8016515336759761014">"klávesnica – francúzština"</string>
     <string name="subtype_mode_fr_CA_keyboard" msgid="2628517247158376263">"klávesnica – francúzština (Kanada)"</string>
     <string name="subtype_mode_fr_CH_keyboard" msgid="6742806653181621228">"klávesnica – francúzština (Švajč.)"</string>
+    <string name="subtype_mode_iw_keyboard" msgid="1787536828253289950">"klávesnica – hebrejčina"</string>
     <string name="subtype_mode_it_keyboard" msgid="4934199655425394484">"klávesnica – taliančina"</string>
     <string name="subtype_mode_nb_keyboard" msgid="1175783216100212360">"klávesnica – nórčina"</string>
     <string name="subtype_mode_nl_keyboard" msgid="5090278083256037936">"klávesnica – holandčina"</string>
diff --git a/java/res/values-sl/strings.xml b/java/res/values-sl/strings.xml
index 715e6c5..91f9036 100644
--- a/java/res/values-sl/strings.xml
+++ b/java/res/values-sl/strings.xml
@@ -27,7 +27,9 @@
     <string name="sound_on_keypress" msgid="6093592297198243644">"Zvok ob pritisku tipke"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"Pojavno okno ob pritisku tipke"</string>
     <string name="general_category" msgid="1859088467017573195">"Splošno"</string>
-    <string name="prediction_category" msgid="6361242011806282176">"Popravek besedila"</string>
+    <string name="correction_category" msgid="2236750915056607613">"Popravek besedila"</string>
+    <string name="ngram_category" msgid="5337109164339320257">"Predlogi, ki temeljijo na prejšnjih besedah"</string>
+    <string name="misc_category" msgid="6894192814868233453">"Druge možnosti"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Samodejne velike začetnice"</string>
     <string name="quick_fixes" msgid="5353213327680897927">"Hitri popravki"</string>
     <string name="quick_fixes_summary" msgid="3405028402510332373">"Popravi pogoste tipkarske napake"</string>
@@ -36,6 +38,7 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Vedno pokaži"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"Pokaži v pokončnem načinu"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Vedno skrij"</string>
+    <string name="prefs_use_spacebar_language_switch" msgid="8828538114550634449">"Preklopite med jeziki s preslednico"</string>
     <string name="prefs_settings_key" msgid="4623341240804046498">"Pokaži tipko za nastavitve"</string>
     <string name="settings_key_mode_auto_name" msgid="2993460277873684680">"Samodejno"</string>
     <string name="settings_key_mode_always_show_name" msgid="3047567041784760575">"Vedno pokaži"</string>
@@ -45,8 +48,10 @@
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Izklopljeno"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Zmerno"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"Agresivno"</string>
-    <string name="bigram_suggestion" msgid="1323347224043514969">"Bigramni predlogi"</string>
+    <string name="bigram_suggestion" msgid="2636414079905220518">"Bigramni predlogi"</string>
     <string name="bigram_suggestion_summary" msgid="4383845146070101531">"Predlog izboljšaj s prejšnjo besedo"</string>
+    <string name="bigram_prediction" msgid="8914273444762259739">"Bigramsko predvidevanje"</string>
+    <string name="bigram_prediction_summary" msgid="1747261921174300098">"Uporabi prejšnjo besedo tudi za predvidevanje"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g>: shranjeno"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Pojdi"</string>
     <string name="label_next_key" msgid="362972844525672568">"Naprej"</string>
@@ -56,18 +61,6 @@
     <string name="label_more_key" msgid="3760239494604948502">"Več"</string>
     <string name="label_pause_key" msgid="181098308428035340">"Premor"</string>
     <string name="label_wait_key" msgid="6402152600878093134">"Čakaj"</string>
-    <string name="description_delete_key" msgid="5586406298531883960">"Izbriši"</string>
-    <string name="description_return_key" msgid="8750044000806461678">"Vračalka"</string>
-    <string name="description_settings_key" msgid="7484527796782969219">"Nastavitve"</string>
-    <string name="description_shift_key" msgid="346906866277787836">"Dvigalka"</string>
-    <string name="description_space_key" msgid="8512130111575878517">"Preslednica"</string>
-    <string name="description_switch_alpha_symbol_key" msgid="4537975384274405537">"Znaki"</string>
-    <string name="description_tab_key" msgid="828186583738307137">"Tabulatorka"</string>
-    <string name="description_voice_key" msgid="3057731675774652754">"Glasovni vnos"</string>
-    <string name="description_symbols_on" msgid="2994366855822840559">"Znaki vklopljeni"</string>
-    <string name="description_symbols_off" msgid="3209578267079515136">"Znaki izklopljeni"</string>
-    <string name="description_shift_on" msgid="6983188949895971587">"Dvigalka vklopljena"</string>
-    <string name="description_shift_off" msgid="8553265474523069034">"Dvigalka izklopljena"</string>
     <string name="voice_warning_title" msgid="4419354150908395008">"Glasovni vnos"</string>
     <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"Glasovni vnos trenutno ni podprt v vašem jeziku, deluje pa v angleščini."</string>
     <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"Glasovni vnos uporablja Googlovo prepoznavanje govora. Zanj velja "<a href="http://m.google.com/privacy">"pravilnik o zasebnosti za mobilne naprave"</a>"."</string>
@@ -106,6 +99,7 @@
     <string name="prefs_enable_recorrection_summary" msgid="5082041365862396329">"Dotaknite se vnesenih besed in jih popravite, samo ko so predlogi vidni"</string>
     <string name="keyboard_layout" msgid="437433231038683666">"Tema tipkovnice"</string>
     <string name="subtype_mode_cs_keyboard" msgid="1141718931112377586">"Češka tipkovnica"</string>
+    <string name="subtype_mode_ar_keyboard" msgid="2655338636329774995">"Arabska tipkovnica"</string>
     <string name="subtype_mode_da_keyboard" msgid="1243570804427922104">"Danska tipkovnica"</string>
     <string name="subtype_mode_de_keyboard" msgid="1990979135959462145">"Nemška tipkovnica"</string>
     <string name="subtype_mode_en_GB_keyboard" msgid="7945856548410373708">"Tipkovnica za britansko angleščino"</string>
@@ -115,6 +109,7 @@
     <string name="subtype_mode_fr_keyboard" msgid="8016515336759761014">"Francoska tipkovnica"</string>
     <string name="subtype_mode_fr_CA_keyboard" msgid="2628517247158376263">"Tipkovnica za kanadsko francoščino"</string>
     <string name="subtype_mode_fr_CH_keyboard" msgid="6742806653181621228">"Tipkovnica za švicarsko francoščino"</string>
+    <string name="subtype_mode_iw_keyboard" msgid="1787536828253289950">"Hebrejska tipkovnica"</string>
     <string name="subtype_mode_it_keyboard" msgid="4934199655425394484">"Italijanska tipkovnica"</string>
     <string name="subtype_mode_nb_keyboard" msgid="1175783216100212360">"Norveška tipkovnica"</string>
     <string name="subtype_mode_nl_keyboard" msgid="5090278083256037936">"Nizozemska tipkovnica"</string>
diff --git a/java/res/values-sr/strings.xml b/java/res/values-sr/strings.xml
index 1157327..b20f1df 100644
--- a/java/res/values-sr/strings.xml
+++ b/java/res/values-sr/strings.xml
@@ -27,7 +27,9 @@
     <string name="sound_on_keypress" msgid="6093592297198243644">"Звук на притисак тастера"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"Искачући прозор приликом притиска тастера"</string>
     <string name="general_category" msgid="1859088467017573195">"Опште"</string>
-    <string name="prediction_category" msgid="6361242011806282176">"Исправљање текста"</string>
+    <string name="correction_category" msgid="2236750915056607613">"Исправљање текста"</string>
+    <string name="ngram_category" msgid="5337109164339320257">"Предлози на основу претходних речи"</string>
+    <string name="misc_category" msgid="6894192814868233453">"Друге опције:"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Аутоматски унос великих слова"</string>
     <string name="quick_fixes" msgid="5353213327680897927">"Брзе исправке"</string>
     <string name="quick_fixes_summary" msgid="3405028402510332373">"Исправља честе грешке у куцању"</string>
@@ -36,6 +38,7 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Увек прикажи"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"Прикажи у усправном режиму"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Увек сакриј"</string>
+    <string name="prefs_use_spacebar_language_switch" msgid="8828538114550634449">"Користи размак за избор језика"</string>
     <string name="prefs_settings_key" msgid="4623341240804046498">"Прикажи тастер за подешавања"</string>
     <string name="settings_key_mode_auto_name" msgid="2993460277873684680">"Аутоматски"</string>
     <string name="settings_key_mode_always_show_name" msgid="3047567041784760575">"Увек прикажи"</string>
@@ -45,8 +48,10 @@
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Искључи"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Умерено"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"Агресивно"</string>
-    <string name="bigram_suggestion" msgid="1323347224043514969">"Bigram предлози"</string>
+    <string name="bigram_suggestion" msgid="2636414079905220518">"Bigram предлози"</string>
     <string name="bigram_suggestion_summary" msgid="4383845146070101531">"Користи претходну реч за побољшање предлога"</string>
+    <string name="bigram_prediction" msgid="8914273444762259739">"Bigram предвиђање"</string>
+    <string name="bigram_prediction_summary" msgid="1747261921174300098">"Користи претходну реч и за предвиђање"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : Сачувано"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Иди"</string>
     <string name="label_next_key" msgid="362972844525672568">"Следеће"</string>
@@ -56,18 +61,6 @@
     <string name="label_more_key" msgid="3760239494604948502">"Још"</string>
     <string name="label_pause_key" msgid="181098308428035340">"Паузирај"</string>
     <string name="label_wait_key" msgid="6402152600878093134">"Сачекајте"</string>
-    <string name="description_delete_key" msgid="5586406298531883960">"Delete"</string>
-    <string name="description_return_key" msgid="8750044000806461678">"Return"</string>
-    <string name="description_settings_key" msgid="7484527796782969219">"Подешавања"</string>
-    <string name="description_shift_key" msgid="346906866277787836">"Shift"</string>
-    <string name="description_space_key" msgid="8512130111575878517">"Размак"</string>
-    <string name="description_switch_alpha_symbol_key" msgid="4537975384274405537">"Симболи"</string>
-    <string name="description_tab_key" msgid="828186583738307137">"Tab"</string>
-    <string name="description_voice_key" msgid="3057731675774652754">"Гласовни унос"</string>
-    <string name="description_symbols_on" msgid="2994366855822840559">"Симболи су укључени"</string>
-    <string name="description_symbols_off" msgid="3209578267079515136">"Симболи су искључени"</string>
-    <string name="description_shift_on" msgid="6983188949895971587">"Shift је укључен"</string>
-    <string name="description_shift_off" msgid="8553265474523069034">"Shift је искључен"</string>
     <string name="voice_warning_title" msgid="4419354150908395008">"Гласовни унос"</string>
     <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"Гласовни унос тренутно није подржан за ваш језик, али функционише на енглеском."</string>
     <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"Гласовни унос користи Google-ову функцију за препознавање гласа. Примењује се "<a href="http://m.google.com/privacy">"политика приватности за мобилне уређаје"</a>"."</string>
@@ -106,6 +99,7 @@
     <string name="prefs_enable_recorrection_summary" msgid="5082041365862396329">"Додирните унете речи да бисте их исправили само када су предлози видљиви"</string>
     <string name="keyboard_layout" msgid="437433231038683666">"Тема тастатуре"</string>
     <string name="subtype_mode_cs_keyboard" msgid="1141718931112377586">"Језик тастатуре: чешки"</string>
+    <string name="subtype_mode_ar_keyboard" msgid="2655338636329774995">"Језик тастатуре: арапски"</string>
     <string name="subtype_mode_da_keyboard" msgid="1243570804427922104">"Језик тастатуре: дански"</string>
     <string name="subtype_mode_de_keyboard" msgid="1990979135959462145">"Језик тастатуре: немачки"</string>
     <string name="subtype_mode_en_GB_keyboard" msgid="7945856548410373708">"Језик тастатуре: енглески (УК)"</string>
@@ -115,6 +109,7 @@
     <string name="subtype_mode_fr_keyboard" msgid="8016515336759761014">"Језик тастатуре: француски"</string>
     <string name="subtype_mode_fr_CA_keyboard" msgid="2628517247158376263">"Језик тастатуре: француски (Канада)"</string>
     <string name="subtype_mode_fr_CH_keyboard" msgid="6742806653181621228">"Језик тастатуре: француски (Швајц.)"</string>
+    <string name="subtype_mode_iw_keyboard" msgid="1787536828253289950">"Језик тастатуре: хебрејски"</string>
     <string name="subtype_mode_it_keyboard" msgid="4934199655425394484">"Језик тастатуре: италијански"</string>
     <string name="subtype_mode_nb_keyboard" msgid="1175783216100212360">"Језик тастатуре: норвешки"</string>
     <string name="subtype_mode_nl_keyboard" msgid="5090278083256037936">"Језик тастатуре: холандски"</string>
diff --git a/java/res/values-sv/strings.xml b/java/res/values-sv/strings.xml
index b8c62f4..2d35fd8 100644
--- a/java/res/values-sv/strings.xml
+++ b/java/res/values-sv/strings.xml
@@ -27,7 +27,9 @@
     <string name="sound_on_keypress" msgid="6093592297198243644">"Knappljud"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"Popup vid knapptryck"</string>
     <string name="general_category" msgid="1859088467017573195">"Allmänt"</string>
-    <string name="prediction_category" msgid="6361242011806282176">"Textkorrigering"</string>
+    <string name="correction_category" msgid="2236750915056607613">"Textkorrigering"</string>
+    <string name="ngram_category" msgid="5337109164339320257">"Förslag baserade på tidigare ord"</string>
+    <string name="misc_category" msgid="6894192814868233453">"Andra alternativ"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Automatiska versaler"</string>
     <string name="quick_fixes" msgid="5353213327680897927">"Snabba lösningar"</string>
     <string name="quick_fixes_summary" msgid="3405028402510332373">"Åtgärdar automatiskt vanliga misstag"</string>
@@ -36,6 +38,7 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Visa alltid"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"Visa stående"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Dölj alltid"</string>
+    <string name="prefs_use_spacebar_language_switch" msgid="8828538114550634449">"Byt språk m. mellanslag"</string>
     <string name="prefs_settings_key" msgid="4623341240804046498">"Visa inställningsknapp"</string>
     <string name="settings_key_mode_auto_name" msgid="2993460277873684680">"Automatiskt"</string>
     <string name="settings_key_mode_always_show_name" msgid="3047567041784760575">"Visa alltid"</string>
@@ -45,8 +48,10 @@
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Av"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Måttlig"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"Aggressiv"</string>
-    <string name="bigram_suggestion" msgid="1323347224043514969">"Bigramförslag"</string>
+    <string name="bigram_suggestion" msgid="2636414079905220518">"Bigramförslag"</string>
     <string name="bigram_suggestion_summary" msgid="4383845146070101531">"Förbättra förslaget med föregående ord"</string>
+    <string name="bigram_prediction" msgid="8914273444762259739">"Bigramförslag"</string>
+    <string name="bigram_prediction_summary" msgid="1747261921174300098">"Använd även föregående ord för att ge förslag"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g>: sparat"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Kör"</string>
     <string name="label_next_key" msgid="362972844525672568">"Nästa"</string>
@@ -56,18 +61,6 @@
     <string name="label_more_key" msgid="3760239494604948502">"Mer"</string>
     <string name="label_pause_key" msgid="181098308428035340">"Pausa"</string>
     <string name="label_wait_key" msgid="6402152600878093134">"Vänta"</string>
-    <string name="description_delete_key" msgid="5586406298531883960">"Ta bort"</string>
-    <string name="description_return_key" msgid="8750044000806461678">"Retur"</string>
-    <string name="description_settings_key" msgid="7484527796782969219">"Inställningar"</string>
-    <string name="description_shift_key" msgid="346906866277787836">"Skift"</string>
-    <string name="description_space_key" msgid="8512130111575878517">"Blanksteg"</string>
-    <string name="description_switch_alpha_symbol_key" msgid="4537975384274405537">"Symboler"</string>
-    <string name="description_tab_key" msgid="828186583738307137">"Tabb"</string>
-    <string name="description_voice_key" msgid="3057731675774652754">"Röstinmatning"</string>
-    <string name="description_symbols_on" msgid="2994366855822840559">"Aktivera symboler"</string>
-    <string name="description_symbols_off" msgid="3209578267079515136">"Inaktivera symboler"</string>
-    <string name="description_shift_on" msgid="6983188949895971587">"Aktivera Skift"</string>
-    <string name="description_shift_off" msgid="8553265474523069034">"Inaktivera Skift"</string>
     <string name="voice_warning_title" msgid="4419354150908395008">"Röstindata"</string>
     <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"Röstindata stöds inte på ditt språk än, men tjänsten fungerar på engelska."</string>
     <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"Röstinmatning använder sig av Googles tjänst för taligenkänning. "<a href="http://m.google.com/privacy">"Sekretesspolicyn för mobila enheter"</a>" gäller."</string>
@@ -106,6 +99,7 @@
     <string name="prefs_enable_recorrection_summary" msgid="5082041365862396329">"Tryck på skrivna ord om du vill korrigera dem, endast när förslag visas"</string>
     <string name="keyboard_layout" msgid="437433231038683666">"Tangentbordstema"</string>
     <string name="subtype_mode_cs_keyboard" msgid="1141718931112377586">"Tjeckiskt tangentbord"</string>
+    <string name="subtype_mode_ar_keyboard" msgid="2655338636329774995">"Arabiskt tangentbord"</string>
     <string name="subtype_mode_da_keyboard" msgid="1243570804427922104">"Danskt tangentbord"</string>
     <string name="subtype_mode_de_keyboard" msgid="1990979135959462145">"Tyskt tangentbord"</string>
     <string name="subtype_mode_en_GB_keyboard" msgid="7945856548410373708">"Engelskt tangentbord (Storbrit.)"</string>
@@ -115,6 +109,7 @@
     <string name="subtype_mode_fr_keyboard" msgid="8016515336759761014">"Franskt tangentbord"</string>
     <string name="subtype_mode_fr_CA_keyboard" msgid="2628517247158376263">"Franskt tangentbord (Kanada)"</string>
     <string name="subtype_mode_fr_CH_keyboard" msgid="6742806653181621228">"Franskt tangentbord (Schweiz)"</string>
+    <string name="subtype_mode_iw_keyboard" msgid="1787536828253289950">"Hebreiskt tangentbord"</string>
     <string name="subtype_mode_it_keyboard" msgid="4934199655425394484">"Italienskt tangentbord"</string>
     <string name="subtype_mode_nb_keyboard" msgid="1175783216100212360">"Norskt tangentbord"</string>
     <string name="subtype_mode_nl_keyboard" msgid="5090278083256037936">"Holländskt tangentbord"</string>
diff --git a/java/res/values-th/strings.xml b/java/res/values-th/strings.xml
index 836b987..cd7fe95 100644
--- a/java/res/values-th/strings.xml
+++ b/java/res/values-th/strings.xml
@@ -27,7 +27,9 @@
     <string name="sound_on_keypress" msgid="6093592297198243644">"ส่งเสียงเมื่อกดปุ่ม"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"ป๊อปอัปเมื่อกดแป้น"</string>
     <string name="general_category" msgid="1859088467017573195">"ทั่วไป"</string>
-    <string name="prediction_category" msgid="6361242011806282176">"การแก้ไขข้อความ"</string>
+    <string name="correction_category" msgid="2236750915056607613">"การแก้ไขข้อความ"</string>
+    <string name="ngram_category" msgid="5337109164339320257">"ข้อเสนอแนะตามคำก่อนหน้านี้"</string>
+    <string name="misc_category" msgid="6894192814868233453">"ตัวเลือกอื่นๆ"</string>
     <string name="auto_cap" msgid="1719746674854628252">"ปรับเป็นตัวพิมพ์ใหญ่อัตโนมัติ"</string>
     <string name="quick_fixes" msgid="5353213327680897927">"แก้ไขด่วน"</string>
     <string name="quick_fixes_summary" msgid="3405028402510332373">"แก้ไขข้อผิดพลาดในการพิมพ์ที่พบบ่อย"</string>
@@ -36,6 +38,7 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"แสดงทุกครั้ง"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"แสดงในโหมดแนวตั้ง"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"ซ่อนทุกครั้ง"</string>
+    <string name="prefs_use_spacebar_language_switch" msgid="8828538114550634449">"ใช้แป้น Spacebar เพื่อสลับภาษา"</string>
     <string name="prefs_settings_key" msgid="4623341240804046498">"แสดงแป้นการตั้งค่า"</string>
     <string name="settings_key_mode_auto_name" msgid="2993460277873684680">"อัตโนมัติ"</string>
     <string name="settings_key_mode_always_show_name" msgid="3047567041784760575">"แสดงตลอดเวลา"</string>
@@ -45,8 +48,10 @@
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"ปิด"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"ปานกลาง"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"เข้มงวด"</string>
-    <string name="bigram_suggestion" msgid="1323347224043514969">"คำแนะนำ Bigram"</string>
+    <string name="bigram_suggestion" msgid="2636414079905220518">"คำแนะนำ Bigram"</string>
     <string name="bigram_suggestion_summary" msgid="4383845146070101531">"ใช้คำก่อนหน้านี้เพื่อปรับปรุงคำแนะนำ"</string>
+    <string name="bigram_prediction" msgid="8914273444762259739">"การคาดคะเน Bigram"</string>
+    <string name="bigram_prediction_summary" msgid="1747261921174300098">"ใช้คำก่อนหน้านี้สำหรับการคาดคะเน"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : บันทึกแล้ว"</string>
     <string name="label_go_key" msgid="1635148082137219148">"ไป"</string>
     <string name="label_next_key" msgid="362972844525672568">"ถัดไป"</string>
@@ -56,18 +61,6 @@
     <string name="label_more_key" msgid="3760239494604948502">"เพิ่มเติม"</string>
     <string name="label_pause_key" msgid="181098308428035340">"หยุดชั่วคราว"</string>
     <string name="label_wait_key" msgid="6402152600878093134">"รอ"</string>
-    <string name="description_delete_key" msgid="5586406298531883960">"ลบ"</string>
-    <string name="description_return_key" msgid="8750044000806461678">"Return"</string>
-    <string name="description_settings_key" msgid="7484527796782969219">"การตั้งค่า"</string>
-    <string name="description_shift_key" msgid="346906866277787836">"Shift"</string>
-    <string name="description_space_key" msgid="8512130111575878517">"Space"</string>
-    <string name="description_switch_alpha_symbol_key" msgid="4537975384274405537">"สัญลักษณ์"</string>
-    <string name="description_tab_key" msgid="828186583738307137">"Tab"</string>
-    <string name="description_voice_key" msgid="3057731675774652754">"ป้อนข้อมูลด้วยเสียง"</string>
-    <string name="description_symbols_on" msgid="2994366855822840559">"สัญลักษณ์เปิดอยู่"</string>
-    <string name="description_symbols_off" msgid="3209578267079515136">"สัญลักษณ์ปิดอยู่"</string>
-    <string name="description_shift_on" msgid="6983188949895971587">"Shift เปิดอยู่"</string>
-    <string name="description_shift_off" msgid="8553265474523069034">"Shift ปิดอยู่"</string>
     <string name="voice_warning_title" msgid="4419354150908395008">"การป้อนข้อมูลด้วยเสียง"</string>
     <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"ขณะนี้การป้อนข้อมูลด้วยเสียงยังไม่ได้รับการสนับสนุนในภาษาของคุณ แต่ใช้ได้ในภาษาอังกฤษ"</string>
     <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"ป้อนข้อมูลด้วยเสียงใช้การจดจำคำพูดของ Google "<a href="http://m.google.com/privacy">" นโยบายส่วนบุคคลของมือถือ"</a>"มีผลบังคับใช้"</string>
@@ -106,6 +99,7 @@
     <string name="prefs_enable_recorrection_summary" msgid="5082041365862396329">"แตะคำที่ป้อนไว้เพื่อแก้ไข เฉพาะเมื่อเห็นข้อเสนอแนะเท่านั้น"</string>
     <string name="keyboard_layout" msgid="437433231038683666">"ชุดรูปแบบแป้นพิมพ์"</string>
     <string name="subtype_mode_cs_keyboard" msgid="1141718931112377586">"แป้นพิมพ์ภาษาเช็ก"</string>
+    <string name="subtype_mode_ar_keyboard" msgid="2655338636329774995">"แป้นพิมพ์ภาษาอาหรับ"</string>
     <string name="subtype_mode_da_keyboard" msgid="1243570804427922104">"แป้นพิมพ์ภาษาเดนมาร์ก"</string>
     <string name="subtype_mode_de_keyboard" msgid="1990979135959462145">"แป้นพิมพ์ภาษาเยอรมัน"</string>
     <string name="subtype_mode_en_GB_keyboard" msgid="7945856548410373708">"แป้นพิมพ์ภาษาอังกฤษ (สหราชอาณาจักร)"</string>
@@ -115,6 +109,7 @@
     <string name="subtype_mode_fr_keyboard" msgid="8016515336759761014">"แป้นพิมพ์ภาษาฝรั่งเศส"</string>
     <string name="subtype_mode_fr_CA_keyboard" msgid="2628517247158376263">"แป้นพิมพ์ภาษาฝรั่งเศส (แคนาดา)"</string>
     <string name="subtype_mode_fr_CH_keyboard" msgid="6742806653181621228">"แป้นพิมพ์ภาษาฝรั่งเศส (สวิตเซอร์แลนด์)"</string>
+    <string name="subtype_mode_iw_keyboard" msgid="1787536828253289950">"แป้นพิมพ์ภาษาฮิบรู"</string>
     <string name="subtype_mode_it_keyboard" msgid="4934199655425394484">"แป้นพิมพ์ภาษาอิตาลี"</string>
     <string name="subtype_mode_nb_keyboard" msgid="1175783216100212360">"แป้นพิมพ์ภาษานอร์เวย์"</string>
     <string name="subtype_mode_nl_keyboard" msgid="5090278083256037936">"แป้นพิมพ์ภาษาดัตช์"</string>
diff --git a/java/res/values-tl/strings.xml b/java/res/values-tl/strings.xml
index 386b1fd..da62eaf 100644
--- a/java/res/values-tl/strings.xml
+++ b/java/res/values-tl/strings.xml
@@ -27,7 +27,9 @@
     <string name="sound_on_keypress" msgid="6093592297198243644">"Tunog sa keypress"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"Popup sa keypress"</string>
     <string name="general_category" msgid="1859088467017573195">"Pangkalahatan"</string>
-    <string name="prediction_category" msgid="6361242011806282176">"Pagwawasto ng teksto"</string>
+    <string name="correction_category" msgid="2236750915056607613">"Pagwawasto ng teksto"</string>
+    <string name="ngram_category" msgid="5337109164339320257">"Mga suhestiyon batay sa mga nakaraang salita"</string>
+    <string name="misc_category" msgid="6894192814868233453">"Iba pang mga pagpipilian"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Auto-capitalization"</string>
     <string name="quick_fixes" msgid="5353213327680897927">"Mga mabilisang pagsasaayos"</string>
     <string name="quick_fixes_summary" msgid="3405028402510332373">"Itinatama ang mga karaniwang na-type na mali"</string>
@@ -36,6 +38,7 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Palaging ipakita"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"Ipakita sa portrait mode"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Palaging itago"</string>
+    <string name="prefs_use_spacebar_language_switch" msgid="8828538114550634449">"Gamitin ang panglipat ng wika sa spacebar"</string>
     <string name="prefs_settings_key" msgid="4623341240804046498">"Ipakita ang key ng mga setting"</string>
     <string name="settings_key_mode_auto_name" msgid="2993460277873684680">"Awtomatiko"</string>
     <string name="settings_key_mode_always_show_name" msgid="3047567041784760575">"Palaging ipakita"</string>
@@ -45,8 +48,10 @@
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Naka-off"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Modest"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"Agresibo"</string>
-    <string name="bigram_suggestion" msgid="1323347224043514969">"Mga Suhestiyon na Bigram"</string>
+    <string name="bigram_suggestion" msgid="2636414079905220518">"Mga bigram na suhestiyon"</string>
     <string name="bigram_suggestion_summary" msgid="4383845146070101531">"Gamitin ang nakaraang salita upang pahusayin ang suhestiyon"</string>
+    <string name="bigram_prediction" msgid="8914273444762259739">"Bigram na hula"</string>
+    <string name="bigram_prediction_summary" msgid="1747261921174300098">"Gamitin ang nakaraang salita para din sa hula"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : Na-save"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Punta"</string>
     <string name="label_next_key" msgid="362972844525672568">"Susunod"</string>
@@ -56,18 +61,6 @@
     <string name="label_more_key" msgid="3760239494604948502">"Higit pa"</string>
     <string name="label_pause_key" msgid="181098308428035340">"Pause"</string>
     <string name="label_wait_key" msgid="6402152600878093134">"Intay"</string>
-    <string name="description_delete_key" msgid="5586406298531883960">"Tanggalin"</string>
-    <string name="description_return_key" msgid="8750044000806461678">"Bumalik"</string>
-    <string name="description_settings_key" msgid="7484527796782969219">"Mga Setting"</string>
-    <string name="description_shift_key" msgid="346906866277787836">"Shift"</string>
-    <string name="description_space_key" msgid="8512130111575878517">"Puwang"</string>
-    <string name="description_switch_alpha_symbol_key" msgid="4537975384274405537">"Mga Simbolo"</string>
-    <string name="description_tab_key" msgid="828186583738307137">"Tab"</string>
-    <string name="description_voice_key" msgid="3057731675774652754">"Input ng Boses"</string>
-    <string name="description_symbols_on" msgid="2994366855822840559">"Naka-on ang mga simbolo"</string>
-    <string name="description_symbols_off" msgid="3209578267079515136">"Naka-off ang mga simbolo"</string>
-    <string name="description_shift_on" msgid="6983188949895971587">"Naka-on ang shift"</string>
-    <string name="description_shift_off" msgid="8553265474523069034">"Naka-off ang shift"</string>
     <string name="voice_warning_title" msgid="4419354150908395008">"Pag-input ng boses"</string>
     <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"Hindi kasalukuyang suportado ang pag-input ng boses para sa iyong wika, ngunit gumagana sa Ingles."</string>
     <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"Gumagamit ang pag-input ng boses ng speech recognition ng Google. Nalalapat "<a href="http://m.google.com/privacy">"Ang Patakaran sa Privacy ng Mobile"</a>"."</string>
@@ -106,6 +99,7 @@
     <string name="prefs_enable_recorrection_summary" msgid="5082041365862396329">"Pindutin ang mga inilagay na salita upang iwasto ang mga ito, kapag nakikita lang ang mga suhestiyon"</string>
     <string name="keyboard_layout" msgid="437433231038683666">"Tema ng Keyboard"</string>
     <string name="subtype_mode_cs_keyboard" msgid="1141718931112377586">"Czech na Keyboard"</string>
+    <string name="subtype_mode_ar_keyboard" msgid="2655338636329774995">"Arabic na Keyboard"</string>
     <string name="subtype_mode_da_keyboard" msgid="1243570804427922104">"Danish na Keyboard"</string>
     <string name="subtype_mode_de_keyboard" msgid="1990979135959462145">"German na Keyboard"</string>
     <string name="subtype_mode_en_GB_keyboard" msgid="7945856548410373708">"Ingles (UK) na Keyboard"</string>
@@ -115,6 +109,7 @@
     <string name="subtype_mode_fr_keyboard" msgid="8016515336759761014">"French na Keyboard"</string>
     <string name="subtype_mode_fr_CA_keyboard" msgid="2628517247158376263">"French (Canada) na Keyboard"</string>
     <string name="subtype_mode_fr_CH_keyboard" msgid="6742806653181621228">"French (Switzerland) na Keyboard"</string>
+    <string name="subtype_mode_iw_keyboard" msgid="1787536828253289950">"Hebrew na Keyboard"</string>
     <string name="subtype_mode_it_keyboard" msgid="4934199655425394484">"Italian na Keyboard"</string>
     <string name="subtype_mode_nb_keyboard" msgid="1175783216100212360">"Norwegian na Keyboard"</string>
     <string name="subtype_mode_nl_keyboard" msgid="5090278083256037936">"Dutch na Keyboard"</string>
diff --git a/java/res/values-tr/strings.xml b/java/res/values-tr/strings.xml
index 71a6215..2607ae2 100644
--- a/java/res/values-tr/strings.xml
+++ b/java/res/values-tr/strings.xml
@@ -27,7 +27,9 @@
     <string name="sound_on_keypress" msgid="6093592297198243644">"Tuşa basıldığında ses çıkar"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"Tuşa basıldığında pop-up aç"</string>
     <string name="general_category" msgid="1859088467017573195">"Genel"</string>
-    <string name="prediction_category" msgid="6361242011806282176">"Metin düzeltme"</string>
+    <string name="correction_category" msgid="2236750915056607613">"Metin düzeltme"</string>
+    <string name="ngram_category" msgid="5337109164339320257">"Önceki kelimelere dayalı öneriler"</string>
+    <string name="misc_category" msgid="6894192814868233453">"Diğer seçenekler"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Otomatik olarak büyük harf yap"</string>
     <string name="quick_fixes" msgid="5353213327680897927">"Hızlı onarımlar"</string>
     <string name="quick_fixes_summary" msgid="3405028402510332373">"Yaygın olarak yapılan yazım hatalarını düzeltir"</string>
@@ -36,6 +38,7 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Her zaman göster"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"Dikey modda göster"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Her zaman gizle"</string>
+    <string name="prefs_use_spacebar_language_switch" msgid="8828538114550634449">"Dil geçişi içn boşluk çubğn kullan"</string>
     <string name="prefs_settings_key" msgid="4623341240804046498">"Ayarları göster tuşu"</string>
     <string name="settings_key_mode_auto_name" msgid="2993460277873684680">"Otomatik"</string>
     <string name="settings_key_mode_always_show_name" msgid="3047567041784760575">"Her zaman göster"</string>
@@ -45,8 +48,10 @@
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Kapalı"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Ölçülü"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"Agresif"</string>
-    <string name="bigram_suggestion" msgid="1323347224043514969">"Bigram Önerileri"</string>
+    <string name="bigram_suggestion" msgid="2636414079905220518">"Bigram önerileri"</string>
     <string name="bigram_suggestion_summary" msgid="4383845146070101531">"Öneriyi geliştirmek için önceki kelimeyi kullanın"</string>
+    <string name="bigram_prediction" msgid="8914273444762259739">"Bigram tahmini"</string>
+    <string name="bigram_prediction_summary" msgid="1747261921174300098">"Önceki kelimeyi de tahmin için kullan"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : Kaydedildi"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Git"</string>
     <string name="label_next_key" msgid="362972844525672568">"İleri"</string>
@@ -56,18 +61,6 @@
     <string name="label_more_key" msgid="3760239494604948502">"Diğer"</string>
     <string name="label_pause_key" msgid="181098308428035340">"Durkl"</string>
     <string name="label_wait_key" msgid="6402152600878093134">"Bekle"</string>
-    <string name="description_delete_key" msgid="5586406298531883960">"Sil"</string>
-    <string name="description_return_key" msgid="8750044000806461678">"Return"</string>
-    <string name="description_settings_key" msgid="7484527796782969219">"Ayarlar"</string>
-    <string name="description_shift_key" msgid="346906866277787836">"Üst Karakter"</string>
-    <string name="description_space_key" msgid="8512130111575878517">"Boşluk"</string>
-    <string name="description_switch_alpha_symbol_key" msgid="4537975384274405537">"Simgeler"</string>
-    <string name="description_tab_key" msgid="828186583738307137">"Sekme"</string>
-    <string name="description_voice_key" msgid="3057731675774652754">"Ses Girişi"</string>
-    <string name="description_symbols_on" msgid="2994366855822840559">"Simgeler açık"</string>
-    <string name="description_symbols_off" msgid="3209578267079515136">"Simgeler kapalı"</string>
-    <string name="description_shift_on" msgid="6983188949895971587">"Üst Karakter açık"</string>
-    <string name="description_shift_off" msgid="8553265474523069034">"Üst Karakter kapalı"</string>
     <string name="voice_warning_title" msgid="4419354150908395008">"Ses girişi"</string>
     <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"Ses girişi, şu anda sizin diliniz için desteklenmiyor ama İngilizce dilinde kullanılabilir."</string>
     <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"Ses girişi Google\'ın konuşma tanıma işlevini kullanır. "<a href="http://m.google.com/privacy">" Mobil Gizlilik Politikası"</a>" geçerlidir."</string>
@@ -106,6 +99,7 @@
     <string name="prefs_enable_recorrection_summary" msgid="5082041365862396329">"Yalnızca öneriler görünür olduğunda, düzeltmek için girilen kelimelere dokunun"</string>
     <string name="keyboard_layout" msgid="437433231038683666">"Klavye Teması"</string>
     <string name="subtype_mode_cs_keyboard" msgid="1141718931112377586">"Çekçe Klavye"</string>
+    <string name="subtype_mode_ar_keyboard" msgid="2655338636329774995">"Arapça Klavye"</string>
     <string name="subtype_mode_da_keyboard" msgid="1243570804427922104">"Danca Klavye"</string>
     <string name="subtype_mode_de_keyboard" msgid="1990979135959462145">"Almanca Klavye"</string>
     <string name="subtype_mode_en_GB_keyboard" msgid="7945856548410373708">"İngilizce (İngiltere) Klavye"</string>
@@ -115,6 +109,7 @@
     <string name="subtype_mode_fr_keyboard" msgid="8016515336759761014">"Fransızca Klavye"</string>
     <string name="subtype_mode_fr_CA_keyboard" msgid="2628517247158376263">"Fransızca (Kanada) Klavye"</string>
     <string name="subtype_mode_fr_CH_keyboard" msgid="6742806653181621228">"Fransızca (İsviçre) Klavye"</string>
+    <string name="subtype_mode_iw_keyboard" msgid="1787536828253289950">"İbranice Klavye"</string>
     <string name="subtype_mode_it_keyboard" msgid="4934199655425394484">"İtalyanca Klavye"</string>
     <string name="subtype_mode_nb_keyboard" msgid="1175783216100212360">"Norveççe Klavye"</string>
     <string name="subtype_mode_nl_keyboard" msgid="5090278083256037936">"Felemenkçe Klavye"</string>
diff --git a/java/res/values-uk/strings.xml b/java/res/values-uk/strings.xml
index 2690632..a2f589d 100644
--- a/java/res/values-uk/strings.xml
+++ b/java/res/values-uk/strings.xml
@@ -27,7 +27,9 @@
     <string name="sound_on_keypress" msgid="6093592297198243644">"Звук при натиску клав."</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"Сплив. при нат.клав."</string>
     <string name="general_category" msgid="1859088467017573195">"Загальні"</string>
-    <string name="prediction_category" msgid="6361242011806282176">"Виправлення тексту"</string>
+    <string name="correction_category" msgid="2236750915056607613">"Виправлення тексту"</string>
+    <string name="ngram_category" msgid="5337109164339320257">"Пропозиції на основі попередніх слів"</string>
+    <string name="misc_category" msgid="6894192814868233453">"Інші опції"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Авто викор. вел. літер"</string>
     <string name="quick_fixes" msgid="5353213327680897927">"Шв. виправлення"</string>
     <string name="quick_fixes_summary" msgid="3405028402510332373">"Виправляє поширені помилки"</string>
@@ -36,6 +38,7 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Завжди показувати"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"Показувати в книжковому режимі"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Завжди ховати"</string>
+    <string name="prefs_use_spacebar_language_switch" msgid="8828538114550634449">"Використ. зміну мови пробілом"</string>
     <string name="prefs_settings_key" msgid="4623341240804046498">"Показ. клав. налашт."</string>
     <string name="settings_key_mode_auto_name" msgid="2993460277873684680">"Автоматично"</string>
     <string name="settings_key_mode_always_show_name" msgid="3047567041784760575">"Завжди показ."</string>
@@ -45,8 +48,10 @@
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Вимк."</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Середнє"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"Повне"</string>
-    <string name="bigram_suggestion" msgid="1323347224043514969">"Двобуквені пропозиції"</string>
+    <string name="bigram_suggestion" msgid="2636414079905220518">"Пропозиції з двох слів"</string>
     <string name="bigram_suggestion_summary" msgid="4383845146070101531">"Викор. попер. слово для покращ. пропозиції"</string>
+    <string name="bigram_prediction" msgid="8914273444762259739">"Передбачений запит  із двох слів"</string>
+    <string name="bigram_prediction_summary" msgid="1747261921174300098">"Використовувати попереднє слово також як передбачений запит"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : збережено"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Іти"</string>
     <string name="label_next_key" msgid="362972844525672568">"Далі"</string>
@@ -56,18 +61,6 @@
     <string name="label_more_key" msgid="3760239494604948502">"Більше"</string>
     <string name="label_pause_key" msgid="181098308428035340">"Пауза"</string>
     <string name="label_wait_key" msgid="6402152600878093134">"Чек."</string>
-    <string name="description_delete_key" msgid="5586406298531883960">"Клавіша Delete"</string>
-    <string name="description_return_key" msgid="8750044000806461678">"Клавіша Return"</string>
-    <string name="description_settings_key" msgid="7484527796782969219">"Клавіша Settings"</string>
-    <string name="description_shift_key" msgid="346906866277787836">"Клавіша Shift"</string>
-    <string name="description_space_key" msgid="8512130111575878517">"Клавіша Space"</string>
-    <string name="description_switch_alpha_symbol_key" msgid="4537975384274405537">"Клавіша Symbols"</string>
-    <string name="description_tab_key" msgid="828186583738307137">"Клавіша Tab"</string>
-    <string name="description_voice_key" msgid="3057731675774652754">"Клавіша Voice Input"</string>
-    <string name="description_symbols_on" msgid="2994366855822840559">"Символи ввімкнено"</string>
-    <string name="description_symbols_off" msgid="3209578267079515136">"Символи вимкнено"</string>
-    <string name="description_shift_on" msgid="6983188949895971587">"Shift увімкнено"</string>
-    <string name="description_shift_off" msgid="8553265474523069034">"Shift вимкнено"</string>
     <string name="voice_warning_title" msgid="4419354150908395008">"Голос. ввід"</string>
     <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"Голос. ввід наразі не підтрим. для вашої мови, але можна користуватися англійською."</string>
     <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"Голосовий ввід використовує розпізнавання мовлення Google. Застосовується "<a href="http://m.google.com/privacy">"Політика конфіденційності для мобільних пристроїв"</a>"."</string>
@@ -106,6 +99,7 @@
     <string name="prefs_enable_recorrection_summary" msgid="5082041365862396329">"Торкніться введених слів, щоб виправити їх, лише коли ввімкнено пропозиції"</string>
     <string name="keyboard_layout" msgid="437433231038683666">"Тема клавіатури"</string>
     <string name="subtype_mode_cs_keyboard" msgid="1141718931112377586">"Чеська розкладка"</string>
+    <string name="subtype_mode_ar_keyboard" msgid="2655338636329774995">"Розкладка для арабської мови"</string>
     <string name="subtype_mode_da_keyboard" msgid="1243570804427922104">"Данська розкладка"</string>
     <string name="subtype_mode_de_keyboard" msgid="1990979135959462145">"Німецька розкладка"</string>
     <string name="subtype_mode_en_GB_keyboard" msgid="7945856548410373708">"Англ. розкладка (Великобританія)"</string>
@@ -115,6 +109,7 @@
     <string name="subtype_mode_fr_keyboard" msgid="8016515336759761014">"Французька розкладка"</string>
     <string name="subtype_mode_fr_CA_keyboard" msgid="2628517247158376263">"Французька розкладка (Канада)"</string>
     <string name="subtype_mode_fr_CH_keyboard" msgid="6742806653181621228">"Французька розкладка (Швейцарія)"</string>
+    <string name="subtype_mode_iw_keyboard" msgid="1787536828253289950">"Розкладка для івриту"</string>
     <string name="subtype_mode_it_keyboard" msgid="4934199655425394484">"Італійська розкладка"</string>
     <string name="subtype_mode_nb_keyboard" msgid="1175783216100212360">"Норвезька розкладка"</string>
     <string name="subtype_mode_nl_keyboard" msgid="5090278083256037936">"Голланд. розклад."</string>
diff --git a/java/res/values-vi/strings.xml b/java/res/values-vi/strings.xml
index 70defe3..81be826 100644
--- a/java/res/values-vi/strings.xml
+++ b/java/res/values-vi/strings.xml
@@ -27,7 +27,9 @@
     <string name="sound_on_keypress" msgid="6093592297198243644">"Âm thanh khi nhấn phím"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"Cửa sổ bật lên khi nhấn phím"</string>
     <string name="general_category" msgid="1859088467017573195">"Chung"</string>
-    <string name="prediction_category" msgid="6361242011806282176">"Sửa văn bản"</string>
+    <string name="correction_category" msgid="2236750915056607613">"Sửa văn bản"</string>
+    <string name="ngram_category" msgid="5337109164339320257">"Đề xuất dựa trên các từ trước đó"</string>
+    <string name="misc_category" msgid="6894192814868233453">"Tùy chọn khác"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Tự động viết hoa"</string>
     <string name="quick_fixes" msgid="5353213327680897927">"Sửa nhanh"</string>
     <string name="quick_fixes_summary" msgid="3405028402510332373">"Sửa lỗi nhập thông thường"</string>
@@ -36,6 +38,7 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Luôn hiển thị"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"Hiển thị trên chế độ khổ đứng"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Luôn ẩn"</string>
+    <string name="prefs_use_spacebar_language_switch" msgid="8828538114550634449">"Sử dụng trình chuyển đổi ngôn ngữ thanh cách"</string>
     <string name="prefs_settings_key" msgid="4623341240804046498">"Hiển thị phím cài đặt"</string>
     <string name="settings_key_mode_auto_name" msgid="2993460277873684680">"Tự động"</string>
     <string name="settings_key_mode_always_show_name" msgid="3047567041784760575">"Luôn hiển thị"</string>
@@ -45,8 +48,10 @@
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Tắt"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Đơn giản"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"Linh hoạt"</string>
-    <string name="bigram_suggestion" msgid="1323347224043514969">"Đề xuất Bigram"</string>
+    <string name="bigram_suggestion" msgid="2636414079905220518">"Đề xuất Bigram"</string>
     <string name="bigram_suggestion_summary" msgid="4383845146070101531">"Sử dụng từ trước đó để cải tiến đề xuất"</string>
+    <string name="bigram_prediction" msgid="8914273444762259739">"Dự đoán Bigram"</string>
+    <string name="bigram_prediction_summary" msgid="1747261921174300098">"Cũng sử dụng từ trước đó để dự đoán"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : Đã lưu"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Đến"</string>
     <string name="label_next_key" msgid="362972844525672568">"Tiếp theo"</string>
@@ -56,18 +61,6 @@
     <string name="label_more_key" msgid="3760239494604948502">"Khác"</string>
     <string name="label_pause_key" msgid="181098308428035340">"Tạm dừng"</string>
     <string name="label_wait_key" msgid="6402152600878093134">"Đợi"</string>
-    <string name="description_delete_key" msgid="5586406298531883960">"Xóa"</string>
-    <string name="description_return_key" msgid="8750044000806461678">"Quay lại"</string>
-    <string name="description_settings_key" msgid="7484527796782969219">"Cài đặt"</string>
-    <string name="description_shift_key" msgid="346906866277787836">"Shift"</string>
-    <string name="description_space_key" msgid="8512130111575878517">"Dấu cách"</string>
-    <string name="description_switch_alpha_symbol_key" msgid="4537975384274405537">"Biểu tượng"</string>
-    <string name="description_tab_key" msgid="828186583738307137">"Tab"</string>
-    <string name="description_voice_key" msgid="3057731675774652754">"Nhập liệu bằng giọng nói"</string>
-    <string name="description_symbols_on" msgid="2994366855822840559">"Bật biểu tượng"</string>
-    <string name="description_symbols_off" msgid="3209578267079515136">"Tắt biểu tượng"</string>
-    <string name="description_shift_on" msgid="6983188949895971587">"Bật Shift"</string>
-    <string name="description_shift_off" msgid="8553265474523069034">"Tắt Shift"</string>
     <string name="voice_warning_title" msgid="4419354150908395008">"Nhập liệu bằng giọng nói"</string>
     <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"Nhập liệu bằng giọng nói hiện không được hỗ trợ cho ngôn ngữ của bạn nhưng hoạt động với ngôn ngữ tiếng Anh."</string>
     <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"Nhập liệu bằng giọng nói sử dụng nhận dạng giọng nói của Google. Áp dụng "<a href="http://m.google.com/privacy">"Chính sách bảo mật dành cho điện thoại di động"</a>"."</string>
@@ -106,6 +99,7 @@
     <string name="prefs_enable_recorrection_summary" msgid="5082041365862396329">"Chạm các từ đã nhập để sửa, chỉ khi các đề xuất hiển thị"</string>
     <string name="keyboard_layout" msgid="437433231038683666">"Chủ đề bàn phím"</string>
     <string name="subtype_mode_cs_keyboard" msgid="1141718931112377586">"Bàn phím tiếng Séc"</string>
+    <string name="subtype_mode_ar_keyboard" msgid="2655338636329774995">"Bàn phím tiếng Ả Rập"</string>
     <string name="subtype_mode_da_keyboard" msgid="1243570804427922104">"Bàn phím tiếng Đan Mạch"</string>
     <string name="subtype_mode_de_keyboard" msgid="1990979135959462145">"Bàn phím tiếng Đức"</string>
     <string name="subtype_mode_en_GB_keyboard" msgid="7945856548410373708">"Bàn phím tiếng Anh (Anh)"</string>
@@ -115,6 +109,7 @@
     <string name="subtype_mode_fr_keyboard" msgid="8016515336759761014">"Bàn phím tiếng Pháp"</string>
     <string name="subtype_mode_fr_CA_keyboard" msgid="2628517247158376263">"Bàn phím tiếng Pháp (Canada)"</string>
     <string name="subtype_mode_fr_CH_keyboard" msgid="6742806653181621228">"Bàn phím tiếng Pháp (Thụy Sĩ)"</string>
+    <string name="subtype_mode_iw_keyboard" msgid="1787536828253289950">"Bàn phím tiếng Do Thái"</string>
     <string name="subtype_mode_it_keyboard" msgid="4934199655425394484">"Bàn phím tiếng Ý"</string>
     <string name="subtype_mode_nb_keyboard" msgid="1175783216100212360">"Bàn phím tiếng Na Uy"</string>
     <string name="subtype_mode_nl_keyboard" msgid="5090278083256037936">"Bàn phím tiếng Hà Lan"</string>
diff --git a/java/res/values-xlarge-land/dimens.xml b/java/res/values-xlarge-land/dimens.xml
index 625dd26..fd6b1f3 100644
--- a/java/res/values-xlarge-land/dimens.xml
+++ b/java/res/values-xlarge-land/dimens.xml
@@ -19,7 +19,7 @@
 -->
 
 <resources>
-    <!-- keyboardHeight = key_height*4 + key_bottom_gap*3 -->
+    <!-- keyboardHeight = key_height*4 + key_bottom_gap*3, key_height=14.5mm -->
     <dimen name="keyboardHeight">58.0mm</dimen>
     <!-- key_height + key_bottom_gap = popup_key_height -->
     <!-- <dimen name="key_height">14.5mm</dimen> -->
@@ -28,12 +28,14 @@
     <dimen name="popup_key_height">13.0mm</dimen>
     <dimen name="keyboard_top_padding">1.1mm</dimen>
     <dimen name="keyboard_bottom_padding">0.0mm</dimen>
-    <!-- key_height x 1.0 -->
-    <dimen name="key_preview_height">13.0mm</dimen>
+    <dimen name="keyboard_horizontal_edges_padding">0.0mm</dimen>
 
     <dimen name="key_letter_size">28dip</dimen>
     <dimen name="key_label_text_size">20dip</dimen>
     <!-- left or right padding of label alignment -->
     <dimen name="key_label_horizontal_alignment_padding">18dip</dimen>
+    <dimen name="key_preview_height_holo">26.5mm</dimen>
+    <dimen name="key_preview_offset_holo">7.5mm</dimen>
+
     <dimen name="candidate_strip_padding">40.0mm</dimen>
 </resources>
diff --git a/java/res/values-xlarge/config.xml b/java/res/values-xlarge/config.xml
index f075b1b..80ef3cd 100644
--- a/java/res/values-xlarge/config.xml
+++ b/java/res/values-xlarge/config.xml
@@ -22,7 +22,8 @@
     <bool name="config_enable_show_settings_key_option">false</bool>
     <bool name="config_enable_show_subtype_settings">false</bool>
     <bool name="config_enable_show_voice_key_option">false</bool>
-    <bool name="config_enable_show_popup_on_keypress_option">false</bool>
+    <!-- TODO: This configuration value is temporary set true to check popup preview behavior. -->
+    <bool name="config_enable_show_popup_on_keypress_option">true</bool>
     <bool name="config_enable_show_recorrection_option">false</bool>
     <bool name="config_enable_quick_fixes_option">false</bool>
     <bool name="config_enable_bigram_suggestions_option">false</bool>
@@ -36,10 +37,11 @@
     <bool name="config_use_spacebar_language_switcher">false</bool>
     <!-- Showing mini keyboard, just above the touched point if true, aligned to the key if false -->
     <bool name="config_show_mini_keyboard_at_touched_point">true</bool>
-    <!-- The language is never displayed if == 0, always displayed if < 0 -->
-    <integer name="config_delay_before_fadeout_language_on_spacebar">1200</integer>
+    <integer name="config_delay_update_suggestions">180</integer>
     <!-- This configuration is the index of the array {@link KeyboardSwitcher.KEYBOARD_THEMES}. -->
     <string name="config_default_keyboard_theme_id" translatable="false">5</string>
     <string name="config_text_size_of_language_on_spacebar" translatable="false">medium</string>
     <integer name="config_max_popup_keyboard_column">5</integer>
+    <!--  Screen metrics for logging. 0 = "mdpi", 1 = "hdpi", 2 = "xlarge" -->
+    <integer name="log_screen_metrics">2</integer>
 </resources>
diff --git a/java/res/values-xlarge/dimens.xml b/java/res/values-xlarge/dimens.xml
index 6928320..4f78bea 100644
--- a/java/res/values-xlarge/dimens.xml
+++ b/java/res/values-xlarge/dimens.xml
@@ -19,7 +19,7 @@
 -->
 
 <resources>
-    <!-- keyboardHeight = key_height*4 + key_bottom_gap*3 -->
+    <!-- keyboardHeight = key_height*4 + key_bottom_gap*3, key_height=12mm -->
     <dimen name="keyboardHeight">48.0mm</dimen>
     <!-- key_height + key_bottom_gap = popup_key_height -->
     <!-- <dimen name="key_height">14.5mm</dimen> -->
@@ -28,8 +28,8 @@
     <dimen name="popup_key_height">10.0mm</dimen>
     <dimen name="keyboard_top_padding">1.1mm</dimen>
     <dimen name="keyboard_bottom_padding">0.0mm</dimen>
-    <!-- key_height x 1.0 -->
-    <dimen name="key_preview_height">13.0mm</dimen>
+    <dimen name="keyboard_horizontal_edges_padding">0.0mm</dimen>
+    <dimen name="mini_keyboard_horizontal_padding_holo">40dip</dimen>
     <dimen name="mini_keyboard_key_horizontal_padding">12dip</dimen>
     <!-- Amount of allowance for selecting keys in a mini popup keyboard by sliding finger. -->
     <!-- popup_key_height x 1.2 -->
@@ -39,13 +39,18 @@
 
     <dimen name="key_letter_size">26dip</dimen>
     <dimen name="key_label_text_size">16dip</dimen>
-    <dimen name="key_preview_text_size_large">24dip</dimen>
     <!-- left or right padding of label alignment -->
     <dimen name="key_label_horizontal_alignment_padding">6dip</dimen>
+    <dimen name="key_preview_text_size_large">24dip</dimen>
+    <dimen name="key_preview_height_holo">23.0mm</dimen>
+    <dimen name="key_preview_offset_holo">8.0mm</dimen>
 
     <dimen name="candidate_strip_height">46dip</dimen>
+    <!-- candidate_strip_minimum_height =
+         key_preview_height_holo - key_preview_offset_holo + alpha -->
+    <dimen name="candidate_strip_minimum_height">18mm</dimen>
     <dimen name="candidate_strip_padding">15.0mm</dimen>
-    <dimen name="candidate_min_width">0.3in</dimen>
-    <dimen name="candidate_padding">12dip</dimen>
+    <dimen name="candidate_min_width">46dip</dimen>
+    <dimen name="candidate_padding">8dip</dimen>
     <dimen name="candidate_text_size">22dip</dimen>
 </resources>
diff --git a/java/res/values-zh-rCN/strings.xml b/java/res/values-zh-rCN/strings.xml
index 3b092bf..1f15831 100644
--- a/java/res/values-zh-rCN/strings.xml
+++ b/java/res/values-zh-rCN/strings.xml
@@ -27,7 +27,9 @@
     <string name="sound_on_keypress" msgid="6093592297198243644">"按键时播放音效"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"按键时显示弹出窗口"</string>
     <string name="general_category" msgid="1859088467017573195">"常规"</string>
-    <string name="prediction_category" msgid="6361242011806282176">"文本更正"</string>
+    <string name="correction_category" msgid="2236750915056607613">"文本更正"</string>
+    <string name="ngram_category" msgid="5337109164339320257">"根据前面的字词提供建议"</string>
+    <string name="misc_category" msgid="6894192814868233453">"其他选项"</string>
     <string name="auto_cap" msgid="1719746674854628252">"自动大写"</string>
     <string name="quick_fixes" msgid="5353213327680897927">"快速纠正"</string>
     <string name="quick_fixes_summary" msgid="3405028402510332373">"纠正常见的输入错误"</string>
@@ -36,6 +38,7 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"始终显示"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"以纵向模式显示"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"始终隐藏"</string>
+    <string name="prefs_use_spacebar_language_switch" msgid="8828538114550634449">"使用空格键切换语言"</string>
     <string name="prefs_settings_key" msgid="4623341240804046498">"显示设置键"</string>
     <string name="settings_key_mode_auto_name" msgid="2993460277873684680">"自动"</string>
     <string name="settings_key_mode_always_show_name" msgid="3047567041784760575">"始终显示"</string>
@@ -45,8 +48,10 @@
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"关闭"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"部分"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"全部"</string>
-    <string name="bigram_suggestion" msgid="1323347224043514969">"双连词建议"</string>
+    <string name="bigram_suggestion" msgid="2636414079905220518">"双连词建议"</string>
     <string name="bigram_suggestion_summary" msgid="4383845146070101531">"使用以前的字词改进建议"</string>
+    <string name="bigram_prediction" msgid="8914273444762259739">"双连词预测"</string>
+    <string name="bigram_prediction_summary" msgid="1747261921174300098">"结合前一个字词进行预测"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g>:已保存"</string>
     <string name="label_go_key" msgid="1635148082137219148">"开始"</string>
     <string name="label_next_key" msgid="362972844525672568">"下一步"</string>
@@ -56,18 +61,6 @@
     <string name="label_more_key" msgid="3760239494604948502">"更多"</string>
     <string name="label_pause_key" msgid="181098308428035340">"暂停"</string>
     <string name="label_wait_key" msgid="6402152600878093134">"等待"</string>
-    <string name="description_delete_key" msgid="5586406298531883960">"删除"</string>
-    <string name="description_return_key" msgid="8750044000806461678">"回车"</string>
-    <string name="description_settings_key" msgid="7484527796782969219">"设置"</string>
-    <string name="description_shift_key" msgid="346906866277787836">"Shift"</string>
-    <string name="description_space_key" msgid="8512130111575878517">"空格"</string>
-    <string name="description_switch_alpha_symbol_key" msgid="4537975384274405537">"符号"</string>
-    <string name="description_tab_key" msgid="828186583738307137">"Tab"</string>
-    <string name="description_voice_key" msgid="3057731675774652754">"语音输入"</string>
-    <string name="description_symbols_on" msgid="2994366855822840559">"符号模式已打开"</string>
-    <string name="description_symbols_off" msgid="3209578267079515136">"符号模式已关闭"</string>
-    <string name="description_shift_on" msgid="6983188949895971587">"Shift 模式已打开"</string>
-    <string name="description_shift_off" msgid="8553265474523069034">"Shift 模式已关闭"</string>
     <string name="voice_warning_title" msgid="4419354150908395008">"语音输入"</string>
     <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"语音输入功能当前还不支持您的语言,您只能输入英语语音。"</string>
     <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"语音输入采用了 Google 的语音识别技术,因此请遵守"<a href="http://m.google.com/privacy">"“Google 移动”隐私权政策"</a>"。"</string>
@@ -106,6 +99,7 @@
     <string name="prefs_enable_recorrection_summary" msgid="5082041365862396329">"仅在系统显示建议后,才触摸输入的字词进行更正"</string>
     <string name="keyboard_layout" msgid="437433231038683666">"键盘主题"</string>
     <string name="subtype_mode_cs_keyboard" msgid="1141718931112377586">"捷克语键盘"</string>
+    <string name="subtype_mode_ar_keyboard" msgid="2655338636329774995">"阿拉伯语键盘"</string>
     <string name="subtype_mode_da_keyboard" msgid="1243570804427922104">"丹麦语键盘"</string>
     <string name="subtype_mode_de_keyboard" msgid="1990979135959462145">"德语键盘"</string>
     <string name="subtype_mode_en_GB_keyboard" msgid="7945856548410373708">"英语(英国)键盘"</string>
@@ -115,6 +109,7 @@
     <string name="subtype_mode_fr_keyboard" msgid="8016515336759761014">"法语键盘"</string>
     <string name="subtype_mode_fr_CA_keyboard" msgid="2628517247158376263">"法语(加拿大)键盘"</string>
     <string name="subtype_mode_fr_CH_keyboard" msgid="6742806653181621228">"法语(瑞士)键盘"</string>
+    <string name="subtype_mode_iw_keyboard" msgid="1787536828253289950">"希伯来语键盘"</string>
     <string name="subtype_mode_it_keyboard" msgid="4934199655425394484">"意大利语键盘"</string>
     <string name="subtype_mode_nb_keyboard" msgid="1175783216100212360">"挪威语键盘"</string>
     <string name="subtype_mode_nl_keyboard" msgid="5090278083256037936">"荷兰语键盘"</string>
diff --git a/java/res/values-zh-rTW/strings.xml b/java/res/values-zh-rTW/strings.xml
index a3bf911..8a89aeb 100644
--- a/java/res/values-zh-rTW/strings.xml
+++ b/java/res/values-zh-rTW/strings.xml
@@ -27,7 +27,9 @@
     <string name="sound_on_keypress" msgid="6093592297198243644">"按鍵時播放音效"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"按鍵時顯示彈出式視窗"</string>
     <string name="general_category" msgid="1859088467017573195">"一般設定"</string>
-    <string name="prediction_category" msgid="6361242011806282176">"文字修正"</string>
+    <string name="correction_category" msgid="2236750915056607613">"文字修正"</string>
+    <string name="ngram_category" msgid="5337109164339320257">"根據先前字詞產生的建議"</string>
+    <string name="misc_category" msgid="6894192814868233453">"其他選項"</string>
     <string name="auto_cap" msgid="1719746674854628252">"自動大寫"</string>
     <string name="quick_fixes" msgid="5353213327680897927">"快速修正"</string>
     <string name="quick_fixes_summary" msgid="3405028402510332373">"修正一般打字錯誤"</string>
@@ -36,6 +38,7 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"一律顯示"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"以垂直模式顯示"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"永遠隱藏"</string>
+    <string name="prefs_use_spacebar_language_switch" msgid="8828538114550634449">"使用空白鍵切換語言"</string>
     <string name="prefs_settings_key" msgid="4623341240804046498">"顯示設定金鑰"</string>
     <string name="settings_key_mode_auto_name" msgid="2993460277873684680">"自動"</string>
     <string name="settings_key_mode_always_show_name" msgid="3047567041784760575">"一律顯示"</string>
@@ -45,8 +48,10 @@
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"關閉"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"部分"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"全部"</string>
-    <string name="bigram_suggestion" msgid="1323347224043514969">"雙連詞建議"</string>
+    <string name="bigram_suggestion" msgid="2636414079905220518">"雙連詞建議"</string>
     <string name="bigram_suggestion_summary" msgid="4383845146070101531">"根據前一個字詞自動找出更適合的建議"</string>
+    <string name="bigram_prediction" msgid="8914273444762259739">"雙連詞預測"</string>
+    <string name="bigram_prediction_summary" msgid="1747261921174300098">"同樣使用先前的字詞進行預測"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g>:已儲存"</string>
     <string name="label_go_key" msgid="1635148082137219148">"開始"</string>
     <string name="label_next_key" msgid="362972844525672568">"繼續"</string>
@@ -56,18 +61,6 @@
     <string name="label_more_key" msgid="3760239494604948502">"更多"</string>
     <string name="label_pause_key" msgid="181098308428035340">"暫停"</string>
     <string name="label_wait_key" msgid="6402152600878093134">"等候"</string>
-    <string name="description_delete_key" msgid="5586406298531883960">"刪除"</string>
-    <string name="description_return_key" msgid="8750044000806461678">"返回"</string>
-    <string name="description_settings_key" msgid="7484527796782969219">"設定"</string>
-    <string name="description_shift_key" msgid="346906866277787836">"Shift 鍵"</string>
-    <string name="description_space_key" msgid="8512130111575878517">"空白鍵"</string>
-    <string name="description_switch_alpha_symbol_key" msgid="4537975384274405537">"符號"</string>
-    <string name="description_tab_key" msgid="828186583738307137">"Tab 鍵"</string>
-    <string name="description_voice_key" msgid="3057731675774652754">"語音輸入"</string>
-    <string name="description_symbols_on" msgid="2994366855822840559">"開啟符號"</string>
-    <string name="description_symbols_off" msgid="3209578267079515136">"關閉符號"</string>
-    <string name="description_shift_on" msgid="6983188949895971587">"開啟位移"</string>
-    <string name="description_shift_off" msgid="8553265474523069034">"關閉位移"</string>
     <string name="voice_warning_title" msgid="4419354150908395008">"語音輸入"</string>
     <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"語音輸入目前不支援您的語言,但是可以辨識英文。"</string>
     <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"語音輸入使用 Google 的語音辨識功能,並遵循《"<a href="http://m.google.com/privacy">"行動服務隱私權政策"</a>"》。"</string>
@@ -106,6 +99,7 @@
     <string name="prefs_enable_recorrection_summary" msgid="5082041365862396329">"輕觸輸入的字詞即可加以修正 (出現建議時才適用)"</string>
     <string name="keyboard_layout" msgid="437433231038683666">"鍵盤主題"</string>
     <string name="subtype_mode_cs_keyboard" msgid="1141718931112377586">"捷克文鍵盤"</string>
+    <string name="subtype_mode_ar_keyboard" msgid="2655338636329774995">"阿拉伯文鍵盤"</string>
     <string name="subtype_mode_da_keyboard" msgid="1243570804427922104">"丹麥文鍵盤"</string>
     <string name="subtype_mode_de_keyboard" msgid="1990979135959462145">"德文鍵盤"</string>
     <string name="subtype_mode_en_GB_keyboard" msgid="7945856548410373708">"英文 (英國) 鍵盤"</string>
@@ -115,6 +109,7 @@
     <string name="subtype_mode_fr_keyboard" msgid="8016515336759761014">"法文鍵盤"</string>
     <string name="subtype_mode_fr_CA_keyboard" msgid="2628517247158376263">"法文 (加拿大) 鍵盤"</string>
     <string name="subtype_mode_fr_CH_keyboard" msgid="6742806653181621228">"法文 (瑞士) 鍵盤"</string>
+    <string name="subtype_mode_iw_keyboard" msgid="1787536828253289950">"希伯來文鍵盤"</string>
     <string name="subtype_mode_it_keyboard" msgid="4934199655425394484">"義大利文鍵盤"</string>
     <string name="subtype_mode_nb_keyboard" msgid="1175783216100212360">"挪威文鍵盤"</string>
     <string name="subtype_mode_nl_keyboard" msgid="5090278083256037936">"荷蘭文鍵盤"</string>
diff --git a/java/res/values/attrs.xml b/java/res/values/attrs.xml
index f0da274..e88b007 100644
--- a/java/res/values/attrs.xml
+++ b/java/res/values/attrs.xml
@@ -91,6 +91,8 @@
         <attr name="verticalGap" format="dimension|fraction" />
         <!-- Popup keyboard layout template -->
         <attr name="popupKeyboardTemplate" format="reference" />
+        <!-- Locale of the keyboard layout -->
+        <attr name="keyboardLocale" format="string" />
     </declare-styleable>
 
     <declare-styleable name="Keyboard_Key">
@@ -107,8 +109,8 @@
             <!-- Key is anchored to the right of the keyboard. -->
             <flag name="right" value="2" />
         </attr>
-        <!-- Whether this is a modifier key such as Alt or Shift. -->
-        <attr name="isModifier" format="boolean" />
+        <!-- Whether this is a functional key which has different key top than normal key. -->
+        <attr name="isFunctional" format="boolean" />
         <!-- Whether this is a toggle key. -->
         <attr name="isSticky" format="boolean" />
         <!-- Whether long-pressing on this key will make it repeat. -->
@@ -142,6 +144,9 @@
         <attr name="shiftedIcon" format="reference" />
         <!-- The key is enabled and responds on press. -->
         <attr name="enabled" format="boolean" />
+        <!-- Visual insets -->
+        <attr name="visualInsetsLeft" format="dimension|fraction" />
+        <attr name="visualInsetsRight" format="dimension|fraction" />
     </declare-styleable>
 
     <declare-styleable name="Keyboard_Row">
diff --git a/java/res/values/colors.xml b/java/res/values/colors.xml
index 0161589..733a464 100644
--- a/java/res/values/colors.xml
+++ b/java/res/values/colors.xml
@@ -21,11 +21,9 @@
     <color name="candidate_normal">#FFFFFFFF</color>
     <color name="candidate_recommended">#FFFCAE00</color>
     <color name="candidate_other">#FFFCAE00</color>
-    <color name="latinkeyboard_transparent">#00000000</color>
     <color name="latinkeyboard_bar_language_shadow_white">#80000000</color>
     <color name="latinkeyboard_bar_language_shadow_black">#80FFFFFF</color>
     <color name="latinkeyboard_bar_language_text">#FFC0C0C0</color>
-    <color name="latinkeyboard_extension_background">#A0000000</color>
     <color name="latinkeyboard_feedback_language_text">#FFFFFFFF</color>
     <color name="latinkeyboard_key_color_white">#FFFFFFFF</color>
     <color name="latinkeyboard_key_color_black">#FF000000</color>
diff --git a/java/res/values/config.xml b/java/res/values/config.xml
index bdb4409..1d24b10 100644
--- a/java/res/values/config.xml
+++ b/java/res/values/config.xml
@@ -37,20 +37,26 @@
     <bool name="config_default_popup_preview">true</bool>
     <!-- Default values for whether quick fixes and bigram suggestions are activated -->
     <bool name="config_default_quick_fixes">true</bool>
+    <!-- Default value for bigram suggestion: while showing candidates for a word should we weigh
+         in the previous word? -->
     <bool name="config_default_bigram_suggestions">true</bool>
+    <!-- Default value for bigram prediction: after entering a word and a space only, should we look
+         at input history to suggest a hopefully helpful candidate for the next word? -->
+    <bool name="config_default_bigram_prediction">false</bool>
     <bool name="config_default_recorrection_enabled">true</bool>
     <bool name="config_default_sound_enabled">false</bool>
     <bool name="config_use_spacebar_language_switcher">true</bool>
     <!-- Showing mini keyboard, just above the touched point if true, aligned to the key if false -->
     <bool name="config_show_mini_keyboard_at_touched_point">false</bool>
     <!-- The language is never displayed if == 0, always displayed if < 0 -->
-    <integer name="config_delay_before_fadeout_language_on_spacebar">-1</integer>
+    <integer name="config_delay_before_fadeout_language_on_spacebar">1200</integer>
+    <integer name="config_delay_update_suggestions">100</integer>
+    <integer name="config_delay_update_old_suggestions">300</integer>
+    <integer name="config_delay_update_shift_state">100</integer>
     <integer name="config_duration_of_fadeout_language_on_spacebar">50</integer>
-    <integer name="config_final_fadeout_percentage_of_language_on_spacebar">15</integer>
+    <integer name="config_final_fadeout_percentage_of_language_on_spacebar">50</integer>
     <integer name="config_delay_before_preview">0</integer>
-    <integer name="config_delay_after_preview">10</integer>
-    <integer name="config_preview_fadein_anim_time">0</integer>
-    <integer name="config_preview_fadeout_anim_time">70</integer>
+    <integer name="config_delay_after_preview">70</integer>
     <integer name="config_mini_keyboard_fadein_anim_time">0</integer>
     <integer name="config_mini_keyboard_fadeout_anim_time">100</integer>
     <integer name="config_delay_before_key_repeat_start">400</integer>
@@ -65,7 +71,7 @@
     <!-- This configuration is the index of the array {@link KeyboardSwitcher.KEYBOARD_THEMES}. -->
     <string name="config_default_keyboard_theme_id" translatable="false">4</string>
     <string name="config_text_size_of_language_on_spacebar" translatable="false">small</string>
-    <integer name="config_max_popup_keyboard_column">10</integer>
+    <integer name="config_max_popup_keyboard_column">5</integer>
     <!-- Whether or not auto-correction should be enabled by default -->
     <bool name="enable_autocorrect">true</bool>
     <string-array name="auto_correction_threshold_values" translatable="false">
@@ -78,4 +84,7 @@
              will be subject to auto-correction. -->
         <item>0</item>
     </string-array>
+    <!--  Screen metrics for logging. 0 = "mdpi", 1 = "hdpi", 2 = "xlarge" -->
+    <integer name="log_screen_metrics">0</integer>
+    <bool name="config_require_umlaut_processing">false</bool>
 </resources>
diff --git a/java/res/values/dimens.xml b/java/res/values/dimens.xml
index 7f00cdb..69f962f 100644
--- a/java/res/values/dimens.xml
+++ b/java/res/values/dimens.xml
@@ -19,38 +19,45 @@
 -->
 
 <resources>
-    <!-- keyboardHeight = key_height*4 + key_bottom_gap*3 -->
-    <dimen name="keyboardHeight">1.265in</dimen>
+    <!-- keyboardHeight = key_height*4 + key_bottom_gap*3, key_height=0.295in -->
+    <dimen name="keyboardHeight">1.285in</dimen>
     <!-- key_height + key_bottom_gap = popup_key_height -->
-    <!-- <dimen name="key_height">0.290in</dimen> -->
+    <!-- <dimen name="key_height">0.295in</dimen> -->
     <dimen name="key_bottom_gap">0.035in</dimen>
     <dimen name="key_horizontal_gap">0.000in</dimen>
-    <dimen name="popup_key_height">0.325in</dimen>
+    <dimen name="popup_key_height">0.330in</dimen>
     <dimen name="keyboard_top_padding">0.00in</dimen>
     <dimen name="keyboard_bottom_padding">0.06in</dimen>
-    <!-- key_preview_text_size_large x 2 -->
-    <dimen name="key_preview_height">80sp</dimen>
+    <dimen name="keyboard_horizontal_edges_padding">0.0in</dimen>
+    <dimen name="mini_keyboard_horizontal_padding">16dip</dimen>
+    <dimen name="mini_keyboard_horizontal_padding_holo">32dip</dimen>
     <dimen name="mini_keyboard_key_horizontal_padding">8dip</dimen>
     <!-- Amount of allowance for selecting keys in a mini popup keyboard by sliding finger. -->
     <!-- popup_key_height x 1.2 -->
-    <dimen name="mini_keyboard_slide_allowance">0.390in</dimen>
+    <dimen name="mini_keyboard_slide_allowance">0.396in</dimen>
     <!-- popup_key_height x -1.0 -->
-    <dimen name="mini_keyboard_vertical_correction">-0.325in</dimen>
-
-    <dimen name="key_letter_size">0.13in</dimen>
-    <dimen name="key_label_text_size">0.083in</dimen>
-    <dimen name="key_preview_text_size_large">40sp</dimen>
-    <!-- left or right padding of label alignment -->
-    <dimen name="key_label_horizontal_alignment_padding">0.13in</dimen>
-    <dimen name="key_preview_offset">0.000in</dimen>
+    <dimen name="mini_keyboard_vertical_correction">-0.330in</dimen>
     <!-- We use "inch", not "dip" because this value tries dealing with physical distance related
          to user's finger. -->
     <dimen name="keyboard_vertical_correction">-0.05in</dimen>
 
+    <dimen name="key_letter_size">0.13in</dimen>
+    <dimen name="key_label_text_size">0.083in</dimen>
+    <!-- left or right padding of label alignment -->
+    <dimen name="key_label_horizontal_alignment_padding">0.13in</dimen>
+    <dimen name="key_preview_height">80sp</dimen>
+    <dimen name="key_preview_offset">0.000in</dimen>
+    <dimen name="key_preview_text_size_large">36sp</dimen>
+    <dimen name="key_preview_height_holo">130sp</dimen>
+    <dimen name="key_preview_offset_holo">0.193in</dimen>
+
     <dimen name="candidate_strip_height">42dip</dimen>
+    <!-- candidate_strip_minimum_height =
+         key_preview_height_holo - key_preview_offset_holo + alpha -->
+    <dimen name="candidate_strip_minimum_height">100sp</dimen>
     <dimen name="candidate_strip_fading_edge_length">63dip</dimen>
     <dimen name="candidate_strip_padding">0dip</dimen>
-    <dimen name="candidate_min_width">0.3in</dimen>
+    <dimen name="candidate_min_width">32dip</dimen>
     <dimen name="candidate_padding">6dip</dimen>
     <dimen name="candidate_text_size">18dip</dimen>
     <dimen name="spacebar_vertical_correction">4dip</dimen>
diff --git a/java/res/values/donottranslate-altchars.xml b/java/res/values/donottranslate-altchars.xml
index 518e74a..e779575 100644
--- a/java/res/values/donottranslate-altchars.xml
+++ b/java/res/values/donottranslate-altchars.xml
@@ -47,12 +47,9 @@
     <string name="alternates_for_currency_dollar">¢,£,€,¥,₱</string>
     <string name="alternates_for_currency_euro">¢,£,$,¥,₱</string>
     <string name="alternates_for_currency_pound">¢,$,€,¥,₱</string>
-    <string name="alternates_for_mic">"\@drawable/sym_keyboard_settings|\@integer/key_settings,\@drawable/sym_keyboard_mic|\@integer/key_voice"</string>
     <string name="alternates_for_smiley">":-)|:-) ,:-(|:-( ,;-)|;-) ,:-P|:-P ,=-O|=-O ,:-*|:-* ,:O|:O ,B-)|B-) ,:-$|:-$ ,:-!|:-! ,:-[|:-[ ,O:-)|O:-) ,:-\\\\\\\\|:-\\\\\\\\ ,:\'(|:\'( ,:-D|:-D "</string>
-    <string name="alternates_for_settings_slash">"\@drawable/sym_keyboard_settings|\@integer/key_settings,/"</string>
-    <string name="alternates_for_settings_at">"\@drawable/sym_keyboard_settings|\@integer/key_settings,\@"</string>
-    <string name="alternates_for_settings_comma">"\@drawable/sym_keyboard_settings|\@integer/key_settings,\\,"</string>
-    <string name="alternates_for_punctuation">":,/,&amp;,(,),-,+,;,\@,\',\",\?,!,\\,"</string>
+    <string name="alternates_for_punctuation">"\\,,\?,!,:,-,\',\",(,),/,;,+,&amp;,\@"</string>
+    <string name="alternates_for_web_tab_punctuation">".,\\,,\?,!,:,-,\',\",(,),/,;,+,&amp;,\@"</string>
     <string name="keylabel_for_popular_domain">".com"</string>
     <!-- popular web domains for the locale - most popular, displayed on the keyboard -->
     <string name="alternates_for_popular_domain">".net,.org,.gov,.edu"</string>
diff --git a/java/res/values/donottranslate.xml b/java/res/values/donottranslate.xml
index 6a1069e..1cdae3d 100644
--- a/java/res/values/donottranslate.xml
+++ b/java/res/values/donottranslate.xml
@@ -18,12 +18,19 @@
 */
 -->
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- Symbols that are commonly considered word separators in this language -->
-    <string name="word_separators">.\u0009\u0020,;:!?\n()[]*&amp;@{}/&lt;&gt;_+=|\u0022</string>
-    <!-- Symbols that are sentence separators, for purposes of making it hug the last sentence. -->
-    <string name="sentence_separators">.,!?)</string>
     <!-- Symbols that are suggested between words -->
     <string name="suggested_punctuations">!?,\u0022\u0027:();-/@_</string>
+    <!-- Symbols that should be swapped with a magic space -->
+    <string name="magic_space_swapping_symbols">.,;:!?)]}\u0022</string>
+    <!-- Symbols that should strip a magic space -->
+    <string name="magic_space_stripping_symbols">\u0009\u0020\n/_\u0027-</string>
+    <!-- Symbols that should convert magic spaces into real space -->
+    <string name="magic_space_promoting_symbols">([*&amp;@{&lt;&gt;+=|</string>
+    <!-- Symbols that do NOT separate words -->
+    <string name="non_word_separator_symbols">\u0027-</string>
+    <!-- Word separator list is the union of all symbols except those that are not separators:
+    magic_space_swapping_symbols | magic_space_stripping_symbols |
+            magic_space_neutral_symbols \ non_word_separator_symbols -->
 
     <!-- Label for ALT modifier key.  Must be short to fit on key! -->
     <string name="label_alt_key">ALT</string>
diff --git a/java/res/values/keycodes.xml b/java/res/values/keycodes.xml
index d6f9bfc..d5926ec 100644
--- a/java/res/values/keycodes.xml
+++ b/java/res/values/keycodes.xml
@@ -23,29 +23,12 @@
     <integer name="key_tab">9</integer>
     <integer name="key_return">10</integer>
     <integer name="key_space">32</integer>
+    <integer name="key_dash">45</integer>
+    <integer name="key_single_quote">39</integer>
+    <integer name="key_double_quote">34</integer>
     <integer name="key_shift">-1</integer>
     <integer name="key_switch_alpha_symbol">-2</integer>
     <integer name="key_delete">-5</integer>
-    <integer name="key_settings">-100</integer>
-    <integer name="key_voice">-102</integer>
-
-    <!-- Array used for mapping key codes to description strings. -->
-    <array name="key_descriptions">
-        <item>@integer/key_tab</item>
-        <item>@string/description_tab_key</item>
-        <item>@integer/key_return</item>
-        <item>@string/description_return_key</item>
-        <item>@integer/key_space</item>
-        <item>@string/description_space_key</item>
-        <item>@integer/key_shift</item>
-        <item>@string/description_shift_key</item>
-        <item>@integer/key_switch_alpha_symbol</item>
-        <item>@string/description_switch_alpha_symbol_key</item>
-        <item>@integer/key_delete</item>
-        <item>@string/description_delete_key</item>
-        <item>@integer/key_settings</item>
-        <item>@string/description_settings_key</item>
-        <item>@integer/key_voice</item>
-        <item>@string/description_voice_key</item>
-    </array>
+    <integer name="key_settings">-6</integer>
+    <integer name="key_shortcut">-8</integer>
 </resources>
diff --git a/java/res/values/strings.xml b/java/res/values/strings.xml
index 3c0a9c1..823f1e5 100644
--- a/java/res/values/strings.xml
+++ b/java/res/values/strings.xml
@@ -38,7 +38,13 @@
     <string name="general_category">General</string>
 
     <!-- Category title for text prediction -->
-    <string name="prediction_category">Text correction</string>
+    <string name="correction_category">Text correction</string>
+
+    <!-- Category title for ngrams  -->
+    <string name="ngram_category">Suggestions based on previous words</string>
+
+    <!-- Category title for misc options  -->
+    <string name="misc_category">Other options</string>
 
     <!-- Option to enable auto capitalization of sentences -->
     <string name="auto_cap">Auto-capitalization</string>
@@ -55,6 +61,8 @@
     <string name="prefs_suggestion_visibility_show_name">Always show</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name">Show on portrait mode</string>
     <string name="prefs_suggestion_visibility_hide_name">Always hide</string>
+    <!-- Option to enable spacebar language switcher [CHAR LIMIT=20]-->
+    <string name="prefs_use_spacebar_language_switch">Use the spacebar language switcher</string>
 
     <!-- Option to show/hide the settings key -->
     <string name="prefs_settings_key">Show settings key</string>
@@ -78,9 +86,13 @@
     <string name="auto_correction_threshold_mode_aggeressive">Aggressive</string>
 
     <!-- Option to enable bigram correction -->
-    <string name="bigram_suggestion">Bigram Suggestions</string>
+    <string name="bigram_suggestion">Bigram suggestions</string>
     <!-- Description for auto correction -->
     <string name="bigram_suggestion_summary">Use previous word to improve suggestion</string>
+    <!-- Option to enable using user-history bigram when no input -->
+    <string name="bigram_prediction">Bigram prediction</string>
+    <!-- Description for auto correction -->
+    <string name="bigram_prediction_summary">Use previous word also for prediction</string>
 
     <!-- Indicates that a word has been added to the dictionary -->
     <string name="added_word"><xliff:g id="word">%s</xliff:g> : Saved</string>
@@ -102,31 +114,6 @@
     <!-- Label for "Wait" key of phone number keyboard.  Must be short to fit on key! [CHAR LIMIT=5]-->
     <string name="label_wait_key">Wait</string>
 
-    <!-- Spoken text description for delete key. -->
-    <string name="description_delete_key">Delete</string>
-    <!-- Spoken text description for return key. -->
-    <string name="description_return_key">Return</string>
-    <!-- Spoken text description for settings key. -->
-    <string name="description_settings_key">Settings</string>
-    <!-- Spoken text description for shift key. -->
-    <string name="description_shift_key">Shift</string>
-    <!-- Spoken text description for space key. -->
-    <string name="description_space_key">Space</string>
-    <!-- Spoken text description for symbols key. -->
-    <string name="description_switch_alpha_symbol_key">Symbols</string>
-    <!-- Spoken text description for tab key. -->
-    <string name="description_tab_key">Tab</string>
-    <!-- Spoken text description for voice input key. -->
-    <string name="description_voice_key">Voice Input</string>
-    <!-- Spoken text description for symbols mode on. -->
-    <string name="description_symbols_on">Symbols on</string>
-    <!-- Spoken text description for symbols mode off. -->
-    <string name="description_symbols_off">Symbols off</string>
-    <!-- Spoken text description for shift mode on. -->
-    <string name="description_shift_on">Shift on</string>
-    <!-- Spoken text description for shift mode off. -->
-    <string name="description_shift_off">Shift off</string>
-
     <!-- Voice related labels -->
 
     <!-- Title of the warning dialog that shows when a user initiates voice input for
@@ -242,6 +229,8 @@
 
     <!-- Description for Czech keyboard subtype [CHAR LIMIT=35] -->
     <string name="subtype_mode_cs_keyboard">Czech Keyboard</string>
+    <!-- Description for Arabic keyboard subtype [CHAR LIMIT=35] -->
+    <string name="subtype_mode_ar_keyboard">Arabic Keyboard</string>
     <!-- Description for Danish keyboard subtype [CHAR LIMIT=35] -->
     <string name="subtype_mode_da_keyboard">Danish Keyboard</string>
     <!-- Description for German keyboard subtype [CHAR LIMIT=35] -->
@@ -260,12 +249,17 @@
     <string name="subtype_mode_fr_CA_keyboard">French (Canada) Keyboard</string>
     <!-- Description for French (Switzerland) keyboard subtype [CHAR LIMIT=35] -->
     <string name="subtype_mode_fr_CH_keyboard">French (Switzerland) Keyboard</string>
+    <!-- Description for Hebrew keyboard subtype [CHAR LIMIT=35] -->
+    <!-- Java uses the deprecated "iw" code instead of the standard "he" code -->
+    <string name="subtype_mode_iw_keyboard">Hebrew Keyboard</string>
     <!-- Description for Italian keyboard subtype [CHAR LIMIT=35] -->
     <string name="subtype_mode_it_keyboard">Italian Keyboard</string>
     <!-- Description for Norwegian keyboard subtype [CHAR LIMIT=35] -->
     <string name="subtype_mode_nb_keyboard">Norwegian Keyboard</string>
     <!-- Description for Dutch keyboard subtype [CHAR LIMIT=35] -->
     <string name="subtype_mode_nl_keyboard">Dutch Keyboard</string>
+    <!-- Description for Polish keyboard subtype [CHAR LIMIT=35] -->
+    <string name="subtype_mode_pl_keyboard">Polish Keyboard</string>
     <!-- Description for Russian keyboard subtype [CHAR LIMIT=35] -->
     <string name="subtype_mode_ru_keyboard">Russian Keyboard</string>
     <!-- Description for Serbian keyboard subtype [CHAR LIMIT=35] -->
diff --git a/java/res/values/styles.xml b/java/res/values/styles.xml
index 130714f..8a4b16d 100644
--- a/java/res/values/styles.xml
+++ b/java/res/values/styles.xml
@@ -35,11 +35,7 @@
         <item name="backgroundDimAmount">0.5</item>
         <item name="colorScheme">white</item>
     </style>
-    <style name="KeyPreviewAnimation">
-        <item name="android:windowEnterAnimation">@anim/key_preview_fadein</item>
-        <item name="android:windowExitAnimation">@anim/key_preview_fadeout</item>
-    </style>
-    <style name="MiniKeyboardAnimation">
+    <style name="PopupMiniKeyboardAnimation">
         <item name="android:windowEnterAnimation">@anim/mini_keyboard_fadein</item>
         <item name="android:windowExitAnimation">@anim/mini_keyboard_fadeout</item>
     </style>
diff --git a/java/res/xml-xlarge/kbd_qwerty.xml b/java/res/xml-ar/kbd_qwerty.xml
similarity index 87%
copy from java/res/xml-xlarge/kbd_qwerty.xml
copy to java/res/xml-ar/kbd_qwerty.xml
index 1c8d51f..5faf603 100644
--- a/java/res/xml-xlarge/kbd_qwerty.xml
+++ b/java/res/xml-ar/kbd_qwerty.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2010, The Android Open Source Project
+** Copyright 2011, The Android Open Source Project
 **
 ** Licensed under the Apache License, Version 2.0 (the "License");
 ** you may not use this file except in compliance with the License.
@@ -23,11 +23,13 @@
     latin:keyboardHeight="@dimen/keyboardHeight"
     latin:maxKeyboardHeight="50%p"
     latin:rowHeight="25%p"
+    latin:keyWidth="10%p"
     latin:horizontalGap="@dimen/key_horizontal_gap"
     latin:verticalGap="@dimen/key_bottom_gap"
     latin:popupKeyboardTemplate="@xml/kbd_popup_template"
     latin:maxPopupKeyboardColumn="@integer/config_max_popup_keyboard_column"
+    latin:keyboardLocale="ar"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_qwerty_rows" />
+        latin:keyboardLayout="@xml/kbd_ar_rows" />
 </Keyboard>
diff --git a/java/res/xml-cs/kbd_qwerty.xml b/java/res/xml-cs/kbd_qwerty.xml
index 010bdb3..0e6e40d 100644
--- a/java/res/xml-cs/kbd_qwerty.xml
+++ b/java/res/xml-cs/kbd_qwerty.xml
@@ -28,6 +28,7 @@
     latin:verticalGap="@dimen/key_bottom_gap"
     latin:popupKeyboardTemplate="@xml/kbd_popup_template"
     latin:maxPopupKeyboardColumn="@integer/config_max_popup_keyboard_column"
+    latin:keyboardLocale="cs"
 >
     <include
         latin:keyboardLayout="@xml/kbd_qwertz_rows" />
diff --git a/java/res/xml-da/kbd_qwerty.xml b/java/res/xml-da/kbd_qwerty.xml
index 441b7cb..d9847ae 100644
--- a/java/res/xml-da/kbd_qwerty.xml
+++ b/java/res/xml-da/kbd_qwerty.xml
@@ -27,6 +27,7 @@
     latin:verticalGap="@dimen/key_bottom_gap"
     latin:popupKeyboardTemplate="@xml/kbd_popup_template"
     latin:maxPopupKeyboardColumn="@integer/config_max_popup_keyboard_column"
+    latin:keyboardLocale="da"
 >
     <include
         latin:keyboardLayout="@xml/kbd_qwerty_rows_scandinavia" />
diff --git a/java/res/xml-de/kbd_qwerty.xml b/java/res/xml-de/kbd_qwerty.xml
index a23e4fb..e656966 100644
--- a/java/res/xml-de/kbd_qwerty.xml
+++ b/java/res/xml-de/kbd_qwerty.xml
@@ -28,6 +28,7 @@
     latin:verticalGap="@dimen/key_bottom_gap"
     latin:popupKeyboardTemplate="@xml/kbd_popup_template"
     latin:maxPopupKeyboardColumn="@integer/config_max_popup_keyboard_column"
+    latin:keyboardLocale="de_DE"
 >
     <include
         latin:keyboardLayout="@xml/kbd_qwertz_rows" />
diff --git a/java/res/xml-fi/kbd_qwerty.xml b/java/res/xml-fi/kbd_qwerty.xml
index b0a7b3e..ea08d67 100644
--- a/java/res/xml-fi/kbd_qwerty.xml
+++ b/java/res/xml-fi/kbd_qwerty.xml
@@ -27,6 +27,7 @@
     latin:verticalGap="@dimen/key_bottom_gap"
     latin:popupKeyboardTemplate="@xml/kbd_popup_template"
     latin:maxPopupKeyboardColumn="@integer/config_max_popup_keyboard_column"
+    latin:keyboardLocale="fi"
 >
     <include
         latin:keyboardLayout="@xml/kbd_qwerty_rows_scandinavia" />
diff --git a/java/res/xml-fr-rCA/kbd_qwerty.xml b/java/res/xml-fr-rCA/kbd_qwerty.xml
index 92d92f0..f9c2969 100644
--- a/java/res/xml-fr-rCA/kbd_qwerty.xml
+++ b/java/res/xml-fr-rCA/kbd_qwerty.xml
@@ -28,6 +28,7 @@
     latin:verticalGap="@dimen/key_bottom_gap"
     latin:popupKeyboardTemplate="@xml/kbd_popup_template"
     latin:maxPopupKeyboardColumn="@integer/config_max_popup_keyboard_column"
+    latin:keyboardLocale="fr_CA"
 >
     <include
         latin:keyboardLayout="@xml/kbd_qwerty_rows" />
diff --git a/java/res/xml-fr-rCH/kbd_qwerty.xml b/java/res/xml-fr-rCH/kbd_qwerty.xml
index a23e4fb..e47cfd9 100644
--- a/java/res/xml-fr-rCH/kbd_qwerty.xml
+++ b/java/res/xml-fr-rCH/kbd_qwerty.xml
@@ -28,6 +28,7 @@
     latin:verticalGap="@dimen/key_bottom_gap"
     latin:popupKeyboardTemplate="@xml/kbd_popup_template"
     latin:maxPopupKeyboardColumn="@integer/config_max_popup_keyboard_column"
+    latin:keyboardLocale="fr_CH"
 >
     <include
         latin:keyboardLayout="@xml/kbd_qwertz_rows" />
diff --git a/java/res/xml-fr/kbd_qwerty.xml b/java/res/xml-fr/kbd_qwerty.xml
index 2d0b42b..2f8e67b 100644
--- a/java/res/xml-fr/kbd_qwerty.xml
+++ b/java/res/xml-fr/kbd_qwerty.xml
@@ -28,6 +28,7 @@
     latin:verticalGap="@dimen/key_bottom_gap"
     latin:popupKeyboardTemplate="@xml/kbd_popup_template"
     latin:maxPopupKeyboardColumn="@integer/config_max_popup_keyboard_column"
+    latin:keyboardLocale="fr_FR"
 >
     <include
         latin:keyboardLayout="@xml/kbd_azerty_rows" />
diff --git a/java/res/xml-hu/kbd_qwerty.xml b/java/res/xml-hu/kbd_qwerty.xml
index 010bdb3..db729cf 100644
--- a/java/res/xml-hu/kbd_qwerty.xml
+++ b/java/res/xml-hu/kbd_qwerty.xml
@@ -28,6 +28,7 @@
     latin:verticalGap="@dimen/key_bottom_gap"
     latin:popupKeyboardTemplate="@xml/kbd_popup_template"
     latin:maxPopupKeyboardColumn="@integer/config_max_popup_keyboard_column"
+    latin:keyboardLocale="hu"
 >
     <include
         latin:keyboardLayout="@xml/kbd_qwertz_rows" />
diff --git a/java/res/xml-iw/kbd_qwerty.xml b/java/res/xml-iw/kbd_qwerty.xml
index 98bfd7e..4cd565b 100644
--- a/java/res/xml-iw/kbd_qwerty.xml
+++ b/java/res/xml-iw/kbd_qwerty.xml
@@ -1,19 +1,19 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-/* 
+/*
 **
 ** Copyright 2010, The Android Open Source Project
 **
-** Licensed under the Apache License, Version 2.0 (the "License"); 
-** you may not use this file except in compliance with the License. 
-** You may obtain a copy of the License at 
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
 **
-**     http://www.apache.org/licenses/LICENSE-2.0 
+**     http://www.apache.org/licenses/LICENSE-2.0
 **
-** Unless required by applicable law or agreed to in writing, software 
-** distributed under the License is distributed on an "AS IS" BASIS, 
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
-** See the License for the specific language governing permissions and 
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
 ** limitations under the License.
 */
 -->
@@ -28,85 +28,8 @@
     latin:verticalGap="@dimen/key_bottom_gap"
     latin:popupKeyboardTemplate="@xml/kbd_popup_template"
     latin:maxPopupKeyboardColumn="@integer/config_max_popup_keyboard_column"
+    latin:keyboardLocale="iw"
 >
     <include
-        latin:keyboardLayout="@xml/kbd_key_styles" />
-    <Row
-        latin:rowEdgeFlags="top"
-    >
-        <Spacer
-            latin:horizontalGap="5%p" />
-        <Key
-            latin:keyLabel="ק"
-            latin:keyEdgeFlags="left" />
-        <Key
-            latin:keyLabel="ר" />
-        <Key
-            latin:keyLabel="א" />
-        <Key
-            latin:keyLabel="ט" />
-        <Key
-            latin:keyLabel="ו" />
-        <Key
-            latin:keyLabel="ן" />
-        <Key
-            latin:keyLabel="ם" />
-        <Key
-            latin:keyLabel="פ" />
-        <Spacer
-            latin:horizontalGap="1.25%p" />
-        <Key
-            latin:keyStyle="deleteKeyStyle"
-            latin:keyWidth="13.75%p"
-            latin:keyEdgeFlags="right" />
-    </Row>
-    <Row>
-        <Key
-            latin:keyLabel="ש"
-            latin:keyEdgeFlags="left" />
-        <Key
-            latin:keyLabel="ד" />
-        <Key
-            latin:keyLabel="ג" />
-        <Key
-            latin:keyLabel="כ" />
-        <Key
-            latin:keyLabel="ע" />
-        <Key
-            latin:keyLabel="י" />
-        <Key
-            latin:keyLabel="ח" />
-        <Key
-            latin:keyLabel="ל" />
-        <Key
-            latin:keyLabel="ך" />
-        <Key
-            latin:keyLabel="ף"
-            latin:keyEdgeFlags="right" />
-    </Row>
-    <Row>
-        <Spacer
-            latin:horizontalGap="5%p" />
-        <Key
-            latin:keyLabel="ז"
-            latin:keyEdgeFlags="left" />
-        <Key
-            latin:keyLabel="ס" />
-        <Key
-            latin:keyLabel="ב" />
-        <Key
-            latin:keyLabel="ה" />
-        <Key
-            latin:keyLabel="נ" />
-        <Key
-            latin:keyLabel="מ" />
-        <Key
-            latin:keyLabel="צ" />
-        <Key
-            latin:keyLabel="ת" />
-        <Key
-            latin:keyLabel="ץ"
-            latin:keyEdgeFlags="right" />
-    </Row>
-    <include latin:keyboardLayout="@xml/kbd_qwerty_row4" />
+        latin:keyboardLayout="@xml/kbd_iw_rows" />
 </Keyboard>
diff --git a/java/res/xml-nb/kbd_qwerty.xml b/java/res/xml-nb/kbd_qwerty.xml
index 441b7cb..7b20ca2 100644
--- a/java/res/xml-nb/kbd_qwerty.xml
+++ b/java/res/xml-nb/kbd_qwerty.xml
@@ -27,6 +27,7 @@
     latin:verticalGap="@dimen/key_bottom_gap"
     latin:popupKeyboardTemplate="@xml/kbd_popup_template"
     latin:maxPopupKeyboardColumn="@integer/config_max_popup_keyboard_column"
+    latin:keyboardLocale="nb"
 >
     <include
         latin:keyboardLayout="@xml/kbd_qwerty_rows_scandinavia" />
diff --git a/java/res/xml-xlarge/kbd_qwerty.xml b/java/res/xml-pl/kbd_qwerty.xml
similarity index 90%
rename from java/res/xml-xlarge/kbd_qwerty.xml
rename to java/res/xml-pl/kbd_qwerty.xml
index 1c8d51f..fad28d6 100644
--- a/java/res/xml-xlarge/kbd_qwerty.xml
+++ b/java/res/xml-pl/kbd_qwerty.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2010, The Android Open Source Project
+** Copyright 2011, The Android Open Source Project
 **
 ** Licensed under the Apache License, Version 2.0 (the "License");
 ** you may not use this file except in compliance with the License.
@@ -23,10 +23,12 @@
     latin:keyboardHeight="@dimen/keyboardHeight"
     latin:maxKeyboardHeight="50%p"
     latin:rowHeight="25%p"
+    latin:keyWidth="10%p"
     latin:horizontalGap="@dimen/key_horizontal_gap"
     latin:verticalGap="@dimen/key_bottom_gap"
     latin:popupKeyboardTemplate="@xml/kbd_popup_template"
     latin:maxPopupKeyboardColumn="@integer/config_max_popup_keyboard_column"
+    latin:keyboardLocale="pl_PL"
 >
     <include
         latin:keyboardLayout="@xml/kbd_qwerty_rows" />
diff --git a/java/res/xml-ru/kbd_qwerty.xml b/java/res/xml-ru/kbd_qwerty.xml
index 0eb3115..e5aea58 100644
--- a/java/res/xml-ru/kbd_qwerty.xml
+++ b/java/res/xml-ru/kbd_qwerty.xml
@@ -27,6 +27,7 @@
     latin:verticalGap="@dimen/key_bottom_gap"
     latin:popupKeyboardTemplate="@xml/kbd_popup_template"
     latin:maxPopupKeyboardColumn="@integer/config_max_popup_keyboard_column"
+    latin:keyboardLocale="ru_RU"
 >
     <include
         latin:keyboardLayout="@xml/kbd_ru_rows" />
diff --git a/java/res/xml-sr/kbd_qwerty.xml b/java/res/xml-sr/kbd_qwerty.xml
index 3995e4e..9782cd5 100644
--- a/java/res/xml-sr/kbd_qwerty.xml
+++ b/java/res/xml-sr/kbd_qwerty.xml
@@ -27,6 +27,7 @@
     latin:verticalGap="@dimen/key_bottom_gap"
     latin:popupKeyboardTemplate="@xml/kbd_popup_template"
     latin:maxPopupKeyboardColumn="@integer/config_max_popup_keyboard_column"
+    latin:keyboardLocale="sr"
 >
     <include
         latin:keyboardLayout="@xml/kbd_sr_rows" />
diff --git a/java/res/xml-sv/kbd_qwerty.xml b/java/res/xml-sv/kbd_qwerty.xml
index 72bdc33..3ff1679 100644
--- a/java/res/xml-sv/kbd_qwerty.xml
+++ b/java/res/xml-sv/kbd_qwerty.xml
@@ -27,6 +27,7 @@
     latin:verticalGap="@dimen/key_bottom_gap"
     latin:popupKeyboardTemplate="@xml/kbd_popup_template"
     latin:maxPopupKeyboardColumn="@integer/config_max_popup_keyboard_column"
+    latin:keyboardLocale="sv"
 >
     <include
         latin:keyboardLayout="@xml/kbd_qwerty_rows_scandinavia" />
diff --git a/java/res/xml-xlarge/kbd_ar_rows.xml b/java/res/xml-xlarge/kbd_ar_rows.xml
new file mode 100644
index 0000000..e84aae6
--- /dev/null
+++ b/java/res/xml-xlarge/kbd_ar_rows.xml
@@ -0,0 +1,139 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<!-- This file for Arabic layout is an alpha version. It allows to enter   -->
+<!-- some right-to-left text, but it has gone through no study whatsoever, -->
+<!-- and needs to be run through UX.                                       -->
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <include
+        latin:keyboardLayout="@xml/kbd_key_styles" />
+    <Row
+        latin:keyWidth="7.49%p"
+    >
+        <Key
+            latin:keyStyle="tabKeyStyle"
+            latin:keyLabelOption="alignLeft"
+            latin:keyWidth="7.949%p"
+            latin:keyEdgeFlags="left" />
+        <Key
+            latin:keyLabel="ض" />
+        <Key
+            latin:keyLabel="ص" />
+        <Key
+            latin:keyLabel="ث" />
+        <Key
+            latin:keyLabel="ق" />
+        <Key
+            latin:keyLabel="ف"
+            latin:popupCharacters="ف,ڤ" />
+        <Key
+            latin:keyLabel="غ" />
+        <Key
+            latin:keyLabel="ع" />
+        <Key
+            latin:keyLabel="ه"
+            latin:popupCharacters="ه,هـ" />
+        <Key
+            latin:keyLabel="خ" />
+        <Key
+            latin:keyLabel="ح" />
+        <Key
+            latin:keyLabel="ج"
+            latin:popupCharacters="ج,چ" />
+        <Key
+            latin:keyStyle="deleteKeyStyle"
+            latin:keyWidth="9.331%p"
+            latin:keyEdgeFlags="right" />
+    </Row>
+    <Row
+        latin:keyWidth="7.49%p"
+    >
+        <Key
+            latin:keyStyle="toSymbolKeyStyle"
+            latin:keyLabelOption="alignLeft"
+            latin:keyWidth="7.949%p"
+            latin:keyEdgeFlags="left" />
+        <Key
+            latin:keyLabel="ش" />
+        <Key
+            latin:keyLabel="س" />
+        <Key
+            latin:keyLabel="ي" />
+        <Key
+            latin:keyLabel="ب"
+            latin:popupCharacters="ب,پ" />
+        <Key
+            latin:keyLabel="ل"
+            latin:popupCharacters="ل,لا" />
+        <Key
+            latin:keyLabel="ا"
+            latin:popupCharacters="ا,أ,إ,آ" />
+        <Key
+            latin:keyLabel="ت" />
+        <Key
+            latin:keyLabel="ن" />
+        <Key
+            latin:keyLabel="م" />
+        <Key
+            latin:keyLabel="ك"
+            latin:popupCharacters="ك,گ" />
+        <Key
+            latin:keyLabel="ط" />
+        <Key
+            latin:keyStyle="returnKeyStyle"
+            latin:keyWidth="8.593%p"
+            latin:keyEdgeFlags="right" />
+    </Row>
+    <Row
+        latin:keyWidth="8.042%p"
+    >
+        <Key
+            latin:keyLabel="ئ"
+            latin:keyEdgeFlags="left" />
+        <Key
+            latin:keyLabel="ء" />
+        <Key
+            latin:keyLabel="ؤ" />
+        <Key
+            latin:keyLabel="ر" />
+        <Key
+            latin:keyLabel="ذ" />
+        <Key
+            latin:keyLabel="ى" />
+        <Key
+            latin:keyLabel="ة" />
+        <Key
+            latin:keyLabel="و" />
+        <Key
+            latin:keyLabel="ز"
+            latin:popupCharacters="ز,ژ" />
+        <Key
+            latin:keyLabel="ظ" />
+        <Key
+            latin:keyLabel="د" />
+        <Key
+            latin:keyStyle="shiftKeyStyle"
+            latin:keyWidth="11.736%p"
+            latin:keyEdgeFlags="right" />
+    </Row>
+    <include latin:keyboardLayout="@xml/kbd_qwerty_row4" />
+</merge>
diff --git a/java/res/xml-xlarge/kbd_iw_rows.xml b/java/res/xml-xlarge/kbd_iw_rows.xml
new file mode 100644
index 0000000..a3a239d
--- /dev/null
+++ b/java/res/xml-xlarge/kbd_iw_rows.xml
@@ -0,0 +1,130 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <include
+        latin:keyboardLayout="@xml/kbd_key_styles" />
+    <Row
+        latin:keyWidth="8.272%p"
+    >
+        <Key
+            latin:keyStyle="tabKeyStyle"
+            latin:keyLabelOption="alignLeft"
+            latin:keyWidth="7.949%p"
+            latin:keyEdgeFlags="left" />
+        <Key
+            latin:keyLabel="," />
+        <Key
+            latin:keyLabel="." />
+        <Key
+            latin:keyLabel="ק" />
+        <Key
+            latin:keyLabel="ר" />
+        <Key
+            latin:keyLabel="א" />
+        <Key
+            latin:keyLabel="ט" />
+        <Key
+            latin:keyLabel="ו" />
+        <Key
+            latin:keyLabel="ן" />
+        <Key
+            latin:keyLabel="ם" />
+        <Key
+            latin:keyLabel="פ" />
+        <Key
+            latin:keyStyle="deleteKeyStyle"
+            latin:keyWidth="9.331%p"
+            latin:keyEdgeFlags="right" />
+    </Row>
+    <Row
+        latin:keyWidth="8.157%p"
+    >
+        <Key
+            latin:keyStyle="toSymbolKeyStyle"
+            latin:keyLabelOption="alignLeft"
+            latin:keyWidth="10.167%p"
+            latin:keyEdgeFlags="left" />
+        <Key
+            latin:keyLabel="ש" />
+        <Key
+            latin:keyLabel="ד" />
+        <Key
+            latin:keyLabel="ג"
+            latin:popupCharacters="ג,ג׳" />
+        <Key
+            latin:keyLabel="כ" />
+        <Key
+            latin:keyLabel="ע" />
+        <Key
+            latin:keyLabel="י"
+            latin:popupCharacters="י,ײַ" />
+        <Key
+            latin:keyLabel="ח"
+            latin:popupCharacters="ח,ח׳" />
+        <Key
+            latin:keyLabel="ל" />
+        <Key
+            latin:keyLabel="ך" />
+        <Key
+            latin:keyLabel="ף" />
+        <Key
+            latin:keyStyle="returnKeyStyle"
+            latin:keyWidth="8.593%p"
+            latin:keyEdgeFlags="right" />
+    </Row>
+    <Row
+        latin:keyWidth="8.042%p"
+    >
+        <Key
+            latin:keyStyle="shiftKeyStyle"
+            latin:keyWidth="15.192%p"
+            latin:keyEdgeFlags="left" />
+        <Key
+            latin:keyLabel="ז"
+            latin:popupCharacters="ז,ז׳" />
+        <Key
+            latin:keyLabel="ס" />
+        <Key
+            latin:keyLabel="ב" />
+        <Key
+            latin:keyLabel="ה" />
+        <Key
+            latin:keyLabel="נ" />
+        <Key
+            latin:keyLabel="מ" />
+        <Key
+            latin:keyLabel="צ"
+            latin:popupCharacters="צ,צ׳" />
+        <Key
+            latin:keyLabel="ת"
+            latin:popupCharacters="ת,ת׳" />
+        <Key
+            latin:keyLabel="ץ"
+            latin:popupCharacters="ץ,ץ׳" />
+        <Key
+            latin:keyStyle="shiftKeyStyle"
+            latin:keyWidth="12.530%p"
+            latin:keyEdgeFlags="right" />
+    </Row>
+    <include latin:keyboardLayout="@xml/kbd_qwerty_row4" />
+</merge>
diff --git a/java/res/xml-xlarge/kbd_key_styles.xml b/java/res/xml-xlarge/kbd_key_styles.xml
index fc06d00..57eaccb 100644
--- a/java/res/xml-xlarge/kbd_key_styles.xml
+++ b/java/res/xml-xlarge/kbd_key_styles.xml
@@ -28,7 +28,7 @@
         >
             <key-style
                 latin:styleName="functionalKeyStyle"
-                latin:isModifier="true" />
+                latin:isFunctional="true" />
             <key-style
                 latin:styleName="shiftKeyStyle"
                 latin:code="@integer/key_shift"
@@ -73,7 +73,7 @@
                 latin:parentStyle="functionalKeyStyle" />
             <key-style
                 latin:styleName="micKeyStyle"
-                latin:code="@integer/key_voice"
+                latin:code="@integer/key_shortcut"
                 latin:keyIcon="@drawable/sym_keyboard_voice_holo"
                 latin:iconPreview="@drawable/sym_keyboard_feedback_mic"
                 latin:parentStyle="functionalKeyStyle" />
@@ -127,7 +127,7 @@
                 latin:parentStyle="functionalKeyStyle" />
             <key-style
                 latin:styleName="micKeyStyle"
-                latin:code="@integer/key_voice"
+                latin:code="@integer/key_shortcut"
                 latin:keyIcon="@drawable/sym_bkeyboard_mic"
                 latin:iconPreview="@drawable/sym_keyboard_feedback_mic"
                 latin:parentStyle="functionalKeyStyle" />
diff --git a/java/res/xml-xlarge/kbd_sr_rows.xml b/java/res/xml-xlarge/kbd_sr_rows.xml
index ce9e208..be00585 100644
--- a/java/res/xml-xlarge/kbd_sr_rows.xml
+++ b/java/res/xml-xlarge/kbd_sr_rows.xml
@@ -30,7 +30,7 @@
         <Key
             latin:keyStyle="tabKeyStyle"
             latin:keyLabelOption="alignLeft"
-            latin:keyWidth="7.949%p"
+            latin:keyWidth="8.640%p"
             latin:keyEdgeFlags="left" />
         <Key
             latin:keyLabel="љ"
@@ -66,7 +66,7 @@
             latin:keyLabel="ш" />
         <Key
             latin:keyStyle="deleteKeyStyle"
-            latin:keyWidth="9.331%p"
+            latin:keyWidth="8.640%p"
             latin:keyEdgeFlags="right" />
     </Row>
     <Row
@@ -75,7 +75,7 @@
         <Key
             latin:keyStyle="toSymbolKeyStyle"
             latin:keyLabelOption="alignLeft"
-            latin:keyWidth="7.949%p"
+            latin:keyWidth="8.640%p"
             latin:keyEdgeFlags="left" />
         <Key
             latin:keyLabel="а" />
@@ -101,7 +101,7 @@
             latin:keyLabel="ћ" />
         <Key
             latin:keyStyle="returnKeyStyle"
-            latin:keyWidth="9.331%p"
+            latin:keyWidth="8.640%p"
             latin:keyEdgeFlags="right" />
     </Row>
     <Row
@@ -109,12 +109,10 @@
     >
         <Key
             latin:keyStyle="shiftKeyStyle"
-            latin:keyWidth="12.400%p"
+            latin:keyWidth="8.640%p"
             latin:keyEdgeFlags="left" />
         <Key
-            latin:keyLabel="ђ" />
-        <Key
-            latin:keyLabel="ж" />
+            latin:keyLabel="ѕ" />
         <Key
             latin:keyLabel="џ" />
         <Key
@@ -128,6 +126,10 @@
         <Key
             latin:keyLabel="м" />
         <Key
+            latin:keyLabel="ђ" />
+        <Key
+            latin:keyLabel="ж" />
+        <Key
             latin:keyLabel=","
             latin:manualTemporaryUpperCaseCode="33"
             latin:keyHintIcon="@drawable/key_hint_exclamation_holo"
@@ -141,7 +143,7 @@
             latin:popupCharacters="\?" />
         <Key
             latin:keyStyle="shiftKeyStyle"
-            latin:keyWidth="12.400%p"
+            latin:keyWidth="8.640%p"
             latin:keyEdgeFlags="right" />
     </Row>
     <include
diff --git a/java/res/xml-xlarge/kbd_symbols.xml b/java/res/xml-xlarge/kbd_symbols.xml
index 1061178..f1deae0 100644
--- a/java/res/xml-xlarge/kbd_symbols.xml
+++ b/java/res/xml-xlarge/kbd_symbols.xml
@@ -211,9 +211,10 @@
                     latin:keyLabel="-" />
             </case>
             <default>
+                <!-- Note: DroidSans doesn't have double-high-reversed-quotation '\u201f' glyph. -->
                 <Key
                     latin:keyLabel="&quot;"
-                    latin:popupCharacters="“,”,«,»,˝" />
+                    latin:popupCharacters="“,”,„,‟,«,»,‘,’,‚,‛" />
                 <Key
                     latin:keyLabel="_" />
             </default>
diff --git a/java/res/xml-xlarge/kbd_symbols_shift.xml b/java/res/xml-xlarge/kbd_symbols_shift.xml
index 8359b75..cc23358 100644
--- a/java/res/xml-xlarge/kbd_symbols_shift.xml
+++ b/java/res/xml-xlarge/kbd_symbols_shift.xml
@@ -99,7 +99,8 @@
             latin:popupCharacters="↑,↓,←,→" />
         <Key
             latin:keyStyle="nonPasswordSymbolKeyStyle"
-            latin:keyLabel="°" />
+            latin:keyLabel="°"
+            latin:popupCharacters="′,″" />
         <Key
             latin:keyStyle="nonPasswordSymbolKeyStyle"
             latin:keyLabel="±"
diff --git a/java/res/xml/kbd_ar_rows.xml b/java/res/xml/kbd_ar_rows.xml
new file mode 100644
index 0000000..b2ea457
--- /dev/null
+++ b/java/res/xml/kbd_ar_rows.xml
@@ -0,0 +1,119 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<!-- This file for Arabic layout is an alpha version. It allows to enter   -->
+<!-- some right-to-left text, but it has gone through no study whatsoever, -->
+<!-- and needs to be run through UX.                                       -->
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <include
+        latin:keyboardLayout="@xml/kbd_key_styles" />
+    <Row
+        latin:keyWidth="10%p"
+    >
+        <Key
+            latin:keyLabel="ض"
+            latin:keyEdgeFlags="left" />
+        <Key
+            latin:keyLabel="ص" />
+        <Key
+            latin:keyLabel="ق" />
+        <Key
+            latin:keyLabel="ف"
+            latin:popupCharacters="ڤ" />
+        <Key
+            latin:keyLabel="غ" />
+        <Key
+            latin:keyLabel="ع" />
+        <Key
+            latin:keyLabel="ه"
+            latin:popupCharacters="هـ" />
+        <Key
+            latin:keyLabel="خ" />
+        <Key
+            latin:keyLabel="ح" />
+        <Key
+            latin:keyLabel="ج"
+            latin:popupCharacters="چ"
+            latin:keyEdgeFlags="right" />
+    </Row>
+    <Row
+        latin:keyWidth="10%p"
+    >
+        <Key
+            latin:keyLabel="ش"
+            latin:keyEdgeFlags="left" />
+        <Key
+            latin:keyLabel="س" />
+        <Key
+            latin:keyLabel="ي"
+            latin:popupCharacters="ى,ئ" />
+        <Key
+            latin:keyLabel="ب"
+            latin:popupCharacters="پ" />
+        <Key
+            latin:keyLabel="ل"
+            latin:popupCharacters="لا" />
+        <Key
+            latin:keyLabel="ا"
+            latin:popupCharacters="أ,إ,آ,ء" />
+        <Key
+            latin:keyLabel="ت" />
+        <Key
+            latin:keyLabel="ن" />
+        <Key
+            latin:keyLabel="م" />
+        <Key
+            latin:keyLabel="ك"
+            latin:popupCharacters="گ"
+            latin:keyEdgeFlags="right" />
+    </Row>
+    <Row
+        latin:keyWidth="9.7%p"
+    >
+        <Key
+            latin:keyLabel="ظ"
+            latin:keyEdgeFlags="left" />
+        <Key
+            latin:keyLabel="ط" />
+        <Key
+            latin:keyLabel="ذ" />
+        <Key
+            latin:keyLabel="د" />
+        <Key
+            latin:keyLabel="ز"
+            latin:popupCharacters="ژ" />
+        <Key
+            latin:keyLabel="ر" />
+        <Key
+            latin:keyLabel="و"
+            latin:popupCharacters="ؤ" />
+        <Key
+            latin:keyLabel="ة" />
+        <Key
+            latin:keyLabel="ث" />
+        <Key
+            latin:keyStyle="deleteKeyStyle"
+            latin:keyWidth="12%p"
+            latin:keyEdgeFlags="right" />
+    </Row>
+    <include latin:keyboardLayout="@xml/kbd_qwerty_row4" />
+</merge>
diff --git a/java/res/xml/kbd_azerty_rows.xml b/java/res/xml/kbd_azerty_rows.xml
index 9eeb22e..ab3e1a0 100644
--- a/java/res/xml/kbd_azerty_rows.xml
+++ b/java/res/xml/kbd_azerty_rows.xml
@@ -108,6 +108,7 @@
         <Key
             latin:keyStyle="shiftKeyStyle"
             latin:keyWidth="15%p"
+            latin:visualInsetsRight="1%p"
             latin:keyEdgeFlags="left" />
         <Key
             latin:keyLabel="w"
@@ -131,6 +132,7 @@
         <Key
             latin:keyStyle="deleteKeyStyle"
             latin:keyWidth="15%p"
+            latin:visualInsetsLeft="1%p"
             latin:keyEdgeFlags="right" />
     </Row>
     <include
diff --git a/java/res/xml/kbd_iw_rows.xml b/java/res/xml/kbd_iw_rows.xml
new file mode 100644
index 0000000..fb0c2a9
--- /dev/null
+++ b/java/res/xml/kbd_iw_rows.xml
@@ -0,0 +1,106 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<!-- This file for Hebrew layout is an alpha version. It allows to enter   -->
+<!-- some right-to-left text, but it has gone through no study whatsoever, -->
+<!-- and needs to be run through UX.                                       -->
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <include
+        latin:keyboardLayout="@xml/kbd_key_styles" />
+    <Row
+        latin:rowEdgeFlags="top"
+    >
+        <Spacer
+            latin:horizontalGap="5%p" />
+        <Key
+            latin:keyLabel="ק"
+            latin:keyEdgeFlags="left" />
+        <Key
+            latin:keyLabel="ר" />
+        <Key
+            latin:keyLabel="א" />
+        <Key
+            latin:keyLabel="ט" />
+        <Key
+            latin:keyLabel="ו" />
+        <Key
+            latin:keyLabel="ן" />
+        <Key
+            latin:keyLabel="ם" />
+        <Key
+            latin:keyLabel="פ" />
+        <Key
+            latin:keyStyle="deleteKeyStyle"
+            latin:keyWidth="15%p"
+            latin:visualInsetsLeft="1%p"
+            latin:keyEdgeFlags="right" />
+    </Row>
+    <Row>
+        <Key
+            latin:keyLabel="ש"
+            latin:keyEdgeFlags="left" />
+        <Key
+            latin:keyLabel="ד" />
+        <Key
+            latin:keyLabel="ג" />
+        <Key
+            latin:keyLabel="כ" />
+        <Key
+            latin:keyLabel="ע" />
+        <Key
+            latin:keyLabel="י" />
+        <Key
+            latin:keyLabel="ח" />
+        <Key
+            latin:keyLabel="ל" />
+        <Key
+            latin:keyLabel="ך" />
+        <Key
+            latin:keyLabel="ף"
+            latin:keyEdgeFlags="right" />
+    </Row>
+    <Row>
+        <Spacer
+            latin:horizontalGap="5%p" />
+        <Key
+            latin:keyLabel="ז"
+            latin:keyEdgeFlags="left" />
+        <Key
+            latin:keyLabel="ס" />
+        <Key
+            latin:keyLabel="ב" />
+        <Key
+            latin:keyLabel="ה" />
+        <Key
+            latin:keyLabel="נ" />
+        <Key
+            latin:keyLabel="מ" />
+        <Key
+            latin:keyLabel="צ" />
+        <Key
+            latin:keyLabel="ת" />
+        <Key
+            latin:keyLabel="ץ"
+            latin:keyEdgeFlags="right" />
+    </Row>
+    <include latin:keyboardLayout="@xml/kbd_qwerty_row4" />
+</merge>
diff --git a/java/res/xml/kbd_key_styles.xml b/java/res/xml/kbd_key_styles.xml
index 473510e..d4d25d4 100644
--- a/java/res/xml/kbd_key_styles.xml
+++ b/java/res/xml/kbd_key_styles.xml
@@ -21,15 +21,46 @@
 <merge
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
 >
+    <!-- Base key style for the functional key -->
+    <switch>
+       <case
+            latin:colorScheme="white"
+        >
+            <key-style
+                latin:styleName="functionalKeyStyle"
+                latin:isFunctional="true" />
+        </case>
+        <case
+            latin:colorScheme="black"
+        >
+            <key-style
+                latin:styleName="functionalKeyStyle" />
+        </case>
+    </switch>
+    <!-- Base key style for the key which may have settings key as popup key -->
+    <switch>
+        <case
+            latin:hasSettingsKey="true"
+        >
+            <key-style
+                latin:styleName="settingsPopupStyle"
+                latin:parentStyle="functionalKeyStyle" />
+        </case>
+        <!-- latin:hasSettingsKey="false" -->
+        <default>
+            <key-style
+                latin:styleName="settingsPopupStyle"
+                latin:keyHintIcon="@drawable/hint_popup"
+                latin:popupCharacters="\@drawable/sym_keyboard_settings|\@integer/key_settings"
+                latin:parentStyle="functionalKeyStyle" />
+        </default>
+    </switch>
     <!-- Functional key styles -->
     <switch>
         <case
             latin:colorScheme="white"
         >
             <key-style
-                latin:styleName="functionalKeyStyle"
-                latin:isModifier="true" />
-            <key-style
                 latin:styleName="shiftKeyStyle"
                 latin:code="@integer/key_shift"
                 latin:keyIcon="@drawable/sym_keyboard_shift"
@@ -87,12 +118,10 @@
                 latin:parentStyle="functionalKeyStyle" />
             <key-style
                 latin:styleName="micKeyStyle"
-                latin:code="@integer/key_voice"
+                latin:code="@integer/key_shortcut"
                 latin:keyIcon="@drawable/sym_keyboard_mic"
                 latin:iconPreview="@drawable/sym_keyboard_feedback_mic"
-                latin:keyHintIcon="@drawable/hint_popup"
-                latin:popupCharacters="@string/alternates_for_mic"
-                latin:parentStyle="functionalKeyStyle" />
+                latin:parentStyle="settingsPopupStyle" />
             <!-- Note: This key style is not for functional tab key. This is used for the tab key
                  which is laid out as normal letter key. -->
             <key-style
@@ -105,8 +134,6 @@
             latin:colorScheme="black"
         >
             <key-style
-                latin:styleName="functionalKeyStyle" />
-            <key-style
                 latin:styleName="shiftKeyStyle"
                 latin:code="@integer/key_shift"
                 latin:keyIcon="@drawable/sym_bkeyboard_shift"
@@ -164,12 +191,10 @@
                 latin:parentStyle="functionalKeyStyle" />
             <key-style
                 latin:styleName="micKeyStyle"
-                latin:code="@integer/key_voice"
+                latin:code="@integer/key_shortcut"
                 latin:keyIcon="@drawable/sym_bkeyboard_mic"
                 latin:iconPreview="@drawable/sym_keyboard_feedback_mic"
-                latin:keyHintIcon="@drawable/hint_popup"
-                latin:popupCharacters="@string/alternates_for_mic"
-                latin:parentStyle="functionalKeyStyle" />
+                latin:parentStyle="settingsPopupStyle" />
             <!-- Note: This key style is not for functional tab key. This is used for the tab key
                  which is laid out as normal letter key. -->
             <key-style
@@ -287,4 +312,27 @@
         latin:popupCharacters="@string/alternates_for_smiley"
         latin:maxPopupKeyboardColumn="5"
         latin:parentStyle="functionalKeyStyle" />
+    <switch>
+        <case
+            latin:passwordInput="true"
+        >
+            <key-style
+                latin:styleName="nonPasswordSymbolKeyStyle"
+                latin:enabled="false" />
+            <key-style
+                latin:styleName="nonPasswordFunctionalKeyStyle"
+                latin:enabled="false"
+                latin:parentStyle="functionalKeyStyle" />
+        </case>
+        <!-- latin:passwordInput="false" -->
+        <default>
+            <key-style
+                latin:styleName="nonPasswordSymbolKeyStyle"
+                latin:enabled="true" />
+            <key-style
+                latin:styleName="nonPasswordFunctionalKeyStyle"
+                latin:enabled="true"
+                latin:parentStyle="functionalKeyStyle" />
+        </default>
+    </switch>
 </merge>
\ No newline at end of file
diff --git a/java/res/xml/kbd_qwerty.xml b/java/res/xml/kbd_qwerty.xml
index 92d92f0..a4251c0 100644
--- a/java/res/xml/kbd_qwerty.xml
+++ b/java/res/xml/kbd_qwerty.xml
@@ -28,6 +28,7 @@
     latin:verticalGap="@dimen/key_bottom_gap"
     latin:popupKeyboardTemplate="@xml/kbd_popup_template"
     latin:maxPopupKeyboardColumn="@integer/config_max_popup_keyboard_column"
+    latin:keyboardLocale="en_GB,en_US"
 >
     <include
         latin:keyboardLayout="@xml/kbd_qwerty_rows" />
diff --git a/java/res/xml/kbd_qwerty_black_symbol.xml b/java/res/xml/kbd_qwerty_black_symbol.xml
deleted file mode 100644
index 6e45c12..0000000
--- a/java/res/xml/kbd_qwerty_black_symbol.xml
+++ /dev/null
@@ -1,84 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2010, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <switch>
-        <case
-            latin:hasSettingsKey="false"
-        >
-            <switch>
-                <!-- When this qwerty keyboard has no voice key but voice key is enabled, then
-                     symbol keyboard will have mic key. That means we should use "?123mic" key here.
-                     -->
-                <case
-                    latin:voiceKeyEnabled="true"
-                    latin:hasVoiceKey="false"
-                >
-                    <Key
-                        latin:code="@integer/key_switch_alpha_symbol"
-                        latin:keyIcon="@drawable/sym_bkeyboard_123_mic"
-                        latin:iconPreview="@drawable/sym_keyboard_feedback_123_mic"
-                        latin:keyWidth="20%p"
-                        latin:isModifier="true"
-                        latin:keyEdgeFlags="left" />
-                </case>
-                <default>
-                    <Key
-                        latin:code="@integer/key_switch_alpha_symbol"
-                        latin:keyLabel="@string/label_to_symbol_key"
-                        latin:keyWidth="20%p"
-                        latin:isModifier="true"
-                        latin:keyEdgeFlags="left" />
-                </default>
-            </switch>
-        </case>
-        <case
-            latin:hasSettingsKey="true"
-        >
-            <switch>
-                <!-- When this qwerty keyboard has no voice key but voice key is enabled, then
-                     symbol keyboard will have mic key. That means we should use "?123mic" key here.
-                     -->
-                <case
-                    latin:voiceKeyEnabled="true"
-                    latin:hasVoiceKey="false"
-                >
-                    <Key
-                        latin:code="@integer/key_switch_alpha_symbol"
-                        latin:keyIcon="@drawable/sym_bkeyboard_123_mic"
-                        latin:iconPreview="@drawable/sym_keyboard_feedback_123_mic"
-                        latin:keyWidth="15%p"
-                        latin:isModifier="true"
-                        latin:keyEdgeFlags="left" />
-                </case>
-                <default>
-                    <Key
-                        latin:code="@integer/key_switch_alpha_symbol"
-                        latin:keyLabel="@string/label_to_symbol_key"
-                        latin:keyWidth="15%p"
-                        latin:isModifier="true"
-                        latin:keyEdgeFlags="left" />
-                </default>
-            </switch>
-        </case>
-    </switch>
-</merge>
diff --git a/java/res/xml/kbd_qwerty_f1.xml b/java/res/xml/kbd_qwerty_f1.xml
index cbdb8c0..d0e2884 100644
--- a/java/res/xml/kbd_qwerty_f1.xml
+++ b/java/res/xml/kbd_qwerty_f1.xml
@@ -27,18 +27,14 @@
         >
             <Key
                 latin:keyLabel="/"
-                latin:keyHintIcon="@drawable/hint_popup"
-                latin:popupCharacters="@string/alternates_for_settings_slash"
-                latin:isModifier="true" />
+                latin:keyStyle="settingsPopupStyle" />
         </case>
         <case
             latin:mode="email"
         >
             <Key
                 latin:keyLabel="\@"
-                latin:keyHintIcon="@drawable/hint_popup"
-                latin:popupCharacters="@string/alternates_for_settings_at"
-                latin:isModifier="true" />
+                latin:keyStyle="settingsPopupStyle" />
         </case>
         <default>
             <switch>
@@ -48,15 +44,19 @@
                     <Key
                         latin:keyStyle="micKeyStyle" />
                 </case>
+                <!-- latin:hasVoiceKey="false" -->
                 <case
-                    latin:hasVoiceKey="false"
+                    latin:mode="web"
                 >
                     <Key
-                        latin:keyLabel=","
-                        latin:keyHintIcon="@drawable/hint_popup"
-                        latin:popupCharacters="@string/alternates_for_settings_comma"
-                        latin:isModifier="true" />
+                        latin:keyLabel="."
+                        latin:keyStyle="settingsPopupStyle" />
                 </case>
+                <default>
+                    <Key
+                        latin:keyLabel=","
+                        latin:keyStyle="settingsPopupStyle" />
+                </default>
             </switch>
         </default>
     </switch>
diff --git a/java/res/xml/kbd_qwerty_row3.xml b/java/res/xml/kbd_qwerty_row3.xml
index 26608fd..3d106e6 100644
--- a/java/res/xml/kbd_qwerty_row3.xml
+++ b/java/res/xml/kbd_qwerty_row3.xml
@@ -27,6 +27,7 @@
         <Key
             latin:keyStyle="shiftKeyStyle"
             latin:keyWidth="15%p"
+            latin:visualInsetsRight="1%p"
             latin:keyEdgeFlags="left" />
         <Key
             latin:keyLabel="z"
@@ -49,6 +50,7 @@
         <Key
             latin:keyStyle="deleteKeyStyle"
             latin:keyWidth="15%p"
+            latin:visualInsetsLeft="1%p"
             latin:keyEdgeFlags="right" />
     </Row>
 </merge>
diff --git a/java/res/xml/kbd_qwerty_row4.xml b/java/res/xml/kbd_qwerty_row4.xml
index 0db0116..82f5a4a 100644
--- a/java/res/xml/kbd_qwerty_row4.xml
+++ b/java/res/xml/kbd_qwerty_row4.xml
@@ -35,29 +35,28 @@
                     latin:keyEdgeFlags="left" />
                 <include
                     latin:keyboardLayout="@xml/kbd_qwerty_f1" />
+                <Key
+                    latin:keyStyle="spaceKeyStyle"
+                    latin:keyWidth="40%p" />
                 <switch>
                     <case
                         latin:mode="web"
                     >
-                        <Key
-                            latin:keyStyle="spaceKeyStyle"
-                            latin:keyWidth="20%p" />
-                        <Key
-                            latin:keyStyle="tabKeyStyle"
-                            latin:keyWidth="20%p" />
+                         <Key
+                            latin:keyHintIcon="@drawable/hint_popup"
+                            latin:popupCharacters="@string/alternates_for_web_tab_punctuation"
+                            latin:maxPopupKeyboardColumn="8"
+                            latin:keyStyle="tabKeyStyle" />
                     </case>
                     <default>
                         <Key
-                            latin:keyStyle="spaceKeyStyle"
-                            latin:keyWidth="40%p" />
+                            latin:keyLabel="."
+                            latin:keyHintIcon="@drawable/hint_popup"
+                            latin:popupCharacters="@string/alternates_for_punctuation"
+                            latin:maxPopupKeyboardColumn="7"
+                            latin:keyStyle="functionalKeyStyle" />
                     </default>
                 </switch>
-                <Key
-                    latin:keyLabel="."
-                    latin:keyHintIcon="@drawable/hint_popup"
-                    latin:popupCharacters="@string/alternates_for_punctuation"
-                    latin:maxPopupKeyboardColumn="7"
-                    latin:keyStyle="functionalKeyStyle" />
                 <switch>
                     <case
                         latin:mode="im"
@@ -86,28 +85,28 @@
                     latin:keyStyle="settingsKeyStyle" />
                 <include
                     latin:keyboardLayout="@xml/kbd_qwerty_f1" />
+                <Key
+                    latin:keyStyle="spaceKeyStyle"
+                    latin:keyWidth="30%p" />
                 <switch>
                     <case
                         latin:mode="web"
                     >
-                        <Key
-                            latin:keyStyle="spaceKeyStyle"
-                            latin:keyWidth="30%p" />
-                        <Key
+                         <Key
+                            latin:keyHintIcon="@drawable/hint_popup"
+                            latin:popupCharacters="@string/alternates_for_web_tab_punctuation"
+                            latin:maxPopupKeyboardColumn="8"
                             latin:keyStyle="tabKeyStyle" />
                     </case>
                     <default>
                         <Key
-                            latin:keyStyle="spaceKeyStyle"
-                            latin:keyWidth="30%p" />
+                            latin:keyLabel="."
+                            latin:keyHintIcon="@drawable/hint_popup"
+                            latin:popupCharacters="@string/alternates_for_punctuation"
+                            latin:maxPopupKeyboardColumn="7"
+                            latin:keyStyle="functionalKeyStyle" />
                     </default>
                 </switch>
-                <Key
-                    latin:keyLabel="."
-                    latin:keyHintIcon="@drawable/hint_popup"
-                    latin:popupCharacters="@string/alternates_for_punctuation"
-                    latin:maxPopupKeyboardColumn="7"
-                    latin:keyStyle="functionalKeyStyle" />
                 <switch>
                     <case
                         latin:mode="im"
@@ -117,14 +116,6 @@
                             latin:keyWidth="25%p"
                             latin:keyEdgeFlags="right" />
                     </case>
-                    <case
-                        latin:mode="web"
-                    >
-                        <Key
-                            latin:keyStyle="returnKeyStyle"
-                            latin:keyWidth="15%p"
-                            latin:keyEdgeFlags="right" />
-                    </case>
                     <default>
                         <Key
                             latin:keyStyle="returnKeyStyle"
diff --git a/java/res/xml/kbd_qwertz_rows.xml b/java/res/xml/kbd_qwertz_rows.xml
index 375f123..bb41f06 100644
--- a/java/res/xml/kbd_qwertz_rows.xml
+++ b/java/res/xml/kbd_qwertz_rows.xml
@@ -78,6 +78,7 @@
         <Key
             latin:keyStyle="shiftKeyStyle"
             latin:keyWidth="15%p"
+            latin:visualInsetsRight="1%p"
             latin:keyEdgeFlags="left" />
         <Key
             latin:keyLabel="y"
@@ -100,6 +101,7 @@
         <Key
             latin:keyStyle="deleteKeyStyle"
             latin:keyWidth="15%p"
+            latin:visualInsetsLeft="1%p"
             latin:keyEdgeFlags="right" />
     </Row>
    <include
diff --git a/java/res/xml/kbd_sr_rows.xml b/java/res/xml/kbd_sr_rows.xml
index 4a5ed11..8d6b070 100644
--- a/java/res/xml/kbd_sr_rows.xml
+++ b/java/res/xml/kbd_sr_rows.xml
@@ -73,7 +73,7 @@
             latin:keyEdgeFlags="right" />
     </Row>
     <Row
-        latin:keyWidth="8.333%p"
+        latin:keyWidth="9.09%p"
     >
         <Key
             latin:keyLabel="а"
@@ -97,20 +97,17 @@
         <Key
             latin:keyLabel="ч" />
         <Key
-            latin:keyLabel="ћ" />
-        <Key
-            latin:keyLabel="ђ"
+            latin:keyLabel="ћ"
             latin:keyEdgeFlags="right" />
     </Row>
     <Row
-        latin:keyWidth="8.5%p"
+        latin:keyWidth="8.90%p"
     >
         <Key
             latin:keyStyle="shiftKeyStyle"
-            latin:keyWidth="11.75%p"
             latin:keyEdgeFlags="left" />
         <Key
-            latin:keyLabel="ж" />
+            latin:keyLabel="ѕ" />
         <Key
             latin:keyLabel="џ" />
         <Key
@@ -124,8 +121,12 @@
         <Key
             latin:keyLabel="м" />
         <Key
+            latin:keyLabel="ђ" />
+        <Key
+            latin:keyLabel="ж" />
+        <Key
             latin:keyStyle="deleteKeyStyle"
-            latin:keyWidth="11.75%p"
+            latin:keyWidth="11.00%p"
             latin:keyEdgeFlags="right" />
     </Row>
     <include
diff --git a/java/res/xml/kbd_symbols.xml b/java/res/xml/kbd_symbols.xml
index b3b3f4e..9748bce 100644
--- a/java/res/xml/kbd_symbols.xml
+++ b/java/res/xml/kbd_symbols.xml
@@ -100,16 +100,19 @@
         <Key
             latin:keyStyle="altKeyStyle"
             latin:keyWidth="15%p"
+            latin:visualInsetsRight="1%p"
             latin:keyEdgeFlags="left" />
         <Key
             latin:keyLabel="!"
             latin:popupCharacters="¡" />
+        <!-- Note: DroidSans doesn't have double-high-reversed-quotation '\u201f' glyph. -->
         <Key
             latin:keyLabel="&quot;"
-            latin:popupCharacters="“,”,«,»,˝" />
+            latin:popupCharacters="“,”,„,‟,«,»"
+            latin:maxPopupKeyboardColumn="6" />
         <Key
             latin:keyLabel="\'"
-            latin:popupCharacters="‘,’" />
+            latin:popupCharacters="‘,’,‚,‛,´" />
         <Key
             latin:keyLabel=":" />
         <Key
@@ -122,6 +125,7 @@
         <Key
             latin:keyStyle="deleteKeyStyle"
             latin:keyWidth="15%p"
+            latin:visualInsetsLeft="1%p"
             latin:keyEdgeFlags="right" />
     </Row>
     <include latin:keyboardLayout="@xml/kbd_symbols_row4" />
diff --git a/java/res/xml/kbd_symbols_f1.xml b/java/res/xml/kbd_symbols_f1.xml
index 8487b61..0fb7136 100644
--- a/java/res/xml/kbd_symbols_f1.xml
+++ b/java/res/xml/kbd_symbols_f1.xml
@@ -28,14 +28,11 @@
             <Key
                 latin:keyStyle="micKeyStyle" />
         </case>
-        <case
-            latin:hasVoiceKey="false"
-        >
+        <!-- latin:hasVoiceKey="false" -->
+        <default>
             <Key
                 latin:keyLabel=","
-                latin:keyHintIcon="@drawable/hint_popup"
-                latin:popupCharacters="@string/alternates_for_settings_comma"
-                latin:keyStyle="functionalKeyStyle" />
-        </case>
+                latin:keyStyle="settingsPopupStyle" />
+        </default>
     </switch>
 </merge>
diff --git a/java/res/xml/kbd_symbols_shift.xml b/java/res/xml/kbd_symbols_shift.xml
index 368ee80..3978f17 100644
--- a/java/res/xml/kbd_symbols_shift.xml
+++ b/java/res/xml/kbd_symbols_shift.xml
@@ -42,16 +42,21 @@
         <Key
             latin:keyLabel="|" />
         <Key
+            latin:keyStyle="nonPasswordSymbolKeyStyle"
             latin:keyLabel="•"
             latin:popupCharacters="♪,♥,♠,♦,♣" />
         <Key
+            latin:keyStyle="nonPasswordSymbolKeyStyle"
             latin:keyLabel="√" />
         <Key
+            latin:keyStyle="nonPasswordSymbolKeyStyle"
             latin:keyLabel="π"
             latin:popupCharacters="Π" />
         <Key
+            latin:keyStyle="nonPasswordSymbolKeyStyle"
             latin:keyLabel="÷" />
         <Key
+            latin:keyStyle="nonPasswordSymbolKeyStyle"
             latin:keyLabel="×" />
         <Key
             latin:keyLabel="{" />
@@ -64,13 +69,18 @@
             latin:keyStyle="nonSpecialBackgroundTabKeyStyle"
             latin:keyEdgeFlags="left" />
         <Key
+            latin:keyStyle="nonPasswordSymbolKeyStyle"
             latin:keyLabel="£" />
         <Key
+            latin:keyStyle="nonPasswordSymbolKeyStyle"
             latin:keyLabel="¢" />
         <Key
+            latin:keyStyle="nonPasswordSymbolKeyStyle"
             latin:keyLabel="€" />
         <Key
-            latin:keyLabel="°" />
+            latin:keyStyle="nonPasswordSymbolKeyStyle"
+            latin:keyLabel="°"
+            latin:popupCharacters="′,″" />
         <Key
             latin:keyLabel="^"
             latin:popupCharacters="↑,↓,←,→" />
@@ -89,14 +99,19 @@
         <Key
             latin:keyStyle="shiftKeyStyle"
             latin:keyWidth="15%p"
+            latin:visualInsetsRight="1%p"
             latin:keyEdgeFlags="left" />
         <Key
+            latin:keyStyle="nonPasswordSymbolKeyStyle"
             latin:keyLabel="™" />
         <Key
+            latin:keyStyle="nonPasswordSymbolKeyStyle"
             latin:keyLabel="®" />
         <Key
+            latin:keyStyle="nonPasswordSymbolKeyStyle"
             latin:keyLabel="©" />
         <Key
+            latin:keyStyle="nonPasswordSymbolKeyStyle"
             latin:keyLabel="¶"
             latin:popupCharacters="§" />
         <Key
@@ -110,6 +125,7 @@
         <Key
             latin:keyStyle="deleteKeyStyle"
             latin:keyWidth="15%p"
+            latin:visualInsetsLeft="1%p"
             latin:keyEdgeFlags="right" />
     </Row>
     <include latin:keyboardLayout="@xml/kbd_symbols_shift_row4" />
diff --git a/java/res/xml/kbd_symbols_shift_row4.xml b/java/res/xml/kbd_symbols_shift_row4.xml
index 9159bab..4f8567d 100644
--- a/java/res/xml/kbd_symbols_shift_row4.xml
+++ b/java/res/xml/kbd_symbols_shift_row4.xml
@@ -34,13 +34,14 @@
                     latin:keyEdgeFlags="left" />
                 <Key
                     latin:keyLabel="„"
-                    latin:keyStyle="functionalKeyStyle" />
+                    latin:popupCharacters="“,”,„,‟,«,»,‘,’,‚,‛"
+                    latin:keyStyle="nonPasswordFunctionalKeyStyle" />
                 <Key
                     latin:keyStyle="spaceKeyStyle"
                     latin:keyWidth="40%p" />
                 <Key
                     latin:keyLabel="…"
-                    latin:keyStyle="functionalKeyStyle" />
+                    latin:keyStyle="nonPasswordFunctionalKeyStyle" />
                 <switch>
                     <case
                         latin:mode="im"
@@ -69,13 +70,14 @@
                     latin:keyStyle="settingsKeyStyle" />
                 <Key
                     latin:keyLabel="„"
-                    latin:keyStyle="functionalKeyStyle" />
+                    latin:popupCharacters="“,”,„,‟,«,»,‘,’,‚,‛"
+                    latin:keyStyle="nonPasswordFunctionalKeyStyle" />
                 <Key
                     latin:keyStyle="spaceKeyStyle"
                     latin:keyWidth="30%p" />
                 <Key
                     latin:keyLabel="…"
-                    latin:keyStyle="functionalKeyStyle" />
+                    latin:keyStyle="nonPasswordFunctionalKeyStyle" />
                 <switch>
                     <case
                         latin:mode="im"
diff --git a/java/res/xml/method.xml b/java/res/xml/method.xml
index 8dec7ab..df43701 100644
--- a/java/res/xml/method.xml
+++ b/java/res/xml/method.xml
@@ -24,6 +24,7 @@
 <!-- Voice: af, cs, da, de, en, es, fr, it, ja, ko, nl, pl, pt, ru, tr, yue, zh, zu -->
 <!-- TODO: use <lang>_keyboard icon instead of a common keyboard icon. -->
 <!-- TODO: use <lang>_mic icon instead of a common mic icon. -->
+<!-- TODO: remove all comment outed voice subtypes -->
 <!-- If IME doesn't have an applicable subtype, the first subtype will be used as a default
      subtype.-->
 <input-method xmlns:android="http://schemas.android.com/apk/res/android"
@@ -34,28 +35,34 @@
             android:imeSubtypeLocale="en_US"
             android:imeSubtypeMode="keyboard"
     />
-    <subtype android:icon="@drawable/ic_subtype_mic"
-            android:label="@string/subtype_mode_en_voice"
-            android:imeSubtypeLocale="en"
-            android:imeSubtypeMode="voice"
-            android:imeSubtypeExtraValue="excludeFromLastInputMethod,requireNetworkConnectivity"
-    />
+<!--     <subtype android:icon="@drawable/ic_subtype_mic" -->
+<!--             android:label="@string/subtype_mode_en_voice" -->
+<!--             android:imeSubtypeLocale="en" -->
+<!--             android:imeSubtypeMode="voice" -->
+<!--             android:imeSubtypeExtraValue="excludeFromLastInputMethod,requireNetworkConnectivity" -->
+<!--     /> -->
     <subtype android:icon="@drawable/ic_subtype_keyboard"
             android:label="@string/subtype_mode_en_GB_keyboard"
             android:imeSubtypeLocale="en_GB"
             android:imeSubtypeMode="keyboard"
     />
+    <!-- The file for Arabic layout is an alpha version. It needs to be run through UX. -->
+    <subtype android:icon="@drawable/ic_subtype_keyboard"
+            android:label="@string/subtype_mode_ar_keyboard"
+            android:imeSubtypeLocale="ar"
+            android:imeSubtypeMode="keyboard"
+    />
     <subtype android:icon="@drawable/ic_subtype_keyboard"
             android:label="@string/subtype_mode_cs_keyboard"
             android:imeSubtypeLocale="cs"
             android:imeSubtypeMode="keyboard"
     />
-    <subtype android:icon="@drawable/ic_subtype_mic"
-            android:label="@string/subtype_mode_cs_voice"
-            android:imeSubtypeLocale="cs"
-            android:imeSubtypeMode="voice"
-            android:imeSubtypeExtraValue="excludeFromLastInputMethod,requireNetworkConnectivity"
-    />
+<!--     <subtype android:icon="@drawable/ic_subtype_mic" -->
+<!--             android:label="@string/subtype_mode_cs_voice" -->
+<!--             android:imeSubtypeLocale="cs" -->
+<!--             android:imeSubtypeMode="voice" -->
+<!--             android:imeSubtypeExtraValue="excludeFromLastInputMethod,requireNetworkConnectivity" -->
+<!--     /> -->
     <subtype android:icon="@drawable/ic_subtype_keyboard"
             android:label="@string/subtype_mode_da_keyboard"
             android:imeSubtypeLocale="da"
@@ -65,36 +72,35 @@
             android:label="@string/subtype_mode_de_keyboard"
             android:imeSubtypeLocale="de"
             android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="requiresGermanUmlautProcessing"
     />
-    <subtype android:icon="@drawable/ic_subtype_mic"
-            android:label="@string/subtype_mode_de_voice"
-            android:imeSubtypeLocale="de"
-            android:imeSubtypeMode="voice"
-            android:imeSubtypeExtraValue="excludeFromLastInputMethod,requireNetworkConnectivity"
-    />
+<!--     <subtype android:icon="@drawable/ic_subtype_mic" -->
+<!--             android:label="@string/subtype_mode_de_voice" -->
+<!--             android:imeSubtypeLocale="de" -->
+<!--             android:imeSubtypeMode="voice" -->
+<!--             android:imeSubtypeExtraValue="excludeFromLastInputMethod,requireNetworkConnectivity" -->
+<!--     /> -->
     <subtype android:icon="@drawable/ic_subtype_keyboard"
             android:label="@string/subtype_mode_es_keyboard"
             android:imeSubtypeLocale="es"
             android:imeSubtypeMode="keyboard"
     />
-    <subtype android:icon="@drawable/ic_subtype_mic"
-            android:label="@string/subtype_mode_es_voice"
-            android:imeSubtypeLocale="es"
-            android:imeSubtypeMode="voice"
-            android:imeSubtypeExtraValue="excludeFromLastInputMethod,requireNetworkConnectivity"
-    />
+<!--     <subtype android:icon="@drawable/ic_subtype_mic" -->
+<!--             android:label="@string/subtype_mode_es_voice" -->
+<!--             android:imeSubtypeLocale="es" -->
+<!--             android:imeSubtypeMode="voice" -->
+<!--             android:imeSubtypeExtraValue="excludeFromLastInputMethod,requireNetworkConnectivity" -->
+<!--     /> -->
     <subtype android:icon="@drawable/ic_subtype_keyboard"
             android:label="@string/subtype_mode_fr_keyboard"
             android:imeSubtypeLocale="fr"
             android:imeSubtypeMode="keyboard"
     />
-    <subtype android:icon="@drawable/ic_subtype_mic"
-            android:label="@string/subtype_mode_fr_voice"
-            android:imeSubtypeLocale="fr"
-            android:imeSubtypeMode="voice"
-            android:imeSubtypeExtraValue="excludeFromLastInputMethod,requireNetworkConnectivity"
-    />
+<!--     <subtype android:icon="@drawable/ic_subtype_mic" -->
+<!--             android:label="@string/subtype_mode_fr_voice" -->
+<!--             android:imeSubtypeLocale="fr" -->
+<!--             android:imeSubtypeMode="voice" -->
+<!--             android:imeSubtypeExtraValue="excludeFromLastInputMethod,requireNetworkConnectivity" -->
+<!--     /> -->
     <subtype android:icon="@drawable/ic_subtype_keyboard"
             android:label="@string/subtype_mode_fr_CA_keyboard"
             android:imeSubtypeLocale="fr_CA"
@@ -110,12 +116,19 @@
             android:imeSubtypeLocale="it"
             android:imeSubtypeMode="keyboard"
     />
-    <subtype android:icon="@drawable/ic_subtype_mic"
-            android:label="@string/subtype_mode_it_voice"
-            android:imeSubtypeLocale="it"
-            android:imeSubtypeMode="voice"
-            android:imeSubtypeExtraValue="excludeFromLastInputMethod,requireNetworkConnectivity"
+    <!-- Java uses the deprecated "iw" code instead of the standard "he" code for Hebrew. -->
+    <!-- The file for Hebrew layout is an alpha version. It needs to be run through UX. -->
+    <subtype android:icon="@drawable/ic_subtype_keyboard"
+            android:label="@string/subtype_mode_iw_keyboard"
+            android:imeSubtypeLocale="iw"
+            android:imeSubtypeMode="keyboard"
     />
+<!--     <subtype android:icon="@drawable/ic_subtype_mic" -->
+<!--             android:label="@string/subtype_mode_it_voice" -->
+<!--             android:imeSubtypeLocale="it" -->
+<!--             android:imeSubtypeMode="voice" -->
+<!--             android:imeSubtypeExtraValue="excludeFromLastInputMethod,requireNetworkConnectivity" -->
+<!--     /> -->
     <subtype android:icon="@drawable/ic_subtype_keyboard"
             android:label="@string/subtype_mode_nb_keyboard"
             android:imeSubtypeLocale="nb"
@@ -126,11 +139,16 @@
             android:imeSubtypeLocale="nl"
             android:imeSubtypeMode="keyboard"
     />
-    <subtype android:icon="@drawable/ic_subtype_mic"
-            android:label="@string/subtype_mode_nl_voice"
-            android:imeSubtypeLocale="nl"
-            android:imeSubtypeMode="voice"
-            android:imeSubtypeExtraValue="excludeFromLastInputMethod,requireNetworkConnectivity"
+<!--     <subtype android:icon="@drawable/ic_subtype_mic" -->
+<!--             android:label="@string/subtype_mode_nl_voice" -->
+<!--             android:imeSubtypeLocale="nl" -->
+<!--             android:imeSubtypeMode="voice" -->
+<!--             android:imeSubtypeExtraValue="excludeFromLastInputMethod,requireNetworkConnectivity" -->
+<!--     /> -->
+    <subtype android:icon="@drawable/ic_subtype_keyboard"
+            android:label="@string/subtype_mode_pl_keyboard"
+            android:imeSubtypeLocale="pl"
+            android:imeSubtypeMode="keyboard"
     />
     <subtype android:icon="@drawable/ic_subtype_keyboard"
             android:label="@string/subtype_mode_ru_keyboard"
@@ -147,64 +165,64 @@
             android:imeSubtypeLocale="sv"
             android:imeSubtypeMode="keyboard"
     />
-    <subtype android:icon="@drawable/ic_subtype_mic"
-            android:label="@string/subtype_mode_af_voice"
-            android:imeSubtypeLocale="af"
-            android:imeSubtypeMode="voice"
-            android:imeSubtypeExtraValue="excludeFromLastInputMethod,requireNetworkConnectivity"
-    />
-    <subtype android:icon="@drawable/ic_subtype_mic"
-            android:label="@string/subtype_mode_ja_voice"
-            android:imeSubtypeLocale="ja"
-            android:imeSubtypeMode="voice"
-            android:imeSubtypeExtraValue="excludeFromLastInputMethod,requireNetworkConnectivity"
-    />
-    <subtype android:icon="@drawable/ic_subtype_mic"
-            android:label="@string/subtype_mode_ko_voice"
-            android:imeSubtypeLocale="ko"
-            android:imeSubtypeMode="voice"
-            android:imeSubtypeExtraValue="excludeFromLastInputMethod,requireNetworkConnectivity"
-    />
-    <subtype android:icon="@drawable/ic_subtype_mic"
-            android:label="@string/subtype_mode_pl_voice"
-            android:imeSubtypeLocale="pl"
-            android:imeSubtypeMode="voice"
-            android:imeSubtypeExtraValue="excludeFromLastInputMethod,requireNetworkConnectivity"
-    />
-    <subtype android:icon="@drawable/ic_subtype_mic"
-            android:label="@string/subtype_mode_pt_voice"
-            android:imeSubtypeLocale="pt"
-            android:imeSubtypeMode="voice"
-            android:imeSubtypeExtraValue="excludeFromLastInputMethod,requireNetworkConnectivity"
-    />
-    <subtype android:icon="@drawable/ic_subtype_mic"
-            android:label="@string/subtype_mode_ru_voice"
-            android:imeSubtypeLocale="ru"
-            android:imeSubtypeMode="voice"
-            android:imeSubtypeExtraValue="excludeFromLastInputMethod,requireNetworkConnectivity"
-    />
-    <subtype android:icon="@drawable/ic_subtype_mic"
-            android:label="@string/subtype_mode_tr_voice"
-            android:imeSubtypeLocale="tr"
-            android:imeSubtypeMode="voice"
-            android:imeSubtypeExtraValue="excludeFromLastInputMethod,requireNetworkConnectivity"
-    />
-    <subtype android:icon="@drawable/ic_subtype_mic"
-            android:label="@string/subtype_mode_yue_voice"
-            android:imeSubtypeLocale="yue"
-            android:imeSubtypeMode="voice"
-            android:imeSubtypeExtraValue="excludeFromLastInputMethod,requireNetworkConnectivity"
-    />
-    <subtype android:icon="@drawable/ic_subtype_mic"
-            android:label="@string/subtype_mode_zh_voice"
-            android:imeSubtypeLocale="zh"
-            android:imeSubtypeMode="voice"
-            android:imeSubtypeExtraValue="excludeFromLastInputMethod,requireNetworkConnectivity"
-    />
-    <subtype android:icon="@drawable/ic_subtype_mic"
-            android:label="@string/subtype_mode_zu_voice"
-            android:imeSubtypeLocale="zu"
-            android:imeSubtypeMode="voice"
-            android:imeSubtypeExtraValue="excludeFromLastInputMethod,requireNetworkConnectivity"
-    />
+<!--     <subtype android:icon="@drawable/ic_subtype_mic" -->
+<!--             android:label="@string/subtype_mode_af_voice" -->
+<!--             android:imeSubtypeLocale="af" -->
+<!--             android:imeSubtypeMode="voice" -->
+<!--             android:imeSubtypeExtraValue="excludeFromLastInputMethod,requireNetworkConnectivity" -->
+<!--     /> -->
+<!--     <subtype android:icon="@drawable/ic_subtype_mic" -->
+<!--             android:label="@string/subtype_mode_ja_voice" -->
+<!--             android:imeSubtypeLocale="ja" -->
+<!--             android:imeSubtypeMode="voice" -->
+<!--             android:imeSubtypeExtraValue="excludeFromLastInputMethod,requireNetworkConnectivity" -->
+<!--     /> -->
+<!--     <subtype android:icon="@drawable/ic_subtype_mic" -->
+<!--             android:label="@string/subtype_mode_ko_voice" -->
+<!--             android:imeSubtypeLocale="ko" -->
+<!--             android:imeSubtypeMode="voice" -->
+<!--             android:imeSubtypeExtraValue="excludeFromLastInputMethod,requireNetworkConnectivity" -->
+<!--     /> -->
+<!--     <subtype android:icon="@drawable/ic_subtype_mic" -->
+<!--             android:label="@string/subtype_mode_pl_voice" -->
+<!--             android:imeSubtypeLocale="pl" -->
+<!--             android:imeSubtypeMode="voice" -->
+<!--             android:imeSubtypeExtraValue="excludeFromLastInputMethod,requireNetworkConnectivity" -->
+<!--     /> -->
+<!--     <subtype android:icon="@drawable/ic_subtype_mic" -->
+<!--             android:label="@string/subtype_mode_pt_voice" -->
+<!--             android:imeSubtypeLocale="pt" -->
+<!--             android:imeSubtypeMode="voice" -->
+<!--             android:imeSubtypeExtraValue="excludeFromLastInputMethod,requireNetworkConnectivity" -->
+<!--     /> -->
+<!--     <subtype android:icon="@drawable/ic_subtype_mic" -->
+<!--             android:label="@string/subtype_mode_ru_voice" -->
+<!--             android:imeSubtypeLocale="ru" -->
+<!--             android:imeSubtypeMode="voice" -->
+<!--             android:imeSubtypeExtraValue="excludeFromLastInputMethod,requireNetworkConnectivity" -->
+<!--     /> -->
+<!--     <subtype android:icon="@drawable/ic_subtype_mic" -->
+<!--             android:label="@string/subtype_mode_tr_voice" -->
+<!--             android:imeSubtypeLocale="tr" -->
+<!--             android:imeSubtypeMode="voice" -->
+<!--             android:imeSubtypeExtraValue="excludeFromLastInputMethod,requireNetworkConnectivity" -->
+<!--     /> -->
+<!--     <subtype android:icon="@drawable/ic_subtype_mic" -->
+<!--             android:label="@string/subtype_mode_yue_voice" -->
+<!--             android:imeSubtypeLocale="yue" -->
+<!--             android:imeSubtypeMode="voice" -->
+<!--             android:imeSubtypeExtraValue="excludeFromLastInputMethod,requireNetworkConnectivity" -->
+<!--     /> -->
+<!--     <subtype android:icon="@drawable/ic_subtype_mic" -->
+<!--             android:label="@string/subtype_mode_zh_voice" -->
+<!--             android:imeSubtypeLocale="zh" -->
+<!--             android:imeSubtypeMode="voice" -->
+<!--             android:imeSubtypeExtraValue="excludeFromLastInputMethod,requireNetworkConnectivity" -->
+<!--     /> -->
+<!--     <subtype android:icon="@drawable/ic_subtype_mic" -->
+<!--             android:label="@string/subtype_mode_zu_voice" -->
+<!--             android:imeSubtypeLocale="zu" -->
+<!--             android:imeSubtypeMode="voice" -->
+<!--             android:imeSubtypeExtraValue="excludeFromLastInputMethod,requireNetworkConnectivity" -->
+<!--     /> -->
 </input-method>
diff --git a/java/res/xml/prefs.xml b/java/res/xml/prefs.xml
index d031415..24a1d45 100644
--- a/java/res/xml/prefs.xml
+++ b/java/res/xml/prefs.xml
@@ -14,86 +14,66 @@
      limitations under the License.
 -->
 
-<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
-        android:title="@string/english_ime_settings"
-        android:key="english_ime_settings">
-
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:title="@string/english_ime_settings"
+    android:key="english_ime_settings">
     <PreferenceCategory
-            android:title="@string/general_category"
-            android:key="general_settings">
-
+        android:title="@string/general_category"
+        android:key="general_settings">
         <CheckBoxPreference
-                android:key="auto_cap"
-                android:title="@string/auto_cap"
-                android:persistent="true"
-                android:defaultValue="true"
-                />
-
+            android:key="auto_cap"
+            android:title="@string/auto_cap"
+            android:persistent="true"
+            android:defaultValue="true" />
         <CheckBoxPreference
-                android:key="vibrate_on"
-                android:title="@string/vibrate_on_keypress"
-                android:persistent="true"
-                />
-
+            android:key="vibrate_on"
+            android:title="@string/vibrate_on_keypress"
+            android:persistent="true" />
         <CheckBoxPreference
-                android:key="sound_on"
-                android:title="@string/sound_on_keypress"
-                android:defaultValue="@bool/config_default_sound_enabled"
-                android:persistent="true"
-                />
-
+            android:key="sound_on"
+            android:title="@string/sound_on_keypress"
+            android:defaultValue="@bool/config_default_sound_enabled"
+            android:persistent="true" />
         <CheckBoxPreference
-                android:key="popup_on"
-                android:title="@string/popup_on_keypress"
-                android:persistent="true"
-                android:defaultValue="@bool/config_default_popup_preview"
-                />
-
+            android:key="popup_on"
+            android:title="@string/popup_on_keypress"
+            android:persistent="true"
+            android:defaultValue="@bool/config_default_popup_preview" />
         <CheckBoxPreference
-                android:key="recorrection_enabled"
-                android:title="@string/prefs_enable_recorrection"
-                android:summary="@string/prefs_enable_recorrection_summary"
-                android:persistent="true"
-                android:defaultValue="@bool/config_default_recorrection_enabled"
-                />
-
+            android:key="recorrection_enabled"
+            android:title="@string/prefs_enable_recorrection"
+            android:summary="@string/prefs_enable_recorrection_summary"
+            android:persistent="true"
+            android:defaultValue="@bool/config_default_recorrection_enabled" />
         <ListPreference
-                android:key="settings_key"
-                android:title="@string/prefs_settings_key"
-                android:persistent="true"
-                android:entryValues="@array/settings_key_modes_values"
-                android:entries="@array/settings_key_modes"
-                android:defaultValue="@string/settings_key_mode_auto"
-                />
-
+            android:key="settings_key"
+            android:title="@string/prefs_settings_key"
+            android:persistent="true"
+            android:entryValues="@array/settings_key_modes_values"
+            android:entries="@array/settings_key_modes"
+            android:defaultValue="@string/settings_key_mode_auto" />
         <ListPreference
-                android:key="voice_mode"
-                android:title="@string/voice_input"
-                android:persistent="true"
-                android:entryValues="@array/voice_input_modes_values"
-                android:entries="@array/voice_input_modes"
-                android:defaultValue="@string/voice_mode_main"
-                />
-
+            android:key="voice_mode"
+            android:title="@string/voice_input"
+            android:persistent="true"
+            android:entryValues="@array/voice_input_modes_values"
+            android:entries="@array/voice_input_modes"
+            android:defaultValue="@string/voice_mode_main" />
         <PreferenceScreen
-                android:key="subtype_settings"
-                android:title="@string/language_selection_title"
-                android:summary="@string/language_selection_summary" />
-
+            android:key="subtype_settings"
+            android:title="@string/language_selection_title"
+            android:summary="@string/language_selection_summary" />
     </PreferenceCategory>
-
     <PreferenceCategory
-            android:title="@string/prediction_category"
-            android:key="prediction_settings">
-
+        android:title="@string/correction_category"
+        android:key="correction_settings">
         <CheckBoxPreference
             android:key="quick_fixes"
             android:title="@string/quick_fixes"
             android:summary="@string/quick_fixes_summary"
             android:persistent="true"
-            android:defaultValue="true"
-            />
-
+            android:defaultValue="true" />
         <ListPreference
             android:key="auto_correction_threshold"
             android:title="@string/auto_correction"
@@ -101,9 +81,7 @@
             android:persistent="true"
             android:entryValues="@array/auto_correction_threshold_mode_indexes"
             android:entries="@array/auto_correction_threshold_modes"
-            android:defaultValue="@string/auto_correction_threshold_mode_index_modest"
-            />
-
+            android:defaultValue="@string/auto_correction_threshold_mode_index_modest" />
         <ListPreference
             android:key="show_suggestions_setting"
             android:summary="@string/prefs_show_suggestions_summary"
@@ -111,23 +89,53 @@
             android:persistent="true"
             android:entryValues="@array/prefs_suggestion_visibility_values"
             android:entries="@array/prefs_suggestion_visibilities"
-            android:defaultValue="@string/prefs_suggestion_visibility_default_value"
-            />
-
+            android:defaultValue="@string/prefs_suggestion_visibility_default_value" />
+    </PreferenceCategory>
+    <PreferenceCategory
+        android:title="@string/ngram_category"
+        android:key="ngram_settings">
         <CheckBoxPreference
             android:key="bigram_suggestion"
             android:title="@string/bigram_suggestion"
             android:summary="@string/bigram_suggestion_summary"
             android:persistent="true"
-            android:defaultValue="true"
-            />
-    </PreferenceCategory>
-
-    <CheckBoxPreference
-            android:key="usability_study_mode"
-            android:title="@string/prefs_usability_study_mode"
+            android:defaultValue="true" />
+        <CheckBoxPreference
+            android:key="bigram_prediction"
+            android:dependency="bigram_suggestion"
+            android:title="@string/bigram_prediction"
+            android:summary="@string/bigram_prediction_summary"
             android:persistent="true"
-            android:defaultValue="false"
-            />
-
+            android:defaultValue="false" />
+    </PreferenceCategory>
+    <PreferenceCategory
+        android:title="@string/misc_category"
+        android:key="misc_settings">
+      <CheckBoxPreference
+          android:key="usability_study_mode"
+          android:title="@string/prefs_usability_study_mode"
+          android:persistent="true"
+          android:defaultValue="false" />
+      <CheckBoxPreference
+          android:key="enable_logging"
+          android:title="@string/prefs_enable_log"
+          android:summary="@string/prefs_description_log"
+          android:persistent="true"
+          android:defaultValue="true" />
+      <ListPreference
+          android:key="pref_keyboard_layout_20100902"
+          android:title="@string/keyboard_layout"
+          android:persistent="true"
+          android:entryValues="@array/keyboard_layout_modes_values"
+          android:entries="@array/keyboard_layout_modes"
+          android:defaultValue="@string/config_default_keyboard_theme_id" />
+    </PreferenceCategory>
+    <!-- <Preference
+        android:title="Debug Settings"
+        android:key="debug_settings">
+        <intent
+            android:action="android.intent.action.MAIN"
+            android:targetPackage="com.android.inputmethod.latin"
+            android:targetClass="com.android.inputmethod.latin.DebugSettings" />
+    </Preference>-->
 </PreferenceScreen>
diff --git a/java/res/xml/prefs_for_debug.xml b/java/res/xml/prefs_for_debug.xml
index 2dad171..477461d 100644
--- a/java/res/xml/prefs_for_debug.xml
+++ b/java/res/xml/prefs_for_debug.xml
@@ -36,6 +36,13 @@
             />
 
     <CheckBoxPreference
+            android:key="use_spacebar_language_switch"
+            android:title="@string/prefs_use_spacebar_language_switch"
+            android:persistent="true"
+            android:defaultValue="false"
+            />
+
+    <CheckBoxPreference
             android:key="debug_mode"
             android:title="@string/prefs_debug_mode"
             android:persistent="true"
diff --git a/java/src/com/android/inputmethod/compat/AbstractCompatWrapper.java b/java/src/com/android/inputmethod/compat/AbstractCompatWrapper.java
new file mode 100644
index 0000000..6594935
--- /dev/null
+++ b/java/src/com/android/inputmethod/compat/AbstractCompatWrapper.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.compat;
+
+import android.util.Log;
+
+public abstract class AbstractCompatWrapper {
+    private static final String TAG = AbstractCompatWrapper.class.getSimpleName();
+    protected final Object mObj;
+
+    public AbstractCompatWrapper(Object obj) {
+        if (obj == null) {
+            Log.e(TAG, "Invalid input to AbstructCompatWrapper");
+        }
+        mObj = obj;
+    }
+
+    public Object getOriginalObject() {
+        return mObj;
+    }
+
+    public boolean hasOriginalObject() {
+        return mObj != null;
+    }
+}
diff --git a/java/src/com/android/inputmethod/compat/CompatUtils.java b/java/src/com/android/inputmethod/compat/CompatUtils.java
new file mode 100644
index 0000000..0b532f7
--- /dev/null
+++ b/java/src/com/android/inputmethod/compat/CompatUtils.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.compat;
+
+import android.content.Intent;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+
+public class CompatUtils {
+    private static final String TAG = CompatUtils.class.getSimpleName();
+    private static final String EXTRA_INPUT_METHOD_ID = "input_method_id";
+    // TODO: Can these be constants instead of literal String constants?
+    private static final String INPUT_METHOD_SUBTYPE_SETTINGS =
+            "android.settings.INPUT_METHOD_SUBTYPE_SETTINGS";
+    private static final String INPUT_LANGUAGE_SELECTION =
+            "com.android.inputmethod.latin.INPUT_LANGUAGE_SELECTION";
+
+    public static Intent getInputLanguageSelectionIntent(String inputMethodId,
+            int flagsForSubtypeSettings) {
+        final String action;
+        Intent intent;
+        if (InputMethodServiceCompatWrapper.CAN_HANDLE_ON_CURRENT_INPUT_METHOD_SUBTYPE_CHANGED
+                /* android.os.Build.VERSION_CODES.HONEYCOMB */
+                && android.os.Build.VERSION.SDK_INT >=  11) {
+            // Refer to android.provider.Settings.ACTION_INPUT_METHOD_SUBTYPE_SETTINGS
+            action = INPUT_METHOD_SUBTYPE_SETTINGS;
+            intent = new Intent(action);
+            if (!TextUtils.isEmpty(inputMethodId)) {
+                intent.putExtra(EXTRA_INPUT_METHOD_ID, inputMethodId);
+            }
+            if (flagsForSubtypeSettings > 0) {
+                intent.setFlags(flagsForSubtypeSettings);
+            }
+        } else {
+            action = INPUT_LANGUAGE_SELECTION;
+            intent = new Intent(action);
+        }
+        return intent;
+    }
+
+    public static Class<?> getClass(String className) {
+        try {
+            return Class.forName(className);
+        } catch (ClassNotFoundException e) {
+            return null;
+        }
+    }
+
+    public static Method getMethod(Class<?> targetClass, String name,
+            Class<?>... parameterTypes) {
+        if (targetClass == null || TextUtils.isEmpty(name)) return null;
+        try {
+            return targetClass.getMethod(name, parameterTypes);
+        } catch (SecurityException e) {
+            // ignore
+            return null;
+        } catch (NoSuchMethodException e) {
+            // ignore
+            return null;
+        }
+    }
+
+    public static Field getField(Class<?> targetClass, String name) {
+        try {
+            return targetClass.getField(name);
+        } catch (SecurityException e) {
+            // ignore
+            return null;
+        } catch (NoSuchFieldException e) {
+            // ignore
+            return null;
+        }
+    }
+
+    public static Constructor<?> getConstructor(Class<?> targetClass, Class<?>[] types) {
+        if (targetClass == null || types == null) return null;
+        try {
+            return targetClass.getConstructor(types);
+        } catch (SecurityException e) {
+            // ignore
+            return null;
+        } catch (NoSuchMethodException e) {
+            // ignore
+            return null;
+        }
+    }
+
+    public static Object invoke(
+            Object receiver, Object defaultValue, Method method, Object... args) {
+        if (receiver == null || method == null) return defaultValue;
+        try {
+            return method.invoke(receiver, args);
+        } catch (IllegalArgumentException e) {
+            Log.e(TAG, "Exception in invoke: IllegalArgumentException");
+            return defaultValue;
+        } catch (IllegalAccessException e) {
+            Log.e(TAG, "Exception in invoke: IllegalAccessException");
+            return defaultValue;
+        } catch (InvocationTargetException e) {
+            Log.e(TAG, "Exception in invoke: IllegalTargetException");
+            return defaultValue;
+        }
+    }
+
+    public static Object getFieldValue(Object receiver, Object defaultValue, Field field) {
+        if (receiver == null || field == null) return defaultValue;
+        try {
+            return field.get(receiver);
+        } catch (IllegalArgumentException e) {
+            Log.e(TAG, "Exception in getFieldValue: IllegalArgumentException");
+            return defaultValue;
+        } catch (IllegalAccessException e) {
+            Log.e(TAG, "Exception in getFieldValue: IllegalAccessException");
+            return defaultValue;
+        }
+    }
+
+    public static void setFieldValue(Object receiver, Field field, Object value) {
+        if (receiver == null || field == null) return;
+        try {
+            field.set(receiver, value);
+        } catch (IllegalArgumentException e) {
+            Log.e(TAG, "Exception in setFieldValue: IllegalArgumentException");
+        } catch (IllegalAccessException e) {
+            Log.e(TAG, "Exception in setFieldValue: IllegalAccessException");
+        }
+    }
+
+    public static List<InputMethodSubtypeCompatWrapper> copyInputMethodSubtypeListToWrapper(
+            Object listObject) {
+        if (!(listObject instanceof List<?>)) return null;
+        final List<InputMethodSubtypeCompatWrapper> subtypes =
+                new ArrayList<InputMethodSubtypeCompatWrapper>();
+        for (Object o: (List<?>)listObject) {
+            subtypes.add(new InputMethodSubtypeCompatWrapper(o));
+        }
+        return subtypes;
+    }
+}
diff --git a/java/src/com/android/inputmethod/compat/EditorInfoCompatUtils.java b/java/src/com/android/inputmethod/compat/EditorInfoCompatUtils.java
new file mode 100644
index 0000000..f6f4f7a
--- /dev/null
+++ b/java/src/com/android/inputmethod/compat/EditorInfoCompatUtils.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.compat;
+
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
+
+import java.lang.reflect.Field;
+
+public class EditorInfoCompatUtils {
+    private static final Field FIELD_IME_FLAG_NAVIGATE_NEXT = CompatUtils.getField(
+            EditorInfo.class, "IME_FLAG_NAVIGATE_NEXT");
+    private static final Field FIELD_IME_FLAG_NAVIGATE_PREVIOUS = CompatUtils.getField(
+            EditorInfo.class, "IME_FLAG_NAVIGATE_PREVIOUS");
+    private static final Field FIELD_IME_ACTION_PREVIOUS = CompatUtils.getField(
+            EditorInfo.class, "IME_FLAG_ACTION_PREVIOUS");
+    private static final Integer OBJ_IME_FLAG_NAVIGATE_NEXT = (Integer) CompatUtils
+            .getFieldValue(null, null, FIELD_IME_FLAG_NAVIGATE_NEXT);
+    private static final Integer OBJ_IME_FLAG_NAVIGATE_PREVIOUS = (Integer) CompatUtils
+            .getFieldValue(null, null, FIELD_IME_FLAG_NAVIGATE_PREVIOUS);
+    private static final Integer OBJ_IME_ACTION_PREVIOUS = (Integer) CompatUtils
+            .getFieldValue(null, null, FIELD_IME_ACTION_PREVIOUS);
+
+    public static boolean hasFlagNavigateNext(int imeOptions) {
+        if (OBJ_IME_FLAG_NAVIGATE_NEXT == null)
+            return false;
+        return (imeOptions & OBJ_IME_FLAG_NAVIGATE_NEXT) != 0;
+    }
+
+    public static boolean hasFlagNavigatePrevious(int imeOptions) {
+        if (OBJ_IME_FLAG_NAVIGATE_PREVIOUS == null)
+            return false;
+        return (imeOptions & OBJ_IME_FLAG_NAVIGATE_PREVIOUS) != 0;
+    }
+
+    public static void performEditorActionNext(InputConnection ic) {
+        ic.performEditorAction(EditorInfo.IME_ACTION_NEXT);
+    }
+
+    public static void performEditorActionPrevious(InputConnection ic) {
+        if (OBJ_IME_ACTION_PREVIOUS == null)
+            return;
+        ic.performEditorAction(OBJ_IME_ACTION_PREVIOUS);
+    }
+
+    public static String imeOptionsName(int imeOptions) {
+        if (imeOptions == -1)
+            return null;
+        final int actionId = imeOptions & EditorInfo.IME_MASK_ACTION;
+        final String action;
+        switch (actionId) {
+            case EditorInfo.IME_ACTION_UNSPECIFIED:
+                action = "actionUnspecified";
+                break;
+            case EditorInfo.IME_ACTION_NONE:
+                action = "actionNone";
+                break;
+            case EditorInfo.IME_ACTION_GO:
+                action = "actionGo";
+                break;
+            case EditorInfo.IME_ACTION_SEARCH:
+                action = "actionSearch";
+                break;
+            case EditorInfo.IME_ACTION_SEND:
+                action = "actionSend";
+                break;
+            case EditorInfo.IME_ACTION_DONE:
+                action = "actionDone";
+                break;
+            default: {
+                if (OBJ_IME_ACTION_PREVIOUS != null && actionId == OBJ_IME_ACTION_PREVIOUS) {
+                    action = "actionPrevious";
+                } else {
+                    action = "actionUnknown(" + actionId + ")";
+                }
+                break;
+            }
+        }
+        if ((imeOptions & EditorInfo.IME_FLAG_NO_ENTER_ACTION) != 0) {
+            return "flagNoEnterAction|" + action;
+        } else {
+            return action;
+        }
+    }
+}
diff --git a/java/src/com/android/inputmethod/compat/InputConnectionCompatUtils.java b/java/src/com/android/inputmethod/compat/InputConnectionCompatUtils.java
new file mode 100644
index 0000000..c926be0
--- /dev/null
+++ b/java/src/com/android/inputmethod/compat/InputConnectionCompatUtils.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.compat;
+
+import com.android.inputmethod.latin.EditingUtils.SelectedWord;
+
+import android.util.Log;
+import android.view.inputmethod.InputConnection;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+public class InputConnectionCompatUtils {
+    private static final String TAG = InputConnectionCompatUtils.class.getSimpleName();
+    private static final Class<?> CLASS_CorrectionInfo = CompatUtils
+            .getClass("android.view.inputmethod.CorrectionInfo");
+    private static final Class<?>[] INPUT_TYPE_CorrectionInfo = new Class<?>[] { int.class,
+            CharSequence.class, CharSequence.class };
+    private static final Constructor<?> CONSTRUCTOR_CorrectionInfo = CompatUtils
+            .getConstructor(CLASS_CorrectionInfo, INPUT_TYPE_CorrectionInfo);
+    private static final Method METHOD_InputConnection_commitCorrection = CompatUtils
+            .getMethod(InputConnection.class, "commitCorrection", CLASS_CorrectionInfo);
+    private static final Method METHOD_getSelectedText = CompatUtils
+            .getMethod(InputConnection.class, "getSelectedText", int.class);
+    private static final Method METHOD_setComposingRegion = CompatUtils
+            .getMethod(InputConnection.class, "setComposingRegion", int.class, int.class);
+    public static final boolean RECORRECTION_SUPPORTED;
+
+    static {
+        RECORRECTION_SUPPORTED = METHOD_getSelectedText != null
+                && METHOD_setComposingRegion != null;
+    }
+
+    public static void commitCorrection(InputConnection ic, int offset, CharSequence oldText,
+            CharSequence newText) {
+        if (ic == null || CONSTRUCTOR_CorrectionInfo == null
+                || METHOD_InputConnection_commitCorrection == null) {
+            return;
+        }
+        Object[] args = { offset, oldText, newText };
+        try {
+            Object correctionInfo = CONSTRUCTOR_CorrectionInfo.newInstance(args);
+            CompatUtils.invoke(ic, null, METHOD_InputConnection_commitCorrection,
+                    correctionInfo);
+        } catch (IllegalArgumentException e) {
+            Log.e(TAG, "Error in commitCorrection: IllegalArgumentException");
+        } catch (InstantiationException e) {
+            Log.e(TAG, "Error in commitCorrection: InstantiationException");
+        } catch (IllegalAccessException e) {
+            Log.e(TAG, "Error in commitCorrection: IllegalAccessException");
+        } catch (InvocationTargetException e) {
+            Log.e(TAG, "Error in commitCorrection: InvocationTargetException");
+        }
+    }
+
+
+    /**
+     * Returns the selected text between the selStart and selEnd positions.
+     */
+    public static CharSequence getSelectedText(InputConnection ic, int selStart, int selEnd) {
+        // Use reflection, for backward compatibility
+        return (CharSequence) CompatUtils.invoke(
+                ic, null, METHOD_getSelectedText, 0);
+    }
+
+    /**
+     * Tries to set the text into composition mode if there is support for it in the framework.
+     */
+    public static void underlineWord(InputConnection ic, SelectedWord word) {
+        // Use reflection, for backward compatibility
+        // If method not found, there's nothing we can do. It still works but just wont underline
+        // the word.
+        CompatUtils.invoke(
+                ic, null, METHOD_setComposingRegion, word.mStart, word.mEnd);
+    }
+}
diff --git a/java/src/com/android/inputmethod/compat/InputMethodInfoCompatWrapper.java b/java/src/com/android/inputmethod/compat/InputMethodInfoCompatWrapper.java
new file mode 100644
index 0000000..8e22bbc
--- /dev/null
+++ b/java/src/com/android/inputmethod/compat/InputMethodInfoCompatWrapper.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.compat;
+
+import android.content.pm.ServiceInfo;
+import android.view.inputmethod.InputMethodInfo;
+
+import java.lang.reflect.Method;
+
+public class InputMethodInfoCompatWrapper {
+    private final InputMethodInfo mImi;
+    private static final Method METHOD_getSubtypeAt = CompatUtils.getMethod(
+            InputMethodInfo.class, "getSubtypeAt", int.class);
+    private static final Method METHOD_getSubtypeCount = CompatUtils.getMethod(
+            InputMethodInfo.class, "getSubtypeCount");
+
+    public InputMethodInfoCompatWrapper(InputMethodInfo imi) {
+        mImi = imi;
+    }
+
+    public InputMethodInfo getInputMethodInfo() {
+        return mImi;
+    }
+
+    public String getId() {
+        return mImi.getId();
+    }
+
+    public String getPackageName() {
+        return mImi.getPackageName();
+    }
+
+    public ServiceInfo getServiceInfo() {
+        return mImi.getServiceInfo();
+    }
+
+    public int getSubtypeCount() {
+        return (Integer) CompatUtils.invoke(mImi, 0, METHOD_getSubtypeCount);
+    }
+
+    public InputMethodSubtypeCompatWrapper getSubtypeAt(int index) {
+        return new InputMethodSubtypeCompatWrapper(CompatUtils.invoke(mImi, null,
+                METHOD_getSubtypeAt, index));
+    }
+}
diff --git a/java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java b/java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java
new file mode 100644
index 0000000..1cc13f2
--- /dev/null
+++ b/java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.compat;
+
+import com.android.inputmethod.deprecated.LanguageSwitcherProxy;
+import com.android.inputmethod.latin.LatinIME;
+import com.android.inputmethod.latin.SubtypeSwitcher;
+import com.android.inputmethod.latin.Utils;
+
+import android.content.Context;
+import android.os.IBinder;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodManager;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+// TODO: Override this class with the concrete implementation if we need to take care of the
+// performance.
+public class InputMethodManagerCompatWrapper {
+    private static final String TAG = InputMethodManagerCompatWrapper.class.getSimpleName();
+    private static final Method METHOD_getCurrentInputMethodSubtype =
+            CompatUtils.getMethod(InputMethodManager.class, "getCurrentInputMethodSubtype");
+    private static final Method METHOD_getEnabledInputMethodSubtypeList =
+            CompatUtils.getMethod(InputMethodManager.class, "getEnabledInputMethodSubtypeList",
+                    InputMethodInfo.class, boolean.class);
+    private static final Method METHOD_getShortcutInputMethodsAndSubtypes =
+            CompatUtils.getMethod(InputMethodManager.class, "getShortcutInputMethodsAndSubtypes");
+    private static final Method METHOD_setInputMethodAndSubtype =
+            CompatUtils.getMethod(
+                    InputMethodManager.class, "setInputMethodAndSubtype", IBinder.class,
+                    String.class, InputMethodSubtypeCompatWrapper.CLASS_InputMethodSubtype);
+    private static final Method METHOD_switchToLastInputMethod = CompatUtils.getMethod(
+            InputMethodManager.class, "switchToLastInputMethod", IBinder.class);
+
+    private static final InputMethodManagerCompatWrapper sInstance =
+            new InputMethodManagerCompatWrapper();
+
+    public static final boolean SUBTYPE_SUPPORTED;
+
+    static {
+        // This static initializer guarantees that METHOD_getShortcutInputMethodsAndSubtypes is
+        // already instantiated.
+        SUBTYPE_SUPPORTED = METHOD_getShortcutInputMethodsAndSubtypes != null;
+    }
+
+    // For the compatibility, IMM will create dummy subtypes if subtypes are not found.
+    // This is required to be false if the current behavior is broken. For now, it's ok to be true.
+    public static final boolean FORCE_ENABLE_VOICE_EVEN_WITH_NO_VOICE_SUBTYPES =
+            !InputMethodServiceCompatWrapper.CAN_HANDLE_ON_CURRENT_INPUT_METHOD_SUBTYPE_CHANGED;
+    private static final String VOICE_MODE = "voice";
+    private static final String KEYBOARD_MODE = "keyboard";
+
+    private InputMethodManager mImm;
+    private LanguageSwitcherProxy mLanguageSwitcherProxy;
+    private String mLatinImePackageName;
+
+    private InputMethodManagerCompatWrapper() {
+    }
+
+    public static InputMethodManagerCompatWrapper getInstance(Context context) {
+        if (sInstance.mImm == null) {
+            sInstance.init(context);
+        }
+        return sInstance;
+    }
+
+    private synchronized void init(Context context) {
+        mImm = (InputMethodManager) context.getSystemService(
+                Context.INPUT_METHOD_SERVICE);
+        if (context instanceof LatinIME) {
+            mLatinImePackageName = context.getPackageName();
+        }
+        mLanguageSwitcherProxy = LanguageSwitcherProxy.getInstance();
+    }
+
+    public InputMethodSubtypeCompatWrapper getCurrentInputMethodSubtype() {
+        if (!SUBTYPE_SUPPORTED) {
+            return new InputMethodSubtypeCompatWrapper(
+                    0, 0, mLanguageSwitcherProxy.getInputLocale().toString(), KEYBOARD_MODE, "");
+        }
+        Object o = CompatUtils.invoke(mImm, null, METHOD_getCurrentInputMethodSubtype);
+        return new InputMethodSubtypeCompatWrapper(o);
+    }
+
+    public List<InputMethodSubtypeCompatWrapper> getEnabledInputMethodSubtypeList(
+            InputMethodInfoCompatWrapper imi, boolean allowsImplicitlySelectedSubtypes) {
+        if (!SUBTYPE_SUPPORTED) {
+            String[] languages = mLanguageSwitcherProxy.getEnabledLanguages(
+                    allowsImplicitlySelectedSubtypes);
+            List<InputMethodSubtypeCompatWrapper> subtypeList =
+                    new ArrayList<InputMethodSubtypeCompatWrapper>();
+            for (String lang: languages) {
+                subtypeList.add(new InputMethodSubtypeCompatWrapper(0, 0, lang, KEYBOARD_MODE, ""));
+            }
+            return subtypeList;
+        }
+        Object retval = CompatUtils.invoke(mImm, null, METHOD_getEnabledInputMethodSubtypeList,
+                (imi != null ? imi.getInputMethodInfo() : null), allowsImplicitlySelectedSubtypes);
+        if (retval == null || !(retval instanceof List<?>) || ((List<?>)retval).isEmpty()) {
+            if (!FORCE_ENABLE_VOICE_EVEN_WITH_NO_VOICE_SUBTYPES) {
+                // Returns an empty list
+                return Collections.emptyList();
+            }
+            // Creates dummy subtypes
+            @SuppressWarnings("unused")
+            List<InputMethodSubtypeCompatWrapper> subtypeList =
+                    new ArrayList<InputMethodSubtypeCompatWrapper>();
+            InputMethodSubtypeCompatWrapper keyboardSubtype = getLastResortSubtype(KEYBOARD_MODE);
+            InputMethodSubtypeCompatWrapper voiceSubtype = getLastResortSubtype(VOICE_MODE);
+            if (keyboardSubtype != null) {
+                subtypeList.add(keyboardSubtype);
+            }
+            if (voiceSubtype != null) {
+                subtypeList.add(voiceSubtype);
+            }
+            return subtypeList;
+        }
+        return CompatUtils.copyInputMethodSubtypeListToWrapper(retval);
+    }
+
+    private InputMethodInfoCompatWrapper getLatinImeInputMethodInfo() {
+        if (TextUtils.isEmpty(mLatinImePackageName))
+            return null;
+        return Utils.getInputMethodInfo(this, mLatinImePackageName);
+    }
+
+    @SuppressWarnings("unused")
+    private InputMethodSubtypeCompatWrapper getLastResortSubtype(String mode) {
+        if (VOICE_MODE.equals(mode) && !FORCE_ENABLE_VOICE_EVEN_WITH_NO_VOICE_SUBTYPES)
+            return null;
+        Locale inputLocale = SubtypeSwitcher.getInstance().getInputLocale();
+        if (inputLocale == null)
+            return null;
+        return new InputMethodSubtypeCompatWrapper(0, 0, inputLocale.toString(), mode, "");
+    }
+
+    public Map<InputMethodInfoCompatWrapper, List<InputMethodSubtypeCompatWrapper>>
+            getShortcutInputMethodsAndSubtypes() {
+        Object retval = CompatUtils.invoke(mImm, null, METHOD_getShortcutInputMethodsAndSubtypes);
+        if (retval == null || !(retval instanceof Map<?, ?>) || ((Map<?, ?>)retval).isEmpty()) {
+            if (!FORCE_ENABLE_VOICE_EVEN_WITH_NO_VOICE_SUBTYPES) {
+                // Returns an empty map
+                return Collections.emptyMap();
+            }
+            // Creates dummy subtypes
+            @SuppressWarnings("unused")
+            InputMethodInfoCompatWrapper imi = getLatinImeInputMethodInfo();
+            InputMethodSubtypeCompatWrapper voiceSubtype = getLastResortSubtype(VOICE_MODE);
+            if (imi != null && voiceSubtype != null) {
+                Map<InputMethodInfoCompatWrapper, List<InputMethodSubtypeCompatWrapper>>
+                        shortcutMap =
+                                new HashMap<InputMethodInfoCompatWrapper,
+                                        List<InputMethodSubtypeCompatWrapper>>();
+                List<InputMethodSubtypeCompatWrapper> subtypeList =
+                        new ArrayList<InputMethodSubtypeCompatWrapper>();
+                subtypeList.add(voiceSubtype);
+                shortcutMap.put(imi, subtypeList);
+                return shortcutMap;
+            } else {
+                return Collections.emptyMap();
+            }
+        }
+        Map<InputMethodInfoCompatWrapper, List<InputMethodSubtypeCompatWrapper>> shortcutMap =
+                new HashMap<InputMethodInfoCompatWrapper, List<InputMethodSubtypeCompatWrapper>>();
+        final Map<?, ?> retvalMap = (Map<?, ?>)retval;
+        for (Object key : retvalMap.keySet()) {
+            if (!(key instanceof InputMethodInfo)) {
+                Log.e(TAG, "Class type error.");
+                return null;
+            }
+            shortcutMap.put(new InputMethodInfoCompatWrapper((InputMethodInfo)key),
+                    CompatUtils.copyInputMethodSubtypeListToWrapper(retvalMap.get(key)));
+        }
+        return shortcutMap;
+    }
+
+    public void setInputMethodAndSubtype(
+            IBinder token, String id, InputMethodSubtypeCompatWrapper subtype) {
+        if (subtype != null && subtype.hasOriginalObject()) {
+            CompatUtils.invoke(mImm, null, METHOD_setInputMethodAndSubtype,
+                    token, id, subtype.getOriginalObject());
+        }
+    }
+
+    public boolean switchToLastInputMethod(IBinder token) {
+        if (SubtypeSwitcher.getInstance().isDummyVoiceMode()) {
+            return true;
+        }
+        return (Boolean)CompatUtils.invoke(mImm, false, METHOD_switchToLastInputMethod, token);
+    }
+
+    public List<InputMethodInfoCompatWrapper> getEnabledInputMethodList() {
+        if (mImm == null) return null;
+        List<InputMethodInfoCompatWrapper> imis = new ArrayList<InputMethodInfoCompatWrapper>();
+        for (InputMethodInfo imi : mImm.getEnabledInputMethodList()) {
+            imis.add(new InputMethodInfoCompatWrapper(imi));
+        }
+        return imis;
+    }
+
+    public void showInputMethodPicker() {
+        if (mImm == null) return;
+        mImm.showInputMethodPicker();
+    }
+}
diff --git a/java/src/com/android/inputmethod/compat/InputMethodServiceCompatWrapper.java b/java/src/com/android/inputmethod/compat/InputMethodServiceCompatWrapper.java
new file mode 100644
index 0000000..828aea4
--- /dev/null
+++ b/java/src/com/android/inputmethod/compat/InputMethodServiceCompatWrapper.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.compat;
+
+import com.android.inputmethod.deprecated.LanguageSwitcherProxy;
+import com.android.inputmethod.latin.SubtypeSwitcher;
+
+import android.inputmethodservice.InputMethodService;
+import android.view.inputmethod.InputMethodSubtype;
+
+public class InputMethodServiceCompatWrapper extends InputMethodService {
+    // CAN_HANDLE_ON_CURRENT_INPUT_METHOD_SUBTYPE_CHANGED needs to be false if the API level is 10
+    // or previous. Note that InputMethodSubtype was added in the API level 11.
+    // For the API level 11 or later, LatinIME should override onCurrentInputMethodSubtypeChanged().
+    // For the API level 10 or previous, we handle the "subtype changed" events by ourselves
+    // without having support from framework -- onCurrentInputMethodSubtypeChanged().
+    public static final boolean CAN_HANDLE_ON_CURRENT_INPUT_METHOD_SUBTYPE_CHANGED = true;
+
+    private InputMethodManagerCompatWrapper mImm;
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        mImm = InputMethodManagerCompatWrapper.getInstance(this);
+    }
+
+    // When the API level is 10 or previous, notifyOnCurrentInputMethodSubtypeChanged should
+    // handle the event the current subtype was changed. LatinIME calls
+    // notifyOnCurrentInputMethodSubtypeChanged every time LatinIME
+    // changes the current subtype.
+    // This call is required to let LatinIME itself know a subtype changed
+    // event when the API level is 10 or previous.
+    @SuppressWarnings("unused")
+    public void notifyOnCurrentInputMethodSubtypeChanged(InputMethodSubtypeCompatWrapper subtype) {
+        // Do nothing when the API level is 11 or later
+        // and FORCE_ENABLE_VOICE_EVEN_WITH_NO_VOICE_SUBTYPES is not true
+        if (CAN_HANDLE_ON_CURRENT_INPUT_METHOD_SUBTYPE_CHANGED && !InputMethodManagerCompatWrapper.
+                FORCE_ENABLE_VOICE_EVEN_WITH_NO_VOICE_SUBTYPES) {
+            return;
+        }
+        if (subtype == null) {
+            subtype = mImm.getCurrentInputMethodSubtype();
+        }
+        if (subtype != null) {
+            if (!InputMethodManagerCompatWrapper.FORCE_ENABLE_VOICE_EVEN_WITH_NO_VOICE_SUBTYPES
+                    && !subtype.isDummy()) return;
+            if (!InputMethodManagerCompatWrapper.SUBTYPE_SUPPORTED) {
+                LanguageSwitcherProxy.getInstance().setLocale(subtype.getLocale());
+            }
+            SubtypeSwitcher.getInstance().updateSubtype(subtype);
+        }
+    }
+
+    //////////////////////////////////////
+    // Functions using API v11 or later //
+    //////////////////////////////////////
+    @Override
+    public void onCurrentInputMethodSubtypeChanged(InputMethodSubtype subtype) {
+        // Do nothing when the API level is 10 or previous
+        if (!CAN_HANDLE_ON_CURRENT_INPUT_METHOD_SUBTYPE_CHANGED) return;
+        SubtypeSwitcher.getInstance().updateSubtype(
+                new InputMethodSubtypeCompatWrapper(subtype));
+    }
+
+    protected static void setTouchableRegionCompat(InputMethodService.Insets outInsets,
+            int x, int y, int width, int height) {
+        outInsets.touchableInsets = InputMethodService.Insets.TOUCHABLE_INSETS_REGION;
+        outInsets.touchableRegion.set(x, y, width, height);
+    }
+}
diff --git a/java/src/com/android/inputmethod/compat/InputMethodSubtypeCompatWrapper.java b/java/src/com/android/inputmethod/compat/InputMethodSubtypeCompatWrapper.java
new file mode 100644
index 0000000..806c355
--- /dev/null
+++ b/java/src/com/android/inputmethod/compat/InputMethodSubtypeCompatWrapper.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.compat;
+
+import com.android.inputmethod.latin.LatinImeLogger;
+
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.lang.reflect.Method;
+import java.util.Arrays;
+
+// TODO: Override this class with the concrete implementation if we need to take care of the
+// performance.
+public final class InputMethodSubtypeCompatWrapper extends AbstractCompatWrapper {
+    private static final boolean DBG = LatinImeLogger.sDBG;
+    private static final String TAG = InputMethodSubtypeCompatWrapper.class.getSimpleName();
+    private static final String DEFAULT_LOCALE = "en_US";
+    private static final String DEFAULT_MODE = "keyboard";
+
+    public static final Class<?> CLASS_InputMethodSubtype =
+            CompatUtils.getClass("android.view.inputmethod.InputMethodSubtype");
+    private static final Method METHOD_getNameResId =
+            CompatUtils.getMethod(CLASS_InputMethodSubtype, "getNameResId");
+    private static final Method METHOD_getIconResId =
+            CompatUtils.getMethod(CLASS_InputMethodSubtype, "getIconResId");
+    private static final Method METHOD_getLocale =
+            CompatUtils.getMethod(CLASS_InputMethodSubtype, "getLocale");
+    private static final Method METHOD_getMode =
+            CompatUtils.getMethod(CLASS_InputMethodSubtype, "getMode");
+    private static final Method METHOD_getExtraValue =
+            CompatUtils.getMethod(CLASS_InputMethodSubtype, "getExtraValue");
+    private static final Method METHOD_containsExtraValueKey =
+            CompatUtils.getMethod(CLASS_InputMethodSubtype, "containsExtraValueKey", String.class);
+    private static final Method METHOD_getExtraValueOf =
+            CompatUtils.getMethod(CLASS_InputMethodSubtype, "getExtraValueOf", String.class);
+
+    private final int mDummyNameResId;
+    private final int mDummyIconResId;
+    private final String mDummyLocale;
+    private final String mDummyMode;
+    private final String mDummyExtraValues;
+
+    public InputMethodSubtypeCompatWrapper(Object subtype) {
+        super((CLASS_InputMethodSubtype != null && CLASS_InputMethodSubtype.isInstance(subtype))
+                ? subtype : null);
+        mDummyNameResId = 0;
+        mDummyIconResId = 0;
+        mDummyLocale = DEFAULT_LOCALE;
+        mDummyMode = DEFAULT_MODE;
+        mDummyExtraValues = "";
+    }
+
+    // Constructor for creating a dummy subtype.
+    public InputMethodSubtypeCompatWrapper(int nameResId, int iconResId, String locale,
+            String mode, String extraValues) {
+        super(null);
+        if (DBG) {
+            Log.d(TAG, "CreateInputMethodSubtypeCompatWrapper");
+        }
+        mDummyNameResId = nameResId;
+        mDummyIconResId = iconResId;
+        mDummyLocale = locale != null ? locale : "";
+        mDummyMode = mode != null ? mode : "";
+        mDummyExtraValues = extraValues != null ? extraValues : "";
+    }
+
+    public int getNameResId() {
+        if (mObj == null) return mDummyNameResId;
+        return (Integer)CompatUtils.invoke(mObj, 0, METHOD_getNameResId);
+    }
+
+    public int getIconResId() {
+        if (mObj == null) return mDummyIconResId;
+        return (Integer)CompatUtils.invoke(mObj, 0, METHOD_getIconResId);
+    }
+
+    public String getLocale() {
+        if (mObj == null) return mDummyLocale;
+        final String s = (String)CompatUtils.invoke(mObj, null, METHOD_getLocale);
+        if (TextUtils.isEmpty(s)) return DEFAULT_LOCALE;
+        return s;
+    }
+
+    public String getMode() {
+        if (mObj == null) return mDummyMode;
+        String s = (String)CompatUtils.invoke(mObj, null, METHOD_getMode);
+        if (TextUtils.isEmpty(s)) return DEFAULT_MODE;
+        return s;
+    }
+
+    public String getExtraValue() {
+        if (mObj == null) return mDummyExtraValues;
+        return (String)CompatUtils.invoke(mObj, null, METHOD_getExtraValue);
+    }
+
+    public boolean containsExtraValueKey(String key) {
+        return (Boolean)CompatUtils.invoke(mObj, false, METHOD_containsExtraValueKey, key);
+    }
+
+    public String getExtraValueOf(String key) {
+        return (String)CompatUtils.invoke(mObj, null, METHOD_getExtraValueOf, key);
+    }
+
+    public boolean isDummy() {
+        return !hasOriginalObject();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o instanceof InputMethodSubtypeCompatWrapper) {
+            InputMethodSubtypeCompatWrapper subtype = (InputMethodSubtypeCompatWrapper)o;
+            if (mObj == null) {
+                // easy check of dummy subtypes
+                return (mDummyNameResId == subtype.mDummyNameResId
+                        && mDummyIconResId == subtype.mDummyIconResId
+                        && mDummyLocale.equals(subtype.mDummyLocale)
+                        && mDummyMode.equals(subtype.mDummyMode)
+                        && mDummyExtraValues.equals(subtype.mDummyExtraValues));
+            }
+            return mObj.equals(subtype.getOriginalObject());
+        } else {
+            return mObj.equals(o);
+        }
+    }
+
+    @Override
+    public int hashCode() {
+        if (mObj == null) {
+            return hashCodeInternal(mDummyNameResId, mDummyIconResId, mDummyLocale,
+                    mDummyMode, mDummyExtraValues);
+        }
+        return mObj.hashCode();
+    }
+
+    private static int hashCodeInternal(int nameResId, int iconResId, String locale,
+            String mode, String extraValue) {
+        return Arrays
+                .hashCode(new Object[] { nameResId, iconResId, locale, mode, extraValue });
+    }
+}
diff --git a/java/src/com/android/inputmethod/compat/InputTypeCompatUtils.java b/java/src/com/android/inputmethod/compat/InputTypeCompatUtils.java
new file mode 100644
index 0000000..d851741
--- /dev/null
+++ b/java/src/com/android/inputmethod/compat/InputTypeCompatUtils.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.compat;
+
+import android.text.InputType;
+
+import java.lang.reflect.Field;
+
+public class InputTypeCompatUtils {
+    private static final Field FIELD_InputType_TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS =
+            CompatUtils.getField(InputType.class, "TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS");
+    private static final Field FIELD_InputType_TYPE_TEXT_VARIATION_WEB_PASSWORD = CompatUtils
+            .getField(InputType.class, "TYPE_TEXT_VARIATION_WEB_PASSWORD");
+    private static final Field FIELD_InputType_TYPE_NUMBER_VARIATION_PASSWORD = CompatUtils
+            .getField(InputType.class, "TYPE_NUMBER_VARIATION_PASSWORD");
+    private static final Integer OBJ_InputType_TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS =
+            (Integer) CompatUtils.getFieldValue(null, null,
+                    FIELD_InputType_TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS);
+    private static final Integer OBJ_InputType_TYPE_TEXT_VARIATION_WEB_PASSWORD =
+            (Integer) CompatUtils.getFieldValue(null, null,
+                    FIELD_InputType_TYPE_TEXT_VARIATION_WEB_PASSWORD);
+    private static final Integer OBJ_InputType_TYPE_NUMBER_VARIATION_PASSWORD =
+            (Integer) CompatUtils.getFieldValue(null, null,
+                    FIELD_InputType_TYPE_NUMBER_VARIATION_PASSWORD);
+    private static final int WEB_TEXT_PASSWORD_INPUT_TYPE;
+    private static final int NUMBER_PASSWORD_INPUT_TYPE;
+    private static final int TEXT_PASSWORD_INPUT_TYPE =
+            InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD;
+    private static final int TEXT_VISIBLE_PASSWORD_INPUT_TYPE =
+            InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD;
+
+    static {
+        WEB_TEXT_PASSWORD_INPUT_TYPE =
+                OBJ_InputType_TYPE_TEXT_VARIATION_WEB_PASSWORD != null
+                        ? InputType.TYPE_CLASS_TEXT | OBJ_InputType_TYPE_TEXT_VARIATION_WEB_PASSWORD
+                        : 0;
+        NUMBER_PASSWORD_INPUT_TYPE =
+                OBJ_InputType_TYPE_NUMBER_VARIATION_PASSWORD != null
+                        ? InputType.TYPE_CLASS_NUMBER | OBJ_InputType_TYPE_NUMBER_VARIATION_PASSWORD
+                        : 0;
+    }
+
+    private static boolean isWebPasswordInputType(int inputType) {
+        return WEB_TEXT_PASSWORD_INPUT_TYPE != 0
+                && inputType == WEB_TEXT_PASSWORD_INPUT_TYPE;
+    }
+
+    private static boolean isNumberPasswordInputType(int inputType) {
+        return NUMBER_PASSWORD_INPUT_TYPE != 0
+                && inputType == NUMBER_PASSWORD_INPUT_TYPE;
+    }
+
+    private static boolean isTextPasswordInputType(int inputType) {
+        return inputType == TEXT_PASSWORD_INPUT_TYPE;
+    }
+
+    private static boolean isWebEmailAddressVariation(int variation) {
+        return OBJ_InputType_TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS != null
+                && variation == OBJ_InputType_TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS;
+    }
+
+    public static boolean isEmailVariation(int variation) {
+        return variation == InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
+                || isWebEmailAddressVariation(variation);
+    }
+
+    // Please refer to TextView.isPasswordInputType
+    public static boolean isPasswordInputType(int inputType) {
+        final int maskedInputType =
+                inputType & (InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION);
+        return isTextPasswordInputType(maskedInputType) || isWebPasswordInputType(maskedInputType)
+                || isNumberPasswordInputType(maskedInputType);
+    }
+
+    // Please refer to TextView.isVisiblePasswordInputType
+    public static boolean isVisiblePasswordInputType(int inputType) {
+        final int maskedInputType =
+                inputType & (InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION);
+        return maskedInputType == TEXT_VISIBLE_PASSWORD_INPUT_TYPE;
+    }
+}
diff --git a/java/src/com/android/inputmethod/compat/VibratorCompatWrapper.java b/java/src/com/android/inputmethod/compat/VibratorCompatWrapper.java
new file mode 100644
index 0000000..8e2a2e0
--- /dev/null
+++ b/java/src/com/android/inputmethod/compat/VibratorCompatWrapper.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.compat;
+
+import android.content.Context;
+import android.os.Vibrator;
+
+import java.lang.reflect.Method;
+
+public class VibratorCompatWrapper {
+    private static final Method METHOD_hasVibrator = CompatUtils.getMethod(Vibrator.class,
+            "hasVibrator", int.class);
+
+    private static final VibratorCompatWrapper sInstance = new VibratorCompatWrapper();
+    private Vibrator mVibrator;
+
+    private VibratorCompatWrapper() {
+    }
+
+    public static VibratorCompatWrapper getInstance(Context context) {
+        if (sInstance.mVibrator == null) {
+            sInstance.mVibrator =
+                    (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
+        }
+        return sInstance;
+    }
+
+    public boolean hasVibrator() {
+        if (mVibrator == null)
+            return false;
+        return (Boolean) CompatUtils.invoke(mVibrator, true, METHOD_hasVibrator);
+    }
+}
diff --git a/java/src/com/android/inputmethod/deprecated/LanguageSwitcherProxy.java b/java/src/com/android/inputmethod/deprecated/LanguageSwitcherProxy.java
new file mode 100644
index 0000000..e14a49c
--- /dev/null
+++ b/java/src/com/android/inputmethod/deprecated/LanguageSwitcherProxy.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2011 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * 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.deprecated;
+
+import com.android.inputmethod.compat.InputMethodManagerCompatWrapper;
+import com.android.inputmethod.deprecated.languageswitcher.LanguageSwitcher;
+import com.android.inputmethod.latin.LatinIME;
+import com.android.inputmethod.latin.Settings;
+
+import android.content.SharedPreferences;
+import android.content.res.Configuration;
+
+import java.util.Locale;
+
+// This class is used only when the IME doesn't use method.xml for language switching.
+public class LanguageSwitcherProxy implements SharedPreferences.OnSharedPreferenceChangeListener {
+    private static final LanguageSwitcherProxy sInstance = new LanguageSwitcherProxy();
+    private LatinIME mService;
+    private LanguageSwitcher mLanguageSwitcher;
+    private SharedPreferences mPrefs;
+
+    public static LanguageSwitcherProxy getInstance() {
+        if (InputMethodManagerCompatWrapper.SUBTYPE_SUPPORTED) return null;
+        return sInstance;
+    }
+
+    public static void init(LatinIME service, SharedPreferences prefs) {
+        if (InputMethodManagerCompatWrapper.SUBTYPE_SUPPORTED) return;
+        final Configuration conf = service.getResources().getConfiguration();
+        sInstance.mLanguageSwitcher = new LanguageSwitcher(service);
+        sInstance.mLanguageSwitcher.loadLocales(prefs, conf.locale);
+        sInstance.mPrefs = prefs;
+        sInstance.mService = service;
+        prefs.registerOnSharedPreferenceChangeListener(sInstance);
+    }
+
+    public static void onConfigurationChanged(Configuration conf) {
+        if (InputMethodManagerCompatWrapper.SUBTYPE_SUPPORTED) return;
+        sInstance.mLanguageSwitcher.onConfigurationChanged(conf, sInstance.mPrefs);
+    }
+
+    public static void loadSettings() {
+        if (InputMethodManagerCompatWrapper.SUBTYPE_SUPPORTED) return;
+        sInstance.mLanguageSwitcher.loadLocales(sInstance.mPrefs, null);
+    }
+
+    public int getLocaleCount() {
+        return mLanguageSwitcher.getLocaleCount();
+    }
+
+    public String[] getEnabledLanguages(boolean allowImplicitlySelectedLanguages) {
+        return mLanguageSwitcher.getEnabledLanguages(allowImplicitlySelectedLanguages);
+    }
+
+    public Locale getInputLocale() {
+        return mLanguageSwitcher.getInputLocale();
+    }
+
+    public void setLocale(String localeStr) {
+        mLanguageSwitcher.setLocale(localeStr);
+        mLanguageSwitcher.persist(mPrefs);
+    }
+
+    @Override
+    public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
+        // PREF_SELECTED_LANGUAGES: enabled input subtypes
+        // PREF_INPUT_LANGUAGE: current input subtype
+        if (key.equals(Settings.PREF_SELECTED_LANGUAGES)
+                || key.equals(Settings.PREF_INPUT_LANGUAGE)) {
+            mLanguageSwitcher.loadLocales(prefs, null);
+            if (mService != null) {
+                mService.onRefreshKeyboard();
+            }
+        }
+    }
+}
diff --git a/java/src/com/android/inputmethod/voice/VoiceIMEConnector.java b/java/src/com/android/inputmethod/deprecated/VoiceProxy.java
similarity index 83%
rename from java/src/com/android/inputmethod/voice/VoiceIMEConnector.java
rename to java/src/com/android/inputmethod/deprecated/VoiceProxy.java
index 105656f..753dcee 100644
--- a/java/src/com/android/inputmethod/voice/VoiceIMEConnector.java
+++ b/java/src/com/android/inputmethod/deprecated/VoiceProxy.java
@@ -14,8 +14,14 @@
  * the License.
  */
 
-package com.android.inputmethod.voice;
+package com.android.inputmethod.deprecated;
 
+import com.android.inputmethod.compat.InputMethodManagerCompatWrapper;
+import com.android.inputmethod.deprecated.voice.FieldContext;
+import com.android.inputmethod.deprecated.voice.Hints;
+import com.android.inputmethod.deprecated.voice.SettingsUtil;
+import com.android.inputmethod.deprecated.voice.VoiceInput;
+import com.android.inputmethod.deprecated.voice.VoiceInputLogger;
 import com.android.inputmethod.keyboard.KeyboardSwitcher;
 import com.android.inputmethod.latin.EditingUtils;
 import com.android.inputmethod.latin.LatinIME;
@@ -28,6 +34,7 @@
 import com.android.inputmethod.latin.Utils;
 
 import android.app.AlertDialog;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
@@ -54,7 +61,6 @@
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.ExtractedTextRequest;
 import android.view.inputmethod.InputConnection;
-import android.view.inputmethod.InputMethodManager;
 import android.widget.TextView;
 
 import java.util.ArrayList;
@@ -62,8 +68,8 @@
 import java.util.List;
 import java.util.Map;
 
-public class VoiceIMEConnector implements VoiceInput.UiListener {
-    private static final VoiceIMEConnector sInstance = new VoiceIMEConnector();
+public class VoiceProxy implements VoiceInput.UiListener {
+    private static final VoiceProxy sInstance = new VoiceProxy();
 
     public static final boolean VOICE_INSTALLED = true;
     private static final boolean ENABLE_VOICE_BUTTON = true;
@@ -76,8 +82,10 @@
     private static final String PREF_HAS_USED_VOICE_INPUT_UNSUPPORTED_LOCALE =
             "has_used_voice_input_unsupported_locale";
     private static final int RECOGNITIONVIEW_HEIGHT_THRESHOLD_RATIO = 6;
+    // TODO: Adjusted on phones for now
+    private static final int RECOGNITIONVIEW_MINIMUM_HEIGHT_DIP = 244;
 
-    private static final String TAG = VoiceIMEConnector.class.getSimpleName();
+    private static final String TAG = VoiceProxy.class.getSimpleName();
     private static final boolean DEBUG = LatinImeLogger.sDBG;
 
     private boolean mAfterVoiceInput;
@@ -93,7 +101,8 @@
     private boolean mVoiceButtonOnPrimary;
     private boolean mVoiceInputHighlighted;
 
-    private InputMethodManager mImm;
+    private int mMinimumVoiceRecognitionViewHeightPixel;
+    private InputMethodManagerCompatWrapper mImm;
     private LatinIME mService;
     private AlertDialog mVoiceWarningDialog;
     private VoiceInput mVoiceInput;
@@ -101,23 +110,26 @@
     private Hints mHints;
     private UIHandler mHandler;
     private SubtypeSwitcher mSubtypeSwitcher;
+
     // For each word, a list of potential replacements, usually from voice.
     private final Map<String, List<CharSequence>> mWordToSuggestions =
             new HashMap<String, List<CharSequence>>();
 
-    public static VoiceIMEConnector init(LatinIME context, SharedPreferences prefs, UIHandler h) {
+    public static VoiceProxy init(LatinIME context, SharedPreferences prefs, UIHandler h) {
         sInstance.initInternal(context, prefs, h);
         return sInstance;
     }
 
-    public static VoiceIMEConnector getInstance() {
+    public static VoiceProxy getInstance() {
         return sInstance;
     }
 
     private void initInternal(LatinIME service, SharedPreferences prefs, UIHandler h) {
         mService = service;
         mHandler = h;
-        mImm = (InputMethodManager) service.getSystemService(Context.INPUT_METHOD_SERVICE);
+        mMinimumVoiceRecognitionViewHeightPixel = Utils.dipToPixel(
+                Utils.getDipScale(service), RECOGNITIONVIEW_MINIMUM_HEIGHT_DIP);
+        mImm = InputMethodManagerCompatWrapper.getInstance(service);
         mSubtypeSwitcher = SubtypeSwitcher.getInstance();
         if (VOICE_INSTALLED) {
             mVoiceInput = new VoiceInput(service, this);
@@ -133,7 +145,7 @@
         }
     }
 
-    private VoiceIMEConnector() {
+    private VoiceProxy() {
         // Intentional empty constructor for singleton.
     }
 
@@ -536,7 +548,11 @@
                             mService.getResources().getDisplayMetrics().heightPixels;
                     final int currentHeight = popupLayout.getLayoutParams().height;
                     final int keyboardHeight = keyboardView.getHeight();
-                    if (keyboardHeight > currentHeight || keyboardHeight
+                    if (mMinimumVoiceRecognitionViewHeightPixel > keyboardHeight
+                            || mMinimumVoiceRecognitionViewHeightPixel > currentHeight) {
+                        popupLayout.getLayoutParams().height =
+                            mMinimumVoiceRecognitionViewHeightPixel;
+                    } else if (keyboardHeight > currentHeight || keyboardHeight
                             > (displayHeight / RECOGNITIONVIEW_HEIGHT_THRESHOLD_RATIO)) {
                         popupLayout.getLayoutParams().height = keyboardHeight;
                     }
@@ -560,14 +576,24 @@
 
             @Override
             protected void onPostExecute(Boolean result) {
+                // Calls in this method need to be done in the same thread as the thread which
+                // called switchToLastInputMethod()
                 if (!result) {
                     if (DEBUG) {
                         Log.d(TAG, "Couldn't switch back to last IME.");
                     }
-                    // Needs to reset here because LatinIME failed to back to any IME and
-                    // the same voice subtype will be triggered in the next time.
+                    // Because the current IME and subtype failed to switch to any other IME and
+                    // subtype by switchToLastInputMethod, the current IME and subtype should keep
+                    // being LatinIME and voice subtype in the next time. And for re-showing voice
+                    // mode, the state of voice input should be reset and the voice view should be
+                    // hidden.
                     mVoiceInput.reset();
                     mService.requestHideSelf(0);
+                } else {
+                    // Notify an event that the current subtype was changed. This event will be
+                    // handled if "onCurrentInputMethodSubtypeChanged" can't be implemented
+                    // when the API level is 10 or previous.
+                    mService.notifyOnCurrentInputMethodSubtypeChanged(null);
                 }
             }
         }.execute();
@@ -624,6 +650,7 @@
     }
 
     private boolean shouldShowVoiceButton(FieldContext fieldContext, EditorInfo attribute) {
+        @SuppressWarnings("deprecation")
         final boolean noMic = Utils.inPrivateImeOptions(null,
                 LatinIME.IME_OPTION_NO_MICROPHONE_COMPAT, attribute)
                 || Utils.inPrivateImeOptions(mService.getPackageName(),
@@ -674,7 +701,7 @@
     public void onAttachedToWindow() {
         // After onAttachedToWindow, we can show the voice warning dialog. See startListening()
         // above.
-        mSubtypeSwitcher.setVoiceInput(mVoiceInput);
+        VoiceInputWrapper.getInstance().setVoiceInput(mVoiceInput, mSubtypeSwitcher);
     }
 
     public void onConfigurationChanged(Configuration configuration) {
@@ -725,4 +752,91 @@
         List<String> candidates;
         Map<String, List<CharSequence>> alternatives;
     }
+
+    public static class VoiceLoggerWrapper {
+        private static final VoiceLoggerWrapper sLoggerWrapperInstance = new VoiceLoggerWrapper();
+        private VoiceInputLogger mLogger;
+
+        public static VoiceLoggerWrapper getInstance(Context context) {
+            if (sLoggerWrapperInstance.mLogger == null) {
+                // Not thread safe, but it's ok.
+                sLoggerWrapperInstance.mLogger = VoiceInputLogger.getLogger(context);
+            }
+            return sLoggerWrapperInstance;
+        }
+
+        // private for the singleton
+        private VoiceLoggerWrapper() {
+        }
+
+        public void settingsWarningDialogCancel() {
+            mLogger.settingsWarningDialogCancel();
+        }
+
+        public void settingsWarningDialogOk() {
+            mLogger.settingsWarningDialogOk();
+        }
+
+        public void settingsWarningDialogShown() {
+            mLogger.settingsWarningDialogShown();
+        }
+
+        public void settingsWarningDialogDismissed() {
+            mLogger.settingsWarningDialogDismissed();
+        }
+
+        public void voiceInputSettingEnabled(boolean enabled) {
+            if (enabled) {
+                mLogger.voiceInputSettingEnabled();
+            } else {
+                mLogger.voiceInputSettingDisabled();
+            }
+        }
+    }
+
+    public static class VoiceInputWrapper {
+        private static final VoiceInputWrapper sInputWrapperInstance = new VoiceInputWrapper();
+        private VoiceInput mVoiceInput;
+        public static VoiceInputWrapper getInstance() {
+            return sInputWrapperInstance;
+        }
+        public void setVoiceInput(VoiceInput voiceInput, SubtypeSwitcher switcher) {
+            if (mVoiceInput == null && voiceInput != null) {
+                mVoiceInput = voiceInput;
+            }
+            switcher.setVoiceInputWrapper(this);
+        }
+
+        private VoiceInputWrapper() {
+        }
+
+        public void cancel() {
+            if (mVoiceInput != null) mVoiceInput.cancel();
+        }
+
+        public void reset() {
+            if (mVoiceInput != null) mVoiceInput.reset();
+        }
+    }
+
+    // A list of locales which are supported by default for voice input, unless we get a
+    // different list from Gservices.
+    private static final String DEFAULT_VOICE_INPUT_SUPPORTED_LOCALES =
+            "en " +
+            "en_US " +
+            "en_GB " +
+            "en_AU " +
+            "en_CA " +
+            "en_IE " +
+            "en_IN " +
+            "en_NZ " +
+            "en_SG " +
+            "en_ZA ";
+
+    public static String getSupportedLocalesString (ContentResolver resolver) {
+        return SettingsUtil.getSettingsString(
+                resolver,
+                SettingsUtil.LATIN_IME_VOICE_INPUT_SUPPORTED_LOCALES,
+                DEFAULT_VOICE_INPUT_SUPPORTED_LOCALES);
+    }
 }
diff --git a/java/src/com/android/inputmethod/deprecated/compat/VoiceInputLoggerCompatUtils.java b/java/src/com/android/inputmethod/deprecated/compat/VoiceInputLoggerCompatUtils.java
new file mode 100644
index 0000000..488390f
--- /dev/null
+++ b/java/src/com/android/inputmethod/deprecated/compat/VoiceInputLoggerCompatUtils.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.deprecated.compat;
+
+import com.android.common.userhappiness.UserHappinessSignals;
+import com.android.inputmethod.compat.CompatUtils;
+
+import java.lang.reflect.Method;
+
+public class VoiceInputLoggerCompatUtils {
+    public static final String EXTRA_TEXT_REPLACED_LENGTH = "length";
+    public static final String EXTRA_BEFORE_N_BEST_CHOOSE = "before";
+    public static final String EXTRA_AFTER_N_BEST_CHOOSE = "after";
+    private static final Method METHOD_UserHappinessSignals_setHasVoiceLoggingInfo =
+            CompatUtils.getMethod(UserHappinessSignals.class, "setHasVoiceLoggingInfo",
+                    boolean.class);
+
+    public static void setHasVoiceLoggingInfoCompat(boolean hasLoggingInfo) {
+        CompatUtils.invoke(null, null, METHOD_UserHappinessSignals_setHasVoiceLoggingInfo,
+                hasLoggingInfo);
+    }
+}
diff --git a/java/src/com/android/inputmethod/deprecated/languageswitcher/InputLanguageSelection.java b/java/src/com/android/inputmethod/deprecated/languageswitcher/InputLanguageSelection.java
new file mode 100644
index 0000000..a1b49b4
--- /dev/null
+++ b/java/src/com/android/inputmethod/deprecated/languageswitcher/InputLanguageSelection.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2008-2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * 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.deprecated.languageswitcher;
+
+import com.android.inputmethod.keyboard.KeyboardParser;
+import com.android.inputmethod.latin.DictionaryFactory;
+import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.Settings;
+import com.android.inputmethod.latin.SharedPreferencesCompat;
+import com.android.inputmethod.latin.SubtypeSwitcher;
+import com.android.inputmethod.latin.Utils;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.Editor;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.preference.CheckBoxPreference;
+import android.preference.PreferenceActivity;
+import android.preference.PreferenceGroup;
+import android.preference.PreferenceManager;
+import android.text.TextUtils;
+import android.util.Pair;
+
+import java.io.IOException;
+import java.text.Collator;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Locale;
+
+public class InputLanguageSelection extends PreferenceActivity {
+
+    private SharedPreferences mPrefs;
+    private String mSelectedLanguages;
+    private HashMap<CheckBoxPreference, Locale> mLocaleMap =
+            new HashMap<CheckBoxPreference, Locale>();
+
+    private static class Loc implements Comparable<Object> {
+        private static Collator sCollator = Collator.getInstance();
+
+        private String mLabel;
+        public final Locale mLocale;
+
+        public Loc(String label, Locale locale) {
+            this.mLabel = label;
+            this.mLocale = locale;
+        }
+
+        public void setLabel(String label) {
+            this.mLabel = label;
+        }
+
+        @Override
+        public String toString() {
+            return this.mLabel;
+        }
+
+        @Override
+        public int compareTo(Object o) {
+            return sCollator.compare(this.mLabel, ((Loc) o).mLabel);
+        }
+    }
+
+    @Override
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        addPreferencesFromResource(R.xml.language_prefs);
+        // Get the settings preferences
+        mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
+        mSelectedLanguages = mPrefs.getString(Settings.PREF_SELECTED_LANGUAGES, "");
+        String[] languageList = mSelectedLanguages.split(",");
+        ArrayList<Loc> availableLanguages = getUniqueLocales();
+        PreferenceGroup parent = getPreferenceScreen();
+        for (int i = 0; i < availableLanguages.size(); i++) {
+            Locale locale = availableLanguages.get(i).mLocale;
+            final Pair<Boolean, Boolean> hasDictionaryOrLayout = hasDictionaryOrLayout(locale);
+            final boolean hasDictionary = hasDictionaryOrLayout.first;
+            final boolean hasLayout = hasDictionaryOrLayout.second;
+            // Add this locale to the supported list if:
+            // 1) this locale has a layout/ 2) this locale has a dictionary and the length
+            // of the locale is equal to or larger than 5.
+            if (!hasLayout && !(hasDictionary && locale.toString().length() >= 5)) {
+                continue;
+            }
+            CheckBoxPreference pref = new CheckBoxPreference(this);
+            pref.setTitle(SubtypeSwitcher.getFullDisplayName(locale, true));
+            boolean checked = isLocaleIn(locale, languageList);
+            pref.setChecked(checked);
+            if (hasDictionary) {
+                pref.setSummary(R.string.has_dictionary);
+            }
+            mLocaleMap.put(pref, locale);
+            parent.addPreference(pref);
+        }
+    }
+
+    private boolean isLocaleIn(Locale locale, String[] list) {
+        String lang = get5Code(locale);
+        for (int i = 0; i < list.length; i++) {
+            if (lang.equalsIgnoreCase(list[i])) return true;
+        }
+        return false;
+    }
+
+    private Pair<Boolean, Boolean> hasDictionaryOrLayout(Locale locale) {
+        if (locale == null) return new Pair<Boolean, Boolean>(false, false);
+        final Resources res = getResources();
+        final Locale saveLocale = Utils.setSystemLocale(res, locale);
+        final boolean hasDictionary = DictionaryFactory.isDictionaryAvailable(this, locale);
+        boolean hasLayout = false;
+
+        try {
+            final String localeStr = locale.toString();
+            final String[] layoutCountryCodes = KeyboardParser.parseKeyboardLocale(
+                    this, R.xml.kbd_qwerty).split(",", -1);
+            if (!TextUtils.isEmpty(localeStr) && layoutCountryCodes.length > 0) {
+                for (String s : layoutCountryCodes) {
+                    if (s.equals(localeStr)) {
+                        hasLayout = true;
+                        break;
+                    }
+                }
+            }
+        } catch (XmlPullParserException e) {
+        } catch (IOException e) {
+        }
+        Utils.setSystemLocale(res, saveLocale);
+        return new Pair<Boolean, Boolean>(hasDictionary, hasLayout);
+    }
+
+    private String get5Code(Locale locale) {
+        String country = locale.getCountry();
+        return locale.getLanguage()
+                + (TextUtils.isEmpty(country) ? "" : "_" + country);
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        // Save the selected languages
+        String checkedLanguages = "";
+        PreferenceGroup parent = getPreferenceScreen();
+        int count = parent.getPreferenceCount();
+        for (int i = 0; i < count; i++) {
+            CheckBoxPreference pref = (CheckBoxPreference) parent.getPreference(i);
+            if (pref.isChecked()) {
+                checkedLanguages += get5Code(mLocaleMap.get(pref)) + ",";
+            }
+        }
+        if (checkedLanguages.length() < 1) checkedLanguages = null; // Save null
+        Editor editor = mPrefs.edit();
+        editor.putString(Settings.PREF_SELECTED_LANGUAGES, checkedLanguages);
+        SharedPreferencesCompat.apply(editor);
+    }
+
+    public ArrayList<Loc> getUniqueLocales() {
+        String[] locales = getAssets().getLocales();
+        Arrays.sort(locales);
+        ArrayList<Loc> uniqueLocales = new ArrayList<Loc>();
+
+        final int origSize = locales.length;
+        Loc[] preprocess = new Loc[origSize];
+        int finalSize = 0;
+        for (int i = 0 ; i < origSize; i++ ) {
+            String s = locales[i];
+            int len = s.length();
+            String language = "";
+            String country = "";
+            if (len == 5) {
+                language = s.substring(0, 2);
+                country = s.substring(3, 5);
+            } else if (len < 5) {
+                language = s;
+            }
+            Locale l = new Locale(language, country);
+
+            // Exclude languages that are not relevant to LatinIME
+            if (TextUtils.isEmpty(language)) {
+                continue;
+            }
+
+            if (finalSize == 0) {
+                preprocess[finalSize++] =
+                        new Loc(SubtypeSwitcher.getFullDisplayName(l, true), l);
+            } else {
+                // check previous entry:
+                //  same lang and a country -> upgrade to full name and
+                //    insert ours with full name
+                //  diff lang -> insert ours with lang-only name
+                if (preprocess[finalSize-1].mLocale.getLanguage().equals(
+                        language)) {
+                    preprocess[finalSize-1].setLabel(SubtypeSwitcher.getFullDisplayName(
+                            preprocess[finalSize-1].mLocale, false));
+                    preprocess[finalSize++] =
+                            new Loc(SubtypeSwitcher.getFullDisplayName(l, false), l);
+                } else {
+                    String displayName;
+                    if (s.equals("zz_ZZ")) {
+                        // ignore this locale
+                    } else {
+                        displayName = SubtypeSwitcher.getFullDisplayName(l, true);
+                        preprocess[finalSize++] = new Loc(displayName, l);
+                    }
+                }
+            }
+        }
+        for (int i = 0; i < finalSize ; i++) {
+            uniqueLocales.add(preprocess[i]);
+        }
+        return uniqueLocales;
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/LanguageSwitcher.java b/java/src/com/android/inputmethod/deprecated/languageswitcher/LanguageSwitcher.java
similarity index 71%
rename from java/src/com/android/inputmethod/latin/LanguageSwitcher.java
rename to java/src/com/android/inputmethod/deprecated/languageswitcher/LanguageSwitcher.java
index 6faf7f9..5ef236e 100644
--- a/java/src/com/android/inputmethod/latin/LanguageSwitcher.java
+++ b/java/src/com/android/inputmethod/deprecated/languageswitcher/LanguageSwitcher.java
@@ -14,11 +14,18 @@
  * the License.
  */
 
-package com.android.inputmethod.latin;
+package com.android.inputmethod.deprecated.languageswitcher;
+
+import com.android.inputmethod.latin.LatinIME;
+import com.android.inputmethod.latin.LatinImeLogger;
+import com.android.inputmethod.latin.Settings;
+import com.android.inputmethod.latin.SharedPreferencesCompat;
 
 import android.content.SharedPreferences;
 import android.content.SharedPreferences.Editor;
+import android.content.res.Configuration;
 import android.text.TextUtils;
+import android.util.Log;
 
 import java.util.ArrayList;
 import java.util.Locale;
@@ -28,10 +35,15 @@
  * input language that the user has selected.
  */
 public class LanguageSwitcher {
+    private static final String TAG = LanguageSwitcher.class.getSimpleName();
+
+    @SuppressWarnings("unused")
+    private static final String KEYBOARD_MODE = "keyboard";
+    private static final String[] EMPTY_STIRNG_ARRAY = new String[0];
 
     private final ArrayList<Locale> mLocales = new ArrayList<Locale>();
     private final LatinIME mIme;
-    private String[] mSelectedLanguageArray;
+    private String[] mSelectedLanguageArray = EMPTY_STIRNG_ARRAY;
     private String   mSelectedLanguages;
     private int      mCurrentIndex = 0;
     private String   mDefaultInputLanguage;
@@ -46,15 +58,32 @@
         return mLocales.size();
     }
 
+    public void onConfigurationChanged(Configuration conf, SharedPreferences prefs) {
+        final Locale newLocale = conf.locale;
+        if (!getSystemLocale().toString().equals(newLocale.toString())) {
+            loadLocales(prefs, newLocale);
+        }
+    }
+
     /**
      * Loads the currently selected input languages from shared preferences.
-     * @param sp
+     * @param sp shared preference for getting the current input language and enabled languages
+     * @param systemLocale the current system locale, stored for changing the current input language
+     * based on the system current system locale.
      * @return whether there was any change
      */
-    public boolean loadLocales(SharedPreferences sp) {
+    public boolean loadLocales(SharedPreferences sp, Locale systemLocale) {
+        if (LatinImeLogger.sDBG) {
+            Log.d(TAG, "load locales");
+        }
+        if (systemLocale != null) {
+            setSystemLocale(systemLocale);
+        }
         String selectedLanguages = sp.getString(Settings.PREF_SELECTED_LANGUAGES, null);
         String currentLanguage   = sp.getString(Settings.PREF_INPUT_LANGUAGE, null);
-        if (selectedLanguages == null || selectedLanguages.length() < 1) {
+        if (TextUtils.isEmpty(selectedLanguages)) {
+            mSelectedLanguageArray = EMPTY_STIRNG_ARRAY;
+            mSelectedLanguages = null;
             loadDefaults();
             if (mLocales.size() == 0) {
                 return false;
@@ -84,6 +113,9 @@
     }
 
     private void loadDefaults() {
+        if (LatinImeLogger.sDBG) {
+            Log.d(TAG, "load default locales:");
+        }
         mDefaultInputLocale = mIme.getResources().getConfiguration().locale;
         String country = mDefaultInputLocale.getCountry();
         mDefaultInputLanguage = mDefaultInputLocale.getLanguage() +
@@ -112,14 +144,16 @@
     /**
      * Returns the list of enabled language codes.
      */
-    public String[] getEnabledLanguages() {
+    public String[] getEnabledLanguages(boolean allowImplicitlySelectedLanguages) {
+        if (mSelectedLanguageArray.length == 0 && allowImplicitlySelectedLanguages) {
+            return new String[] { mDefaultInputLanguage };
+        }
         return mSelectedLanguageArray;
     }
 
     /**
      * Returns the currently selected input locale, or the display locale if no specific
      * locale was selected for input.
-     * @return
      */
     public Locale getInputLocale() {
         if (getLocaleCount() == 0) return mDefaultInputLocale;
@@ -140,7 +174,6 @@
     /**
      * Returns the next input locale in the list. Wraps around to the beginning of the
      * list if we're at the end of the list.
-     * @return
      */
     public Locale getNextInputLocale() {
         if (getLocaleCount() == 0) return mDefaultInputLocale;
@@ -151,7 +184,7 @@
      * Sets the system locale (display UI) used for comparing with the input language.
      * @param locale the locale of the system
      */
-    public void setSystemLocale(Locale locale) {
+    private void setSystemLocale(Locale locale) {
         mSystemLocale = locale;
     }
 
@@ -159,14 +192,13 @@
      * Returns the system locale.
      * @return the system locale
      */
-    public Locale getSystemLocale() {
+    private Locale getSystemLocale() {
         return mSystemLocale;
     }
 
     /**
      * Returns the previous input locale in the list. Wraps around to the end of the
      * list if we're at the beginning of the list.
-     * @return
      */
     public Locale getPrevInputLocale() {
         if (getLocaleCount() == 0) return mDefaultInputLocale;
@@ -185,6 +217,15 @@
         mCurrentIndex = prevLocaleIndex();
     }
 
+    public void setLocale(String localeStr) {
+        final int N = mLocales.size();
+        for (int i = 0; i < N; ++i) {
+            if (mLocales.get(i).toString().equals(localeStr)) {
+                mCurrentIndex = i;
+            }
+        }
+    }
+
     public void persist(SharedPreferences prefs) {
         Editor editor = prefs.edit();
         editor.putString(Settings.PREF_INPUT_LANGUAGE, getInputLanguage());
diff --git a/java/src/com/android/inputmethod/voice/FieldContext.java b/java/src/com/android/inputmethod/deprecated/voice/FieldContext.java
similarity index 98%
rename from java/src/com/android/inputmethod/voice/FieldContext.java
rename to java/src/com/android/inputmethod/deprecated/voice/FieldContext.java
index dfdfbaa..0ef73d2 100644
--- a/java/src/com/android/inputmethod/voice/FieldContext.java
+++ b/java/src/com/android/inputmethod/deprecated/voice/FieldContext.java
@@ -14,7 +14,7 @@
  * the License.
  */
 
-package com.android.inputmethod.voice;
+package com.android.inputmethod.deprecated.voice;
 
 import android.os.Bundle;
 import android.util.Log;
diff --git a/java/src/com/android/inputmethod/voice/Hints.java b/java/src/com/android/inputmethod/deprecated/voice/Hints.java
similarity index 99%
rename from java/src/com/android/inputmethod/voice/Hints.java
rename to java/src/com/android/inputmethod/deprecated/voice/Hints.java
index d11d3b0..52a4f4e 100644
--- a/java/src/com/android/inputmethod/voice/Hints.java
+++ b/java/src/com/android/inputmethod/deprecated/voice/Hints.java
@@ -14,7 +14,7 @@
  * the License.
  */
 
-package com.android.inputmethod.voice;
+package com.android.inputmethod.deprecated.voice;
 
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.SharedPreferencesCompat;
diff --git a/java/src/com/android/inputmethod/voice/RecognitionView.java b/java/src/com/android/inputmethod/deprecated/voice/RecognitionView.java
similarity index 99%
rename from java/src/com/android/inputmethod/voice/RecognitionView.java
rename to java/src/com/android/inputmethod/deprecated/voice/RecognitionView.java
index 95a79f4..b57c16f 100644
--- a/java/src/com/android/inputmethod/voice/RecognitionView.java
+++ b/java/src/com/android/inputmethod/deprecated/voice/RecognitionView.java
@@ -14,7 +14,7 @@
  * the License.
  */
 
-package com.android.inputmethod.voice;
+package com.android.inputmethod.deprecated.voice;
 
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.SubtypeSwitcher;
@@ -50,7 +50,6 @@
  * plays beeps, shows errors, etc.
  */
 public class RecognitionView {
-    @SuppressWarnings("unused")
     private static final String TAG = "RecognitionView";
 
     private Handler mUiHandler;  // Reference to UI thread
diff --git a/java/src/com/android/inputmethod/voice/SettingsUtil.java b/java/src/com/android/inputmethod/deprecated/voice/SettingsUtil.java
similarity index 98%
rename from java/src/com/android/inputmethod/voice/SettingsUtil.java
rename to java/src/com/android/inputmethod/deprecated/voice/SettingsUtil.java
index 4d746e1..7721fe2 100644
--- a/java/src/com/android/inputmethod/voice/SettingsUtil.java
+++ b/java/src/com/android/inputmethod/deprecated/voice/SettingsUtil.java
@@ -14,7 +14,7 @@
  * the License.
  */
 
-package com.android.inputmethod.voice;
+package com.android.inputmethod.deprecated.voice;
 
 import android.content.ContentResolver;
 import android.provider.Settings;
diff --git a/java/src/com/android/inputmethod/voice/SoundIndicator.java b/java/src/com/android/inputmethod/deprecated/voice/SoundIndicator.java
similarity index 98%
rename from java/src/com/android/inputmethod/voice/SoundIndicator.java
rename to java/src/com/android/inputmethod/deprecated/voice/SoundIndicator.java
index 543290b..8cc79de 100644
--- a/java/src/com/android/inputmethod/voice/SoundIndicator.java
+++ b/java/src/com/android/inputmethod/deprecated/voice/SoundIndicator.java
@@ -14,7 +14,7 @@
  * the License.
  */
 
-package com.android.inputmethod.voice;
+package com.android.inputmethod.deprecated.voice;
 
 import android.content.Context;
 import android.graphics.Bitmap;
diff --git a/java/src/com/android/inputmethod/voice/VoiceInput.java b/java/src/com/android/inputmethod/deprecated/voice/VoiceInput.java
similarity index 99%
rename from java/src/com/android/inputmethod/voice/VoiceInput.java
rename to java/src/com/android/inputmethod/deprecated/voice/VoiceInput.java
index 2df9e85..7ee0de9 100644
--- a/java/src/com/android/inputmethod/voice/VoiceInput.java
+++ b/java/src/com/android/inputmethod/deprecated/voice/VoiceInput.java
@@ -14,7 +14,7 @@
  * the License.
  */
 
-package com.android.inputmethod.voice;
+package com.android.inputmethod.deprecated.voice;
 
 import com.android.inputmethod.latin.EditingUtils;
 import com.android.inputmethod.latin.LatinImeLogger;
diff --git a/java/src/com/android/inputmethod/voice/VoiceInputLogger.java b/java/src/com/android/inputmethod/deprecated/voice/VoiceInputLogger.java
similarity index 95%
rename from java/src/com/android/inputmethod/voice/VoiceInputLogger.java
rename to java/src/com/android/inputmethod/deprecated/voice/VoiceInputLogger.java
index 3e65434..87b9434 100644
--- a/java/src/com/android/inputmethod/voice/VoiceInputLogger.java
+++ b/java/src/com/android/inputmethod/deprecated/voice/VoiceInputLogger.java
@@ -14,10 +14,10 @@
  * the License.
  */
 
-package com.android.inputmethod.voice;
+package com.android.inputmethod.deprecated.voice;
 
 import com.android.common.speech.LoggingEvents;
-import com.android.common.userhappiness.UserHappinessSignals;
+import com.android.inputmethod.deprecated.compat.VoiceInputLoggerCompatUtils;
 
 import android.content.Context;
 import android.content.Intent;
@@ -212,13 +212,12 @@
         setHasLoggingInfo(true);
         Intent i = newLoggingBroadcast(LoggingEvents.VoiceIme.TEXT_MODIFIED);
         i.putExtra(LoggingEvents.VoiceIme.EXTRA_TEXT_MODIFIED_LENGTH, suggestionLength);
-        i.putExtra(LoggingEvents.VoiceIme.EXTRA_TEXT_REPLACED_LENGTH, replacedPhraseLength);
+        i.putExtra(VoiceInputLoggerCompatUtils.EXTRA_TEXT_REPLACED_LENGTH, replacedPhraseLength);
         i.putExtra(LoggingEvents.VoiceIme.EXTRA_TEXT_MODIFIED_TYPE,
                 LoggingEvents.VoiceIme.TEXT_MODIFIED_TYPE_CHOOSE_SUGGESTION);
-
         i.putExtra(LoggingEvents.VoiceIme.EXTRA_N_BEST_CHOOSE_INDEX, index);
-        i.putExtra(LoggingEvents.VoiceIme.EXTRA_BEFORE_N_BEST_CHOOSE, before);
-        i.putExtra(LoggingEvents.VoiceIme.EXTRA_AFTER_N_BEST_CHOOSE, after);
+        i.putExtra(VoiceInputLoggerCompatUtils.EXTRA_BEFORE_N_BEST_CHOOSE, before);
+        i.putExtra(VoiceInputLoggerCompatUtils.EXTRA_AFTER_N_BEST_CHOOSE, after);
         mContext.sendBroadcast(i);
     }
 
@@ -257,7 +256,7 @@
         // 2. type subject in subject field
         // 3. speak message in message field
         // 4. press send
-        UserHappinessSignals.setHasVoiceLoggingInfo(hasLoggingInfo);
+        VoiceInputLoggerCompatUtils.setHasVoiceLoggingInfoCompat(hasLoggingInfo);
     }
 
     private boolean hasLoggingInfo(){
diff --git a/java/src/com/android/inputmethod/voice/WaveformImage.java b/java/src/com/android/inputmethod/deprecated/voice/WaveformImage.java
similarity index 98%
rename from java/src/com/android/inputmethod/voice/WaveformImage.java
rename to java/src/com/android/inputmethod/deprecated/voice/WaveformImage.java
index 8bac669..a3025f2 100644
--- a/java/src/com/android/inputmethod/voice/WaveformImage.java
+++ b/java/src/com/android/inputmethod/deprecated/voice/WaveformImage.java
@@ -14,7 +14,7 @@
  * the License.
  */
 
-package com.android.inputmethod.voice;
+package com.android.inputmethod.deprecated.voice;
 
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
diff --git a/java/src/com/android/inputmethod/voice/Whitelist.java b/java/src/com/android/inputmethod/deprecated/voice/Whitelist.java
similarity index 97%
rename from java/src/com/android/inputmethod/voice/Whitelist.java
rename to java/src/com/android/inputmethod/deprecated/voice/Whitelist.java
index f4c24de..310689c 100644
--- a/java/src/com/android/inputmethod/voice/Whitelist.java
+++ b/java/src/com/android/inputmethod/deprecated/voice/Whitelist.java
@@ -14,7 +14,7 @@
  * the License.
  */
 
-package com.android.inputmethod.voice;
+package com.android.inputmethod.deprecated.voice;
 
 import android.os.Bundle;
 
diff --git a/java/src/com/android/inputmethod/keyboard/Key.java b/java/src/com/android/inputmethod/keyboard/Key.java
index 24e8926..5c59d44 100644
--- a/java/src/com/android/inputmethod/keyboard/Key.java
+++ b/java/src/com/android/inputmethod/keyboard/Key.java
@@ -61,8 +61,11 @@
     public final int mWidth;
     /** Height of the key, not including the gap */
     public final int mHeight;
-    /** The horizontal gap before this key */
+    /** The horizontal gap around this key */
     public final int mGap;
+    /** The visual insets */
+    public final int mVisualInsetsLeft;
+    public final int mVisualInsetsRight;
     /** Whether this key is sticky, i.e., a toggle key */
     public final boolean mSticky;
     /** X coordinate of the key in the keyboard layout */
@@ -83,8 +86,8 @@
      * {@link Keyboard#EDGE_TOP} and {@link Keyboard#EDGE_BOTTOM}.
      */
     public final int mEdgeFlags;
-    /** Whether this is a modifier key, such as Shift or Alt */
-    public final boolean mModifier;
+    /** Whether this is a functional key which has different key top than normal key */
+    public final boolean mFunctional;
     /** Whether this key repeats itself when held down */
     public final boolean mRepeatable;
 
@@ -93,8 +96,8 @@
 
     /** The current pressed state of this key */
     public boolean mPressed;
-    /** If this is a sticky key, is it on? */
-    public boolean mOn;
+    /** If this is a sticky key, is its highlight on? */
+    public boolean mHighlightOn;
     /** Key is enabled and responds on press */
     public boolean mEnabled = true;
 
@@ -144,13 +147,14 @@
         mKeyboard = keyboard;
         mHeight = keyboard.getRowHeight() - keyboard.getVerticalGap();
         mGap = keyboard.getHorizontalGap();
+        mVisualInsetsLeft = mVisualInsetsRight = 0;
         mWidth = width - mGap;
         mEdgeFlags = edgeFlags;
         mHintIcon = null;
         mManualTemporaryUpperCaseHintIcon = null;
         mManualTemporaryUpperCaseCode = Keyboard.CODE_DUMMY;
         mLabelOption = 0;
-        mModifier = false;
+        mFunctional = false;
         mSticky = false;
         mRepeatable = false;
         mPopupCharacters = null;
@@ -224,12 +228,16 @@
                     mKeyboard.getMaxPopupKeyboardColumn());
 
             mRepeatable = style.getBoolean(keyAttr, R.styleable.Keyboard_Key_isRepeatable, false);
-            mModifier = style.getBoolean(keyAttr, R.styleable.Keyboard_Key_isModifier, false);
+            mFunctional = style.getBoolean(keyAttr, R.styleable.Keyboard_Key_isFunctional, false);
             mSticky = style.getBoolean(keyAttr, R.styleable.Keyboard_Key_isSticky, false);
             mEnabled = style.getBoolean(keyAttr, R.styleable.Keyboard_Key_enabled, true);
             mEdgeFlags = style.getFlag(keyAttr, R.styleable.Keyboard_Key_keyEdgeFlags, 0)
                     | row.mRowEdgeFlags;
 
+            mVisualInsetsLeft = KeyboardParser.getDimensionOrFraction(keyAttr,
+                    R.styleable.Keyboard_Key_visualInsetsLeft, mKeyboard.getDisplayHeight(), 0);
+            mVisualInsetsRight = KeyboardParser.getDimensionOrFraction(keyAttr,
+                    R.styleable.Keyboard_Key_visualInsetsRight, mKeyboard.getDisplayHeight(), 0);
             mPreviewIcon = style.getDrawable(keyAttr, R.styleable.Keyboard_Key_iconPreview);
             Keyboard.setDefaultBounds(mPreviewIcon);
             mIcon = style.getDrawable(keyAttr, R.styleable.Keyboard_Key_keyIcon);
@@ -315,26 +323,19 @@
     /**
      * Informs the key that it has been pressed, in case it needs to change its appearance or
      * state.
-     * @see #onReleased(boolean)
+     * @see #onReleased()
      */
     public void onPressed() {
-        mPressed = !mPressed;
+        mPressed = true;
     }
 
     /**
-     * Changes the pressed state of the key. If it is a sticky key, it will also change the
-     * toggled state of the key if the finger was release inside.
-     * @param inside whether the finger was released inside the key
+     * Informs the key that it has been released, in case it needs to change its appearance or
+     * state.
      * @see #onPressed()
      */
-    public void onReleased(boolean inside) {
-        mPressed = !mPressed;
-        if (mSticky && !mKeyboard.isShiftLockEnabled(this))
-            mOn = !mOn;
-    }
-
-    public boolean isInside(int x, int y) {
-        return mKeyboard.isInside(this, x, y);
+    public void onReleased() {
+        mPressed = false;
     }
 
     /**
@@ -377,20 +378,14 @@
         return dx * dx + dy * dy;
     }
 
-    // sticky is used for shift key.  If a key is not sticky and is modifier,
-    // the key will be treated as functional.
-    private boolean isFunctionalKey() {
-        return !mSticky && mModifier;
-    }
-
     /**
      * Returns the drawable state for the key, based on the current state and type of the key.
      * @return the drawable state of the key.
      * @see android.graphics.drawable.StateListDrawable#setState(int[])
      */
     public int[] getCurrentDrawableState() {
-        final boolean pressed = mEnabled && mPressed;
-        if (isFunctionalKey()) {
+        final boolean pressed = mPressed;
+        if (!mSticky && mFunctional) {
             if (pressed) {
                 return KEY_STATE_FUNCTIONAL_PRESSED;
             } else {
@@ -400,7 +395,7 @@
 
         int[] states = KEY_STATE_NORMAL;
 
-        if (mOn) {
+        if (mHighlightOn) {
             if (pressed) {
                 states = KEY_STATE_PRESSED_ON;
             } else {
diff --git a/java/src/com/android/inputmethod/keyboard/KeyDetector.java b/java/src/com/android/inputmethod/keyboard/KeyDetector.java
index a7ede5f..0b13afe 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyDetector.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyDetector.java
@@ -16,36 +16,35 @@
 
 package com.android.inputmethod.keyboard;
 
+import android.util.Log;
+
 import java.util.Arrays;
-import java.util.HashMap;
 import java.util.List;
 
-public abstract class KeyDetector {
-    public static final int NOT_A_KEY = -1;
+public class KeyDetector {
+    private static final String TAG = KeyDetector.class.getSimpleName();
+    private static final boolean DEBUG = false;
+
     public static final int NOT_A_CODE = -1;
+    public static final int NOT_A_KEY = -1;
 
-    protected Keyboard mKeyboard;
+    private Keyboard mKeyboard;
+    private int mCorrectionX;
+    private int mCorrectionY;
+    private boolean mProximityCorrectOn;
+    private int mProximityThresholdSquare;
 
-    private Key[] mKeys;
+    // working area
+    private static final int MAX_NEARBY_KEYS = 12;
+    private final int[] mDistances = new int[MAX_NEARBY_KEYS];
+    private final int[] mIndices = new int[MAX_NEARBY_KEYS];
 
-    protected int mCorrectionX;
-
-    protected int mCorrectionY;
-
-    protected boolean mProximityCorrectOn;
-
-    protected int mProximityThresholdSquare;
-
-    public Key[] setKeyboard(Keyboard keyboard, float correctionX, float correctionY) {
+    public void setKeyboard(Keyboard keyboard, float correctionX, float correctionY) {
         if (keyboard == null)
             throw new NullPointerException();
         mCorrectionX = (int)correctionX;
         mCorrectionY = (int)correctionY;
         mKeyboard = keyboard;
-        List<Key> keys = mKeyboard.getKeys();
-        Key[] array = keys.toArray(new Key[keys.size()]);
-        mKeys = array;
-        return array;
     }
 
     protected int getTouchX(int x) {
@@ -56,11 +55,11 @@
         return y + mCorrectionY;
     }
 
-    protected Key[] getKeys() {
-        if (mKeys == null)
+    protected List<Key> getKeys() {
+        if (mKeyboard == null)
             throw new IllegalStateException("keyboard isn't set");
         // mKeyboard is guaranteed not to be null at setKeybaord() method if mKeys is not null
-        return mKeys;
+        return mKeyboard.getKeys();
     }
 
     public void setProximityCorrectionEnabled(boolean enabled) {
@@ -76,6 +75,17 @@
     }
 
     /**
+     * Computes maximum size of the array that can contain all nearby key indices returned by
+     * {@link #getKeyIndexAndNearbyCodes}.
+     *
+     * @return Returns maximum size of the array that can contain all nearby key indices returned
+     *         by {@link #getKeyIndexAndNearbyCodes}.
+     */
+    protected int getMaxNearbyKeys() {
+        return MAX_NEARBY_KEYS;
+    }
+
+    /**
      * Allocates array that can hold all key indices returned by {@link #getKeyIndexAndNearbyCodes}
      * method. The maximum size of the array should be computed by {@link #getMaxNearbyKeys}.
      *
@@ -89,14 +99,64 @@
         return codes;
     }
 
+    private void initializeNearbyKeys() {
+        Arrays.fill(mDistances, Integer.MAX_VALUE);
+        Arrays.fill(mIndices, NOT_A_KEY);
+    }
+
     /**
-     * Computes maximum size of the array that can contain all nearby key indices returned by
-     * {@link #getKeyIndexAndNearbyCodes}.
+     * Insert the key into nearby keys buffer and sort nearby keys by ascending order of distance.
+     * If the distance of two keys are the same, the key which the point is on should be considered
+     * as a closer one.
      *
-     * @return Returns maximum size of the array that can contain all nearby key indices returned
-     *         by {@link #getKeyIndexAndNearbyCodes}.
+     * @param keyIndex index of the key.
+     * @param distance distance between the key's edge and user touched point.
+     * @param isOnKey true if the point is on the key.
+     * @return order of the key in the nearby buffer, 0 if it is the nearest key.
      */
-    abstract protected int getMaxNearbyKeys();
+    private int sortNearbyKeys(int keyIndex, int distance, boolean isOnKey) {
+        final int[] distances = mDistances;
+        final int[] indices = mIndices;
+        for (int insertPos = 0; insertPos < distances.length; insertPos++) {
+            final int comparingDistance = distances[insertPos];
+            if (distance < comparingDistance || (distance == comparingDistance && isOnKey)) {
+                final int nextPos = insertPos + 1;
+                if (nextPos < distances.length) {
+                    System.arraycopy(distances, insertPos, distances, nextPos,
+                            distances.length - nextPos);
+                    System.arraycopy(indices, insertPos, indices, nextPos,
+                            indices.length - nextPos);
+                }
+                distances[insertPos] = distance;
+                indices[insertPos] = keyIndex;
+                return insertPos;
+            }
+        }
+        return distances.length;
+    }
+
+    private void getNearbyKeyCodes(final int[] allCodes) {
+        final List<Key> keys = getKeys();
+        final int[] indices = mIndices;
+
+        // allCodes[0] should always have the key code even if it is a non-letter key.
+        if (indices[0] == NOT_A_KEY) {
+            allCodes[0] = NOT_A_CODE;
+            return;
+        }
+
+        int numCodes = 0;
+        for (int j = 0; j < indices.length && numCodes < allCodes.length; j++) {
+            final int index = indices[j];
+            if (index == NOT_A_KEY)
+                break;
+            final int code = keys.get(index).mCode;
+            // filter out a non-letter key from nearby keys
+            if (code < Keyboard.CODE_SPACE)
+                continue;
+            allCodes[numCodes++] = code;
+        }
+    }
 
     /**
      * Finds all possible nearby key indices around a touch event point and returns the nearest key
@@ -109,32 +169,34 @@
      * @param allCodes All nearby key code except functional key are returned in this array
      * @return The nearest key index
      */
-    abstract public int getKeyIndexAndNearbyCodes(int x, int y, final int[] allCodes);
+    public int getKeyIndexAndNearbyCodes(int x, int y, final int[] allCodes) {
+        final List<Key> keys = getKeys();
+        final int touchX = getTouchX(x);
+        final int touchY = getTouchY(y);
 
-    /**
-     * Compute the most common key width in order to use it as proximity key detection threshold.
-     *
-     * @param keyboard The keyboard to compute the most common key width
-     * @return The most common key width in the keyboard
-     */
-    public static int getMostCommonKeyWidth(final Keyboard keyboard) {
-        if (keyboard == null) return 0;
-        final List<Key> keys = keyboard.getKeys();
-        if (keys == null || keys.size() == 0) return 0;
-        final HashMap<Integer, Integer> histogram = new HashMap<Integer, Integer>();
-        int maxCount = 0;
-        int mostCommonWidth = 0;
-        for (final Key key : keys) {
-            final Integer width = key.mWidth + key.mGap;
-            Integer count = histogram.get(width);
-            if (count == null)
-                count = 0;
-            histogram.put(width, ++count);
-            if (count > maxCount) {
-                maxCount = count;
-                mostCommonWidth = width;
+        initializeNearbyKeys();
+        int primaryIndex = NOT_A_KEY;
+        for (final int index : mKeyboard.getNearestKeys(touchX, touchY)) {
+            final Key key = keys.get(index);
+            final boolean isOnKey = key.isOnKey(touchX, touchY);
+            final int distance = key.squaredDistanceToEdge(touchX, touchY);
+            if (isOnKey || (mProximityCorrectOn && distance < mProximityThresholdSquare)) {
+                final int insertedPosition = sortNearbyKeys(index, distance, isOnKey);
+                if (insertedPosition == 0 && isOnKey)
+                    primaryIndex = index;
             }
         }
-        return mostCommonWidth;
+
+        if (allCodes != null && allCodes.length > 0) {
+            getNearbyKeyCodes(allCodes);
+            if (DEBUG) {
+                Log.d(TAG, "x=" + x + " y=" + y
+                        + " primary="
+                        + (primaryIndex == NOT_A_KEY ? "none" : keys.get(primaryIndex).mCode)
+                        + " codes=" + Arrays.toString(allCodes));
+            }
+        }
+
+        return primaryIndex;
     }
 }
diff --git a/java/src/com/android/inputmethod/keyboard/KeyStyles.java b/java/src/com/android/inputmethod/keyboard/KeyStyles.java
index 169f2e6..d464c20 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyStyles.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyStyles.java
@@ -185,7 +185,7 @@
             readDrawable(keyAttr, R.styleable.Keyboard_Key_iconPreview);
             readDrawable(keyAttr, R.styleable.Keyboard_Key_keyHintIcon);
             readDrawable(keyAttr, R.styleable.Keyboard_Key_shiftedIcon);
-            readBoolean(keyAttr, R.styleable.Keyboard_Key_isModifier);
+            readBoolean(keyAttr, R.styleable.Keyboard_Key_isFunctional);
             readBoolean(keyAttr, R.styleable.Keyboard_Key_isSticky);
             readBoolean(keyAttr, R.styleable.Keyboard_Key_isRepeatable);
             readBoolean(keyAttr, R.styleable.Keyboard_Key_enabled);
diff --git a/java/src/com/android/inputmethod/keyboard/Keyboard.java b/java/src/com/android/inputmethod/keyboard/Keyboard.java
index 06d4468..492883c 100644
--- a/java/src/com/android/inputmethod/keyboard/Keyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/Keyboard.java
@@ -63,24 +63,22 @@
     public static final int CODE_TAB = '\t';
     public static final int CODE_SPACE = ' ';
     public static final int CODE_PERIOD = '.';
+    public static final int CODE_DASH = '-';
+    public static final int CODE_SINGLE_QUOTE = '\'';
+    public static final int CODE_DOUBLE_QUOTE = '"';
 
     /** Special keys code.  These should be aligned with values/keycodes.xml */
     public static final int CODE_DUMMY = 0;
     public static final int CODE_SHIFT = -1;
     public static final int CODE_SWITCH_ALPHA_SYMBOL = -2;
-    public static final int CODE_CANCEL = -3;
-    public static final int CODE_DONE = -4;
+    public static final int CODE_CAPSLOCK = -3;
+    public static final int CODE_CANCEL = -4;
     public static final int CODE_DELETE = -5;
-    public static final int CODE_ALT = -6;
+    public static final int CODE_SETTINGS = -6;
+    public static final int CODE_SETTINGS_LONGPRESS = -7;
+    public static final int CODE_SHORTCUT = -8;
     // Code value representing the code is not specified.
     public static final int CODE_UNSPECIFIED = -99;
-    public static final int CODE_SETTINGS = -100;
-    public static final int CODE_SETTINGS_LONGPRESS = -101;
-    // TODO: remove this once LatinIME stops referring to this.
-    public static final int CODE_VOICE = -102;
-    public static final int CODE_CAPSLOCK = -103;
-    public static final int CODE_NEXT_LANGUAGE = -104;
-    public static final int CODE_PREV_LANGUAGE = -105;
 
     /** Horizontal gap default for all rows */
     private int mDefaultHorizontalGap;
@@ -128,6 +126,8 @@
     /** Height of keyboard */
     private int mKeyboardHeight;
 
+    private int mMostCommonKeyWidth = 0;
+
     public final KeyboardId mId;
 
     // Variables for pre-computing nearest keys.
@@ -165,7 +165,9 @@
         GRID_HEIGHT = res.getInteger(R.integer.config_keyboard_grid_height);
         GRID_SIZE = GRID_WIDTH * GRID_HEIGHT;
 
-        mDisplayWidth = width;
+        final int horizontalEdgesPadding = (int)res.getDimension(
+                R.dimen.keyboard_horizontal_edges_padding);
+        mDisplayWidth = width - horizontalEdgesPadding * 2;
         mDisplayHeight = height;
 
         mDefaultHorizontalGap = 0;
@@ -293,7 +295,7 @@
     public boolean setShiftLocked(boolean newShiftLockState) {
         final Map<Key, Drawable> shiftedIcons = getShiftedIcons();
         for (final Key key : getShiftKeys()) {
-            key.mOn = newShiftLockState;
+            key.mHighlightOn = newShiftLockState;
             key.setIcon(newShiftLockState ? shiftedIcons.get(key) : mNormalShiftIcons.get(key));
         }
         mShiftState.setShiftLocked(newShiftLockState);
@@ -381,10 +383,6 @@
         mProximityInfo.setProximityInfo(mGridNeighbors, getMinWidth(), getHeight(), mKeys);
     }
 
-    public boolean isInside(Key key, int x, int y) {
-        return key.isOnKey(x, y);
-    }
-
     /**
      * Returns the indices of the keys that are closest to the given point.
      * @param x the x-coordinate of the point
@@ -403,6 +401,41 @@
         return EMPTY_INT_ARRAY;
     }
 
+    /**
+     * Compute the most common key width in order to use it as proximity key detection threshold.
+     *
+     * @return The most common key width in the keyboard
+     */
+    public int getMostCommonKeyWidth() {
+        if (mMostCommonKeyWidth == 0) {
+            final HashMap<Integer, Integer> histogram = new HashMap<Integer, Integer>();
+            int maxCount = 0;
+            int mostCommonWidth = 0;
+            for (final Key key : mKeys) {
+                final Integer width = key.mWidth + key.mGap;
+                Integer count = histogram.get(width);
+                if (count == null)
+                    count = 0;
+                histogram.put(width, ++count);
+                if (count > maxCount) {
+                    maxCount = count;
+                    mostCommonWidth = width;
+                }
+            }
+            mMostCommonKeyWidth = mostCommonWidth;
+        }
+        return mMostCommonKeyWidth;
+    }
+
+    /**
+     * Return true if spacebar needs showing preview even when "popup on keypress" is off.
+     * @param keyIndex index of the pressing key
+     * @return true if spacebar needs showing preview
+     */
+    public boolean needSpacebarPreview(int keyIndex) {
+        return false;
+    }
+
     private void loadKeyboard(Context context, int xmlLayoutResId) {
         try {
             KeyboardParser parser = new KeyboardParser(this, context.getResources());
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardId.java b/java/src/com/android/inputmethod/keyboard/KeyboardId.java
index d09f678..f68b68f 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardId.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardId.java
@@ -16,8 +16,9 @@
 
 package com.android.inputmethod.keyboard;
 
+import com.android.inputmethod.compat.EditorInfoCompatUtils;
+import com.android.inputmethod.compat.InputTypeCompatUtils;
 import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.Utils;
 
 import android.view.inputmethod.EditorInfo;
 
@@ -62,8 +63,8 @@
         this.mMode = mode;
         this.mXmlId = xmlId;
         this.mColorScheme = colorScheme;
-        this.mPasswordInput = Utils.isPasswordInputType(inputType)
-                || Utils.isVisiblePasswordInputType(inputType);
+        this.mPasswordInput = InputTypeCompatUtils.isPasswordInputType(inputType)
+                || InputTypeCompatUtils.isVisiblePasswordInputType(inputType);
         this.mHasSettingsKey = hasSettingsKey;
         this.mVoiceKeyEnabled = voiceKeyEnabled;
         this.mHasVoiceKey = hasVoiceKey;
@@ -140,7 +141,7 @@
                 mLocale,
                 (mOrientation == 1 ? "port" : "land"),
                 modeName(mMode),
-                imeOptionsName(mImeAction),
+                EditorInfoCompatUtils.imeOptionsName(mImeAction),
                 (mPasswordInput ? " passwordInput" : ""),
                 (mHasSettingsKey ? " hasSettingsKey" : ""),
                 (mVoiceKeyEnabled ? " voiceKeyEnabled" : ""),
@@ -170,26 +171,4 @@
         }
         return null;
     }
-
-    public static String imeOptionsName(int imeOptions) {
-        if (imeOptions == -1) return null;
-        final int actionNo = imeOptions & EditorInfo.IME_MASK_ACTION;
-        final String action;
-        switch (actionNo) {
-        case EditorInfo.IME_ACTION_UNSPECIFIED: action = "actionUnspecified"; break;
-        case EditorInfo.IME_ACTION_NONE: action = "actionNone"; break;
-        case EditorInfo.IME_ACTION_GO: action = "actionGo"; break;
-        case EditorInfo.IME_ACTION_SEARCH: action = "actionSearch"; break;
-        case EditorInfo.IME_ACTION_SEND: action = "actionSend"; break;
-        case EditorInfo.IME_ACTION_DONE: action = "actionDone"; break;
-        case EditorInfo.IME_ACTION_PREVIOUS: action = "actionPrevious"; break;
-        default: action = "actionUnknown(" + actionNo + ")"; break;
-        }
-        if ((imeOptions & EditorInfo.IME_FLAG_NO_ENTER_ACTION) != 0) {
-            return "flagNoEnterAction|" + action;
-        } else {
-            return action;
-        }
-    }
 }
-
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardParser.java b/java/src/com/android/inputmethod/keyboard/KeyboardParser.java
index feb56ab..69ae788 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardParser.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardParser.java
@@ -16,11 +16,13 @@
 
 package com.android.inputmethod.keyboard;
 
+import com.android.inputmethod.compat.EditorInfoCompatUtils;
 import com.android.inputmethod.latin.R;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
+import android.content.Context;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
@@ -121,6 +123,7 @@
     private final Keyboard mKeyboard;
     private final Resources mResources;
 
+    private int mHorizontalEdgesPadding;
     private int mCurrentX = 0;
     private int mCurrentY = 0;
     private int mMaxRowWidth = 0;
@@ -131,6 +134,7 @@
     public KeyboardParser(Keyboard keyboard, Resources res) {
         mKeyboard = keyboard;
         mResources = res;
+        mHorizontalEdgesPadding = (int)res.getDimension(R.dimen.keyboard_horizontal_edges_padding);
     }
 
     public int getMaxRowWidth() {
@@ -150,6 +154,7 @@
                 final String tag = parser.getName();
                 if (TAG_KEYBOARD.equals(tag)) {
                     parseKeyboardAttributes(parser);
+                    startKeyboard();
                     parseKeyboardContent(parser, mKeyboard.getKeys());
                     break;
                 } else {
@@ -159,6 +164,27 @@
         }
     }
 
+    public static String parseKeyboardLocale(
+            Context context, int resId) throws XmlPullParserException, IOException {
+        final Resources res = context.getResources();
+        final XmlResourceParser parser = res.getXml(resId);
+        if (parser == null) return "";
+        int event;
+        while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) {
+            if (event == XmlPullParser.START_TAG) {
+                final String tag = parser.getName();
+                if (TAG_KEYBOARD.equals(tag)) {
+                    final TypedArray keyboardAttr = res.obtainAttributes(Xml.asAttributeSet(parser),
+                            R.styleable.Keyboard);
+                    return keyboardAttr.getString(R.styleable.Keyboard_keyboardLocale);
+                } else {
+                    throw new IllegalStartTag(parser, TAG_KEYBOARD);
+                }
+            }
+        }
+        return "";
+    }
+
     private void parseKeyboardAttributes(XmlResourceParser parser) {
         final Keyboard keyboard = mKeyboard;
         final TypedArray keyboardAttr = mResources.obtainAttributes(Xml.asAttributeSet(parser),
@@ -453,7 +479,7 @@
                     booleanAttr(a, R.styleable.Keyboard_Case_hasSettingsKey, "hasSettingsKey"),
                     booleanAttr(a, R.styleable.Keyboard_Case_voiceKeyEnabled, "voiceKeyEnabled"),
                     booleanAttr(a, R.styleable.Keyboard_Case_hasVoiceKey, "hasVoiceKey"),
-                    textAttr(KeyboardId.imeOptionsName(
+                    textAttr(EditorInfoCompatUtils.imeOptionsName(
                             a.getInt(R.styleable.Keyboard_Case_imeAction, -1)), "imeAction"),
                     textAttr(a.getString(R.styleable.Keyboard_Case_languageCode), "languageCode"),
                     textAttr(a.getString(R.styleable.Keyboard_Case_countryCode), "countryCode"),
@@ -519,25 +545,32 @@
         throw new NonEmptyTag(tag, parser);
     }
 
+    private void startKeyboard() {
+        mCurrentY += (int)mResources.getDimension(R.dimen.keyboard_top_padding);
+    }
+
     private void startRow(Row row) {
         mCurrentX = 0;
+        setSpacer(mHorizontalEdgesPadding);
         mCurrentRow = row;
     }
 
     private void endRow() {
         if (mCurrentRow == null)
             throw new InflateException("orphant end row tag");
+        setSpacer(mHorizontalEdgesPadding);
+        if (mCurrentX > mMaxRowWidth)
+            mMaxRowWidth = mCurrentX;
         mCurrentY += mCurrentRow.mDefaultHeight;
         mCurrentRow = null;
     }
 
     private void endKey(Key key) {
         mCurrentX += key.mGap + key.mWidth;
-        if (mCurrentX > mMaxRowWidth)
-            mMaxRowWidth = mCurrentX;
     }
 
     private void endKeyboard(int defaultVerticalGap) {
+        mCurrentY += (int)mResources.getDimension(R.dimen.keyboard_bottom_padding);
         mTotalHeight = mCurrentY - defaultVerticalGap;
     }
 
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
index 64a23ab..333fbc7 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
@@ -16,6 +16,7 @@
 
 package com.android.inputmethod.keyboard;
 
+import com.android.inputmethod.compat.InputMethodManagerCompatWrapper;
 import com.android.inputmethod.latin.LatinIME;
 import com.android.inputmethod.latin.LatinImeLogger;
 import com.android.inputmethod.latin.R;
@@ -29,7 +30,6 @@
 import android.util.Log;
 import android.view.InflateException;
 import android.view.inputmethod.EditorInfo;
-import android.view.inputmethod.InputMethodManager;
 
 import java.lang.ref.SoftReference;
 import java.util.HashMap;
@@ -57,6 +57,7 @@
     private LatinKeyboardView mInputView;
     private LatinIME mInputMethodService;
 
+    // TODO: Combine these key state objects with auto mode switch state.
     private ShiftKeyState mShiftKeyState = new ShiftKeyState("Shift");
     private ModifierKeyState mSymbolKeyState = new ModifierKeyState("Symbol");
 
@@ -75,13 +76,17 @@
     private boolean mVoiceKeyEnabled;
     private boolean mVoiceButtonOnPrimary;
 
-    private static final int AUTO_MODE_SWITCH_STATE_ALPHA = 0;
-    private static final int AUTO_MODE_SWITCH_STATE_SYMBOL_BEGIN = 1;
-    private static final int AUTO_MODE_SWITCH_STATE_SYMBOL = 2;
+    // TODO: Encapsulate these state handling to separate class and combine with ShiftKeyState
+    // and ModifierKeyState.
+    private static final int SWITCH_STATE_ALPHA = 0;
+    private static final int SWITCH_STATE_SYMBOL_BEGIN = 1;
+    private static final int SWITCH_STATE_SYMBOL = 2;
     // The following states are used only on the distinct multi-touch panel devices.
-    private static final int AUTO_MODE_SWITCH_STATE_MOMENTARY = 3;
-    private static final int AUTO_MODE_SWITCH_STATE_CHORDING = 4;
-    private int mAutoModeSwitchState = AUTO_MODE_SWITCH_STATE_ALPHA;
+    private static final int SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL = 3;
+    private static final int SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE = 4;
+    private static final int SWITCH_STATE_CHORDING_ALPHA = 5;
+    private static final int SWITCH_STATE_CHORDING_SYMBOL = 6;
+    private int mSwitchState = SWITCH_STATE_ALPHA;
 
     // Indicates whether or not we have the settings key in option of settings
     private boolean mSettingsKeyEnabledInSettings;
@@ -124,7 +129,7 @@
 
     public void loadKeyboard(EditorInfo attribute, boolean voiceKeyEnabled,
             boolean voiceButtonOnPrimary) {
-        mAutoModeSwitchState = AUTO_MODE_SWITCH_STATE_ALPHA;
+        mSwitchState = SWITCH_STATE_ALPHA;
         try {
             loadKeyboardInternal(attribute, voiceKeyEnabled, voiceButtonOnPrimary, false);
         } catch (RuntimeException e) {
@@ -146,14 +151,9 @@
         // Update the settings key state because number of enabled IMEs could have been changed
         mSettingsKeyEnabledInSettings = getSettingsKeyMode(mPrefs, mInputMethodService);
         final KeyboardId id = getKeyboardId(attribute, isSymbols);
-
-        final Keyboard oldKeyboard = mInputView.getKeyboard();
-        if (oldKeyboard != null && oldKeyboard.mId.equals(id))
-            return;
-
         makeSymbolsKeyboardIds(id.mMode, attribute);
         mCurrentId = id;
-        mInputView.setPreviewEnabled(mInputMethodService.getPopupOn());
+        mInputView.setKeyPreviewEnabled(mInputMethodService.getPopupOn());
         setKeyboard(getKeyboard(id));
     }
 
@@ -169,7 +169,8 @@
         final SoftReference<LatinKeyboard> ref = mKeyboardCache.get(id);
         LatinKeyboard keyboard = (ref == null) ? null : ref.get();
         if (keyboard == null) {
-            final Locale savedLocale =  mSubtypeSwitcher.changeSystemLocale(
+            final Resources res = mInputMethodService.getResources();
+            final Locale savedLocale = Utils.setSystemLocale(res,
                     mSubtypeSwitcher.getInputLocale());
 
             keyboard = new LatinKeyboard(mInputMethodService, id);
@@ -183,7 +184,7 @@
                 Log.d(TAG, "keyboard cache size=" + mKeyboardCache.size() + ": "
                         + ((ref == null) ? "LOAD" : "GCed") + " id=" + id);
 
-            mSubtypeSwitcher.changeSystemLocale(savedLocale);
+            Utils.setSystemLocale(res, savedLocale);
         } else if (DEBUG) {
             Log.d(TAG, "keyboard cache size=" + mKeyboardCache.size() + ": HIT  id=" + id);
         }
@@ -195,6 +196,7 @@
         // we should reset the text fade factor. It is also applicable to shortcut key.
         keyboard.setSpacebarTextFadeFactor(0.0f, null);
         keyboard.updateShortcutKey(mSubtypeSwitcher.isShortcutImeReady(), null);
+        keyboard.setSpacebarSlidingLanguageSwitchDiff(0);
         return keyboard;
     }
 
@@ -263,10 +265,10 @@
         int xmlId = mode == KeyboardId.MODE_PHONE ? R.xml.kbd_phone : R.xml.kbd_symbols;
         final String xmlName = res.getResourceEntryName(xmlId);
         mSymbolsId = new KeyboardId(xmlName, xmlId, colorScheme, locale, orientation, mode,
-                attribute, hasSettingsKey, mVoiceKeyEnabled, hasVoiceKey, true);
+                attribute, hasSettingsKey, mVoiceKeyEnabled, hasVoiceKey, false);
         xmlId = mode == KeyboardId.MODE_PHONE ? R.xml.kbd_phone_symbols : R.xml.kbd_symbols_shift;
         mSymbolsShiftedId = new KeyboardId(xmlName, xmlId, colorScheme, locale, orientation, mode,
-                attribute, hasSettingsKey, mVoiceKeyEnabled, hasVoiceKey, true);
+                attribute, hasSettingsKey, mVoiceKeyEnabled, hasVoiceKey, false);
     }
 
     public int getKeyboardMode() {
@@ -296,12 +298,6 @@
         return null;
     }
 
-    public void keyReleased() {
-        LatinKeyboard latinKeyboard = getLatinKeyboard();
-        if (latinKeyboard != null)
-            latinKeyboard.keyReleased();
-    }
-
     public boolean isShiftedOrShiftLocked() {
         LatinKeyboard latinKeyboard = getLatinKeyboard();
         if (latinKeyboard != null)
@@ -344,8 +340,7 @@
             // state when shift key is pressed to go to normal mode.
             // On the other hand, on distinct multi touch panel device, turning off the shift locked
             // state with shift key pressing is handled by onReleaseShift().
-            if ((!hasDistinctMultitouch() || isAccessibilityEnabled())
-                    && !shifted && latinKeyboard.isShiftLocked()) {
+            if (!hasDistinctMultitouch() && !shifted && latinKeyboard.isShiftLocked()) {
                 latinKeyboard.setShiftLocked(false);
             }
             if (latinKeyboard.setShifted(shifted)) {
@@ -443,9 +438,6 @@
     public void onPressShift(boolean withSliding) {
         if (!isKeyboardAvailable())
             return;
-        // If accessibility is enabled, disable momentary shift lock.
-        if (isAccessibilityEnabled())
-            return;
         ShiftKeyState shiftKeyState = mShiftKeyState;
         if (DEBUG_STATE)
             Log.d(TAG, "onPressShift:"
@@ -475,15 +467,13 @@
             // In symbol mode, just toggle symbol and symbol more keyboard.
             shiftKeyState.onPress();
             toggleShift();
+            mSwitchState = SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE;
         }
     }
 
     public void onReleaseShift(boolean withSliding) {
         if (!isKeyboardAvailable())
             return;
-        // If accessibility is enabled, disable momentary shift lock.
-        if (isAccessibilityEnabled())
-            return;
         ShiftKeyState shiftKeyState = mShiftKeyState;
         if (DEBUG_STATE)
             Log.d(TAG, "onReleaseShift:"
@@ -496,6 +486,10 @@
             } else if (isShiftLocked() && !shiftKeyState.isIgnoring() && !withSliding) {
                 // Shift has been pressed without chording while caps lock state.
                 toggleCapsLock();
+                // To be able to turn off caps lock by "double tap" on shift key, we should ignore
+                // the second tap of the "double tap" from now for a while because we just have
+                // already turned off caps lock above.
+                mInputView.startIgnoringDoubleTap();
             } else if (isShiftedOrShiftLocked() && shiftKeyState.isPressingOnShifted()
                     && !withSliding) {
                 // Shift has been pressed without chording while shifted state.
@@ -506,42 +500,40 @@
                 // transited from automatic temporary upper case.
                 toggleShift();
             }
+        } else {
+            // In symbol mode, snap back to the previous keyboard mode if the user chords the shift
+            // key and another key, then releases the shift key.
+            if (mSwitchState == SWITCH_STATE_CHORDING_SYMBOL) {
+                toggleShift();
+            }
         }
         shiftKeyState.onRelease();
     }
 
     public void onPressSymbol() {
-        // If accessibility is enabled, disable momentary symbol lock.
-        if (isAccessibilityEnabled())
-            return;
         if (DEBUG_STATE)
             Log.d(TAG, "onPressSymbol:"
                     + " keyboard=" + getLatinKeyboard().getKeyboardShiftState()
                     + " symbolKeyState=" + mSymbolKeyState);
         changeKeyboardMode();
         mSymbolKeyState.onPress();
-        mAutoModeSwitchState = AUTO_MODE_SWITCH_STATE_MOMENTARY;
+        mSwitchState = SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL;
     }
 
     public void onReleaseSymbol() {
-        // If accessibility is enabled, disable momentary symbol lock.
-        if (isAccessibilityEnabled())
-            return;
         if (DEBUG_STATE)
             Log.d(TAG, "onReleaseSymbol:"
                     + " keyboard=" + getLatinKeyboard().getKeyboardShiftState()
                     + " symbolKeyState=" + mSymbolKeyState);
         // Snap back to the previous keyboard mode if the user chords the mode change key and
-        // other key, then released the mode change key.
-        if (mAutoModeSwitchState == AUTO_MODE_SWITCH_STATE_CHORDING)
+        // another key, then releases the mode change key.
+        if (mSwitchState == SWITCH_STATE_CHORDING_ALPHA) {
             changeKeyboardMode();
+        }
         mSymbolKeyState.onRelease();
     }
 
     public void onOtherKeyPressed() {
-        // If accessibility is enabled, disable momentary mode locking.
-        if (isAccessibilityEnabled())
-            return;
         if (DEBUG_STATE)
             Log.d(TAG, "onOtherKeyPressed:"
                     + " keyboard=" + getLatinKeyboard().getKeyboardShiftState()
@@ -553,8 +545,13 @@
 
     public void onCancelInput() {
         // Snap back to the previous keyboard mode if the user cancels sliding input.
-        if (mAutoModeSwitchState == AUTO_MODE_SWITCH_STATE_MOMENTARY && getPointerCount() == 1)
-            changeKeyboardMode();
+        if (getPointerCount() == 1) {
+            if (mSwitchState == SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL) {
+                changeKeyboardMode();
+            } else if (mSwitchState == SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE) {
+                toggleShift();
+            }
+        }
     }
 
     private void toggleShiftInSymbol() {
@@ -565,22 +562,21 @@
             mCurrentId = mSymbolsShiftedId;
             keyboard = getKeyboard(mCurrentId);
             // Symbol shifted keyboard has an ALT key that has a caps lock style indicator. To
-            // enable the indicator, we need to call enableShiftLock() and setShiftLocked(true).
-            // Thus we can keep the ALT key's Key.on value true while LatinKey.onRelease() is
-            // called.
+            // enable the indicator, we need to call setShiftLocked(true).
             keyboard.setShiftLocked(true);
         } else {
             mCurrentId = mSymbolsId;
             keyboard = getKeyboard(mCurrentId);
             // Symbol keyboard has an ALT key that has a caps lock style indicator. To disable the
-            // indicator, we need to call enableShiftLock() and setShiftLocked(false).
-            keyboard.setShifted(false);
+            // indicator, we need to call setShiftLocked(false).
+            keyboard.setShiftLocked(false);
         }
         setKeyboard(keyboard);
     }
 
-    public boolean isInMomentaryAutoModeSwitchState() {
-        return mAutoModeSwitchState == AUTO_MODE_SWITCH_STATE_MOMENTARY;
+    public boolean isInMomentarySwitchState() {
+        return mSwitchState == SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL
+                || mSwitchState == SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE;
     }
 
     public boolean isVibrateAndSoundFeedbackRequired() {
@@ -594,42 +590,62 @@
     private void toggleKeyboardMode() {
         loadKeyboardInternal(mAttribute, mVoiceKeyEnabled, mVoiceButtonOnPrimary, !mIsSymbols);
         if (mIsSymbols) {
-            mAutoModeSwitchState = AUTO_MODE_SWITCH_STATE_SYMBOL_BEGIN;
+            mSwitchState = SWITCH_STATE_SYMBOL_BEGIN;
         } else {
-            mAutoModeSwitchState = AUTO_MODE_SWITCH_STATE_ALPHA;
+            mSwitchState = SWITCH_STATE_ALPHA;
         }
     }
 
-    public boolean isAccessibilityEnabled() {
-        return mInputView != null && mInputView.isAccessibilityEnabled();
-    }
-
     public boolean hasDistinctMultitouch() {
         return mInputView != null && mInputView.hasDistinctMultitouch();
     }
 
+    private static boolean isSpaceCharacter(int c) {
+        return c == Keyboard.CODE_SPACE || c == Keyboard.CODE_ENTER;
+    }
+
+    private static boolean isQuoteCharacter(int c) {
+        // Apostrophe, quotation mark.
+        if (c == Keyboard.CODE_SINGLE_QUOTE || c == Keyboard.CODE_DOUBLE_QUOTE)
+            return true;
+        // \u2018: Left single quotation mark
+        // \u2019: Right single quotation mark
+        // \u201a: Single low-9 quotation mark
+        // \u201b: Single high-reversed-9 quotation mark
+        // \u201c: Left double quotation mark
+        // \u201d: Right double quotation mark
+        // \u201e: Double low-9 quotation mark
+        // \u201f: Double high-reversed-9 quotation mark
+        if (c >= '\u2018' && c <= '\u201f')
+            return true;
+        // \u00ab: Left-pointing double angle quotation mark
+        // \u00bb: Right-pointing double angle quotation mark
+        if (c == '\u00ab' || c == '\u00bb')
+            return true;
+        return false;
+    }
+
     /**
      * Updates state machine to figure out when to automatically snap back to the previous mode.
      */
-    public void onKey(int key) {
+    public void onKey(int code) {
         if (DEBUG_STATE)
-            Log.d(TAG, "onKey: code=" + key + " autoModeSwitchState=" + mAutoModeSwitchState
+            Log.d(TAG, "onKey: code=" + code + " switchState=" + mSwitchState
                     + " pointers=" + getPointerCount());
-        switch (mAutoModeSwitchState) {
-        case AUTO_MODE_SWITCH_STATE_MOMENTARY:
+        switch (mSwitchState) {
+        case SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL:
             // Only distinct multi touch devices can be in this state.
             // On non-distinct multi touch devices, mode change key is handled by
             // {@link LatinIME#onCodeInput}, not by {@link LatinIME#onPress} and
-            // {@link LatinIME#onRelease}. So, on such devices, {@link #mAutoModeSwitchState} starts
-            // from {@link #AUTO_MODE_SWITCH_STATE_SYMBOL_BEGIN}, or
-            // {@link #AUTO_MODE_SWITCH_STATE_ALPHA}, not from
-            // {@link #AUTO_MODE_SWITCH_STATE_MOMENTARY}.
-            if (key == Keyboard.CODE_SWITCH_ALPHA_SYMBOL) {
+            // {@link LatinIME#onRelease}. So, on such devices, {@link #mSwitchState} starts
+            // from {@link #SWITCH_STATE_SYMBOL_BEGIN}, or {@link #SWITCH_STATE_ALPHA}, not from
+            // {@link #SWITCH_STATE_MOMENTARY}.
+            if (code == Keyboard.CODE_SWITCH_ALPHA_SYMBOL) {
                 // Detected only the mode change key has been pressed, and then released.
                 if (mIsSymbols) {
-                    mAutoModeSwitchState = AUTO_MODE_SWITCH_STATE_SYMBOL_BEGIN;
+                    mSwitchState = SWITCH_STATE_SYMBOL_BEGIN;
                 } else {
-                    mAutoModeSwitchState = AUTO_MODE_SWITCH_STATE_ALPHA;
+                    mSwitchState = SWITCH_STATE_ALPHA;
                 }
             } else if (getPointerCount() == 1) {
                 // Snap back to the previous keyboard mode if the user pressed the mode change key
@@ -640,18 +656,38 @@
             } else {
                 // Chording input is being started. The keyboard mode will be snapped back to the
                 // previous mode in {@link onReleaseSymbol} when the mode change key is released.
-                mAutoModeSwitchState = AUTO_MODE_SWITCH_STATE_CHORDING;
+                mSwitchState = SWITCH_STATE_CHORDING_ALPHA;
             }
             break;
-        case AUTO_MODE_SWITCH_STATE_SYMBOL_BEGIN:
-            if (key != Keyboard.CODE_SPACE && key != Keyboard.CODE_ENTER && key >= 0) {
-                mAutoModeSwitchState = AUTO_MODE_SWITCH_STATE_SYMBOL;
+        case SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE:
+            if (code == Keyboard.CODE_SHIFT) {
+                // Detected only the shift key has been pressed on symbol layout, and then released.
+                mSwitchState = SWITCH_STATE_SYMBOL_BEGIN;
+            } else if (getPointerCount() == 1) {
+                // Snap back to the previous keyboard mode if the user pressed the shift key on
+                // symbol mode and slid to other key, then released the finger.
+                toggleShift();
+                mSwitchState = SWITCH_STATE_SYMBOL;
+            } else {
+                // Chording input is being started. The keyboard mode will be snapped back to the
+                // previous mode in {@link onReleaseShift} when the shift key is released.
+                mSwitchState = SWITCH_STATE_CHORDING_SYMBOL;
             }
             break;
-        case AUTO_MODE_SWITCH_STATE_SYMBOL:
+        case SWITCH_STATE_SYMBOL_BEGIN:
+            if (!isSpaceCharacter(code) && code >= 0) {
+                mSwitchState = SWITCH_STATE_SYMBOL;
+            }
+            // Snap back to alpha keyboard mode immediately if user types a quote character.
+            if (isQuoteCharacter(code)) {
+                changeKeyboardMode();
+            }
+            break;
+        case SWITCH_STATE_SYMBOL:
+        case SWITCH_STATE_CHORDING_SYMBOL:
             // Snap back to alpha keyboard mode if user types one or more non-space/enter
-            // characters followed by a space/enter.
-            if (key == Keyboard.CODE_ENTER || key == Keyboard.CODE_SPACE) {
+            // characters followed by a space/enter or a quote character.
+            if (isSpaceCharacter(code) || isQuoteCharacter(code)) {
                 changeKeyboardMode();
             }
             break;
@@ -752,8 +788,7 @@
             if (settingsKeyMode.equals(resources.getString(SETTINGS_KEY_MODE_ALWAYS_SHOW))
                     || (settingsKeyMode.equals(resources.getString(SETTINGS_KEY_MODE_AUTO))
                             && Utils.hasMultipleEnabledIMEsOrSubtypes(
-                                    ((InputMethodManager) context.getSystemService(
-                                            Context.INPUT_METHOD_SERVICE))))) {
+                                    (InputMethodManagerCompatWrapper.getInstance(context))))) {
                 return true;
             }
             return false;
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
index 61af15b..c551ed4 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
@@ -18,7 +18,6 @@
 
 import com.android.inputmethod.latin.LatinImeLogger;
 import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.SubtypeSwitcher;
 
 import android.content.Context;
 import android.content.pm.PackageManager;
@@ -36,24 +35,23 @@
 import android.graphics.drawable.Drawable;
 import android.os.Handler;
 import android.os.Message;
-import android.os.SystemClock;
-import android.provider.Settings;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.util.TypedValue;
 import android.view.GestureDetector;
-import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
-import android.view.ViewGroup.LayoutParams;
-import android.view.WindowManager;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.view.ViewGroup.MarginLayoutParams;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
 import android.widget.PopupWindow;
 import android.widget.TextView;
 
 import java.util.ArrayList;
 import java.util.HashMap;
-import java.util.List;
 import java.util.WeakHashMap;
 
 /**
@@ -74,7 +72,7 @@
     private static final boolean DEBUG_SHOW_ALIGN = false;
     private static final boolean DEBUG_KEYBOARD_GRID = false;
 
-    private static final boolean ENABLE_CAPSLOCK_BY_LONGPRESS = false;
+    private static final boolean ENABLE_CAPSLOCK_BY_LONGPRESS = true;
     private static final boolean ENABLE_CAPSLOCK_BY_DOUBLETAP = true;
 
     public static final int COLOR_SCHEME_WHITE = 0;
@@ -106,35 +104,23 @@
 
     // Main keyboard
     private Keyboard mKeyboard;
-    private Key[] mKeys;
 
-    // Key preview popup
+    // Key preview
     private boolean mInForeground;
     private TextView mPreviewText;
-    private PopupWindow mPreviewPopup;
     private int mPreviewTextSizeLarge;
-    private int[] mOffsetInWindow;
-    private int mOldPreviewKeyIndex = KeyDetector.NOT_A_KEY;
-    private boolean mShowPreview = true;
-    private int mPopupPreviewOffsetX;
-    private int mPopupPreviewOffsetY;
-    private int mWindowY;
-    private int mPopupPreviewDisplayedY;
+    private boolean mShowKeyPreview = true;
+    private int mKeyPreviewDisplayedY;
     private final int mDelayBeforePreview;
     private final int mDelayAfterPreview;
+    private ViewGroup mPreviewPlacer;
+    private final int[] mCoordinates = new int[2];
 
-    // Popup mini keyboard
-    private PopupWindow mMiniKeyboardPopup;
-    private KeyboardView mMiniKeyboardView;
-    private View mMiniKeyboardParent;
-    private final WeakHashMap<Key, View> mMiniKeyboardCache = new WeakHashMap<Key, View>();
-    private int mMiniKeyboardOriginX;
-    private int mMiniKeyboardOriginY;
-    private long mMiniKeyboardPopupTime;
-    private int[] mWindowOffset;
-    private final float mMiniKeyboardSlideAllowance;
-    private int mMiniKeyboardTrackerId;
-    private final boolean mConfigShowMiniKeyboardAtTouchedPoint;
+    // Mini keyboard
+    private PopupWindow mPopupWindow;
+    private PopupPanel mPopupMiniKeyboardPanel;
+    private final WeakHashMap<Key, PopupPanel> mPopupPanelCache =
+            new WeakHashMap<Key, PopupPanel>();
 
     /** Listener for {@link KeyboardActionListener}. */
     private KeyboardActionListener mKeyboardActionListener;
@@ -146,14 +132,12 @@
 
     private final boolean mHasDistinctMultitouch;
     private int mOldPointerCount = 1;
+    private int mOldKeyIndex;
 
-    // Accessibility
-    private boolean mIsAccessibilityEnabled;
-
-    protected KeyDetector mKeyDetector = new ProximityKeyDetector();
+    protected KeyDetector mKeyDetector = new KeyDetector();
 
     // Swipe gesture detector
-    private GestureDetector mGestureDetector;
+    protected GestureDetector mGestureDetector;
     private final SwipeTracker mSwipeTracker = new SwipeTracker();
     private final int mSwipeThreshold;
     private final boolean mDisambiguateSwipe;
@@ -190,65 +174,67 @@
     private final UIHandler mHandler = new UIHandler();
 
     class UIHandler extends Handler {
-        private static final int MSG_POPUP_PREVIEW = 1;
-        private static final int MSG_DISMISS_PREVIEW = 2;
+        private static final int MSG_SHOW_KEY_PREVIEW = 1;
+        private static final int MSG_DISMISS_KEY_PREVIEW = 2;
         private static final int MSG_REPEAT_KEY = 3;
         private static final int MSG_LONGPRESS_KEY = 4;
         private static final int MSG_LONGPRESS_SHIFT_KEY = 5;
+        private static final int MSG_IGNORE_DOUBLE_TAP = 6;
 
         private boolean mInKeyRepeat;
 
         @Override
         public void handleMessage(Message msg) {
+            final PointerTracker tracker = (PointerTracker) msg.obj;
             switch (msg.what) {
-                case MSG_POPUP_PREVIEW:
-                    showKey(msg.arg1, (PointerTracker)msg.obj);
-                    break;
-                case MSG_DISMISS_PREVIEW:
-                    mPreviewPopup.dismiss();
-                    break;
-                case MSG_REPEAT_KEY: {
-                    final PointerTracker tracker = (PointerTracker)msg.obj;
-                    tracker.repeatKey(msg.arg1);
-                    startKeyRepeatTimer(mKeyRepeatInterval, msg.arg1, tracker);
-                    break;
-                }
-                case MSG_LONGPRESS_KEY: {
-                    final PointerTracker tracker = (PointerTracker)msg.obj;
-                    openPopupIfRequired(msg.arg1, tracker);
-                    break;
-                }
-                case MSG_LONGPRESS_SHIFT_KEY: {
-                    final PointerTracker tracker = (PointerTracker)msg.obj;
-                    onLongPressShiftKey(tracker);
-                    break;
-                }
+            case MSG_SHOW_KEY_PREVIEW:
+                showKey(msg.arg1, tracker);
+                break;
+            case MSG_DISMISS_KEY_PREVIEW:
+                mPreviewText.setVisibility(View.INVISIBLE);
+                break;
+            case MSG_REPEAT_KEY:
+                tracker.onRepeatKey(msg.arg1);
+                startKeyRepeatTimer(mKeyRepeatInterval, msg.arg1, tracker);
+                break;
+            case MSG_LONGPRESS_KEY:
+                openMiniKeyboardIfRequired(msg.arg1, tracker);
+                break;
+            case MSG_LONGPRESS_SHIFT_KEY:
+                onLongPressShiftKey(tracker);
+                break;
             }
         }
 
-        public void popupPreview(long delay, int keyIndex, PointerTracker tracker) {
-            removeMessages(MSG_POPUP_PREVIEW);
-            if (mPreviewPopup.isShowing() && mPreviewText.getVisibility() == VISIBLE) {
+        public void showKeyPreview(long delay, int keyIndex, PointerTracker tracker) {
+            removeMessages(MSG_SHOW_KEY_PREVIEW);
+            if (mPreviewText.getVisibility() == VISIBLE || delay == 0) {
                 // Show right away, if it's already visible and finger is moving around
                 showKey(keyIndex, tracker);
             } else {
-                sendMessageDelayed(obtainMessage(MSG_POPUP_PREVIEW, keyIndex, 0, tracker),
-                        delay);
+                sendMessageDelayed(
+                        obtainMessage(MSG_SHOW_KEY_PREVIEW, keyIndex, 0, tracker), delay);
             }
         }
 
-        public void cancelPopupPreview() {
-            removeMessages(MSG_POPUP_PREVIEW);
+        public void cancelShowKeyPreview(PointerTracker tracker) {
+            removeMessages(MSG_SHOW_KEY_PREVIEW, tracker);
         }
 
-        public void dismissPreview(long delay) {
-            if (mPreviewPopup.isShowing()) {
-                sendMessageDelayed(obtainMessage(MSG_DISMISS_PREVIEW), delay);
-            }
+        public void cancelAllShowKeyPreviews() {
+            removeMessages(MSG_SHOW_KEY_PREVIEW);
         }
 
-        public void cancelDismissPreview() {
-            removeMessages(MSG_DISMISS_PREVIEW);
+        public void dismissKeyPreview(long delay, PointerTracker tracker) {
+            sendMessageDelayed(obtainMessage(MSG_DISMISS_KEY_PREVIEW, tracker), delay);
+        }
+
+        public void cancelDismissKeyPreview(PointerTracker tracker) {
+            removeMessages(MSG_DISMISS_KEY_PREVIEW, tracker);
+        }
+
+        public void cancelAllDismissKeyPreviews() {
+            removeMessages(MSG_DISMISS_KEY_PREVIEW);
         }
 
         public void startKeyRepeatTimer(long delay, int keyIndex, PointerTracker tracker) {
@@ -286,12 +272,22 @@
         public void cancelKeyTimers() {
             cancelKeyRepeatTimer();
             cancelLongPressTimers();
+            removeMessages(MSG_IGNORE_DOUBLE_TAP);
+        }
+
+        public void startIgnoringDoubleTap() {
+            sendMessageDelayed(obtainMessage(MSG_IGNORE_DOUBLE_TAP),
+                    ViewConfiguration.getDoubleTapTimeout());
+        }
+
+        public boolean isIgnoringDoubleTap() {
+            return hasMessages(MSG_IGNORE_DOUBLE_TAP);
         }
 
         public void cancelAllMessages() {
             cancelKeyTimers();
-            cancelPopupPreview();
-            cancelDismissPreview();
+            cancelAllShowKeyPreviews();
+            cancelAllDismissKeyPreviews();
         }
     }
 
@@ -367,29 +363,17 @@
 
         final Resources res = getResources();
 
-        mPreviewPopup = new PopupWindow(context);
         if (previewLayout != 0) {
             mPreviewText = (TextView) LayoutInflater.from(context).inflate(previewLayout, null);
             mPreviewTextSizeLarge = (int) res.getDimension(R.dimen.key_preview_text_size_large);
-            mPreviewPopup.setContentView(mPreviewText);
-            mPreviewPopup.setBackgroundDrawable(null);
         } else {
-            mShowPreview = false;
+            mShowKeyPreview = false;
         }
-        mPreviewPopup.setTouchable(false);
-        mPreviewPopup.setAnimationStyle(R.style.KeyPreviewAnimation);
         mDelayBeforePreview = res.getInteger(R.integer.config_delay_before_preview);
         mDelayAfterPreview = res.getInteger(R.integer.config_delay_after_preview);
         mKeyLabelHorizontalPadding = (int)res.getDimension(
                 R.dimen.key_label_horizontal_alignment_padding);
 
-        mMiniKeyboardParent = this;
-        mMiniKeyboardPopup = new PopupWindow(context);
-        mMiniKeyboardPopup.setBackgroundDrawable(null);
-        mMiniKeyboardPopup.setAnimationStyle(R.style.MiniKeyboardAnimation);
-        // Allow popup window to be drawn off the screen.
-        mMiniKeyboardPopup.setClippingEnabled(false);
-
         mPaint = new Paint();
         mPaint.setAntiAlias(true);
         mPaint.setTextSize(keyTextSize);
@@ -400,11 +384,8 @@
         mKeyBackground.getPadding(mPadding);
 
         mSwipeThreshold = (int) (500 * res.getDisplayMetrics().density);
-        // TODO: Refer frameworks/base/core/res/res/values/config.xml
+        // TODO: Refer to frameworks/base/core/res/res/values/config.xml
         mDisambiguateSwipe = res.getBoolean(R.bool.config_swipeDisambiguation);
-        mMiniKeyboardSlideAllowance = res.getDimension(R.dimen.mini_keyboard_slide_allowance);
-        mConfigShowMiniKeyboardAtTouchedPoint = res.getBoolean(
-                R.bool.config_show_mini_keyboard_at_touched_point);
 
         GestureDetector.SimpleOnGestureListener listener =
                 new GestureDetector.SimpleOnGestureListener() {
@@ -455,7 +436,12 @@
                     final PointerTracker tracker = getPointerTracker(id);
                     // If the second down event is also on shift key.
                     if (tracker.isOnShiftKey((int)secondDown.getX(), (int)secondDown.getY())) {
-                        onDoubleTapShiftKey(tracker);
+                        // Detected a double tap on shift key. If we are in the ignoring double tap
+                        // mode, it means we have already turned off caps lock in
+                        // {@link KeyboardSwitcher#onReleaseShift} .
+                        final boolean ignoringDoubleTap = mHandler.isIgnoringDoubleTap();
+                        if (!ignoringDoubleTap)
+                            onDoubleTapShiftKey(tracker);
                         return true;
                     }
                     // Otherwise these events should not be handled as double tap.
@@ -474,6 +460,11 @@
         mKeyRepeatInterval = res.getInteger(R.integer.config_key_repeat_interval);
     }
 
+    public void startIgnoringDoubleTap() {
+        if (ENABLE_CAPSLOCK_BY_DOUBLETAP)
+            mHandler.startIgnoringDoubleTap();
+    }
+
     public void setOnKeyboardActionListener(KeyboardActionListener listener) {
         mKeyboardActionListener = listener;
         for (PointerTracker tracker : mPointerTrackers) {
@@ -498,23 +489,23 @@
      */
     public void setKeyboard(Keyboard keyboard) {
         if (mKeyboard != null) {
-            dismissKeyPreview();
+            dismissAllKeyPreviews();
         }
         // Remove any pending messages, except dismissing preview
         mHandler.cancelKeyTimers();
-        mHandler.cancelPopupPreview();
+        mHandler.cancelAllShowKeyPreviews();
         mKeyboard = keyboard;
         LatinImeLogger.onSetKeyboard(keyboard);
-        mKeys = mKeyDetector.setKeyboard(keyboard, -getPaddingLeft(),
+        mKeyDetector.setKeyboard(keyboard, -getPaddingLeft(),
                 -getPaddingTop() + mVerticalCorrection);
         for (PointerTracker tracker : mPointerTrackers) {
-            tracker.setKeyboard(keyboard, mKeys, mKeyHysteresisDistance);
+            tracker.setKeyboard(keyboard, mKeyHysteresisDistance);
         }
         requestLayout();
         mKeyboardChanged = true;
         invalidateAllKeys();
-        mKeyDetector.setProximityThreshold(KeyDetector.getMostCommonKeyWidth(keyboard));
-        mMiniKeyboardCache.clear();
+        mKeyDetector.setProximityThreshold(keyboard.getMostCommonKeyWidth());
+        mPopupPanelCache.clear();
     }
 
     /**
@@ -536,56 +527,28 @@
     }
 
     /**
-     * Enables or disables accessibility.
-     * @param accessibilityEnabled whether or not to enable accessibility
-     */
-    public void setAccessibilityEnabled(boolean accessibilityEnabled) {
-        mIsAccessibilityEnabled = accessibilityEnabled;
-
-        // Propagate this change to all existing pointer trackers.
-        for (PointerTracker tracker : mPointerTrackers) {
-            tracker.setAccessibilityEnabled(accessibilityEnabled);
-        }
-    }
-
-    /**
-     * Returns whether the device has accessibility enabled.
-     * @return true if the device has accessibility enabled.
-     */
-    @Override
-    public boolean isAccessibilityEnabled() {
-        return mIsAccessibilityEnabled;
-    }
-
-    /**
      * Enables or disables the key feedback popup. This is a popup that shows a magnified
      * version of the depressed key. By default the preview is enabled.
-     * @param previewEnabled whether or not to enable the key feedback popup
-     * @see #isPreviewEnabled()
+     * @param previewEnabled whether or not to enable the key feedback preview
+     * @see #isKeyPreviewEnabled()
      */
-    public void setPreviewEnabled(boolean previewEnabled) {
-        mShowPreview = previewEnabled;
+    public void setKeyPreviewEnabled(boolean previewEnabled) {
+        mShowKeyPreview = previewEnabled;
     }
 
     /**
-     * Returns the enabled state of the key feedback popup.
-     * @return whether or not the key feedback popup is enabled
-     * @see #setPreviewEnabled(boolean)
+     * Returns the enabled state of the key feedback preview
+     * @return whether or not the key feedback preview is enabled
+     * @see #setKeyPreviewEnabled(boolean)
      */
-    public boolean isPreviewEnabled() {
-        return mShowPreview;
+    public boolean isKeyPreviewEnabled() {
+        return mShowKeyPreview;
     }
 
     public int getColorScheme() {
         return mColorScheme;
     }
 
-    public void setPopupOffset(int x, int y) {
-        mPopupPreviewOffsetX = x;
-        mPopupPreviewOffsetY = y;
-        mPreviewPopup.dismiss();
-    }
-
     /**
      * When enabled, calls to {@link KeyboardActionListener#onCodeInput} will include key
      * codes for adjacent keys.  When disabled, only the primary key code will be
@@ -651,152 +614,32 @@
         }
         final Canvas canvas = mCanvas;
         canvas.clipRect(mDirtyRect, Op.REPLACE);
+        canvas.drawColor(Color.BLACK, PorterDuff.Mode.CLEAR);
 
         if (mKeyboard == null) return;
 
-        final Paint paint = mPaint;
-        final Drawable keyBackground = mKeyBackground;
-        final Rect padding = mPadding;
-        final int kbdPaddingLeft = getPaddingLeft();
-        final int kbdPaddingTop = getPaddingTop();
-        final Key[] keys = mKeys;
-        final boolean isManualTemporaryUpperCase = mKeyboard.isManualTemporaryUpperCase();
-        final boolean drawSingleKey = (mInvalidatedKey != null
-                && mInvalidatedKeyRect.contains(mDirtyRect));
-
-        canvas.drawColor(0x00000000, PorterDuff.Mode.CLEAR);
-        final int keyCount = keys.length;
-        for (int i = 0; i < keyCount; i++) {
-            final Key key = keys[i];
-            if (drawSingleKey && key != mInvalidatedKey) {
-                continue;
+        if (mInvalidatedKey != null && mInvalidatedKeyRect.contains(mDirtyRect)) {
+            // Draw a single key.
+            onBufferDrawKey(canvas, mInvalidatedKey);
+        } else {
+            // Draw all keys.
+            for (final Key key : mKeyboard.getKeys()) {
+                onBufferDrawKey(canvas, key);
             }
-            int[] drawableState = key.getCurrentDrawableState();
-            keyBackground.setState(drawableState);
-
-            // Switch the character to uppercase if shift is pressed
-            String label = key.mLabel == null? null : adjustCase(key.mLabel).toString();
-
-            final Rect bounds = keyBackground.getBounds();
-            if (key.mWidth != bounds.right || key.mHeight != bounds.bottom) {
-                keyBackground.setBounds(0, 0, key.mWidth, key.mHeight);
-            }
-            canvas.translate(key.mX + kbdPaddingLeft, key.mY + kbdPaddingTop);
-            keyBackground.draw(canvas);
-
-            final int rowHeight = padding.top + key.mHeight;
-            // Draw key label
-            if (label != null) {
-                // For characters, use large font. For labels like "Done", use small font.
-                final int labelSize = getLabelSizeAndSetPaint(label, key.mLabelOption, paint);
-                final int labelCharHeight = getLabelCharHeight(labelSize, paint);
-
-                // Vertical label text alignment.
-                final float baseline;
-                if ((key.mLabelOption & KEY_LABEL_OPTION_ALIGN_BOTTOM) != 0) {
-                    baseline = key.mHeight -
-                            + labelCharHeight * KEY_LABEL_VERTICAL_PADDING_FACTOR;
-                    if (DEBUG_SHOW_ALIGN)
-                        drawHorizontalLine(canvas, (int)baseline, key.mWidth, 0xc0008000,
-                                new Paint());
-                } else { // Align center
-                    final float centerY = (key.mHeight + padding.top - padding.bottom) / 2;
-                    baseline = centerY
-                            + labelCharHeight * KEY_LABEL_VERTICAL_ADJUSTMENT_FACTOR_CENTER;
-                    if (DEBUG_SHOW_ALIGN)
-                        drawHorizontalLine(canvas, (int)baseline, key.mWidth, 0xc0008000,
-                                new Paint());
-                }
-                // Horizontal label text alignment
-                final int positionX;
-                if ((key.mLabelOption & KEY_LABEL_OPTION_ALIGN_LEFT) != 0) {
-                    positionX = mKeyLabelHorizontalPadding + padding.left;
-                    paint.setTextAlign(Align.LEFT);
-                    if (DEBUG_SHOW_ALIGN)
-                        drawVerticalLine(canvas, positionX, rowHeight, 0xc0800080, new Paint());
-                } else if ((key.mLabelOption & KEY_LABEL_OPTION_ALIGN_RIGHT) != 0) {
-                    positionX = key.mWidth - mKeyLabelHorizontalPadding - padding.right;
-                    paint.setTextAlign(Align.RIGHT);
-                    if (DEBUG_SHOW_ALIGN)
-                        drawVerticalLine(canvas, positionX, rowHeight, 0xc0808000, new Paint());
-                } else {
-                    positionX = (key.mWidth + padding.left - padding.right) / 2;
-                    paint.setTextAlign(Align.CENTER);
-                    if (DEBUG_SHOW_ALIGN) {
-                        if (label.length() > 1)
-                            drawVerticalLine(canvas, positionX, rowHeight, 0xc0008080, new Paint());
-                    }
-                }
-                if (key.mManualTemporaryUpperCaseHintIcon != null && isManualTemporaryUpperCase) {
-                    paint.setColor(mKeyTextColorDisabled);
-                } else {
-                    paint.setColor(mKeyTextColor);
-                }
-                if (key.mEnabled) {
-                    // Set a drop shadow for the text
-                    paint.setShadowLayer(mShadowRadius, 0, 0, mShadowColor);
-                } else {
-                    // Make label invisible
-                    paint.setColor(Color.TRANSPARENT);
-                }
-                canvas.drawText(label, positionX, baseline, paint);
-                // Turn off drop shadow
-                paint.setShadowLayer(0, 0, 0, 0);
-            }
-            // Draw key icon
-            final Drawable icon = key.getIcon();
-            if (key.mLabel == null && icon != null) {
-                final int drawableWidth = icon.getIntrinsicWidth();
-                final int drawableHeight = icon.getIntrinsicHeight();
-                final int drawableX;
-                final int drawableY = (
-                        key.mHeight + padding.top - padding.bottom - drawableHeight) / 2;
-                if ((key.mLabelOption & KEY_LABEL_OPTION_ALIGN_LEFT) != 0) {
-                    drawableX = padding.left + mKeyLabelHorizontalPadding;
-                    if (DEBUG_SHOW_ALIGN)
-                        drawVerticalLine(canvas, drawableX, rowHeight, 0xc0800080, new Paint());
-                } else if ((key.mLabelOption & KEY_LABEL_OPTION_ALIGN_RIGHT) != 0) {
-                    drawableX = key.mWidth - padding.right - mKeyLabelHorizontalPadding
-                            - drawableWidth;
-                    if (DEBUG_SHOW_ALIGN)
-                        drawVerticalLine(canvas, drawableX + drawableWidth, rowHeight,
-                                0xc0808000, new Paint());
-                } else { // Align center
-                    drawableX = (key.mWidth + padding.left - padding.right - drawableWidth) / 2;
-                    if (DEBUG_SHOW_ALIGN)
-                        drawVerticalLine(canvas, drawableX + drawableWidth / 2, rowHeight,
-                                0xc0008080, new Paint());
-                }
-                drawIcon(canvas, icon, drawableX, drawableY, drawableWidth, drawableHeight);
-                if (DEBUG_SHOW_ALIGN)
-                    drawRectangle(canvas, drawableX, drawableY, drawableWidth, drawableHeight,
-                            0x80c00000, new Paint());
-            }
-            if (key.mHintIcon != null) {
-                final int drawableWidth = key.mWidth;
-                final int drawableHeight = key.mHeight;
-                final int drawableX = 0;
-                final int drawableY = HINT_ICON_VERTICAL_ADJUSTMENT_PIXEL;
-                Drawable hintIcon = (isManualTemporaryUpperCase
-                        && key.mManualTemporaryUpperCaseHintIcon != null)
-                        ? key.mManualTemporaryUpperCaseHintIcon : key.mHintIcon;
-                drawIcon(canvas, hintIcon, drawableX, drawableY, drawableWidth, drawableHeight);
-                if (DEBUG_SHOW_ALIGN)
-                    drawRectangle(canvas, drawableX, drawableY, drawableWidth, drawableHeight,
-                            0x80c0c000, new Paint());
-            }
-            canvas.translate(-key.mX - kbdPaddingLeft, -key.mY - kbdPaddingTop);
         }
 
-        // TODO: Move this function to ProximityInfo for getting rid of public declarations for
+        // TODO: Move this function to ProximityInfo for getting rid of
+        // public declarations for
         // GRID_WIDTH and GRID_HEIGHT
         if (DEBUG_KEYBOARD_GRID) {
             Paint p = new Paint();
             p.setStyle(Paint.Style.STROKE);
             p.setStrokeWidth(1.0f);
             p.setColor(0x800000c0);
-            int cw = (mKeyboard.getMinWidth() + mKeyboard.GRID_WIDTH - 1) / mKeyboard.GRID_WIDTH;
-            int ch = (mKeyboard.getHeight() + mKeyboard.GRID_HEIGHT - 1) / mKeyboard.GRID_HEIGHT;
+            int cw = (mKeyboard.getMinWidth() + mKeyboard.GRID_WIDTH - 1)
+                    / mKeyboard.GRID_WIDTH;
+            int ch = (mKeyboard.getHeight() + mKeyboard.GRID_HEIGHT - 1)
+                    / mKeyboard.GRID_HEIGHT;
             for (int i = 0; i <= mKeyboard.GRID_WIDTH; i++)
                 canvas.drawLine(i * cw, 0, i * cw, ch * mKeyboard.GRID_HEIGHT, p);
             for (int i = 0; i <= mKeyboard.GRID_HEIGHT; i++)
@@ -804,9 +647,9 @@
         }
 
         // Overlay a dark rectangle to dim the keyboard
-        if (mMiniKeyboardView != null) {
-            paint.setColor((int) (mBackgroundDimAmount * 0xFF) << 24);
-            canvas.drawRect(0, 0, width, height, paint);
+        if (mPopupMiniKeyboardPanel != null) {
+            mPaint.setColor((int) (mBackgroundDimAmount * 0xFF) << 24);
+            canvas.drawRect(0, 0, width, height, mPaint);
         }
 
         mInvalidatedKey = null;
@@ -814,6 +657,134 @@
         mDirtyRect.setEmpty();
     }
 
+    private void onBufferDrawKey(final Canvas canvas, final Key key) {
+        final Paint paint = mPaint;
+        final Drawable keyBackground = mKeyBackground;
+        final Rect padding = mPadding;
+        final int kbdPaddingLeft = getPaddingLeft();
+        final int kbdPaddingTop = getPaddingTop();
+        final int keyDrawX = key.mX + key.mVisualInsetsLeft;
+        final int keyDrawWidth = key.mWidth - key.mVisualInsetsLeft - key.mVisualInsetsRight;
+        final int rowHeight = padding.top + key.mHeight;
+        final boolean isManualTemporaryUpperCase = mKeyboard.isManualTemporaryUpperCase();
+
+        canvas.translate(keyDrawX + kbdPaddingLeft, key.mY + kbdPaddingTop);
+
+        // Draw key background.
+        final int[] drawableState = key.getCurrentDrawableState();
+        keyBackground.setState(drawableState);
+        final Rect bounds = keyBackground.getBounds();
+        if (keyDrawWidth != bounds.right || key.mHeight != bounds.bottom) {
+            keyBackground.setBounds(0, 0, keyDrawWidth, key.mHeight);
+        }
+        keyBackground.draw(canvas);
+
+        // Draw key label.
+        if (key.mLabel != null) {
+            // Switch the character to uppercase if shift is pressed
+            final String label = key.mLabel == null ? null : adjustCase(key.mLabel).toString();
+            // For characters, use large font. For labels like "Done", use small font.
+            final int labelSize = getLabelSizeAndSetPaint(label, key.mLabelOption, paint);
+            final int labelCharHeight = getLabelCharHeight(labelSize, paint);
+
+            // Vertical label text alignment.
+            final float baseline;
+            if ((key.mLabelOption & KEY_LABEL_OPTION_ALIGN_BOTTOM) != 0) {
+                baseline = key.mHeight - labelCharHeight * KEY_LABEL_VERTICAL_PADDING_FACTOR;
+                if (DEBUG_SHOW_ALIGN)
+                    drawHorizontalLine(canvas, (int)baseline, keyDrawWidth, 0xc0008000,
+                            new Paint());
+            } else { // Align center
+                final float centerY = (key.mHeight + padding.top - padding.bottom) / 2;
+                baseline = centerY + labelCharHeight * KEY_LABEL_VERTICAL_ADJUSTMENT_FACTOR_CENTER;
+                if (DEBUG_SHOW_ALIGN)
+                    drawHorizontalLine(canvas, (int)baseline, keyDrawWidth, 0xc0008000,
+                            new Paint());
+            }
+            // Horizontal label text alignment
+            final int positionX;
+            if ((key.mLabelOption & KEY_LABEL_OPTION_ALIGN_LEFT) != 0) {
+                positionX = mKeyLabelHorizontalPadding + padding.left;
+                paint.setTextAlign(Align.LEFT);
+                if (DEBUG_SHOW_ALIGN)
+                    drawVerticalLine(canvas, positionX, rowHeight, 0xc0800080, new Paint());
+            } else if ((key.mLabelOption & KEY_LABEL_OPTION_ALIGN_RIGHT) != 0) {
+                positionX = keyDrawWidth - mKeyLabelHorizontalPadding - padding.right;
+                paint.setTextAlign(Align.RIGHT);
+                if (DEBUG_SHOW_ALIGN)
+                    drawVerticalLine(canvas, positionX, rowHeight, 0xc0808000, new Paint());
+            } else {
+                positionX = (keyDrawWidth + padding.left - padding.right) / 2;
+                paint.setTextAlign(Align.CENTER);
+                if (DEBUG_SHOW_ALIGN) {
+                    if (label.length() > 1)
+                        drawVerticalLine(canvas, positionX, rowHeight, 0xc0008080, new Paint());
+                }
+            }
+            if (key.mManualTemporaryUpperCaseHintIcon != null && isManualTemporaryUpperCase) {
+                paint.setColor(mKeyTextColorDisabled);
+            } else {
+                paint.setColor(mKeyTextColor);
+            }
+            if (key.mEnabled) {
+                // Set a drop shadow for the text
+                paint.setShadowLayer(mShadowRadius, 0, 0, mShadowColor);
+            } else {
+                // Make label invisible
+                paint.setColor(Color.TRANSPARENT);
+            }
+            canvas.drawText(label, positionX, baseline, paint);
+            // Turn off drop shadow
+            paint.setShadowLayer(0, 0, 0, 0);
+        }
+
+        // Draw key icon.
+        final Drawable icon = key.getIcon();
+        if (key.mLabel == null && icon != null) {
+            final int drawableWidth = icon.getIntrinsicWidth();
+            final int drawableHeight = icon.getIntrinsicHeight();
+            final int drawableX;
+            final int drawableY = (key.mHeight + padding.top - padding.bottom - drawableHeight) / 2;
+            if ((key.mLabelOption & KEY_LABEL_OPTION_ALIGN_LEFT) != 0) {
+                drawableX = padding.left + mKeyLabelHorizontalPadding;
+                if (DEBUG_SHOW_ALIGN)
+                    drawVerticalLine(canvas, drawableX, rowHeight, 0xc0800080, new Paint());
+            } else if ((key.mLabelOption & KEY_LABEL_OPTION_ALIGN_RIGHT) != 0) {
+                drawableX = keyDrawWidth - padding.right - mKeyLabelHorizontalPadding
+                        - drawableWidth;
+                if (DEBUG_SHOW_ALIGN)
+                    drawVerticalLine(canvas, drawableX + drawableWidth, rowHeight,
+                            0xc0808000, new Paint());
+            } else { // Align center
+                drawableX = (keyDrawWidth + padding.left - padding.right - drawableWidth) / 2;
+                if (DEBUG_SHOW_ALIGN)
+                    drawVerticalLine(canvas, drawableX + drawableWidth / 2, rowHeight,
+                            0xc0008080, new Paint());
+            }
+            drawIcon(canvas, icon, drawableX, drawableY, drawableWidth, drawableHeight);
+            if (DEBUG_SHOW_ALIGN)
+                drawRectangle(canvas, drawableX, drawableY, drawableWidth, drawableHeight,
+                        0x80c00000, new Paint());
+        }
+
+        // Draw hint icon.
+        if (key.mHintIcon != null) {
+            final int drawableWidth = keyDrawWidth;
+            final int drawableHeight = key.mHeight;
+            final int drawableX = 0;
+            final int drawableY = HINT_ICON_VERTICAL_ADJUSTMENT_PIXEL;
+            Drawable hintIcon = (isManualTemporaryUpperCase
+                    && key.mManualTemporaryUpperCaseHintIcon != null)
+                    ? key.mManualTemporaryUpperCaseHintIcon : key.mHintIcon;
+            drawIcon(canvas, hintIcon, drawableX, drawableY, drawableWidth, drawableHeight);
+            if (DEBUG_SHOW_ALIGN)
+                drawRectangle(canvas, drawableX, drawableY, drawableWidth, drawableHeight,
+                        0x80c0c000, new Paint());
+        }
+
+        canvas.translate(-keyDrawX - kbdPaddingLeft, -key.mY - kbdPaddingTop);
+    }
+
     public int getLabelSizeAndSetPaint(CharSequence label, int keyLabelOption, Paint paint) {
         // For characters, use large font. For labels like "Done", use small font.
         final int labelSize;
@@ -885,118 +856,119 @@
     }
 
     // TODO: clean up this method.
-    private void dismissKeyPreview() {
-        for (PointerTracker tracker : mPointerTrackers)
-            tracker.releaseKey();
-        showPreview(KeyDetector.NOT_A_KEY, null);
+    private void dismissAllKeyPreviews() {
+        for (PointerTracker tracker : mPointerTrackers) {
+            tracker.setReleasedKeyGraphics();
+            dismissKeyPreview(tracker);
+        }
     }
 
     @Override
-    public void showPreview(int keyIndex, PointerTracker tracker) {
-        int oldKeyIndex = mOldPreviewKeyIndex;
-        mOldPreviewKeyIndex = keyIndex;
-        // We should re-draw popup preview when 1) we need to hide the preview, 2) we will show
-        // the space key preview and 3) pointer moves off the space key to other letter key, we
-        // should hide the preview of the previous key.
-        final boolean hidePreviewOrShowSpaceKeyPreview = (tracker == null)
-                || (SubtypeSwitcher.getInstance().useSpacebarLanguageSwitcher()
-                        && SubtypeSwitcher.getInstance().needsToDisplayLanguage()
-                        && (tracker.isSpaceKey(keyIndex) || tracker.isSpaceKey(oldKeyIndex)));
-        // If key changed and preview is on or the key is space (language switch is enabled)
-        if (oldKeyIndex != keyIndex && (mShowPreview || (hidePreviewOrShowSpaceKeyPreview))) {
-            if (keyIndex == KeyDetector.NOT_A_KEY) {
-                mHandler.cancelPopupPreview();
-                mHandler.dismissPreview(mDelayAfterPreview);
-            } else if (tracker != null) {
-                mHandler.popupPreview(mDelayBeforePreview, keyIndex, tracker);
-            }
+    public void showKeyPreview(int keyIndex, PointerTracker tracker) {
+        if (mShowKeyPreview) {
+            mHandler.showKeyPreview(mDelayBeforePreview, keyIndex, tracker);
+        } else if (mKeyboard.needSpacebarPreview(keyIndex)) {
+            // Show key preview (in this case, slide language switcher) without any delay.
+            showKey(keyIndex, tracker);
         }
     }
 
-    // TODO Must fix popup preview on xlarge layout
+    @Override
+    public void dismissKeyPreview(PointerTracker tracker) {
+        if (mShowKeyPreview) {
+            mHandler.cancelShowKeyPreview(tracker);
+            mHandler.dismissKeyPreview(mDelayAfterPreview, tracker);
+        } else if (mKeyboard.needSpacebarPreview(KeyDetector.NOT_A_KEY)) {
+            // Dismiss key preview (in this case, slide language switcher) without any delay.
+            mPreviewText.setVisibility(View.INVISIBLE);
+        }
+    }
+
+    private void addKeyPreview(TextView keyPreview) {
+        ViewGroup placer = mPreviewPlacer;
+        if (placer == null) {
+            final FrameLayout screenContent = (FrameLayout) getRootView().findViewById(
+                    android.R.id.content);
+            if (android.os.Build.VERSION.SDK_INT >= /* HONEYCOMB */11) {
+                placer = screenContent;
+            } else {
+                // Insert LinearLayout to be able to setMargin because pre-Honeycomb FrameLayout
+                // could not handle setMargin properly.
+                placer = new LinearLayout(getContext());
+                screenContent.addView(placer);
+            }
+            mPreviewPlacer = placer;
+        }
+        if (placer instanceof FrameLayout) {
+            // Honeycomb or later.
+            placer.addView(keyPreview, new FrameLayout.LayoutParams(0, 0));
+        } else {
+            // Gingerbread or ealier.
+            placer.addView(keyPreview, new LinearLayout.LayoutParams(0, 0));
+        }
+    }
+
+    // TODO: Introduce minimum duration for displaying key previews
+    // TODO: Display up to two key previews when the user presses two keys at the same time
     private void showKey(final int keyIndex, PointerTracker tracker) {
-        Key key = tracker.getKey(keyIndex);
+        final TextView previewText = mPreviewText;
+        // If the key preview has no parent view yet, add it to the ViewGroup which can place
+        // key preview absolutely in SoftInputWindow.
+        if (previewText.getParent() == null) {
+            addKeyPreview(previewText);
+        }
+
+        final Key key = tracker.getKey(keyIndex);
         // If keyIndex is invalid or IME is already closed, we must not show key preview.
-        // Trying to show preview PopupWindow while root window is closed causes
+        // Trying to show key preview while root window is closed causes
         // WindowManager.BadTokenException.
         if (key == null || !mInForeground)
             return;
+
+        mHandler.cancelAllDismissKeyPreviews();
+
+        final int keyDrawX = key.mX + key.mVisualInsetsLeft;
+        final int keyDrawWidth = key.mWidth - key.mVisualInsetsLeft - key.mVisualInsetsRight;
         // What we show as preview should match what we show on key top in onBufferDraw(). 
         if (key.mLabel != null) {
             // TODO Should take care of temporaryShiftLabel here.
-            mPreviewText.setCompoundDrawables(null, null, null, null);
-            mPreviewText.setText(adjustCase(tracker.getPreviewText(key)));
+            previewText.setCompoundDrawables(null, null, null, null);
+            previewText.setText(adjustCase(tracker.getPreviewText(key)));
             if (key.mLabel.length() > 1) {
-                mPreviewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, mKeyLetterSize);
-                mPreviewText.setTypeface(Typeface.DEFAULT_BOLD);
+                previewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, mKeyLetterSize);
+                previewText.setTypeface(Typeface.DEFAULT_BOLD);
             } else {
-                mPreviewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, mPreviewTextSizeLarge);
-                mPreviewText.setTypeface(mKeyLetterStyle);
+                previewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, mPreviewTextSizeLarge);
+                previewText.setTypeface(mKeyLetterStyle);
             }
         } else {
             final Drawable previewIcon = key.getPreviewIcon();
-            mPreviewText.setCompoundDrawables(null, null, null,
+            previewText.setCompoundDrawables(null, null, null,
                    previewIcon != null ? previewIcon : key.getIcon());
-            mPreviewText.setText(null);
-        }
-        mPreviewText.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
-                MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
-        int popupWidth = Math.max(mPreviewText.getMeasuredWidth(), key.mWidth
-                + mPreviewText.getPaddingLeft() + mPreviewText.getPaddingRight());
-        final int popupHeight = mPreviewHeight;
-        LayoutParams lp = mPreviewText.getLayoutParams();
-        if (lp != null) {
-            lp.width = popupWidth;
-            lp.height = popupHeight;
-        }
-
-        int popupPreviewX = key.mX - (popupWidth - key.mWidth) / 2;
-        int popupPreviewY = key.mY - popupHeight + mPreviewOffset;
-
-        mHandler.cancelDismissPreview();
-        if (mOffsetInWindow == null) {
-            mOffsetInWindow = new int[2];
-            getLocationInWindow(mOffsetInWindow);
-            mOffsetInWindow[0] += mPopupPreviewOffsetX; // Offset may be zero
-            mOffsetInWindow[1] += mPopupPreviewOffsetY; // Offset may be zero
-            int[] windowLocation = new int[2];
-            getLocationOnScreen(windowLocation);
-            mWindowY = windowLocation[1];
+            previewText.setText(null);
         }
         // Set the preview background state
-        mPreviewText.getBackground().setState(
+        previewText.getBackground().setState(
                 key.mPopupCharacters != null ? LONG_PRESSABLE_STATE_SET : EMPTY_STATE_SET);
-        popupPreviewX += mOffsetInWindow[0];
-        popupPreviewY += mOffsetInWindow[1];
 
-        // If the popup cannot be shown above the key, put it on the side
-        if (popupPreviewY + mWindowY < 0) {
-            // If the key you're pressing is on the left side of the keyboard, show the popup on
-            // the right, offset by enough to see at least one key to the left/right.
-            if (key.mX + key.mWidth <= getWidth() / 2) {
-                popupPreviewX += (int) (key.mWidth * 2.5);
-            } else {
-                popupPreviewX -= (int) (key.mWidth * 2.5);
-            }
-            popupPreviewY += popupHeight;
-        }
+        previewText.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
+                MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
+        final int previewWidth = Math.max(previewText.getMeasuredWidth(), keyDrawWidth
+                + previewText.getPaddingLeft() + previewText.getPaddingRight());
+        final int previewHeight = mPreviewHeight;
+        getLocationInWindow(mCoordinates);
+        final int previewX = keyDrawX - (previewWidth - keyDrawWidth) / 2 + mCoordinates[0];
+        final int previewY = key.mY - previewHeight + mCoordinates[1] + mPreviewOffset;
+        // Record key preview position to display mini-keyboard later at the same position
+        mKeyPreviewDisplayedY = previewY;
 
-        try {
-            if (mPreviewPopup.isShowing()) {
-                mPreviewPopup.update(popupPreviewX, popupPreviewY, popupWidth, popupHeight);
-            } else {
-                mPreviewPopup.setWidth(popupWidth);
-                mPreviewPopup.setHeight(popupHeight);
-                mPreviewPopup.showAtLocation(mMiniKeyboardParent, Gravity.NO_GRAVITY,
-                        popupPreviewX, popupPreviewY);
-            }
-        } catch (WindowManager.BadTokenException e) {
-            // Swallow the exception which will be happened when IME is already closed.
-            Log.w(TAG, "LatinIME is already closed when tried showing key preview.");
-        }
-        // Record popup preview position to display mini-keyboard later at the same positon
-        mPopupPreviewDisplayedY = popupPreviewY;
-        mPreviewText.setVisibility(VISIBLE);
+        // Place the key preview.
+        // TODO: Adjust position of key previews which touch screen edges
+        final MarginLayoutParams lp = (MarginLayoutParams)previewText.getLayoutParams();
+        lp.width = previewWidth;
+        lp.height = previewHeight;
+        lp.setMargins(previewX, previewY, 0, 0);
+        previewText.setVisibility(VISIBLE);
     }
 
     /**
@@ -1023,70 +995,72 @@
         if (key == null)
             return;
         mInvalidatedKey = key;
-        mInvalidatedKeyRect.set(0, 0, key.mWidth, key.mHeight);
-        mInvalidatedKeyRect.offset(key.mX + getPaddingLeft(), key.mY + getPaddingTop());
+        final int x = key.mX + getPaddingLeft();
+        final int y = key.mY + getPaddingTop();
+        mInvalidatedKeyRect.set(x, y, x + key.mWidth, y + key.mHeight);
         mDirtyRect.union(mInvalidatedKeyRect);
         onBufferDraw();
         invalidate(mInvalidatedKeyRect);
     }
 
-    private boolean openPopupIfRequired(int keyIndex, PointerTracker tracker) {
+    private boolean openMiniKeyboardIfRequired(int keyIndex, PointerTracker tracker) {
         // Check if we have a popup layout specified first.
         if (mPopupLayout == 0) {
             return false;
         }
 
-        Key popupKey = tracker.getKey(keyIndex);
-        if (popupKey == null)
+        final Key parentKey = tracker.getKey(keyIndex);
+        if (parentKey == null)
             return false;
-        boolean result = onLongPress(popupKey, tracker);
+        boolean result = onLongPress(parentKey, tracker);
         if (result) {
-            dismissKeyPreview();
-            mMiniKeyboardTrackerId = tracker.mPointerId;
-            // Mark this tracker "already processed" and remove it from the pointer queue
-            tracker.setAlreadyProcessed();
-            mPointerQueue.remove(tracker);
+            dismissAllKeyPreviews();
+            tracker.onLongPressed(mPointerQueue);
         }
         return result;
     }
 
     private void onLongPressShiftKey(PointerTracker tracker) {
-        tracker.setAlreadyProcessed();
-        mPointerQueue.remove(tracker);
+        tracker.onLongPressed(mPointerQueue);
         mKeyboardActionListener.onCodeInput(Keyboard.CODE_CAPSLOCK, null, 0, 0);
     }
 
-    private void onDoubleTapShiftKey(PointerTracker tracker) {
+    private void onDoubleTapShiftKey(@SuppressWarnings("unused") PointerTracker tracker) {
         // When shift key is double tapped, the first tap is correctly processed as usual tap. And
         // the second tap is treated as this double tap event, so that we need not mark tracker
         // calling setAlreadyProcessed() nor remove the tracker from mPointerQueueueue.
         mKeyboardActionListener.onCodeInput(Keyboard.CODE_CAPSLOCK, null, 0, 0);
     }
 
-    private View inflateMiniKeyboardContainer(Key popupKey) {
+    // This default implementation returns a popup mini keyboard panel.
+    // A derived class may return a language switcher popup panel, for instance.
+    protected PopupPanel onCreatePopupPanel(Key parentKey) {
+        if (parentKey.mPopupCharacters == null)
+            return null;
+
         final View container = LayoutInflater.from(getContext()).inflate(mPopupLayout, null);
         if (container == null)
             throw new NullPointerException();
 
-        final KeyboardView miniKeyboardView =
-                (KeyboardView)container.findViewById(R.id.KeyboardView);
+        final PopupMiniKeyboardView miniKeyboardView =
+                (PopupMiniKeyboardView)container.findViewById(R.id.mini_keyboard_view);
         miniKeyboardView.setOnKeyboardActionListener(new KeyboardActionListener() {
             @Override
             public void onCodeInput(int primaryCode, int[] keyCodes, int x, int y) {
                 mKeyboardActionListener.onCodeInput(primaryCode, keyCodes, x, y);
-                dismissPopupKeyboard();
+                dismissMiniKeyboard();
             }
 
             @Override
             public void onTextInput(CharSequence text) {
                 mKeyboardActionListener.onTextInput(text);
-                dismissPopupKeyboard();
+                dismissMiniKeyboard();
             }
 
             @Override
             public void onCancelInput() {
                 mKeyboardActionListener.onCancelInput();
-                dismissPopupKeyboard();
+                dismissMiniKeyboard();
             }
 
             @Override
@@ -1102,112 +1076,57 @@
                 mKeyboardActionListener.onRelease(primaryCode, withSliding);
             }
         });
-        // Override default ProximityKeyDetector.
-        miniKeyboardView.mKeyDetector = new MiniKeyboardKeyDetector(mMiniKeyboardSlideAllowance);
-        // Remove gesture detector on mini-keyboard
-        miniKeyboardView.mGestureDetector = null;
 
         final Keyboard keyboard = new MiniKeyboardBuilder(this, mKeyboard.getPopupKeyboardResId(),
-                popupKey).build();
+                parentKey).build();
         miniKeyboardView.setKeyboard(keyboard);
-        miniKeyboardView.mMiniKeyboardParent = this;
 
         container.measure(MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.AT_MOST),
                 MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.AT_MOST));
 
-        return container;
-    }
-
-    private static boolean isOneRowKeys(List<Key> keys) {
-        if (keys.size() == 0) return false;
-        final int edgeFlags = keys.get(0).mEdgeFlags;
-        // HACK: The first key of mini keyboard which was inflated from xml and has multiple rows,
-        // does not have both top and bottom edge flags on at the same time.  On the other hand,
-        // the first key of mini keyboard that was created with popupCharacters must have both top
-        // and bottom edge flags on.
-        // When you want to use one row mini-keyboard from xml file, make sure that the row has
-        // both top and bottom edge flags set.
-        return (edgeFlags & Keyboard.EDGE_TOP) != 0
-                && (edgeFlags & Keyboard.EDGE_BOTTOM) != 0;
+        return miniKeyboardView;
     }
 
     /**
-     * Called when a key is long pressed. By default this will open any popup keyboard associated
-     * with this key through the attributes popupLayout and popupCharacters.
-     * @param popupKey the key that was long pressed
+     * Called when a key is long pressed. By default this will open mini keyboard associated
+     * with this key.
+     * @param parentKey the key that was long pressed
+     * @param tracker the pointer tracker which pressed the parent key
      * @return true if the long press is handled, false otherwise. Subclasses should call the
      * method on the base class if the subclass doesn't wish to handle the call.
      */
-    protected boolean onLongPress(Key popupKey, PointerTracker tracker) {
-        if (popupKey.mPopupCharacters == null)
-            return false;
-
-        View container = mMiniKeyboardCache.get(popupKey);
-        if (container == null) {
-            container = inflateMiniKeyboardContainer(popupKey);
-            mMiniKeyboardCache.put(popupKey, container);
+    protected boolean onLongPress(Key parentKey, PointerTracker tracker) {
+        PopupPanel popupPanel = mPopupPanelCache.get(parentKey);
+        if (popupPanel == null) {
+            popupPanel = onCreatePopupPanel(parentKey);
+            if (popupPanel == null)
+                return false;
+            mPopupPanelCache.put(parentKey, popupPanel);
         }
-        mMiniKeyboardView = (KeyboardView)container.findViewById(R.id.KeyboardView);
-        final MiniKeyboard miniKeyboard = (MiniKeyboard)mMiniKeyboardView.getKeyboard();
-
-        if (mWindowOffset == null) {
-            mWindowOffset = new int[2];
-            getLocationInWindow(mWindowOffset);
+        if (mPopupWindow == null) {
+            mPopupWindow = new PopupWindow(getContext());
+            mPopupWindow.setBackgroundDrawable(null);
+            mPopupWindow.setAnimationStyle(R.style.PopupMiniKeyboardAnimation);
+            // Allow popup window to be drawn off the screen.
+            mPopupWindow.setClippingEnabled(false);
         }
-        final int pointX = (mConfigShowMiniKeyboardAtTouchedPoint) ? tracker.getLastX()
-                : popupKey.mX + popupKey.mWidth / 2;
-        final int popupX = pointX - miniKeyboard.getDefaultCoordX()
-                - container.getPaddingLeft()
-                + getPaddingLeft() + mWindowOffset[0];
-        final int popupY = popupKey.mY - mKeyboard.getVerticalGap()
-                - (container.getMeasuredHeight() - container.getPaddingBottom())
-                + getPaddingTop() + mWindowOffset[1];
-        final int x = popupX;
-        final int y = mShowPreview && isOneRowKeys(miniKeyboard.getKeys())
-                ? mPopupPreviewDisplayedY : popupY;
-
-        mMiniKeyboardOriginX = x + container.getPaddingLeft() - mWindowOffset[0];
-        mMiniKeyboardOriginY = y + container.getPaddingTop() - mWindowOffset[1];
-        mMiniKeyboardView.setPopupOffset(x, y);
-        if (miniKeyboard.setShifted(
-                mKeyboard == null ? false : mKeyboard.isShiftedOrShiftLocked())) {
-            mMiniKeyboardView.invalidateAllKeys();
-        }
-        // Mini keyboard needs no pop-up key preview displayed.
-        mMiniKeyboardView.setPreviewEnabled(false);
-        mMiniKeyboardPopup.setContentView(container);
-        mMiniKeyboardPopup.setWidth(container.getMeasuredWidth());
-        mMiniKeyboardPopup.setHeight(container.getMeasuredHeight());
-        mMiniKeyboardPopup.showAtLocation(this, Gravity.NO_GRAVITY, x, y);
-
-        // Inject down event on the key to mini keyboard.
-        final long eventTime = SystemClock.uptimeMillis();
-        mMiniKeyboardPopupTime = eventTime;
-        final MotionEvent downEvent = generateMiniKeyboardMotionEvent(MotionEvent.ACTION_DOWN,
-                pointX, popupKey.mY + popupKey.mHeight / 2, eventTime);
-        mMiniKeyboardView.onTouchEvent(downEvent);
-        downEvent.recycle();
+        mPopupMiniKeyboardPanel = popupPanel;
+        popupPanel.showPanel(this, parentKey, tracker, mKeyPreviewDisplayedY, mPopupWindow);
 
         invalidateAllKeys();
         return true;
     }
 
-    private MotionEvent generateMiniKeyboardMotionEvent(int action, int x, int y, long eventTime) {
-        return MotionEvent.obtain(mMiniKeyboardPopupTime, eventTime, action,
-                    x - mMiniKeyboardOriginX, y - mMiniKeyboardOriginY, 0);
-    }
-
     private PointerTracker getPointerTracker(final int id) {
         final ArrayList<PointerTracker> pointers = mPointerTrackers;
-        final Key[] keys = mKeys;
         final KeyboardActionListener listener = mKeyboardActionListener;
 
         // Create pointer trackers until we can get 'id+1'-th tracker, if needed.
         for (int i = pointers.size(); i <= id; i++) {
             final PointerTracker tracker =
-                new PointerTracker(i, mHandler, mKeyDetector, this, getResources());
-            if (keys != null)
-                tracker.setKeyboard(mKeyboard, keys, mKeyHysteresisDistance);
+                new PointerTracker(i, this, mHandler, mKeyDetector, this);
+            if (mKeyboard != null)
+                tracker.setKeyboard(mKeyboard, mKeyHysteresisDistance);
             if (listener != null)
                 tracker.setOnKeyboardActionListener(listener);
             pointers.add(tracker);
@@ -1217,8 +1136,8 @@
     }
 
     public boolean isInSlidingKeyInput() {
-        if (mMiniKeyboardView != null) {
-            return mMiniKeyboardView.isInSlidingKeyInput();
+        if (mPopupMiniKeyboardPanel != null) {
+            return mPopupMiniKeyboardPanel.isInSlidingKeyInput();
         } else {
             return mPointerQueue.isInSlidingKeyInput();
         }
@@ -1238,20 +1157,17 @@
         // TODO: cleanup this code into a multi-touch to single-touch event converter class?
         // If the device does not have distinct multi-touch support panel, ignore all multi-touch
         // events except a transition from/to single-touch.
-        if ((!mHasDistinctMultitouch || mIsAccessibilityEnabled)
-                && pointerCount > 1 && oldPointerCount > 1) {
+        if (!mHasDistinctMultitouch && pointerCount > 1 && oldPointerCount > 1) {
             return true;
         }
 
         // Track the last few movements to look for spurious swipes.
         mSwipeTracker.addMovement(me);
 
-        // Gesture detector must be enabled only when mini-keyboard is not on the screen and
-        // accessibility is not enabled.
-        // TODO: Reconcile gesture detection and accessibility features.
-        if (mMiniKeyboardView == null && !mIsAccessibilityEnabled
-                && mGestureDetector != null && mGestureDetector.onTouchEvent(me)) {
-            dismissKeyPreview();
+        // Gesture detector must be enabled only when mini-keyboard is not on the screen.
+        if (mPopupMiniKeyboardPanel == null && mGestureDetector != null
+                && mGestureDetector.onTouchEvent(me)) {
+            dismissAllKeyPreviews();
             mHandler.cancelKeyTimers();
             return true;
         }
@@ -1262,26 +1178,13 @@
         final int x = (int)me.getX(index);
         final int y = (int)me.getY(index);
 
-        // Needs to be called after the gesture detector gets a turn, as it may have
-        // displayed the mini keyboard
-        if (mMiniKeyboardView != null) {
-            final int miniKeyboardPointerIndex = me.findPointerIndex(mMiniKeyboardTrackerId);
-            if (miniKeyboardPointerIndex >= 0 && miniKeyboardPointerIndex < pointerCount) {
-                final int miniKeyboardX = (int)me.getX(miniKeyboardPointerIndex);
-                final int miniKeyboardY = (int)me.getY(miniKeyboardPointerIndex);
-                MotionEvent translated = generateMiniKeyboardMotionEvent(action,
-                        miniKeyboardX, miniKeyboardY, eventTime);
-                mMiniKeyboardView.onTouchEvent(translated);
-                translated.recycle();
-            }
-            return true;
+        // Needs to be called after the gesture detector gets a turn, as it may have displayed the
+        // mini keyboard
+        if (mPopupMiniKeyboardPanel != null) {
+            return mPopupMiniKeyboardPanel.onTouchEvent(me);
         }
 
         if (mHandler.isInKeyRepeat()) {
-            // It will keep being in the key repeating mode while the key is being pressed.
-            if (action == MotionEvent.ACTION_MOVE) {
-                return true;
-            }
             final PointerTracker tracker = getPointerTracker(id);
             // Key repeating timer will be canceled if 2 or more keys are in action, and current
             // event (UP or DOWN) is non-modifier key.
@@ -1294,17 +1197,26 @@
         // TODO: cleanup this code into a multi-touch to single-touch event converter class?
         // Translate mutli-touch event to single-touch events on the device that has no distinct
         // multi-touch panel.
-        if (!mHasDistinctMultitouch || mIsAccessibilityEnabled) {
+        if (!mHasDistinctMultitouch) {
             // Use only main (id=0) pointer tracker.
             PointerTracker tracker = getPointerTracker(0);
             if (pointerCount == 1 && oldPointerCount == 2) {
                 // Multi-touch to single touch transition.
-                // Send a down event for the latest pointer.
-                tracker.onDownEvent(x, y, eventTime, null);
+                // Send a down event for the latest pointer if the key is different from the
+                // previous key.
+                final int newKeyIndex = tracker.getKeyIndexOn(x, y);
+                if (mOldKeyIndex != newKeyIndex) {
+                    tracker.onDownEvent(x, y, eventTime, null);
+                    if (action == MotionEvent.ACTION_UP)
+                        tracker.onUpEvent(x, y, eventTime, null);
+                }
             } else if (pointerCount == 2 && oldPointerCount == 1) {
                 // Single-touch to multi-touch transition.
                 // Send an up event for the last pointer.
-                tracker.onUpEvent(tracker.getLastX(), tracker.getLastY(), eventTime, null);
+                final int lastX = tracker.getLastX();
+                final int lastY = tracker.getLastY();
+                mOldKeyIndex = tracker.getKeyIndexOn(lastX, lastY);
+                tracker.onUpEvent(lastX, lastY, eventTime, null);
             } else if (pointerCount == 1 && oldPointerCount == 1) {
                 tracker.onTouchEvent(action, x, y, eventTime, null);
             } else {
@@ -1345,12 +1257,12 @@
     }
 
     public void closing() {
-        mPreviewPopup.dismiss();
+        mPreviewText.setVisibility(View.GONE);
         mHandler.cancelAllMessages();
 
-        dismissPopupKeyboard();
+        dismissMiniKeyboard();
         mDirtyRect.union(0, 0, getWidth(), getHeight());
-        mMiniKeyboardCache.clear();
+        mPopupPanelCache.clear();
         requestLayout();
     }
 
@@ -1365,21 +1277,17 @@
         closing();
     }
 
-    private void dismissPopupKeyboard() {
-        if (mMiniKeyboardPopup.isShowing()) {
-            mMiniKeyboardPopup.dismiss();
-            mMiniKeyboardView = null;
-            mMiniKeyboardOriginX = 0;
-            mMiniKeyboardOriginY = 0;
+    private boolean dismissMiniKeyboard() {
+        if (mPopupWindow != null && mPopupWindow.isShowing()) {
+            mPopupWindow.dismiss();
+            mPopupMiniKeyboardPanel = null;
             invalidateAllKeys();
-        }
-    }
-
-    public boolean handleBack() {
-        if (mMiniKeyboardPopup.isShowing()) {
-            dismissPopupKeyboard();
             return true;
         }
         return false;
     }
+
+    public boolean handleBack() {
+        return dismissMiniKeyboard();
+    }
 }
diff --git a/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java b/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java
index 5820049..c279769 100644
--- a/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java
@@ -26,6 +26,9 @@
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Color;
+import android.graphics.ColorFilter;
+import android.graphics.ColorMatrix;
+import android.graphics.ColorMatrixColorFilter;
 import android.graphics.Paint;
 import android.graphics.Paint.Align;
 import android.graphics.PorterDuff;
@@ -33,40 +36,48 @@
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 
+import java.lang.ref.SoftReference;
+import java.util.Arrays;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Locale;
 
 // TODO: We should remove this class
 public class LatinKeyboard extends Keyboard {
-    public static final int OPACITY_FULLY_OPAQUE = 255;
     private static final int SPACE_LED_LENGTH_PERCENT = 80;
 
+    public static final int CODE_NEXT_LANGUAGE = -100;
+    public static final int CODE_PREV_LANGUAGE = -101;
+
     private final Context mContext;
+    private final SubtypeSwitcher mSubtypeSwitcher = SubtypeSwitcher.getInstance();
 
     /* Space key and its icons, drawables and colors. */
     private final Key mSpaceKey;
     private final Drawable mSpaceIcon;
     private final Drawable mSpacePreviewIcon;
-    private final int[] mSpaceKeyIndexArray;
+    private final int mSpaceKeyIndex;
     private final Drawable mSpaceAutoCorrectionIndicator;
     private final Drawable mButtonArrowLeftIcon;
     private final Drawable mButtonArrowRightIcon;
     private final int mSpacebarTextColor;
     private final int mSpacebarTextShadowColor;
-    private final int mSpacebarVerticalCorrection;
     private float mSpacebarTextFadeFactor = 0.0f;
-    private int mSpaceDragStartX;
-    private int mSpaceDragLastDiff;
-    private boolean mCurrentlyInSpace;
+    private final int mSpacebarLanguageSwitchThreshold;
+    private int mSpacebarSlidingLanguageSwitchDiff;
     private SlidingLocaleDrawable mSlidingLocaleIcon;
+    private final HashMap<Integer, SoftReference<BitmapDrawable>> mSpaceDrawableCache =
+            new HashMap<Integer, SoftReference<BitmapDrawable>>();
 
     /* Shortcut key and its icons if available */
     private final Key mShortcutKey;
     private final Drawable mEnabledShortcutIcon;
     private final Drawable mDisabledShortcutIcon;
 
-    private static final float SPACEBAR_DRAG_THRESHOLD = 0.8f;
-    // Minimum width of space key preview (proportional to keyboard width)
+    // Minimum width of spacebar dragging to trigger the language switch (represented by the number
+    // of the most common key width of this keyboard).
+    private static final int SPACEBAR_DRAG_WIDTH = 3;
+    // Minimum width of space key preview (proportional to keyboard width).
     private static final float SPACEBAR_POPUP_MIN_RATIO = 0.4f;
     // Height in space key the language name will be drawn. (proportional to space key height)
     public static final float SPACEBAR_LANGUAGE_BASELINE = 0.6f;
@@ -92,7 +103,7 @@
             case CODE_SPACE:
                 spaceKeyIndex = index;
                 break;
-            case CODE_VOICE:
+            case CODE_SHORTCUT:
                 shortcutKeyIndex = index;
                 break;
             }
@@ -102,7 +113,7 @@
         mSpaceKey = (spaceKeyIndex >= 0) ? keys.get(spaceKeyIndex) : null;
         mSpaceIcon = (mSpaceKey != null) ? mSpaceKey.getIcon() : null;
         mSpacePreviewIcon = (mSpaceKey != null) ? mSpaceKey.getPreviewIcon() : null;
-        mSpaceKeyIndexArray = new int[] { spaceKeyIndex };
+        mSpaceKeyIndex = spaceKeyIndex;
 
         mShortcutKey = (shortcutKeyIndex >= 0) ? keys.get(shortcutKeyIndex) : null;
         mEnabledShortcutIcon = (mShortcutKey != null) ? mShortcutKey.getIcon() : null;
@@ -120,8 +131,8 @@
         mSpaceAutoCorrectionIndicator = res.getDrawable(R.drawable.sym_keyboard_space_led);
         mButtonArrowLeftIcon = res.getDrawable(R.drawable.sym_keyboard_language_arrows_left);
         mButtonArrowRightIcon = res.getDrawable(R.drawable.sym_keyboard_language_arrows_right);
-        mSpacebarVerticalCorrection = res.getDimensionPixelOffset(
-                R.dimen.spacebar_vertical_correction);
+        // The threshold is "key width" x 1.25
+        mSpacebarLanguageSwitchThreshold = (getMostCommonKeyWidth() * 5) / 4;
     }
 
     public void setSpacebarTextFadeFactor(float fadeFactor, LatinKeyboardView view) {
@@ -137,6 +148,12 @@
         return newColor;
     }
 
+    private static ColorFilter getSpacebarDrawableFilter(float fadeFactor) {
+        final ColorMatrix colorMatrix = new ColorMatrix();
+        colorMatrix.setScale(1, 1, 1, fadeFactor);
+        return new ColorMatrixColorFilter(colorMatrix);
+    }
+
     public void updateShortcutKey(boolean available, LatinKeyboardView view) {
         if (mShortcutKey == null)
             return;
@@ -157,19 +174,14 @@
     private void updateSpacebarForLocale(boolean isAutoCorrection) {
         if (mSpaceKey == null)
             return;
-        final Resources res = mContext.getResources();
         // If application locales are explicitly selected.
-        if (SubtypeSwitcher.getInstance().needsToDisplayLanguage()) {
-            mSpaceKey.setIcon(new BitmapDrawable(res,
-                    drawSpacebar(OPACITY_FULLY_OPAQUE, isAutoCorrection)));
+        if (mSubtypeSwitcher.needsToDisplayLanguage()) {
+            mSpaceKey.setIcon(getSpaceDrawable(
+                    mSubtypeSwitcher.getInputLocale(), isAutoCorrection));
+        } else if (isAutoCorrection) {
+            mSpaceKey.setIcon(getSpaceDrawable(null, true));
         } else {
-            // sym_keyboard_space_led can be shared with Black and White symbol themes.
-            if (isAutoCorrection) {
-                mSpaceKey.setIcon(new BitmapDrawable(res,
-                        drawSpacebar(OPACITY_FULLY_OPAQUE, isAutoCorrection)));
-            } else {
-                mSpaceKey.setIcon(mSpaceIcon);
-            }
+            mSpaceKey.setIcon(mSpaceIcon);
         }
     }
 
@@ -182,8 +194,7 @@
 
     // Layout local language name and left and right arrow on spacebar.
     private static String layoutSpacebar(Paint paint, Locale locale, Drawable lArrow,
-            Drawable rArrow, int width, int height, float origTextSize,
-            boolean allowVariableTextSize) {
+            Drawable rArrow, int width, int height, float origTextSize) {
         final float arrowWidth = lArrow.getIntrinsicWidth();
         final float arrowHeight = lArrow.getIntrinsicHeight();
         final float maxTextWidth = width - (arrowWidth + arrowWidth);
@@ -194,17 +205,23 @@
         int textWidth = getTextWidth(paint, language, origTextSize, bounds);
         // Assuming text width and text size are proportional to each other.
         float textSize = origTextSize * Math.min(maxTextWidth / textWidth, 1.0f);
+        // allow variable text size
+        textWidth = getTextWidth(paint, language, textSize, bounds);
+        // If text size goes too small or text does not fit, use middle or short name
+        final boolean useMiddleName = (textSize / origTextSize < MINIMUM_SCALE_OF_LANGUAGE_NAME)
+                || (textWidth > maxTextWidth);
 
         final boolean useShortName;
-        if (allowVariableTextSize) {
-            textWidth = getTextWidth(paint, language, textSize, bounds);
-            // If text size goes too small or text does not fit, use short name
-            useShortName = textSize / origTextSize < MINIMUM_SCALE_OF_LANGUAGE_NAME
-                    || textWidth > maxTextWidth;
+        if (useMiddleName) {
+            language = SubtypeSwitcher.getMiddleDisplayLanguage(locale);
+            textWidth = getTextWidth(paint, language, origTextSize, bounds);
+            textSize = origTextSize * Math.min(maxTextWidth / textWidth, 1.0f);
+            useShortName = (textSize / origTextSize < MINIMUM_SCALE_OF_LANGUAGE_NAME)
+                    || (textWidth > maxTextWidth);
         } else {
-            useShortName = textWidth > maxTextWidth;
-            textSize = origTextSize;
+            useShortName = false;
         }
+
         if (useShortName) {
             language = SubtypeSwitcher.getShortDisplayLanguage(locale);
             textWidth = getTextWidth(paint, language, origTextSize, bounds);
@@ -223,19 +240,31 @@
         return language;
     }
 
-    private Bitmap drawSpacebar(int opacity, boolean isAutoCorrection) {
+    private BitmapDrawable getSpaceDrawable(Locale locale, boolean isAutoCorrection) {
+        final Integer hashCode = Arrays.hashCode(
+                new Object[] { locale, isAutoCorrection, mSpacebarTextFadeFactor });
+        final SoftReference<BitmapDrawable> ref = mSpaceDrawableCache.get(hashCode);
+        BitmapDrawable drawable = (ref == null) ? null : ref.get();
+        if (drawable == null) {
+            drawable = new BitmapDrawable(mContext.getResources(), drawSpacebar(
+                    locale, isAutoCorrection, mSpacebarTextFadeFactor));
+            mSpaceDrawableCache.put(hashCode, new SoftReference<BitmapDrawable>(drawable));
+        }
+        return drawable;
+    }
+
+    private Bitmap drawSpacebar(Locale inputLocale, boolean isAutoCorrection,
+            float textFadeFactor) {
         final int width = mSpaceKey.mWidth;
         final int height = mSpaceIcon != null ? mSpaceIcon.getIntrinsicHeight() : mSpaceKey.mHeight;
         final Bitmap buffer = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
         final Canvas canvas = new Canvas(buffer);
         final Resources res = mContext.getResources();
-        canvas.drawColor(res.getColor(R.color.latinkeyboard_transparent), PorterDuff.Mode.CLEAR);
+        canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
 
-        SubtypeSwitcher subtypeSwitcher = SubtypeSwitcher.getInstance();
         // If application locales are explicitly selected.
-        if (subtypeSwitcher.needsToDisplayLanguage()) {
+        if (inputLocale != null) {
             final Paint paint = new Paint();
-            paint.setAlpha(opacity);
             paint.setAntiAlias(true);
             paint.setTextAlign(Align.CENTER);
 
@@ -252,11 +281,9 @@
                 defaultTextSize = 14;
             }
 
-            final boolean allowVariableTextSize = true;
-            final String language = layoutSpacebar(paint, subtypeSwitcher.getInputLocale(),
+            final String language = layoutSpacebar(paint, inputLocale,
                     mButtonArrowLeftIcon, mButtonArrowRightIcon, width, height,
-                    getTextSizeFromTheme(mContext.getTheme(), textStyle, defaultTextSize),
-                    allowVariableTextSize);
+                    getTextSizeFromTheme(mContext.getTheme(), textStyle, defaultTextSize));
 
             // Draw language text with shadow
             // In case there is no space icon, we will place the language text at the center of
@@ -265,14 +292,19 @@
             final float textHeight = -paint.ascent() + descent;
             final float baseline = (mSpaceIcon != null) ? height * SPACEBAR_LANGUAGE_BASELINE
                     : height / 2 + textHeight / 2;
-            paint.setColor(getSpacebarTextColor(mSpacebarTextShadowColor, mSpacebarTextFadeFactor));
+            paint.setColor(getSpacebarTextColor(mSpacebarTextShadowColor, textFadeFactor));
             canvas.drawText(language, width / 2, baseline - descent - 1, paint);
-            paint.setColor(getSpacebarTextColor(mSpacebarTextColor, mSpacebarTextFadeFactor));
+            paint.setColor(getSpacebarTextColor(mSpacebarTextColor, textFadeFactor));
             canvas.drawText(language, width / 2, baseline - descent, paint);
 
-            // Put arrows that are already layed out on either side of the text
-            if (SubtypeSwitcher.getInstance().useSpacebarLanguageSwitcher()
-                    && subtypeSwitcher.getEnabledKeyboardLocaleCount() > 1) {
+            // Put arrows that are already laid out on either side of the text
+            // Because language switch is disabled on phone and number layouts, hide arrows.
+            // TODO: Sort out how to enable language switch on these layouts.
+            if (mSubtypeSwitcher.useSpacebarLanguageSwitcher()
+                    && mSubtypeSwitcher.getEnabledKeyboardLocaleCount() > 1
+                    && !(isPhoneKeyboard() || isNumberKeyboard())) {
+                mButtonArrowLeftIcon.setColorFilter(getSpacebarDrawableFilter(textFadeFactor));
+                mButtonArrowRightIcon.setColorFilter(getSpacebarDrawableFilter(textFadeFactor));
                 mButtonArrowLeftIcon.draw(canvas);
                 mButtonArrowRightIcon.draw(canvas);
             }
@@ -297,7 +329,14 @@
         return buffer;
     }
 
-    private void updateLocaleDrag(int diff) {
+    public void setSpacebarSlidingLanguageSwitchDiff(int diff) {
+        mSpacebarSlidingLanguageSwitchDiff = diff;
+    }
+
+    public void updateSpacebarPreviewIcon(int diff) {
+        if (mSpacebarSlidingLanguageSwitchDiff == diff)
+            return;
+        mSpacebarSlidingLanguageSwitchDiff = diff;
         if (mSlidingLocaleIcon == null) {
             final int width = Math.max(mSpaceKey.mWidth,
                     (int)(getMinWidth() * SPACEBAR_POPUP_MIN_RATIO));
@@ -305,7 +344,6 @@
             mSlidingLocaleIcon =
                     new SlidingLocaleDrawable(mContext, mSpacePreviewIcon, width, height);
             mSlidingLocaleIcon.setBounds(0, 0, width, height);
-            mSpaceKey.setPreviewIcon(mSlidingLocaleIcon);
         }
         mSlidingLocaleIcon.setDiff(diff);
         if (Math.abs(diff) == Integer.MAX_VALUE) {
@@ -316,69 +354,49 @@
         mSpaceKey.getPreviewIcon().invalidateSelf();
     }
 
-    public int getLanguageChangeDirection() {
-        if (mSpaceKey == null || SubtypeSwitcher.getInstance().getEnabledKeyboardLocaleCount() <= 1
-                || Math.abs(mSpaceDragLastDiff) < mSpaceKey.mWidth * SPACEBAR_DRAG_THRESHOLD) {
-            return 0; // No change
-        }
-        return mSpaceDragLastDiff > 0 ? 1 : -1;
-    }
-
-    public void keyReleased() {
-        mCurrentlyInSpace = false;
-        mSpaceDragLastDiff = 0;
-        if (mSpaceKey != null) {
-            updateLocaleDrag(Integer.MAX_VALUE);
-        }
+    public boolean shouldTriggerSpacebarSlidingLanguageSwitch(int diff) {
+        // On phone and number layouts, sliding language switch is disabled.
+        // TODO: Sort out how to enable language switch on these layouts.
+        if (isPhoneKeyboard() || isNumberKeyboard())
+            return false;
+        return Math.abs(diff) > mSpacebarLanguageSwitchThreshold;
     }
 
     /**
-     * Does the magic of locking the touch gesture into the spacebar when
-     * switching input languages.
+     * Return true if spacebar needs showing preview even when "popup on keypress" is off.
+     * @param keyIndex index of the pressing key
+     * @return true if spacebar needs showing preview
      */
     @Override
-    public boolean isInside(Key key, int pointX, int pointY) {
-        int x = pointX;
-        int y = pointY;
-        final int code = key.mCode;
-        if (code == CODE_SPACE) {
-            y += mSpacebarVerticalCorrection;
-            if (SubtypeSwitcher.getInstance().useSpacebarLanguageSwitcher()
-                    && SubtypeSwitcher.getInstance().getEnabledKeyboardLocaleCount() > 1) {
-                if (mCurrentlyInSpace) {
-                    int diff = x - mSpaceDragStartX;
-                    if (Math.abs(diff - mSpaceDragLastDiff) > 0) {
-                        updateLocaleDrag(diff);
-                    }
-                    mSpaceDragLastDiff = diff;
-                    return true;
-                } else {
-                    boolean isOnSpace = key.isOnKey(x, y);
-                    if (isOnSpace) {
-                        mCurrentlyInSpace = true;
-                        mSpaceDragStartX = x;
-                        updateLocaleDrag(0);
-                    }
-                    return isOnSpace;
-                }
-            }
+    public boolean needSpacebarPreview(int keyIndex) {
+        // This method is called when "popup on keypress" is off.
+        if (!mSubtypeSwitcher.useSpacebarLanguageSwitcher())
+            return false;
+        // Dismiss key preview.
+        if (keyIndex == KeyDetector.NOT_A_KEY)
+            return true;
+        // Key is not a spacebar.
+        if (keyIndex != mSpaceKeyIndex)
+            return false;
+        // The language switcher will be displayed only when the dragging distance is greater
+        // than the threshold.
+        return shouldTriggerSpacebarSlidingLanguageSwitch(mSpacebarSlidingLanguageSwitchDiff);
+    }
+
+    public int getLanguageChangeDirection() {
+        if (mSpaceKey == null || mSubtypeSwitcher.getEnabledKeyboardLocaleCount() <= 1
+                || Math.abs(mSpacebarSlidingLanguageSwitchDiff)
+                    < getMostCommonKeyWidth() * SPACEBAR_DRAG_WIDTH) {
+            return 0; // No change
         }
-
-        // Lock into the spacebar
-        if (mCurrentlyInSpace) return false;
-
-        return key.isOnKey(x, y);
+        return mSpacebarSlidingLanguageSwitchDiff > 0 ? 1 : -1;
     }
 
     @Override
     public int[] getNearestKeys(int x, int y) {
-        if (mCurrentlyInSpace) {
-            return mSpaceKeyIndexArray;
-        } else {
-            // Avoid dead pixels at edges of the keyboard
-            return super.getNearestKeys(Math.max(0, Math.min(x, getMinWidth() - 1)),
-                    Math.max(0, Math.min(y, getHeight() - 1)));
-        }
+        // Avoid dead pixels at edges of the keyboard
+        return super.getNearestKeys(Math.max(0, Math.min(x, getMinWidth() - 1)),
+                Math.max(0, Math.min(y, getHeight() - 1)));
     }
 
     private static int getTextSizeFromTheme(Theme theme, int style, int defValue) {
diff --git a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
index 77e9cae..c98076f 100644
--- a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
@@ -16,9 +16,9 @@
 
 package com.android.inputmethod.keyboard;
 
+import com.android.inputmethod.deprecated.VoiceProxy;
 import com.android.inputmethod.latin.LatinImeLogger;
 import com.android.inputmethod.latin.Utils;
-import com.android.inputmethod.voice.VoiceIMEConnector;
 
 import android.content.Context;
 import android.graphics.Canvas;
@@ -55,24 +55,19 @@
     }
 
     @Override
-    public void setPreviewEnabled(boolean previewEnabled) {
+    public void setKeyPreviewEnabled(boolean previewEnabled) {
         LatinKeyboard latinKeyboard = getLatinKeyboard();
         if (latinKeyboard != null
                 && (latinKeyboard.isPhoneKeyboard() || latinKeyboard.isNumberKeyboard())) {
             // Phone and number keyboard never shows popup preview (except language switch).
-            super.setPreviewEnabled(false);
+            super.setKeyPreviewEnabled(false);
         } else {
-            super.setPreviewEnabled(previewEnabled);
+            super.setKeyPreviewEnabled(previewEnabled);
         }
     }
 
     @Override
     public void setKeyboard(Keyboard newKeyboard) {
-        final LatinKeyboard oldKeyboard = getLatinKeyboard();
-        if (oldKeyboard != null) {
-            // Reset old keyboard state before switching to new keyboard.
-            oldKeyboard.keyReleased();
-        }
         super.setKeyboard(newKeyboard);
         // One-seventh of the keyboard width seems like a reasonable threshold
         mJumpThresholdSquare = newKeyboard.getMinWidth() / 7;
@@ -145,10 +140,6 @@
         // If device has distinct multi touch panel, there is no need to check sudden jump.
         if (hasDistinctMultitouch())
             return false;
-        // If accessibiliy is enabled, stop looking for sudden jumps because it interferes
-        // with touch exploration of the keyboard.
-        if (isAccessibilityEnabled())
-            return false;
         final int action = me.getAction();
         final int x = (int) me.getX();
         final int y = (int) me.getY();
@@ -216,8 +207,7 @@
 
     @Override
     public boolean onTouchEvent(MotionEvent me) {
-        LatinKeyboard keyboard = getLatinKeyboard();
-        if (keyboard == null) return true;
+        if (getLatinKeyboard() == null) return true;
 
         // If there was a sudden jump, return without processing the actual motion event.
         if (handleSuddenJump(me)) {
@@ -226,24 +216,6 @@
             return true;
         }
 
-        // Reset any bounding box controls in the keyboard
-        if (me.getAction() == MotionEvent.ACTION_DOWN) {
-            keyboard.keyReleased();
-        }
-
-        if (me.getAction() == MotionEvent.ACTION_UP) {
-            int languageDirection = keyboard.getLanguageChangeDirection();
-            if (languageDirection != 0) {
-                getOnKeyboardActionListener().onCodeInput(
-                        languageDirection == 1
-                        ? Keyboard.CODE_NEXT_LANGUAGE : Keyboard.CODE_PREV_LANGUAGE,
-                        null, mLastX, mLastY);
-                me.setAction(MotionEvent.ACTION_CANCEL);
-                keyboard.keyReleased();
-                return super.onTouchEvent(me);
-            }
-        }
-
         return super.onTouchEvent(me);
     }
 
@@ -264,6 +236,6 @@
     @Override
     protected void onAttachedToWindow() {
         // Token is available from here.
-        VoiceIMEConnector.getInstance().onAttachedToWindow();
+        VoiceProxy.getInstance().onAttachedToWindow();
     }
 }
diff --git a/java/src/com/android/inputmethod/keyboard/MiniKeyboard.java b/java/src/com/android/inputmethod/keyboard/MiniKeyboard.java
index 3b1408c..5dde15e 100644
--- a/java/src/com/android/inputmethod/keyboard/MiniKeyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/MiniKeyboard.java
@@ -18,6 +18,8 @@
 
 import android.content.Context;
 
+import java.util.List;
+
 public class MiniKeyboard extends Keyboard {
     private int mDefaultKeyCoordX;
 
@@ -32,4 +34,19 @@
     public int getDefaultCoordX() {
         return mDefaultKeyCoordX;
     }
+
+    public boolean isOneRowKeyboard() {
+        final List<Key> keys = getKeys();
+        if (keys.size() == 0) return false;
+        final int edgeFlags = keys.get(0).mEdgeFlags;
+        // HACK: The first key of mini keyboard which was inflated from xml and has multiple rows,
+        // does not have both top and bottom edge flags on at the same time.  On the other hand,
+        // the first key of mini keyboard that was created with popupCharacters must have both top
+        // and bottom edge flags on.
+        // When you want to use one row mini-keyboard from xml file, make sure that the row has
+        // both top and bottom edge flags set.
+        return (edgeFlags & Keyboard.EDGE_TOP) != 0
+                && (edgeFlags & Keyboard.EDGE_BOTTOM) != 0;
+
+    }
 }
diff --git a/java/src/com/android/inputmethod/keyboard/MiniKeyboardBuilder.java b/java/src/com/android/inputmethod/keyboard/MiniKeyboardBuilder.java
index 53dab94..e540fa1 100644
--- a/java/src/com/android/inputmethod/keyboard/MiniKeyboardBuilder.java
+++ b/java/src/com/android/inputmethod/keyboard/MiniKeyboardBuilder.java
@@ -34,7 +34,7 @@
     /* package */ static class MiniKeyboardLayoutParams {
         public final int mKeyWidth;
         public final int mRowHeight;
-        /* package */ final boolean mTopRowNeedsCentering;
+        /* package */ final int mTopRowAdjustment;
         public final int mNumRows;
         public final int mNumColumns;
         public final int mLeftKeys;
@@ -55,29 +55,52 @@
             if (parentKeyboardWidth / keyWidth < maxColumns)
                 throw new IllegalArgumentException("Keyboard is too small to hold mini keyboard: "
                         + parentKeyboardWidth + " " + keyWidth + " " + maxColumns);
-            final int numRows = (numKeys + maxColumns - 1) / maxColumns;
             mKeyWidth = keyWidth;
             mRowHeight = rowHeight;
-            mNumRows = numRows;
 
-            final int numColumns = Math.min(numKeys, maxColumns);
-            final int topRowKeys = numKeys % numColumns;
+            final int numRows = (numKeys + maxColumns - 1) / maxColumns;
+            mNumRows = numRows;
+            final int numColumns = getOptimizedColumns(numKeys, maxColumns);
             mNumColumns = numColumns;
-            mTopRowNeedsCentering = topRowKeys != 0 && (numColumns - topRowKeys) % 2 != 0;
 
             final int numLeftKeys = (numColumns - 1) / 2;
             final int numRightKeys = numColumns - numLeftKeys; // including default key.
             final int maxLeftKeys = coordXInParent / keyWidth;
             final int maxRightKeys = Math.max(1, (parentKeyboardWidth - coordXInParent) / keyWidth);
+            int leftKeys, rightKeys;
             if (numLeftKeys > maxLeftKeys) {
-                mLeftKeys = maxLeftKeys;
-                mRightKeys = numColumns - maxLeftKeys;
+                leftKeys = maxLeftKeys;
+                rightKeys = numColumns - maxLeftKeys;
             } else if (numRightKeys > maxRightKeys) {
-                mLeftKeys = numColumns - maxRightKeys;
-                mRightKeys = maxRightKeys;
+                leftKeys = numColumns - maxRightKeys;
+                rightKeys = maxRightKeys;
             } else {
-                mLeftKeys = numLeftKeys;
-                mRightKeys = numRightKeys;
+                leftKeys = numLeftKeys;
+                rightKeys = numRightKeys;
+            }
+            // Shift right if the left edge of mini keyboard is on the edge of parent keyboard
+            // unless the parent key is on the left edge.
+            if (leftKeys * keyWidth >= coordXInParent && leftKeys > 0) {
+                leftKeys--;
+                rightKeys++;
+            }
+            // Shift left if the right edge of mini keyboard is on the edge of parent keyboard
+            // unless the parent key is on the right edge.
+            if (rightKeys * keyWidth + coordXInParent >= parentKeyboardWidth && rightKeys > 1) {
+                leftKeys++;
+                rightKeys--;
+            }
+            mLeftKeys = leftKeys;
+            mRightKeys = rightKeys;
+
+            // Centering of the top row.
+            final boolean onEdge = (leftKeys == 0 || rightKeys == 1);
+            if (numRows < 2 || onEdge || getTopRowEmptySlots(numKeys, numColumns) % 2 == 0) {
+                mTopRowAdjustment = 0;
+            } else if (mLeftKeys < mRightKeys - 1) {
+                mTopRowAdjustment = 1;
+            } else {
+                mTopRowAdjustment = -1;
             }
         }
 
@@ -113,14 +136,32 @@
             return pos;
         }
 
+        private static int getTopRowEmptySlots(int numKeys, int numColumns) {
+            final int remainingKeys = numKeys % numColumns;
+            if (remainingKeys == 0) {
+                return 0;
+            } else {
+                return numColumns - remainingKeys;
+            }
+        }
+
+        private int getOptimizedColumns(int numKeys, int maxColumns) {
+            int numColumns = Math.min(numKeys, maxColumns);
+            while (getTopRowEmptySlots(numKeys, numColumns) >= mNumRows) {
+                numColumns--;
+            }
+            return numColumns;
+        }
+
         public int getDefaultKeyCoordX() {
             return mLeftKeys * mKeyWidth;
         }
 
         public int getX(int n, int row) {
             final int x = getColumnPos(n) * mKeyWidth + getDefaultKeyCoordX();
-            if (isLastRow(row) && mTopRowNeedsCentering)
-                return x - mKeyWidth / 2;
+            if (isTopRow(row)) {
+                return x + mTopRowAdjustment * (mKeyWidth / 2);
+            }
             return x;
         }
 
@@ -131,27 +172,27 @@
         public int getRowFlags(int row) {
             int rowFlags = 0;
             if (row == 0) rowFlags |= Keyboard.EDGE_TOP;
-            if (isLastRow(row)) rowFlags |= Keyboard.EDGE_BOTTOM;
+            if (isTopRow(row)) rowFlags |= Keyboard.EDGE_BOTTOM;
             return rowFlags;
         }
 
-        private boolean isLastRow(int rowCount) {
+        private boolean isTopRow(int rowCount) {
             return rowCount == mNumRows - 1;
         }
     }
 
-    public MiniKeyboardBuilder(KeyboardView view, int layoutTemplateResId, Key popupKey) {
+    public MiniKeyboardBuilder(KeyboardView view, int layoutTemplateResId, Key parentKey) {
         final Context context = view.getContext();
         mRes = context.getResources();
         final MiniKeyboard keyboard = new MiniKeyboard(context, layoutTemplateResId, null);
         mKeyboard = keyboard;
-        mPopupCharacters = popupKey.mPopupCharacters;
+        mPopupCharacters = parentKey.mPopupCharacters;
 
         final int keyWidth = getMaxKeyWidth(view, mPopupCharacters, keyboard.getKeyWidth());
         final MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(
-                mPopupCharacters.length, popupKey.mMaxPopupColumn,
+                mPopupCharacters.length, parentKey.mMaxPopupColumn,
                 keyWidth, keyboard.getRowHeight(),
-                popupKey.mX + (popupKey.mWidth + popupKey.mGap) / 2 - keyWidth / 2,
+                parentKey.mX + (parentKey.mWidth + parentKey.mGap) / 2 - keyWidth / 2,
                 view.getMeasuredWidth());
         mParams = params;
 
diff --git a/java/src/com/android/inputmethod/keyboard/MiniKeyboardKeyDetector.java b/java/src/com/android/inputmethod/keyboard/MiniKeyboardKeyDetector.java
index a8750d3..c4459f6 100644
--- a/java/src/com/android/inputmethod/keyboard/MiniKeyboardKeyDetector.java
+++ b/java/src/com/android/inputmethod/keyboard/MiniKeyboardKeyDetector.java
@@ -16,9 +16,9 @@
 
 package com.android.inputmethod.keyboard;
 
-public class MiniKeyboardKeyDetector extends KeyDetector {
-    private static final int MAX_NEARBY_KEYS = 1;
+import java.util.List;
 
+public class MiniKeyboardKeyDetector extends KeyDetector {
     private final int mSlideAllowanceSquare;
     private final int mSlideAllowanceSquareTop;
 
@@ -31,20 +31,21 @@
 
     @Override
     protected int getMaxNearbyKeys() {
-        return MAX_NEARBY_KEYS;
+        // No nearby key will be returned.
+        return 1;
     }
 
     @Override
     public int getKeyIndexAndNearbyCodes(int x, int y, final int[] allCodes) {
-        final Key[] keys = getKeys();
+        final List<Key> keys = getKeys();
         final int touchX = getTouchX(x);
         final int touchY = getTouchY(y);
 
         int nearestIndex = NOT_A_KEY;
         int nearestDist = (y < 0) ? mSlideAllowanceSquareTop : mSlideAllowanceSquare;
-        final int keyCount = keys.length;
+        final int keyCount = keys.size();
         for (int index = 0; index < keyCount; index++) {
-            final int dist = keys[index].squaredDistanceToEdge(touchX, touchY);
+            final int dist = keys.get(index).squaredDistanceToEdge(touchX, touchY);
             if (dist < nearestDist) {
                 nearestIndex = index;
                 nearestDist = dist;
@@ -52,7 +53,7 @@
         }
 
         if (allCodes != null && nearestIndex != NOT_A_KEY)
-            allCodes[0] = keys[nearestIndex].mCode;
+            allCodes[0] = keys.get(nearestIndex).mCode;
         return nearestIndex;
     }
 }
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
index 7468578..953d487 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
@@ -19,12 +19,14 @@
 import com.android.inputmethod.keyboard.KeyboardView.UIHandler;
 import com.android.inputmethod.latin.LatinImeLogger;
 import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.SubtypeSwitcher;
 
 import android.content.res.Resources;
 import android.util.Log;
 import android.view.MotionEvent;
 
 import java.util.Arrays;
+import java.util.List;
 
 public class PointerTracker {
     private static final String TAG = PointerTracker.class.getSimpleName();
@@ -36,9 +38,9 @@
 
     public interface UIProxy {
         public void invalidateKey(Key key);
-        public void showPreview(int keyIndex, PointerTracker tracker);
+        public void showKeyPreview(int keyIndex, PointerTracker tracker);
+        public void dismissKeyPreview(PointerTracker tracker);
         public boolean hasDistinctMultitouch();
-        public boolean isAccessibilityEnabled();
     }
 
     public final int mPointerId;
@@ -48,9 +50,7 @@
     private final int mLongPressKeyTimeout;
     private final int mLongPressShiftKeyTimeout;
 
-    // Miscellaneous constants
-    private static final int NOT_A_KEY = KeyDetector.NOT_A_KEY;
-
+    private final KeyboardView mKeyboardView;
     private final UIProxy mProxy;
     private final UIHandler mHandler;
     private final KeyDetector mKeyDetector;
@@ -63,15 +63,12 @@
     private final int mTouchNoiseThresholdDistanceSquared;
 
     private Keyboard mKeyboard;
-    private Key[] mKeys;
+    private List<Key> mKeys;
     private int mKeyHysteresisDistanceSquared = -1;
     private int mKeyQuarterWidthSquared;
 
     private final PointerTrackerKeyState mKeyState;
 
-    // true if accessibility is enabled in the parent keyboard
-    private boolean mIsAccessibilityEnabled;
-
     // true if keyboard layout has been changed.
     private boolean mKeyboardLayoutHasBeenChanged;
 
@@ -87,8 +84,14 @@
     // true if sliding key is allowed.
     private boolean mIsAllowedSlidingKeyInput;
 
-    // pressed key
-    private int mPreviousKey = NOT_A_KEY;
+    // ignore modifier key if true
+    private boolean mIgnoreModifierKey;
+
+    // TODO: Remove these hacking variables
+    // true if this pointer is in sliding language switch
+    private boolean mIsInSlidingLanguageSwitch;
+    private int mSpaceKeyIndex;
+    private final SubtypeSwitcher mSubtypeSwitcher;
 
     // Empty {@link KeyboardActionListener}
     private static final KeyboardActionListener EMPTY_LISTENER = new KeyboardActionListener() {
@@ -106,18 +109,19 @@
         public void onSwipeDown() {}
     };
 
-    public PointerTracker(int id, UIHandler handler, KeyDetector keyDetector, UIProxy proxy,
-            Resources res) {
+    public PointerTracker(int id, KeyboardView keyboardView, UIHandler handler,
+            KeyDetector keyDetector, UIProxy proxy) {
         if (proxy == null || handler == null || keyDetector == null)
             throw new NullPointerException();
         mPointerId = id;
+        mKeyboardView = keyboardView;
         mProxy = proxy;
         mHandler = handler;
         mKeyDetector = keyDetector;
         mKeyboardSwitcher = KeyboardSwitcher.getInstance();
         mKeyState = new PointerTrackerKeyState(keyDetector);
-        mIsAccessibilityEnabled = proxy.isAccessibilityEnabled();
         mHasDistinctMultitouch = proxy.hasDistinctMultitouch();
+        final Resources res = mKeyboardView.getResources();
         mConfigSlidingKeyInputEnabled = res.getBoolean(R.bool.config_sliding_key_input_enabled);
         mDelayBeforeKeyRepeatStart = res.getInteger(R.integer.config_delay_before_key_repeat_start);
         mLongPressKeyTimeout = res.getInteger(R.integer.config_long_press_key_timeout);
@@ -127,20 +131,21 @@
                 R.dimen.config_touch_noise_threshold_distance);
         mTouchNoiseThresholdDistanceSquared = (int)(
                 touchNoiseThresholdDistance * touchNoiseThresholdDistance);
+        mSubtypeSwitcher = SubtypeSwitcher.getInstance();
     }
 
     public void setOnKeyboardActionListener(KeyboardActionListener listener) {
         mListener = listener;
     }
 
-    public void setAccessibilityEnabled(boolean accessibilityEnabled) {
-        mIsAccessibilityEnabled = accessibilityEnabled;
-    }
-
     // Returns true if keyboard has been changed by this callback.
     private boolean callListenerOnPressAndCheckKeyboardLayoutChange(Key key, boolean withSliding) {
+        final boolean ignoreModifierKey = mIgnoreModifierKey && isModifierCode(key.mCode);
         if (DEBUG_LISTENER)
-            Log.d(TAG, "onPress    : " + keyCodePrintable(key.mCode) + " sliding=" + withSliding);
+            Log.d(TAG, "onPress    : " + keyCodePrintable(key.mCode) + " sliding=" + withSliding
+                    + " ignoreModifier=" + ignoreModifierKey);
+        if (ignoreModifierKey)
+            return false;
         if (key.mEnabled) {
             mListener.onPress(key.mCode, withSliding);
             final boolean keyboardLayoutHasBeenChanged = mKeyboardLayoutHasBeenChanged;
@@ -153,9 +158,13 @@
     // Note that we need primaryCode argument because the keyboard may in shifted state and the
     // primaryCode is different from {@link Key#mCode}.
     private void callListenerOnCodeInput(Key key, int primaryCode, int[] keyCodes, int x, int y) {
+        final boolean ignoreModifierKey = mIgnoreModifierKey && isModifierCode(key.mCode);
         if (DEBUG_LISTENER)
             Log.d(TAG, "onCodeInput: " + keyCodePrintable(primaryCode)
-                    + " codes="+ Arrays.toString(keyCodes) + " x=" + x + " y=" + y);
+                    + " codes="+ Arrays.toString(keyCodes) + " x=" + x + " y=" + y
+                    + " ignoreModifier=" + ignoreModifierKey);
+        if (ignoreModifierKey)
+            return;
         if (key.mEnabled)
             mListener.onCodeInput(primaryCode, keyCodes, x, y);
     }
@@ -170,8 +179,12 @@
     // Note that we need primaryCode argument because the keyboard may in shifted state and the
     // primaryCode is different from {@link Key#mCode}.
     private void callListenerOnRelease(Key key, int primaryCode, boolean withSliding) {
+        final boolean ignoreModifierKey = mIgnoreModifierKey && isModifierCode(key.mCode);
         if (DEBUG_LISTENER)
-            Log.d(TAG, "onRelease  : " + keyCodePrintable(primaryCode) + " sliding=" + withSliding);
+            Log.d(TAG, "onRelease  : " + keyCodePrintable(primaryCode) + " sliding="
+                    + withSliding + " ignoreModifier=" + ignoreModifierKey);
+        if (ignoreModifierKey)
+            return;
         if (key.mEnabled)
             mListener.onRelease(primaryCode, withSliding);
     }
@@ -182,11 +195,11 @@
         mListener.onCancelInput();
     }
 
-    public void setKeyboard(Keyboard keyboard, Key[] keys, float keyHysteresisDistance) {
-        if (keyboard == null || keys == null || keyHysteresisDistance < 0)
+    public void setKeyboard(Keyboard keyboard, float keyHysteresisDistance) {
+        if (keyboard == null || keyHysteresisDistance < 0)
             throw new IllegalArgumentException();
         mKeyboard = keyboard;
-        mKeys = keys;
+        mKeys = keyboard.getKeys();
         mKeyHysteresisDistanceSquared = (int)(keyHysteresisDistance * keyHysteresisDistance);
         final int keyQuarterWidth = keyboard.getKeyWidth() / 4;
         mKeyQuarterWidthSquared = keyQuarterWidth * keyQuarterWidth;
@@ -199,11 +212,11 @@
     }
 
     private boolean isValidKeyIndex(int keyIndex) {
-        return keyIndex >= 0 && keyIndex < mKeys.length;
+        return keyIndex >= 0 && keyIndex < mKeys.size();
     }
 
     public Key getKey(int keyIndex) {
-        return isValidKeyIndex(keyIndex) ? mKeys[keyIndex] : null;
+        return isValidKeyIndex(keyIndex) ? mKeys.get(keyIndex) : null;
     }
 
     private static boolean isModifierCode(int primaryCode) {
@@ -229,34 +242,33 @@
         return key != null && key.mCode == Keyboard.CODE_SHIFT;
     }
 
+    public int getKeyIndexOn(int x, int y) {
+        return mKeyDetector.getKeyIndexAndNearbyCodes(x, y, null);
+    }
+
     public boolean isSpaceKey(int keyIndex) {
         Key key = getKey(keyIndex);
         return key != null && key.mCode == Keyboard.CODE_SPACE;
     }
 
-    public void releaseKey() {
-        updateKeyGraphics(NOT_A_KEY);
+    public void setReleasedKeyGraphics() {
+        setReleasedKeyGraphics(mKeyState.getKeyIndex());
     }
 
-    private void updateKeyGraphics(int keyIndex) {
-        int oldKeyIndex = mPreviousKey;
-        mPreviousKey = keyIndex;
-        if (keyIndex != oldKeyIndex) {
-            if (isValidKeyIndex(oldKeyIndex)) {
-                // if new key index is not a key, old key was just released inside of the key.
-                final boolean inside = (keyIndex == NOT_A_KEY);
-                mKeys[oldKeyIndex].onReleased(inside);
-                mProxy.invalidateKey(mKeys[oldKeyIndex]);
-            }
-            if (isValidKeyIndex(keyIndex)) {
-                mKeys[keyIndex].onPressed();
-                mProxy.invalidateKey(mKeys[keyIndex]);
-            }
+    private void setReleasedKeyGraphics(int keyIndex) {
+        final Key key = getKey(keyIndex);
+        if (key != null) {
+            key.onReleased();
+            mProxy.invalidateKey(key);
         }
     }
 
-    public void setAlreadyProcessed() {
-        mKeyAlreadyProcessed = true;
+    private void setPressedKeyGraphics(int keyIndex) {
+        final Key key = getKey(keyIndex);
+        if (key != null && key.mEnabled) {
+            key.onPressed();
+            mProxy.invalidateKey(key);
+        }
     }
 
     private void checkAssertion(PointerTrackerQueue queue) {
@@ -302,7 +314,7 @@
                 if (DEBUG_MODE)
                     Log.w(TAG, "onDownEvent: ignore potential noise: time=" + deltaT
                             + " distance=" + distanceSquared);
-                setAlreadyProcessed();
+                mKeyAlreadyProcessed = true;
                 return;
             }
         }
@@ -321,32 +333,33 @@
     private void onDownEventInternal(int x, int y, long eventTime) {
         int keyIndex = mKeyState.onDownKey(x, y, eventTime);
         // Sliding key is allowed when 1) enabled by configuration, 2) this pointer starts sliding
-        // from modifier key, 3) this pointer is on mini-keyboard, or 4) accessibility is enabled.
+        // from modifier key, or 3) this pointer is on mini-keyboard.
         mIsAllowedSlidingKeyInput = mConfigSlidingKeyInputEnabled || isModifierInternal(keyIndex)
-                || mKeyDetector instanceof MiniKeyboardKeyDetector
-                || mIsAccessibilityEnabled;
+                || mKeyDetector instanceof MiniKeyboardKeyDetector;
         mKeyboardLayoutHasBeenChanged = false;
         mKeyAlreadyProcessed = false;
         mIsRepeatableKey = false;
         mIsInSlidingKeyInput = false;
+        mIsInSlidingLanguageSwitch = false;
+        mIgnoreModifierKey = false;
         if (isValidKeyIndex(keyIndex)) {
             // This onPress call may have changed keyboard layout. Those cases are detected at
             // {@link #setKeyboard}. In those cases, we should update keyIndex according to the new
             // keyboard layout.
-            if (callListenerOnPressAndCheckKeyboardLayoutChange(mKeys[keyIndex], false))
+            if (callListenerOnPressAndCheckKeyboardLayoutChange(getKey(keyIndex), false))
                 keyIndex = mKeyState.onDownKey(x, y, eventTime);
-        }
-        if (isValidKeyIndex(keyIndex)) {
-            // Accessibility disables key repeat because users may need to pause on a key to hear
-            // its spoken description.
-            if (mKeys[keyIndex].mRepeatable && !mIsAccessibilityEnabled) {
-                repeatKey(keyIndex);
-                mHandler.startKeyRepeatTimer(mDelayBeforeKeyRepeatStart, keyIndex, this);
-                mIsRepeatableKey = true;
-            }
+
+            startRepeatKey(keyIndex);
             startLongPressTimer(keyIndex);
+            showKeyPreview(keyIndex);
+            setPressedKeyGraphics(keyIndex);
         }
-        showKeyPreviewAndUpdateKeyGraphics(keyIndex);
+    }
+
+    private void startSlidingKeyInput(Key key) {
+        if (!mIsInSlidingKeyInput)
+            mIgnoreModifierKey = isModifierCode(key.mCode);
+        mIsInSlidingKeyInput = true;
     }
 
     public void onMoveEvent(int x, int y, long eventTime, PointerTrackerQueue queue) {
@@ -357,10 +370,17 @@
             return;
         final PointerTrackerKeyState keyState = mKeyState;
 
+        // TODO: Remove this hacking code
+        if (mIsInSlidingLanguageSwitch) {
+            ((LatinKeyboard)mKeyboard).updateSpacebarPreviewIcon(x - keyState.getKeyX());
+            showKeyPreview(mSpaceKeyIndex);
+            return;
+        }
         final int lastX = keyState.getLastX();
         final int lastY = keyState.getLastY();
+        final int oldKeyIndex = keyState.getKeyIndex();
+        final Key oldKey = getKey(oldKeyIndex);
         int keyIndex = keyState.onMoveKey(x, y);
-        final Key oldKey = getKey(keyState.getKeyIndex());
         if (isValidKeyIndex(keyIndex)) {
             if (oldKey == null) {
                 // The pointer has been slid in to the new key, but the finger was not on any keys.
@@ -372,13 +392,17 @@
                     keyIndex = keyState.onMoveKey(x, y);
                 keyState.onMoveToNewKey(keyIndex, x, y);
                 startLongPressTimer(keyIndex);
-            } else if (!isMinorMoveBounce(x, y, keyIndex)) {
+                showKeyPreview(keyIndex);
+                setPressedKeyGraphics(keyIndex);
+            } else if (isMajorEnoughMoveToBeOnNewKey(x, y, keyIndex)) {
                 // The pointer has been slid in to the new key from the previous key, we must call
                 // onRelease() first to notify that the previous key has been released, then call
                 // onPress() to notify that the new key is being pressed.
-                mIsInSlidingKeyInput = true;
+                setReleasedKeyGraphics(oldKeyIndex);
                 callListenerOnRelease(oldKey, oldKey.mCode, true);
-                mHandler.cancelLongPressTimers();
+                startSlidingKeyInput(oldKey);
+                mHandler.cancelKeyTimers();
+                startRepeatKey(keyIndex);
                 if (mIsAllowedSlidingKeyInput) {
                     // This onPress call may have changed keyboard layout. Those cases are detected
                     // at {@link #setKeyboard}. In those cases, we should update keyIndex according
@@ -387,6 +411,8 @@
                         keyIndex = keyState.onMoveKey(x, y);
                     keyState.onMoveToNewKey(keyIndex, x, y);
                     startLongPressTimer(keyIndex);
+                    setPressedKeyGraphics(keyIndex);
+                    showKeyPreview(keyIndex);
                 } else {
                     // HACK: On some devices, quick successive touches may be translated to sudden
                     // move by touch panel firmware. This hack detects the case and translates the
@@ -398,32 +424,50 @@
                         if (DEBUG_MODE)
                             Log.w(TAG, String.format("onMoveEvent: sudden move is translated to "
                                     + "up[%d,%d]/down[%d,%d] events", lastX, lastY, x, y));
-                        onUpEventInternal(lastX, lastY, eventTime);
+                        onUpEventInternal(lastX, lastY, eventTime, true);
                         onDownEventInternal(x, y, eventTime);
                     } else {
-                        setAlreadyProcessed();
-                        showKeyPreviewAndUpdateKeyGraphics(NOT_A_KEY);
+                        mKeyAlreadyProcessed = true;
+                        dismissKeyPreview();
+                        setReleasedKeyGraphics(oldKeyIndex);
                     }
-                    return;
+                }
+            }
+            // TODO: Remove this hack code
+            else if (isSpaceKey(keyIndex) && !mIsInSlidingLanguageSwitch
+                    && mKeyboard instanceof LatinKeyboard) {
+                final LatinKeyboard keyboard = ((LatinKeyboard)mKeyboard);
+                if (mSubtypeSwitcher.useSpacebarLanguageSwitcher()
+                        && mSubtypeSwitcher.getEnabledKeyboardLocaleCount() > 1) {
+                    final int diff = x - keyState.getKeyX();
+                    if (keyboard.shouldTriggerSpacebarSlidingLanguageSwitch(diff)) {
+                        // Detect start sliding language switch.
+                        mIsInSlidingLanguageSwitch = true;
+                        mSpaceKeyIndex = keyIndex;
+                        keyboard.updateSpacebarPreviewIcon(diff);
+                        // Display spacebar slide language switcher.
+                        showKeyPreview(keyIndex);
+                        if (queue != null)
+                            queue.releaseAllPointersExcept(this, eventTime, true);
+                    }
                 }
             }
         } else {
-            if (oldKey != null && !isMinorMoveBounce(x, y, keyIndex)) {
+            if (oldKey != null && isMajorEnoughMoveToBeOnNewKey(x, y, keyIndex)) {
                 // The pointer has been slid out from the previous key, we must call onRelease() to
                 // notify that the previous key has been released.
-                mIsInSlidingKeyInput = true;
+                setReleasedKeyGraphics(oldKeyIndex);
                 callListenerOnRelease(oldKey, oldKey.mCode, true);
+                startSlidingKeyInput(oldKey);
                 mHandler.cancelLongPressTimers();
                 if (mIsAllowedSlidingKeyInput) {
-                    keyState.onMoveToNewKey(keyIndex, x ,y);
+                    keyState.onMoveToNewKey(keyIndex, x, y);
                 } else {
-                    setAlreadyProcessed();
-                    showKeyPreviewAndUpdateKeyGraphics(NOT_A_KEY);
-                    return;
+                    mKeyAlreadyProcessed = true;
+                    dismissKeyPreview();
                 }
             }
         }
-        showKeyPreviewAndUpdateKeyGraphics(mKeyState.getKeyIndex());
     }
 
     public void onUpEvent(int x, int y, long eventTime, PointerTrackerQueue queue) {
@@ -435,42 +479,69 @@
             if (isModifier()) {
                 // Before processing an up event of modifier key, all pointers already being
                 // tracked should be released.
-                queue.releaseAllPointersExcept(this, eventTime);
+                queue.releaseAllPointersExcept(this, eventTime, true);
             } else {
                 queue.releaseAllPointersOlderThan(this, eventTime);
             }
             queue.remove(this);
         }
-        onUpEventInternal(x, y, eventTime);
+        onUpEventInternal(x, y, eventTime, true);
     }
 
-    public void onUpEventForRelease(int x, int y, long eventTime) {
-        onUpEventInternal(x, y, eventTime);
+    // Let this pointer tracker know that one of newer-than-this pointer trackers got an up event.
+    // This pointer tracker needs to keep the key top graphics "pressed", but needs to get a
+    // "virtual" up event.
+    public void onPhantomUpEvent(int x, int y, long eventTime, boolean updateReleasedKeyGraphics) {
+        if (DEBUG_EVENT)
+            printTouchEvent("onPhntEvent:", x, y, eventTime);
+        onUpEventInternal(x, y, eventTime, updateReleasedKeyGraphics);
+        mKeyAlreadyProcessed = true;
     }
 
-    private void onUpEventInternal(int pointX, int pointY, long eventTime) {
-        int x = pointX;
-        int y = pointY;
+    private void onUpEventInternal(int x, int y, long eventTime,
+            boolean updateReleasedKeyGraphics) {
         mHandler.cancelKeyTimers();
-        mHandler.cancelPopupPreview();
-        showKeyPreviewAndUpdateKeyGraphics(NOT_A_KEY);
+        mHandler.cancelShowKeyPreview(this);
         mIsInSlidingKeyInput = false;
+        final PointerTrackerKeyState keyState = mKeyState;
+        final int keyX, keyY;
+        if (isMajorEnoughMoveToBeOnNewKey(x, y, keyState.onMoveKey(x, y))) {
+            keyX = x;
+            keyY = y;
+        } else {
+            // Use previous fixed key coordinates.
+            keyX = keyState.getKeyX();
+            keyY = keyState.getKeyY();
+        }
+        final int keyIndex = keyState.onUpKey(keyX, keyY, eventTime);
+        dismissKeyPreview();
+        if (updateReleasedKeyGraphics)
+            setReleasedKeyGraphics(keyIndex);
         if (mKeyAlreadyProcessed)
             return;
-        final PointerTrackerKeyState keyState = mKeyState;
-        int keyIndex = keyState.onUpKey(x, y, eventTime);
-        if (isMinorMoveBounce(x, y, keyIndex)) {
-            // Use previous fixed key index and coordinates.
-            keyIndex = keyState.getKeyIndex();
-            x = keyState.getKeyX();
-            y = keyState.getKeyY();
+        // TODO: Remove this hacking code
+        if (mIsInSlidingLanguageSwitch) {
+            setReleasedKeyGraphics(mSpaceKeyIndex);
+            final int languageDir = ((LatinKeyboard)mKeyboard).getLanguageChangeDirection();
+            if (languageDir != 0) {
+                final int code = (languageDir == 1)
+                        ? LatinKeyboard.CODE_NEXT_LANGUAGE : LatinKeyboard.CODE_PREV_LANGUAGE;
+                // This will change keyboard layout.
+                mListener.onCodeInput(code, new int[] {code}, keyX, keyY);
+            }
+            mIsInSlidingLanguageSwitch = false;
+            ((LatinKeyboard)mKeyboard).setSpacebarSlidingLanguageSwitchDiff(0);
+            return;
         }
         if (!mIsRepeatableKey) {
-            detectAndSendKey(keyIndex, x, y);
+            detectAndSendKey(keyIndex, keyX, keyY);
         }
+    }
 
-        if (isValidKeyIndex(keyIndex))
-            mProxy.invalidateKey(mKeys[keyIndex]);
+    public void onLongPressed(PointerTrackerQueue queue) {
+        mKeyAlreadyProcessed = true;
+        if (queue != null)
+            queue.remove(this);
     }
 
     public void onCancelEvent(int x, int y, long eventTime, PointerTrackerQueue queue) {
@@ -478,22 +549,34 @@
         if (DEBUG_EVENT)
             printTouchEvent("onCancelEvt:", x, y, eventTime);
 
-        if (queue != null)
+        if (queue != null) {
+            queue.releaseAllPointersExcept(this, eventTime, true);
             queue.remove(this);
+        }
         onCancelEventInternal();
     }
 
     private void onCancelEventInternal() {
         mHandler.cancelKeyTimers();
-        mHandler.cancelPopupPreview();
-        showKeyPreviewAndUpdateKeyGraphics(NOT_A_KEY);
+        mHandler.cancelShowKeyPreview(this);
+        dismissKeyPreview();
+        setReleasedKeyGraphics(mKeyState.getKeyIndex());
         mIsInSlidingKeyInput = false;
-        int keyIndex = mKeyState.getKeyIndex();
-        if (isValidKeyIndex(keyIndex))
-           mProxy.invalidateKey(mKeys[keyIndex]);
     }
 
-    public void repeatKey(int keyIndex) {
+    private void startRepeatKey(int keyIndex) {
+        final Key key = getKey(keyIndex);
+        if (key != null && key.mRepeatable) {
+            dismissKeyPreview();
+            onRepeatKey(keyIndex);
+            mHandler.startKeyRepeatTimer(mDelayBeforeKeyRepeatStart, keyIndex, this);
+            mIsRepeatableKey = true;
+        } else {
+            mIsRepeatableKey = false;
+        }
+    }
+
+    public void onRepeatKey(int keyIndex) {
         Key key = getKey(keyIndex);
         if (key != null) {
             detectAndSendKey(keyIndex, key.mX, key.mY);
@@ -512,39 +595,46 @@
         return mKeyState.getDownTime();
     }
 
-    private boolean isMinorMoveBounce(int x, int y, int newKey) {
+    private boolean isMajorEnoughMoveToBeOnNewKey(int x, int y, int newKey) {
         if (mKeys == null || mKeyHysteresisDistanceSquared < 0)
             throw new IllegalStateException("keyboard and/or hysteresis not set");
         int curKey = mKeyState.getKeyIndex();
         if (newKey == curKey) {
-            return true;
-        } else if (isValidKeyIndex(curKey)) {
-            return mKeys[curKey].squaredDistanceToEdge(x, y) < mKeyHysteresisDistanceSquared;
-        } else {
             return false;
+        } else if (isValidKeyIndex(curKey)) {
+            return mKeys.get(curKey).squaredDistanceToEdge(x, y) >= mKeyHysteresisDistanceSquared;
+        } else {
+            return true;
         }
     }
 
-    private void showKeyPreviewAndUpdateKeyGraphics(int keyIndex) {
-        updateKeyGraphics(keyIndex);
-        // The modifier key, such as shift key, should not be shown as preview when multi-touch is
-        // supported. On the other hand, if multi-touch is not supported, the modifier key should
-        // be shown as preview. If accessibility is turned on, the modifier key should be shown as
-        // preview.
-        if (mHasDistinctMultitouch && isModifier() && !mIsAccessibilityEnabled) {
-            mProxy.showPreview(NOT_A_KEY, this);
-        } else {
-            mProxy.showPreview(keyIndex, this);
-        }
+    // The modifier key, such as shift key, should not show its key preview.
+    private boolean isKeyPreviewNotRequired(int keyIndex) {
+        final Key key = getKey(keyIndex);
+        if (!key.mEnabled)
+            return true;
+        // Such as spacebar sliding language switch.
+        if (mKeyboard.needSpacebarPreview(keyIndex))
+            return false;
+        final int code = key.mCode;
+        return isModifierCode(code) || code == Keyboard.CODE_DELETE
+                || code == Keyboard.CODE_ENTER || code == Keyboard.CODE_SPACE;
+    }
+
+    private void showKeyPreview(int keyIndex) {
+        if (isKeyPreviewNotRequired(keyIndex))
+            return;
+        mProxy.showKeyPreview(keyIndex, this);
+    }
+
+    private void dismissKeyPreview() {
+        mProxy.dismissKeyPreview(this);
     }
 
     private void startLongPressTimer(int keyIndex) {
-        // Accessibility disables long press because users are likely to need to pause on a key
-        // for an unspecified duration in order to hear the key's spoken description.
-        if (mIsAccessibilityEnabled) {
-            return;
-        }
         Key key = getKey(keyIndex);
+        if (!key.mEnabled)
+            return;
         if (key.mCode == Keyboard.CODE_SHIFT) {
             mHandler.startLongPressShiftTimer(mLongPressShiftKeyTimeout, keyIndex, this);
         } else if (key.mManualTemporaryUpperCaseCode != Keyboard.CODE_DUMMY
@@ -552,7 +642,7 @@
             // We need not start long press timer on the key which has manual temporary upper case
             // code defined and the keyboard is in manual temporary upper case mode.
             return;
-        } else if (mKeyboardSwitcher.isInMomentaryAutoModeSwitchState()) {
+        } else if (mKeyboardSwitcher.isInMomentarySwitchState()) {
             // We use longer timeout for sliding finger input started from the symbols mode key.
             mHandler.startLongPressTimer(mLongPressKeyTimeout * 3, keyIndex, this);
         } else {
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTrackerKeyState.java b/java/src/com/android/inputmethod/keyboard/PointerTrackerKeyState.java
index a62ed96..b3ed1e2 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTrackerKeyState.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTrackerKeyState.java
@@ -92,6 +92,7 @@
 
     public int onUpKey(int x, int y, long eventTime) {
         mUpTime = eventTime;
+        mKeyIndex = KeyDetector.NOT_A_KEY;
         return onMoveKeyInternal(x, y);
     }
 }
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTrackerQueue.java b/java/src/com/android/inputmethod/keyboard/PointerTrackerQueue.java
index 928f3cd..68de8df 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTrackerQueue.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTrackerQueue.java
@@ -29,29 +29,28 @@
         if (mQueue.lastIndexOf(tracker) < 0) {
             return;
         }
-        LinkedList<PointerTracker> queue = mQueue;
+        final LinkedList<PointerTracker> queue = mQueue;
         int oldestPos = 0;
         for (PointerTracker t = queue.get(oldestPos); t != tracker; t = queue.get(oldestPos)) {
             if (t.isModifier()) {
                 oldestPos++;
             } else {
-                t.onUpEventForRelease(t.getLastX(), t.getLastY(), eventTime);
-                t.setAlreadyProcessed();
+                t.onPhantomUpEvent(t.getLastX(), t.getLastY(), eventTime, true);
                 queue.remove(oldestPos);
             }
         }
     }
 
     public void releaseAllPointers(long eventTime) {
-        releaseAllPointersExcept(null, eventTime);
+        releaseAllPointersExcept(null, eventTime, true);
     }
 
-    public void releaseAllPointersExcept(PointerTracker tracker, long eventTime) {
+    public void releaseAllPointersExcept(PointerTracker tracker, long eventTime,
+            boolean updateReleasedKeyGraphics) {
         for (PointerTracker t : mQueue) {
             if (t == tracker)
                 continue;
-            t.onUpEventForRelease(t.getLastX(), t.getLastY(), eventTime);
-            t.setAlreadyProcessed();
+            t.onPhantomUpEvent(t.getLastX(), t.getLastY(), eventTime, updateReleasedKeyGraphics);
         }
         mQueue.clear();
         if (tracker != null)
diff --git a/java/src/com/android/inputmethod/keyboard/PopupMiniKeyboardView.java b/java/src/com/android/inputmethod/keyboard/PopupMiniKeyboardView.java
new file mode 100644
index 0000000..12031f1
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/PopupMiniKeyboardView.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.keyboard;
+
+import com.android.inputmethod.latin.R;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.os.SystemClock;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.PopupWindow;
+
+/**
+ * A view that renders a virtual {@link MiniKeyboard}. It handles rendering of keys and detecting
+ * key presses and touch movements.
+ */
+public class PopupMiniKeyboardView extends KeyboardView implements PopupPanel {
+    private final int[] mCoordinates = new int[2];
+    private final boolean mConfigShowMiniKeyboardAtTouchedPoint;
+
+    private int mOriginX;
+    private int mOriginY;
+    private int mTrackerId;
+    private long mDownTime;
+
+    public PopupMiniKeyboardView(Context context, AttributeSet attrs) {
+        this(context, attrs, R.attr.keyboardViewStyle);
+    }
+
+    public PopupMiniKeyboardView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+
+        final Resources res = context.getResources();
+        mConfigShowMiniKeyboardAtTouchedPoint = res.getBoolean(
+                R.bool.config_show_mini_keyboard_at_touched_point);
+        // Override default ProximityKeyDetector.
+        mKeyDetector = new MiniKeyboardKeyDetector(res.getDimension(
+                R.dimen.mini_keyboard_slide_allowance));
+        // Remove gesture detector on mini-keyboard
+        mGestureDetector = null;
+        setKeyPreviewEnabled(false);
+    }
+
+    @Override
+    public void setKeyPreviewEnabled(boolean previewEnabled) {
+        // Mini keyboard needs no pop-up key preview displayed.
+        super.setKeyPreviewEnabled(false);
+    }
+
+    @Override
+    public void showPanel(KeyboardView parentKeyboardView, Key parentKey,
+            PointerTracker tracker, int keyPreviewY, PopupWindow window) {
+        final View container = (View)getParent();
+        final MiniKeyboard miniKeyboard = (MiniKeyboard)getKeyboard();
+        final Keyboard parentKeyboard = parentKeyboardView.getKeyboard();
+
+        parentKeyboardView.getLocationInWindow(mCoordinates);
+        final int pointX = (mConfigShowMiniKeyboardAtTouchedPoint) ? tracker.getLastX()
+                : parentKey.mX + parentKey.mWidth / 2;
+        final int pointY = parentKey.mY;
+        final int miniKeyboardX = pointX - miniKeyboard.getDefaultCoordX()
+                - container.getPaddingLeft()
+                + parentKeyboardView.getPaddingLeft() + mCoordinates[0];
+        final int miniKeyboardY = pointY - parentKeyboard.getVerticalGap()
+                - (container.getMeasuredHeight() - container.getPaddingBottom())
+                + parentKeyboardView.getPaddingTop() + mCoordinates[1];
+        final int x = miniKeyboardX;
+        final int y = parentKeyboardView.isKeyPreviewEnabled() && miniKeyboard.isOneRowKeyboard()
+                ? keyPreviewY : miniKeyboardY;
+
+        if (miniKeyboard.setShifted(parentKeyboard.isShiftedOrShiftLocked())) {
+            invalidateAllKeys();
+        }
+        window.setContentView(container);
+        window.setWidth(container.getMeasuredWidth());
+        window.setHeight(container.getMeasuredHeight());
+        window.showAtLocation(parentKeyboardView, Gravity.NO_GRAVITY, x, y);
+
+        mOriginX = x + container.getPaddingLeft() - mCoordinates[0];
+        mOriginY = y + container.getPaddingTop() - mCoordinates[1];
+        mTrackerId = tracker.mPointerId;
+        mDownTime = SystemClock.uptimeMillis();
+
+        // Inject down event on the key to mini keyboard.
+        final MotionEvent downEvent = translateMotionEvent(MotionEvent.ACTION_DOWN, pointX,
+                pointY + parentKey.mHeight / 2, mDownTime);
+        onTouchEvent(downEvent);
+        downEvent.recycle();
+    }
+
+    private MotionEvent translateMotionEvent(int action, float x, float y, long eventTime) {
+        return MotionEvent.obtain(mDownTime, eventTime, action, x - mOriginX, y - mOriginY, 0);
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent me) {
+        final int index = me.getActionIndex();
+        final int id = me.getPointerId(index);
+        if (id == mTrackerId) {
+            final MotionEvent translated = translateMotionEvent(me.getAction(), me.getX(index),
+                    me.getY(index), me.getEventTime());
+            super.onTouchEvent(translated);
+            translated.recycle();
+        }
+        return true;
+    }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/PopupPanel.java b/java/src/com/android/inputmethod/keyboard/PopupPanel.java
new file mode 100644
index 0000000..6f2b161
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/PopupPanel.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.keyboard;
+
+import android.view.MotionEvent;
+import android.widget.PopupWindow;
+
+public interface PopupPanel {
+    /**
+     * Show popup panel.
+     * @param parentKeyboardView the parent KeyboardView that has the parent key.
+     * @param parentKey the parent key that is the source of this popup panel
+     * @param tracker the pointer tracker that pressesd the parent key
+     * @param keyPreviewY the Y-coordinate of key preview
+     * @param window PopupWindow to be used to show this popup panel
+     */
+    public void showPanel(KeyboardView parentKeyboardView, Key parentKey,
+            PointerTracker tracker, int keyPreviewY, PopupWindow window);
+
+    /**
+     * Check if the pointer is in siding key input mode.
+     * @return true if the pointer is sliding key input mode.
+     */
+    public boolean isInSlidingKeyInput();
+
+    /**
+     * The motion event handler.
+     * @param me the MotionEvent to be processed.
+     * @return true if the motion event is processed and should be consumed.
+     */
+    public boolean onTouchEvent(MotionEvent me);
+}
diff --git a/java/src/com/android/inputmethod/keyboard/ProximityKeyDetector.java b/java/src/com/android/inputmethod/keyboard/ProximityKeyDetector.java
deleted file mode 100644
index 87f3e14..0000000
--- a/java/src/com/android/inputmethod/keyboard/ProximityKeyDetector.java
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * Copyright (C) 2010 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * 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.util.Log;
-
-import java.util.Arrays;
-
-public class ProximityKeyDetector extends KeyDetector {
-    private static final String TAG = ProximityKeyDetector.class.getSimpleName();
-    private static final boolean DEBUG = false;
-
-    private static final int MAX_NEARBY_KEYS = 12;
-
-    // working area
-    private final int[] mDistances = new int[MAX_NEARBY_KEYS];
-    private final int[] mIndices = new int[MAX_NEARBY_KEYS];
-
-    @Override
-    protected int getMaxNearbyKeys() {
-        return MAX_NEARBY_KEYS;
-    }
-
-    private void initializeNearbyKeys() {
-        Arrays.fill(mDistances, Integer.MAX_VALUE);
-        Arrays.fill(mIndices, NOT_A_KEY);
-    }
-
-    /**
-     * Insert the key into nearby keys buffer and sort nearby keys by ascending order of distance.
-     *
-     * @param keyIndex index of the key.
-     * @param distance distance between the key's edge and user touched point.
-     * @param isOnKey true if the point is on the key.
-     * @return order of the key in the nearby buffer, 0 if it is the nearest key.
-     */
-    private int sortNearbyKeys(int keyIndex, int distance, boolean isOnKey) {
-        final int[] distances = mDistances;
-        final int[] indices = mIndices;
-        for (int insertPos = 0; insertPos < distances.length; insertPos++) {
-            final int comparingDistance = distances[insertPos];
-            if (distance < comparingDistance || (distance == comparingDistance && isOnKey)) {
-                final int nextPos = insertPos + 1;
-                if (nextPos < distances.length) {
-                    System.arraycopy(distances, insertPos, distances, nextPos,
-                            distances.length - nextPos);
-                    System.arraycopy(indices, insertPos, indices, nextPos,
-                            indices.length - nextPos);
-                }
-                distances[insertPos] = distance;
-                indices[insertPos] = keyIndex;
-                return insertPos;
-            }
-        }
-        return distances.length;
-    }
-
-    private void getNearbyKeyCodes(final int[] allCodes) {
-        final Key[] keys = getKeys();
-        final int[] indices = mIndices;
-
-        // allCodes[0] should always have the key code even if it is a non-letter key.
-        if (indices[0] == NOT_A_KEY) {
-            allCodes[0] = NOT_A_CODE;
-            return;
-        }
-
-        int numCodes = 0;
-        for (int j = 0; j < indices.length && numCodes < allCodes.length; j++) {
-            final int index = indices[j];
-            if (index == NOT_A_KEY)
-                break;
-            final int code = keys[index].mCode;
-            // filter out a non-letter key from nearby keys
-            if (code < Keyboard.CODE_SPACE)
-                continue;
-            allCodes[numCodes++] = code;
-        }
-    }
-
-    @Override
-    public int getKeyIndexAndNearbyCodes(int x, int y, final int[] allCodes) {
-        final Key[] keys = getKeys();
-        final int touchX = getTouchX(x);
-        final int touchY = getTouchY(y);
-
-        initializeNearbyKeys();
-        int primaryIndex = NOT_A_KEY;
-        for (final int index : mKeyboard.getNearestKeys(touchX, touchY)) {
-            final Key key = keys[index];
-            final boolean isInside = key.isInside(touchX, touchY);
-            final int distance = key.squaredDistanceToEdge(touchX, touchY);
-            if (isInside || (mProximityCorrectOn && distance < mProximityThresholdSquare)) {
-                final int insertedPosition = sortNearbyKeys(index, distance, isInside);
-                if (insertedPosition == 0 && isInside)
-                    primaryIndex = index;
-            }
-        }
-
-        if (allCodes != null && allCodes.length > 0) {
-            getNearbyKeyCodes(allCodes);
-            if (DEBUG) {
-                Log.d(TAG, "x=" + x + " y=" + y
-                        + " primary="
-                        + (primaryIndex == NOT_A_KEY ? "none" : keys[primaryIndex].mCode)
-                        + " codes=" + Arrays.toString(allCodes));
-            }
-        }
-
-        return primaryIndex;
-    }
-}
diff --git a/java/src/com/android/inputmethod/keyboard/SlidingLocaleDrawable.java b/java/src/com/android/inputmethod/keyboard/SlidingLocaleDrawable.java
index 41f8c2a..b279c1c 100644
--- a/java/src/com/android/inputmethod/keyboard/SlidingLocaleDrawable.java
+++ b/java/src/com/android/inputmethod/keyboard/SlidingLocaleDrawable.java
@@ -23,6 +23,7 @@
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
+import android.graphics.Color;
 import android.graphics.ColorFilter;
 import android.graphics.Paint;
 import android.graphics.PixelFormat;
@@ -63,9 +64,8 @@
         mHeight = height;
         final TextPaint textPaint = new TextPaint();
         textPaint.setTextSize(getTextSizeFromTheme(android.R.style.TextAppearance_Medium, 18));
-        textPaint.setColor(R.color.latinkeyboard_transparent);
+        textPaint.setColor(Color.TRANSPARENT);
         textPaint.setTextAlign(Align.CENTER);
-        textPaint.setAlpha(LatinKeyboard.OPACITY_FULLY_OPAQUE);
         textPaint.setAntiAlias(true);
         mTextPaint = textPaint;
         mMiddleX = (mWidth - mBackground.getIntrinsicWidth()) / 2;
diff --git a/java/src/com/android/inputmethod/latin/AccessibilityUtils.java b/java/src/com/android/inputmethod/latin/AccessibilityUtils.java
deleted file mode 100644
index cd3f9e0..0000000
--- a/java/src/com/android/inputmethod/latin/AccessibilityUtils.java
+++ /dev/null
@@ -1,211 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.latin;
-
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.content.res.TypedArray;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityManager;
-
-import com.android.inputmethod.keyboard.Keyboard;
-import com.android.inputmethod.keyboard.KeyboardSwitcher;
-
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * Utility functions for accessibility support.
- */
-public class AccessibilityUtils {
-    /** Shared singleton instance. */
-    private static final AccessibilityUtils sInstance = new AccessibilityUtils();
-    private /* final */ LatinIME mService;
-    private /* final */ AccessibilityManager mAccessibilityManager;
-    private /* final */ Map<Integer, CharSequence> mDescriptions;
-
-    /**
-     * Returns a shared instance of AccessibilityUtils.
-     *
-     * @return A shared instance of AccessibilityUtils.
-     */
-    public static AccessibilityUtils getInstance() {
-        return sInstance;
-    }
-
-    /**
-     * Initializes (or re-initializes) the shared instance of AccessibilityUtils
-     * with the specified parent service and preferences.
-     *
-     * @param service The parent input method service.
-     * @param prefs The parent preferences.
-     */
-    public static void init(LatinIME service, SharedPreferences prefs) {
-        sInstance.initialize(service, prefs);
-    }
-
-    private AccessibilityUtils() {
-        // This class is not publicly instantiable.
-    }
-
-    /**
-     * Initializes (or re-initializes) with the specified parent service and
-     * preferences.
-     *
-     * @param service The parent input method service.
-     * @param prefs The parent preferences.
-     */
-    private void initialize(LatinIME service, SharedPreferences prefs) {
-        mService = service;
-        mAccessibilityManager = (AccessibilityManager) service.getSystemService(
-                Context.ACCESSIBILITY_SERVICE);
-        mDescriptions = null;
-    }
-
-    /**
-     * Returns true if accessibility is enabled.
-     *
-     * @return {@code true} if accessibility is enabled.
-     */
-    public boolean isAccessibilityEnabled() {
-        return mAccessibilityManager.isEnabled();
-    }
-
-    /**
-     * Speaks a key's action after it has been released. Does not speak letter
-     * keys since typed keys are already spoken aloud by TalkBack.
-     * <p>
-     * No-op if accessibility is not enabled.
-     * </p>
-     *
-     * @param primaryCode The primary code of the released key.
-     * @param switcher The input method's {@link KeyboardSwitcher}.
-     */
-    public void onRelease(int primaryCode, KeyboardSwitcher switcher) {
-        if (!isAccessibilityEnabled()) {
-            return;
-        }
-
-        int resId = -1;
-
-        switch (primaryCode) {
-            case Keyboard.CODE_SHIFT: {
-                if (switcher.isShiftedOrShiftLocked()) {
-                    resId = R.string.description_shift_on;
-                } else {
-                    resId = R.string.description_shift_off;
-                }
-                break;
-            }
-
-            case Keyboard.CODE_SWITCH_ALPHA_SYMBOL: {
-                if (switcher.isAlphabetMode()) {
-                    resId = R.string.description_symbols_off;
-                } else {
-                    resId = R.string.description_symbols_on;
-                }
-                break;
-            }
-        }
-
-        if (resId >= 0) {
-            speakDescription(mService.getResources().getText(resId));
-        }
-    }
-
-    /**
-     * Speaks a key's description for accessibility. If a key has an explicit
-     * description defined in keycodes.xml, that will be used. Otherwise, if the
-     * key is a Unicode character, then its character will be used.
-     * <p>
-     * No-op if accessibility is not enabled.
-     * </p>
-     *
-     * @param primaryCode The primary code of the pressed key.
-     * @param switcher The input method's {@link KeyboardSwitcher}.
-     */
-    public void onPress(int primaryCode, KeyboardSwitcher switcher) {
-        if (!isAccessibilityEnabled()) {
-            return;
-        }
-
-        // TODO Use the current keyboard state to read "Switch to symbols"
-        // instead of just "Symbols" (and similar for shift key).
-        CharSequence description = describeKey(primaryCode);
-        if (description == null && Character.isDefined((char) primaryCode)) {
-            description = Character.toString((char) primaryCode);
-        }
-
-        if (description != null) {
-            speakDescription(description);
-        }
-    }
-
-    /**
-     * Returns a text description for a given key code. If the key does not have
-     * an explicit description, returns <code>null</code>.
-     *
-     * @param keyCode An integer key code.
-     * @return A {@link CharSequence} describing the key or <code>null</code> if
-     *         no description is available.
-     */
-    private CharSequence describeKey(int keyCode) {
-        // If not loaded yet, load key descriptions from XML file.
-        if (mDescriptions == null) {
-            mDescriptions = loadDescriptions();
-        }
-
-        return mDescriptions.get(keyCode);
-    }
-
-    /**
-     * Loads key descriptions from resources.
-     */
-    private Map<Integer, CharSequence> loadDescriptions() {
-        final Map<Integer, CharSequence> descriptions = new HashMap<Integer, CharSequence>();
-        final TypedArray array = mService.getResources().obtainTypedArray(R.array.key_descriptions);
-
-        // Key descriptions are stored as a key code followed by a string.
-        for (int i = 0; i < array.length() - 1; i += 2) {
-            int code = array.getInteger(i, 0);
-            CharSequence desc = array.getText(i + 1);
-
-            descriptions.put(code, desc);
-        }
-
-        array.recycle();
-
-        return descriptions;
-    }
-
-    /**
-     * Sends a character sequence to be read aloud.
-     *
-     * @param description The {@link CharSequence} to be read aloud.
-     */
-    private void speakDescription(CharSequence description) {
-        // TODO We need to add an AccessibilityEvent type for IMEs.
-        final AccessibilityEvent event = AccessibilityEvent.obtain(
-                AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
-        event.setPackageName(mService.getPackageName());
-        event.setClassName(getClass().getName());
-        event.setAddedCount(description.length());
-        event.getText().add(description);
-
-        mAccessibilityManager.sendAccessibilityEvent(event);
-    }
-}
diff --git a/java/src/com/android/inputmethod/latin/AssetFileAddress.java b/java/src/com/android/inputmethod/latin/AssetFileAddress.java
new file mode 100644
index 0000000..074ecac
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/AssetFileAddress.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import java.io.File;
+
+/**
+ * Immutable class to hold the address of an asset.
+ * As opposed to a normal file, an asset is usually represented as a contiguous byte array in
+ * the package file. Open it correctly thus requires the name of the package it is in, but
+ * also the offset in the file and the length of this data. This class encapsulates these three.
+ */
+class AssetFileAddress {
+    public final String mFilename;
+    public final long mOffset;
+    public final long mLength;
+
+    public AssetFileAddress(final String filename, final long offset, final long length) {
+        mFilename = filename;
+        mOffset = offset;
+        mLength = length;
+    }
+
+    public static AssetFileAddress makeFromFileName(final String filename) {
+        if (null == filename) return null;
+        File f = new File(filename);
+        if (null == f || !f.isFile()) return null;
+        return new AssetFileAddress(filename, 0l, f.length());
+    }
+
+    public static AssetFileAddress makeFromFileNameAndOffset(final String filename,
+            final long offset, final long length) {
+        if (null == filename) return null;
+        File f = new File(filename);
+        if (null == f || !f.isFile()) return null;
+        return new AssetFileAddress(filename, offset, length);
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/AutoCorrection.java b/java/src/com/android/inputmethod/latin/AutoCorrection.java
index 092f7ad..d311979 100644
--- a/java/src/com/android/inputmethod/latin/AutoCorrection.java
+++ b/java/src/com/android/inputmethod/latin/AutoCorrection.java
@@ -48,7 +48,7 @@
     }
 
     public void updateAutoCorrectionStatus(Map<String, Dictionary> dictionaries,
-            WordComposer wordComposer, ArrayList<CharSequence> suggestions, int[] priorities,
+            WordComposer wordComposer, ArrayList<CharSequence> suggestions, int[] sortedScores,
             CharSequence typedWord, double autoCorrectionThreshold, int correctionMode,
             CharSequence quickFixedWord, CharSequence whitelistedWord) {
         if (hasAutoCorrectionForWhitelistedWord(whitelistedWord)) {
@@ -62,7 +62,7 @@
             mHasAutoCorrection = true;
             mAutoCorrectionWord = quickFixedWord;
         } else if (hasAutoCorrectionForBinaryDictionary(wordComposer, suggestions, correctionMode,
-                priorities, typedWord, autoCorrectionThreshold)) {
+                sortedScores, typedWord, autoCorrectionThreshold)) {
             mHasAutoCorrection = true;
             mAutoCorrectionWord = suggestions.get(0);
         }
@@ -114,13 +114,13 @@
     }
 
     private boolean hasAutoCorrectionForBinaryDictionary(WordComposer wordComposer,
-            ArrayList<CharSequence> suggestions, int correctionMode, int[] priorities,
+            ArrayList<CharSequence> suggestions, int correctionMode, int[] sortedScores,
             CharSequence typedWord, double autoCorrectionThreshold) {
         if (wordComposer.size() > 1 && (correctionMode == Suggest.CORRECTION_FULL
                 || correctionMode == Suggest.CORRECTION_FULL_BIGRAM)
-                && typedWord != null && suggestions.size() > 0 && priorities.length > 0) {
+                && typedWord != null && suggestions.size() > 0 && sortedScores.length > 0) {
             final CharSequence autoCorrectionCandidate = suggestions.get(0);
-            final int autoCorrectionCandidateScore = priorities[0];
+            final int autoCorrectionCandidateScore = sortedScores[0];
             // TODO: when the normalized score of the first suggestion is nearly equals to
             //       the normalized score of the second suggestion, behave less aggressive.
             mNormalizedScore = Utils.calcNormalizedScore(
diff --git a/java/src/com/android/inputmethod/latin/AutoDictionary.java b/java/src/com/android/inputmethod/latin/AutoDictionary.java
index a00b091..307b81d 100644
--- a/java/src/com/android/inputmethod/latin/AutoDictionary.java
+++ b/java/src/com/android/inputmethod/latin/AutoDictionary.java
@@ -27,7 +27,6 @@
 import android.util.Log;
 
 import java.util.HashMap;
-import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
 
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
index 08ddd25..d95fb96 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
@@ -21,10 +21,7 @@
 import com.android.inputmethod.keyboard.ProximityInfo;
 
 import android.content.Context;
-import android.content.res.AssetFileDescriptor;
-import android.util.Log;
 
-import java.io.File;
 import java.util.Arrays;
 
 /**
@@ -32,8 +29,11 @@
  */
 public class BinaryDictionary extends Dictionary {
 
+    public static final String DICTIONARY_PACK_AUTHORITY =
+            "com.android.inputmethod.latin.dictionarypack";
+
     /**
-     * There is difference between what java and native code can handle.
+     * There is a difference between what java and native code can handle.
      * This value should only be used in BinaryDictionary.java
      * It is necessary to keep it at this value because some languages e.g. German have
      * really long words.
@@ -41,101 +41,59 @@
     public static final int MAX_WORD_LENGTH = 48;
     public static final int MAX_WORDS = 18;
 
+    @SuppressWarnings("unused")
     private static final String TAG = "BinaryDictionary";
     private static final int MAX_PROXIMITY_CHARS_SIZE = ProximityInfo.MAX_PROXIMITY_CHARS_SIZE;
     private static final int MAX_BIGRAMS = 60;
 
     private static final int TYPED_LETTER_MULTIPLIER = 2;
 
-    private static final BinaryDictionary sInstance = new BinaryDictionary();
     private int mDicTypeId;
     private int mNativeDict;
-    private long mDictLength;
     private final int[] mInputCodes = new int[MAX_WORD_LENGTH * MAX_PROXIMITY_CHARS_SIZE];
     private final char[] mOutputChars = new char[MAX_WORD_LENGTH * MAX_WORDS];
     private final char[] mOutputChars_bigrams = new char[MAX_WORD_LENGTH * MAX_BIGRAMS];
-    private final int[] mFrequencies = new int[MAX_WORDS];
-    private final int[] mFrequencies_bigrams = new int[MAX_BIGRAMS];
+    private final int[] mScores = new int[MAX_WORDS];
+    private final int[] mBigramScores = new int[MAX_BIGRAMS];
 
     private final KeyboardSwitcher mKeyboardSwitcher = KeyboardSwitcher.getInstance();
-    private final SubtypeSwitcher mSubtypeSwitcher = SubtypeSwitcher.getInstance();
 
-    private static class Flags {
-        private static class FlagEntry {
-            public final String mName;
-            public final int mValue;
-            public FlagEntry(String name, int value) {
-                mName = name;
-                mValue = value;
-            }
-        }
-        public static final FlagEntry[] ALL_FLAGS = {
-            // Here should reside all flags that trigger some special processing
-            // These *must* match the definition in UnigramDictionary enum in
-            // unigram_dictionary.h so please update both at the same time.
-            new FlagEntry("requiresGermanUmlautProcessing", 0x1)
-        };
-    }
+    public static final Flag FLAG_REQUIRES_GERMAN_UMLAUT_PROCESSING =
+            new Flag(R.bool.config_require_umlaut_processing, 0x1);
+
+    // Can create a new flag from extravalue :
+    // public static final Flag FLAG_MYFLAG =
+    //         new Flag("my_flag", 0x02);
+
+    private static final Flag[] ALL_FLAGS = {
+        // Here should reside all flags that trigger some special processing
+        // These *must* match the definition in UnigramDictionary enum in
+        // unigram_dictionary.h so please update both at the same time.
+        FLAG_REQUIRES_GERMAN_UMLAUT_PROCESSING,
+    };
+
     private int mFlags = 0;
 
-    private BinaryDictionary() {
-    }
-
     /**
-     * Initialize a dictionary from a raw resource file
-     * @param context application context for reading resources
-     * @param resId the resource containing the raw binary dictionary
-     * @return initialized instance of BinaryDictionary
+     * Constructor for the binary dictionary. This is supposed to be called from the
+     * dictionary factory.
+     * All implementations should pass null into flagArray, except for testing purposes.
+     * @param context the context to access the environment from.
+     * @param filename the name of the file to read through native code.
+     * @param offset the offset of the dictionary data within the file.
+     * @param length the length of the binary data.
+     * @param flagArray the flags to limit the dictionary to, or null for default.
      */
-    public static BinaryDictionary initDictionary(Context context, int resId, int dicTypeId) {
-        synchronized (sInstance) {
-            sInstance.closeInternal();
-            try {
-                final AssetFileDescriptor afd = context.getResources().openRawResourceFd(resId);
-                if (afd == null) {
-                    Log.e(TAG, "Found the resource but it is compressed. resId=" + resId);
-                    return null;
-                }
-                final String sourceDir = context.getApplicationInfo().sourceDir;
-                final File packagePath = new File(sourceDir);
-                // TODO: Come up with a way to handle a directory.
-                if (!packagePath.isFile()) {
-                    Log.e(TAG, "sourceDir is not a file: " + sourceDir);
-                    return null;
-                }
-                sInstance.loadDictionary(sourceDir, afd.getStartOffset(), afd.getLength());
-                sInstance.mDicTypeId = dicTypeId;
-            } catch (android.content.res.Resources.NotFoundException e) {
-                Log.e(TAG, "Could not find the resource. resId=" + resId);
-                return null;
-            }
-        }
-        sInstance.initFlags();
-        return sInstance;
-    }
-
-    /* package for test */ static BinaryDictionary initDictionary(File dictionary, long startOffset,
-            long length, int dicTypeId) {
-        synchronized (sInstance) {
-            sInstance.closeInternal();
-            if (dictionary.isFile()) {
-                sInstance.loadDictionary(dictionary.getAbsolutePath(), startOffset, length);
-                sInstance.mDicTypeId = dicTypeId;
-            } else {
-                Log.e(TAG, "Could not find the file. path=" + dictionary.getAbsolutePath());
-                return null;
-            }
-        }
-        return sInstance;
-    }
-
-    private void initFlags() {
-        int flags = 0;
-        for (Flags.FlagEntry entry : Flags.ALL_FLAGS) {
-            if (mSubtypeSwitcher.currentSubtypeContainsExtraValueKey(entry.mName))
-                flags |= entry.mValue;
-        }
-        mFlags = flags;
+    public BinaryDictionary(final Context context,
+            final String filename, final long offset, final long length, Flag[] flagArray) {
+        // Note: at the moment a binary dictionary is always of the "main" type.
+        // Initializing this here will help transitioning out of the scheme where
+        // the Suggest class knows everything about every single dictionary.
+        mDicTypeId = Suggest.DIC_MAIN;
+        // TODO: Stop relying on the state of SubtypeSwitcher, get it as a parameter
+        mFlags = Flag.initFlags(null == flagArray ? ALL_FLAGS : flagArray, context,
+                SubtypeSwitcher.getInstance());
+        loadDictionary(filename, offset, length);
     }
 
     static {
@@ -149,16 +107,15 @@
     private native boolean isValidWordNative(int nativeData, char[] word, int wordLength);
     private native int getSuggestionsNative(int dict, int proximityInfo, int[] xCoordinates,
             int[] yCoordinates, int[] inputCodes, int codesSize, int flags, char[] outputChars,
-            int[] frequencies);
+            int[] scores);
     private native int getBigramsNative(int dict, char[] prevWord, int prevWordLength,
-            int[] inputCodes, int inputCodesLength, char[] outputChars, int[] frequencies,
+            int[] inputCodes, int inputCodesLength, char[] outputChars, int[] scores,
             int maxWordLength, int maxBigrams, int maxAlternatives);
 
     private final void loadDictionary(String path, long startOffset, long length) {
         mNativeDict = openNative(path, startOffset, length,
-                    TYPED_LETTER_MULTIPLIER, FULL_WORD_FREQ_MULTIPLIER,
+                    TYPED_LETTER_MULTIPLIER, FULL_WORD_SCORE_MULTIPLIER,
                     MAX_WORD_LENGTH, MAX_WORDS, MAX_PROXIMITY_CHARS_SIZE);
-        mDictLength = length;
     }
 
     @Override
@@ -168,27 +125,32 @@
 
         char[] chars = previousWord.toString().toCharArray();
         Arrays.fill(mOutputChars_bigrams, (char) 0);
-        Arrays.fill(mFrequencies_bigrams, 0);
+        Arrays.fill(mBigramScores, 0);
 
         int codesSize = codes.size();
+        if (codesSize <= 0) {
+            // Do not return bigrams from BinaryDictionary when nothing was typed.
+            // Only use user-history bigrams (or whatever other bigram dictionaries decide).
+            return;
+        }
         Arrays.fill(mInputCodes, -1);
         int[] alternatives = codes.getCodesAt(0);
         System.arraycopy(alternatives, 0, mInputCodes, 0,
                 Math.min(alternatives.length, MAX_PROXIMITY_CHARS_SIZE));
 
         int count = getBigramsNative(mNativeDict, chars, chars.length, mInputCodes, codesSize,
-                mOutputChars_bigrams, mFrequencies_bigrams, MAX_WORD_LENGTH, MAX_BIGRAMS,
+                mOutputChars_bigrams, mBigramScores, MAX_WORD_LENGTH, MAX_BIGRAMS,
                 MAX_PROXIMITY_CHARS_SIZE);
 
         for (int j = 0; j < count; ++j) {
-            if (mFrequencies_bigrams[j] < 1) break;
+            if (mBigramScores[j] < 1) break;
             final int start = j * MAX_WORD_LENGTH;
             int len = 0;
             while (len <  MAX_WORD_LENGTH && mOutputChars_bigrams[start + len] != 0) {
                 ++len;
             }
             if (len > 0) {
-                callback.addWord(mOutputChars_bigrams, start, len, mFrequencies_bigrams[j],
+                callback.addWord(mOutputChars_bigrams, start, len, mBigramScores[j],
                         mDicTypeId, DataType.BIGRAM);
             }
         }
@@ -197,17 +159,17 @@
     @Override
     public void getWords(final WordComposer codes, final WordCallback callback) {
         final int count = getSuggestions(codes, mKeyboardSwitcher.getLatinKeyboard(),
-                mOutputChars, mFrequencies);
+                mOutputChars, mScores);
 
         for (int j = 0; j < count; ++j) {
-            if (mFrequencies[j] < 1) break;
+            if (mScores[j] < 1) break;
             final int start = j * MAX_WORD_LENGTH;
             int len = 0;
             while (len < MAX_WORD_LENGTH && mOutputChars[start + len] != 0) {
                 ++len;
             }
             if (len > 0) {
-                callback.addWord(mOutputChars, start, len, mFrequencies[j], mDicTypeId,
+                callback.addWord(mOutputChars, start, len, mScores[j], mDicTypeId,
                         DataType.UNIGRAM);
             }
         }
@@ -218,7 +180,7 @@
     }
 
     /* package for test */ int getSuggestions(final WordComposer codes, final Keyboard keyboard,
-            char[] outputChars, int[] frequencies) {
+            char[] outputChars, int[] scores) {
         if (!isValidDictionary()) return -1;
 
         final int codesSize = codes.size();
@@ -232,12 +194,12 @@
                     Math.min(alternatives.length, MAX_PROXIMITY_CHARS_SIZE));
         }
         Arrays.fill(outputChars, (char) 0);
-        Arrays.fill(frequencies, 0);
+        Arrays.fill(scores, 0);
 
         return getSuggestionsNative(
                 mNativeDict, keyboard.getProximityInfo(),
                 codes.getXCoordinates(), codes.getYCoordinates(), mInputCodes, codesSize,
-                mFlags, outputChars, frequencies);
+                mFlags, outputChars, scores);
     }
 
     @Override
@@ -247,10 +209,6 @@
         return isValidWordNative(mNativeDict, chars, chars.length);
     }
 
-    public long getSize() {
-        return mDictLength; // This value is initialized in loadDictionary()
-    }
-
     @Override
     public synchronized void close() {
         closeInternal();
@@ -260,7 +218,6 @@
         if (mNativeDict != 0) {
             closeNative(mNativeDict);
             mNativeDict = 0;
-            mDictLength = 0;
         }
     }
 
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
new file mode 100644
index 0000000..76a230f
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.res.AssetFileDescriptor;
+import android.net.Uri;
+import android.text.TextUtils;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * Group class for static methods to help with creation and getting of the binary dictionary
+ * file from the dictionary provider
+ */
+public class BinaryDictionaryFileDumper {
+    /**
+     * The size of the temporary buffer to copy files.
+     */
+    static final int FILE_READ_BUFFER_SIZE = 1024;
+
+    // Prevents this class to be accidentally instantiated.
+    private BinaryDictionaryFileDumper() {
+    }
+
+    /**
+     * Generates a file name that matches the locale passed as an argument.
+     * The file name is basically the result of the .toString() method, except we replace
+     * any @File.separator with an underscore to avoid generating a file name that may not
+     * be created.
+     * @param locale the locale for which to get the file name
+     * @param context the context to use for getting the directory
+     * @return the name of the file to be created
+     */
+    private static String getCacheFileNameForLocale(Locale locale, Context context) {
+        // The following assumes two things :
+        // 1. That File.separator is not the same character as "_"
+        //    I don't think any android system will ever use "_" as a path separator
+        // 2. That no two locales differ by only a File.separator versus a "_"
+        //    Since "_" can't be part of locale components this should be safe.
+        // Examples:
+        // en -> en
+        // en_US_POSIX -> en_US_POSIX
+        // en__foo/bar -> en__foo_bar
+        final String[] separator = { File.separator };
+        final String[] empty = { "_" };
+        final CharSequence basename = TextUtils.replace(locale.toString(), separator, empty);
+        return context.getFilesDir() + File.separator + basename;
+    }
+
+    /**
+     * Return for a given locale the provider URI to query to get the dictionary.
+     */
+    public static Uri getProviderUri(Locale locale) {
+        return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
+                .authority(BinaryDictionary.DICTIONARY_PACK_AUTHORITY).appendPath(
+                        locale.toString()).build();
+    }
+
+    /**
+     * Queries a content provider for dictionary data for some locale and returns the file addresses
+     *
+     * This will query a content provider for dictionary data for a given locale, and return
+     * the addresses of a file set the members of which are suitable to be mmap'ed. It will copy
+     * them to local storage if needed.
+     * It should also check the dictionary versions to avoid unnecessary copies but this is
+     * still in TODO state.
+     * This will make the data from the content provider the cached dictionary for this locale,
+     * overwriting any previous cached data.
+     * @returns the addresses of the files, or null if no data could be obtained.
+     * @throw FileNotFoundException if the provider returns non-existent data.
+     * @throw IOException if the provider-returned data could not be read.
+     */
+    public static List<AssetFileAddress> getDictSetFromContentProvider(Locale locale,
+            Context context) throws FileNotFoundException, IOException {
+        // TODO: check whether the dictionary is the same or not and if it is, return the cached
+        // file.
+        // TODO: This should be able to read a number of files from the dictionary pack, copy
+        // them all and return them.
+        final ContentResolver resolver = context.getContentResolver();
+        final Uri dictionaryPackUri = getProviderUri(locale);
+        final AssetFileDescriptor afd = resolver.openAssetFileDescriptor(dictionaryPackUri, "r");
+        if (null == afd) return null;
+        final String fileName =
+                copyFileTo(afd.createInputStream(), getCacheFileNameForLocale(locale, context));
+        return Arrays.asList(AssetFileAddress.makeFromFileName(fileName));
+    }
+
+    /**
+     * Accepts a file as dictionary data for some locale and returns the name of a file.
+     *
+     * This will make the data in the input file the cached dictionary for this locale, overwriting
+     * any previous cached data.
+     */
+    public static String getDictionaryFileFromFile(String fileName, Locale locale,
+            Context context) throws FileNotFoundException, IOException {
+        return copyFileTo(new FileInputStream(fileName), getCacheFileNameForLocale(locale,
+                context));
+    }
+
+    /**
+     * Accepts a resource number as dictionary data for some locale and returns the name of a file.
+     *
+     * This will make the resource the cached dictionary for this locale, overwriting any previous
+     * cached data.
+     */
+    public static String getDictionaryFileFromResource(int resource, Locale locale,
+            Context context) throws FileNotFoundException, IOException {
+        return copyFileTo(context.getResources().openRawResource(resource),
+                getCacheFileNameForLocale(locale, context));
+    }
+
+    /**
+     * Copies the data in an input stream to a target file, creating the file if necessary and
+     * overwriting it if it already exists.
+     * @param input the stream to be copied.
+     * @param outputFileName the name of a file to copy the data to. It is created if necessary.
+     */
+    private static String copyFileTo(final InputStream input, final String outputFileName)
+            throws FileNotFoundException, IOException {
+        final byte[] buffer = new byte[FILE_READ_BUFFER_SIZE];
+        final FileOutputStream output = new FileOutputStream(outputFileName);
+        for (int readBytes = input.read(buffer); readBytes >= 0; readBytes = input.read(buffer))
+            output.write(buffer, 0, readBytes);
+        input.close();
+        return outputFileName;
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
new file mode 100644
index 0000000..562580d
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import android.content.Context;
+import android.content.res.AssetFileDescriptor;
+import android.util.Log;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * Helper class to get the address of a mmap'able dictionary file.
+ */
+class BinaryDictionaryGetter {
+
+    /**
+     * Used for Log actions from this class
+     */
+    private static final String TAG = BinaryDictionaryGetter.class.getSimpleName();
+
+    // Prevents this from being instantiated
+    private BinaryDictionaryGetter() {}
+
+    /**
+     * Returns a file address from a resource, or null if it cannot be opened.
+     */
+    private static AssetFileAddress loadFallbackResource(Context context, int fallbackResId) {
+        final AssetFileDescriptor afd = context.getResources().openRawResourceFd(fallbackResId);
+        if (afd == null) {
+            Log.e(TAG, "Found the resource but cannot read it. Is it compressed? resId="
+                    + fallbackResId);
+            return null;
+        }
+        return AssetFileAddress.makeFromFileNameAndOffset(
+                context.getApplicationInfo().sourceDir, afd.getStartOffset(), afd.getLength());
+    }
+
+    /**
+     * Returns a list of file addresses for a given locale, trying relevant methods in order.
+     *
+     * Tries to get binary dictionaries from various sources, in order:
+     * - Uses a private method of getting a private dictionaries, as implemented by the
+     *   PrivateBinaryDictionaryGetter class.
+     * If that fails:
+     * - Uses a content provider to get a public dictionary set, as per the protocol described
+     *   in BinaryDictionaryFileDumper.
+     * If that fails:
+     * - Gets a file name from the fallback resource passed as an argument.
+     * If that fails:
+     * - Returns null.
+     * @return The address of a valid file, or null.
+     */
+    public static List<AssetFileAddress> getDictionaryFiles(Locale locale, Context context,
+            int fallbackResId) {
+        // Try first to query a private package signed the same way for private files.
+        final List<AssetFileAddress> privateFiles =
+                PrivateBinaryDictionaryGetter.getDictionaryFiles(locale, context);
+        if (null != privateFiles) {
+            return privateFiles;
+        } else {
+            try {
+                // If that was no-go, try to find a publicly exported dictionary.
+                return BinaryDictionaryFileDumper.getDictSetFromContentProvider(locale, context);
+            } catch (FileNotFoundException e) {
+                Log.e(TAG, "Unable to create dictionary file from provider for locale "
+                        + locale.toString() + ": falling back to internal dictionary");
+                return Arrays.asList(loadFallbackResource(context, fallbackResId));
+            } catch (IOException e) {
+                Log.e(TAG, "Unable to read source data for locale "
+                        + locale.toString() + ": falling back to internal dictionary");
+                return Arrays.asList(loadFallbackResource(context, fallbackResId));
+            }
+        }
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/CandidateView.java b/java/src/com/android/inputmethod/latin/CandidateView.java
index 5719b90..abdf30e 100644
--- a/java/src/com/android/inputmethod/latin/CandidateView.java
+++ b/java/src/com/android/inputmethod/latin/CandidateView.java
@@ -133,7 +133,6 @@
                 ViewGroup.LayoutParams.WRAP_CONTENT);
         mPreviewPopup.setContentView(mPreviewText);
         mPreviewPopup.setBackgroundDrawable(null);
-        mPreviewPopup.setAnimationStyle(R.style.KeyPreviewAnimation);
         mConfigCandidateHighlightFontColorEnabled =
                 res.getBoolean(R.bool.config_candidate_highlight_font_color_enabled);
         mColorNormal = res.getColor(R.color.candidate_normal);
@@ -151,7 +150,7 @@
                 tv.setOnLongClickListener(this);
             ImageView divider = (ImageView)v.findViewById(R.id.candidate_divider);
             // Do not display divider of first candidate.
-            divider.setVisibility(i == 0 ? GONE : VISIBLE);
+            divider.setVisibility(i == 0 ? INVISIBLE : VISIBLE);
             mWords.add(v);
         }
 
@@ -180,7 +179,7 @@
     private void updateSuggestions() {
         final SuggestedWords suggestions = mSuggestions;
         clear();
-        final int count = suggestions.size();
+        final int count = Math.min(mWords.size(), suggestions.size());
         for (int i = 0; i < count; i++) {
             CharSequence word = suggestions.getWord(i);
             if (word == null) continue;
diff --git a/java/src/com/android/inputmethod/latin/ContactsDictionary.java b/java/src/com/android/inputmethod/latin/ContactsDictionary.java
index 048f72d..b057cf4 100644
--- a/java/src/com/android/inputmethod/latin/ContactsDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ContactsDictionary.java
@@ -26,6 +26,8 @@
 import android.text.TextUtils;
 import android.util.Log;
 
+import com.android.inputmethod.keyboard.Keyboard;
+
 public class ContactsDictionary extends ExpandableDictionary {
 
     private static final String[] PROJECTION = {
@@ -95,6 +97,14 @@
         mLastLoadedContacts = SystemClock.uptimeMillis();
     }
 
+    @Override
+    public void getBigrams(final WordComposer codes, final CharSequence previousWord,
+            final WordCallback callback) {
+        // Do not return bigrams from Contacts when nothing was typed.
+        if (codes.size() <= 0) return;
+        super.getBigrams(codes, previousWord, callback);
+    }
+
     private void addWords(Cursor cursor) {
         clearDictionary();
 
@@ -115,8 +125,9 @@
                                 for (j = i + 1; j < len; j++) {
                                     char c = name.charAt(j);
 
-                                    if (!(c == '-' || c == '\'' ||
-                                          Character.isLetter(c))) {
+                                    if (!(c == Keyboard.CODE_DASH
+                                            || c == Keyboard.CODE_SINGLE_QUOTE
+                                            || Character.isLetter(c))) {
                                         break;
                                     }
                                 }
diff --git a/java/src/com/android/inputmethod/latin/DebugSettings.java b/java/src/com/android/inputmethod/latin/DebugSettings.java
index 2f1e7c2..fd62d61 100644
--- a/java/src/com/android/inputmethod/latin/DebugSettings.java
+++ b/java/src/com/android/inputmethod/latin/DebugSettings.java
@@ -33,6 +33,7 @@
 
     private boolean mServiceNeedsRestart = false;
     private CheckBoxPreference mDebugMode;
+    private CheckBoxPreference mUseSpacebarLanguageSwitch;
 
     @Override
     protected void onCreate(Bundle icicle) {
@@ -60,6 +61,13 @@
                 updateDebugMode();
                 mServiceNeedsRestart = true;
             }
+        } else if (key.equals(SubtypeSwitcher.USE_SPACEBAR_LANGUAGE_SWITCH_KEY)) {
+            if (mUseSpacebarLanguageSwitch != null) {
+                mUseSpacebarLanguageSwitch.setChecked(
+                        prefs.getBoolean(SubtypeSwitcher.USE_SPACEBAR_LANGUAGE_SWITCH_KEY,
+                                getResources().getBoolean(
+                                        R.bool.config_use_spacebar_language_switcher)));
+            }
         }
     }
 
diff --git a/java/src/com/android/inputmethod/latin/Dictionary.java b/java/src/com/android/inputmethod/latin/Dictionary.java
index 56f0cc5..c7737b9 100644
--- a/java/src/com/android/inputmethod/latin/Dictionary.java
+++ b/java/src/com/android/inputmethod/latin/Dictionary.java
@@ -29,7 +29,7 @@
     /**
      * The weight to give to a word if it's length is the same as the number of typed characters.
      */
-    protected static final int FULL_WORD_FREQ_MULTIPLIER = 2;
+    protected static final int FULL_WORD_SCORE_MULTIPLIER = 2;
 
     public static enum DataType {
         UNIGRAM, BIGRAM
@@ -42,17 +42,17 @@
     public interface WordCallback {
         /**
          * Adds a word to a list of suggestions. The word is expected to be ordered based on
-         * the provided frequency.
+         * the provided score.
          * @param word the character array containing the word
          * @param wordOffset starting offset of the word in the character array
          * @param wordLength length of valid characters in the character array
-         * @param frequency the frequency of occurrence. This is normalized between 1 and 255, but
+         * @param score the score of occurrence. This is normalized between 1 and 255, but
          * can exceed those limits
          * @param dicTypeId of the dictionary where word was from
          * @param dataType tells type of this data
          * @return true if the word was added, false if no more words are required
          */
-        boolean addWord(char[] word, int wordOffset, int wordLength, int frequency, int dicTypeId,
+        boolean addWord(char[] word, int wordOffset, int wordLength, int score, int dicTypeId,
                 DataType dataType);
     }
 
@@ -61,7 +61,7 @@
      * words are added through the callback object.
      * @param composer the key sequence to match
      * @param callback the callback object to send matched words to as possible candidates
-     * @see WordCallback#addWord(char[], int, int)
+     * @see WordCallback#addWord(char[], int, int, int, int, DataType)
      */
     abstract public void getWords(final WordComposer composer, final WordCallback callback);
 
diff --git a/java/src/com/android/inputmethod/latin/DictionaryCollection.java b/java/src/com/android/inputmethod/latin/DictionaryCollection.java
new file mode 100644
index 0000000..3fcb6ed
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/DictionaryCollection.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * Class for a collection of dictionaries that behave like one dictionary.
+ */
+public class DictionaryCollection extends Dictionary {
+
+    protected final List<Dictionary> mDictionaries;
+
+    public DictionaryCollection() {
+        mDictionaries = new CopyOnWriteArrayList<Dictionary>();
+    }
+
+    public DictionaryCollection(Dictionary... dictionaries) {
+        mDictionaries = new CopyOnWriteArrayList<Dictionary>(dictionaries);
+    }
+
+    public DictionaryCollection(Collection<Dictionary> dictionaries) {
+        mDictionaries = new CopyOnWriteArrayList<Dictionary>(dictionaries);
+    }
+
+    @Override
+    public void getWords(final WordComposer composer, final WordCallback callback) {
+        for (final Dictionary dict : mDictionaries)
+            dict.getWords(composer, callback);
+    }
+
+    @Override
+    public void getBigrams(final WordComposer composer, final CharSequence previousWord,
+            final WordCallback callback) {
+        for (final Dictionary dict : mDictionaries)
+            dict.getBigrams(composer, previousWord, callback);
+    }
+
+    @Override
+    public boolean isValidWord(CharSequence word) {
+        for (final Dictionary dict : mDictionaries)
+            if (dict.isValidWord(word)) return true;
+        return false;
+    }
+
+    @Override
+    public void close() {
+        for (final Dictionary dict : mDictionaries)
+            dict.close();
+    }
+
+    public void addDictionary(Dictionary newDict) {
+        mDictionaries.add(newDict);
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/DictionaryFactory.java b/java/src/com/android/inputmethod/latin/DictionaryFactory.java
new file mode 100644
index 0000000..605676d
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/DictionaryFactory.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import android.content.Context;
+import android.content.res.AssetFileDescriptor;
+import android.content.res.Resources;
+import android.util.Log;
+
+import java.io.File;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * Factory for dictionary instances.
+ */
+public class DictionaryFactory {
+
+    private static String TAG = DictionaryFactory.class.getSimpleName();
+
+    /**
+     * Initializes a dictionary from a dictionary pack.
+     *
+     * This searches for a content provider providing a dictionary pack for the specified
+     * locale. If none is found, it falls back to using the resource passed as fallBackResId
+     * as a dictionary.
+     * @param context application context for reading resources
+     * @param locale the locale for which to create the dictionary
+     * @param fallbackResId the id of the resource to use as a fallback if no pack is found
+     * @return an initialized instance of Dictionary
+     */
+    public static Dictionary createDictionaryFromManager(Context context, Locale locale,
+            int fallbackResId) {
+        if (null == locale) {
+            Log.e(TAG, "No locale defined for dictionary");
+            return new DictionaryCollection(createBinaryDictionary(context, fallbackResId));
+        }
+
+        final List<Dictionary> dictList = new LinkedList<Dictionary>();
+        for (final AssetFileAddress f : BinaryDictionaryGetter.getDictionaryFiles(locale,
+                context, fallbackResId)) {
+            dictList.add(new BinaryDictionary(context, f.mFilename, f.mOffset, f.mLength, null));
+        }
+
+        if (null == dictList) return null;
+        return new DictionaryCollection(dictList);
+    }
+
+    /**
+     * Initializes a dictionary from a raw resource file
+     * @param context application context for reading resources
+     * @param resId the resource containing the raw binary dictionary
+     * @return an initialized instance of BinaryDictionary
+     */
+    protected static BinaryDictionary createBinaryDictionary(Context context, int resId) {
+        AssetFileDescriptor afd = null;
+        try {
+            afd = context.getResources().openRawResourceFd(resId);
+            if (afd == null) {
+                Log.e(TAG, "Found the resource but it is compressed. resId=" + resId);
+                return null;
+            }
+            if (!isFullDictionary(afd)) return null;
+            final String sourceDir = context.getApplicationInfo().sourceDir;
+            final File packagePath = new File(sourceDir);
+            // TODO: Come up with a way to handle a directory.
+            if (!packagePath.isFile()) {
+                Log.e(TAG, "sourceDir is not a file: " + sourceDir);
+                return null;
+            }
+            return new BinaryDictionary(context,
+                    sourceDir, afd.getStartOffset(), afd.getLength(), null);
+        } catch (android.content.res.Resources.NotFoundException e) {
+            Log.e(TAG, "Could not find the resource. resId=" + resId);
+            return null;
+        } finally {
+            if (null != afd) {
+                try {
+                    afd.close();
+                } catch (java.io.IOException e) {
+                    /* IOException on close ? What am I supposed to do ? */
+                }
+            }
+        }
+    }
+
+    /**
+     * Create a dictionary from passed data. This is intended for unit tests only.
+     * @param context the test context to create this data from.
+     * @param dictionary the file to read
+     * @param startOffset the offset in the file where the data starts
+     * @param length the length of the data
+     * @param flagArray the flags to use with this data for testing
+     * @return the created dictionary, or null.
+     */
+    public static Dictionary createDictionaryForTest(Context context, File dictionary,
+            long startOffset, long length, Flag[] flagArray) {
+        if (dictionary.isFile()) {
+            return new BinaryDictionary(context, dictionary.getAbsolutePath(), startOffset, length,
+                    flagArray);
+        } else {
+            Log.e(TAG, "Could not find the file. path=" + dictionary.getAbsolutePath());
+            return null;
+        }
+    }
+
+    /**
+     * Find out whether a dictionary is available for this locale.
+     * @param context the context on which to check resources.
+     * @param locale the locale to check for.
+     * @return whether a (non-placeholder) dictionary is available or not.
+     */
+    public static boolean isDictionaryAvailable(Context context, Locale locale) {
+        final Resources res = context.getResources();
+        final Locale saveLocale = Utils.setSystemLocale(res, locale);
+
+        final int resourceId = Utils.getMainDictionaryResourceId(res);
+        final AssetFileDescriptor afd = res.openRawResourceFd(resourceId);
+        final boolean hasDictionary = isFullDictionary(afd);
+        try {
+            if (null != afd) afd.close();
+        } catch (java.io.IOException e) {
+            /* Um, what can we do here exactly? */
+        }
+
+        Utils.setSystemLocale(res, saveLocale);
+        return hasDictionary;
+    }
+
+    // TODO: Find the Right Way to find out whether the resource is a placeholder or not.
+    // Suggestion : strip the locale, open the placeholder file and store its offset.
+    // Upon opening the file, if it's the same offset, then it's the placeholder.
+    private static final long PLACEHOLDER_LENGTH = 34;
+    /**
+     * Finds out whether the data pointed out by an AssetFileDescriptor is a full
+     * dictionary (as opposed to null, or to a place holder).
+     * @param afd the file descriptor to test, or null
+     * @return true if the dictionary is a real full dictionary, false if it's null or a placeholder
+     */
+    protected static boolean isFullDictionary(final AssetFileDescriptor afd) {
+        return (afd != null && afd.getLength() > PLACEHOLDER_LENGTH);
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/DictionaryPackInstallBroadcastReceiver.java b/java/src/com/android/inputmethod/latin/DictionaryPackInstallBroadcastReceiver.java
new file mode 100644
index 0000000..9d30af8
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/DictionaryPackInstallBroadcastReceiver.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ProviderInfo;
+import android.net.Uri;
+
+/**
+ * Takes action to reload the necessary data when a dictionary pack was added/removed.
+ */
+public class DictionaryPackInstallBroadcastReceiver extends BroadcastReceiver {
+
+    final LatinIME mService;
+    /**
+     * The action of the intent for publishing that new dictionary data is available.
+     */
+    /* package */ static final String NEW_DICTIONARY_INTENT_ACTION =
+            "com.android.inputmethod.latin.dictionarypack.newdict";
+
+    public DictionaryPackInstallBroadcastReceiver(final LatinIME service) {
+        mService = service;
+    }
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        final String action = intent.getAction();
+        final PackageManager manager = context.getPackageManager();
+
+        // We need to reread the dictionary if a new dictionary package is installed.
+        if (action.equals(Intent.ACTION_PACKAGE_ADDED)) {
+            final Uri packageUri = intent.getData();
+            if (null == packageUri) return; // No package name : we can't do anything
+            final String packageName = packageUri.getSchemeSpecificPart();
+            if (null == packageName) return;
+            final PackageInfo packageInfo;
+            try {
+                packageInfo = manager.getPackageInfo(packageName, PackageManager.GET_PROVIDERS);
+            } catch (android.content.pm.PackageManager.NameNotFoundException e) {
+                return; // No package info : we can't do anything
+            }
+            final ProviderInfo[] providers = packageInfo.providers;
+            if (null == providers) return; // No providers : it is not a dictionary.
+
+            // Search for some dictionary pack in the just-installed package. If found, reread.
+            for (ProviderInfo info : providers) {
+                if (BinaryDictionary.DICTIONARY_PACK_AUTHORITY.equals(info.authority)) {
+                    mService.resetSuggestMainDict();
+                    return;
+                }
+            }
+            // If we come here none of the authorities matched the one we searched for.
+            // We can exit safely.
+            return;
+        } else if (action.equals(Intent.ACTION_PACKAGE_REMOVED)
+                && !intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
+            // When the dictionary package is removed, we need to reread dictionary (to use the
+            // next-priority one, or stop using a dictionary at all if this was the only one,
+            // since this is the user request).
+            // If we are replacing the package, we will receive ADDED right away so no need to
+            // remove the dictionary at the moment, since we will do it when we receive the
+            // ADDED broadcast.
+
+            // TODO: Only reload dictionary on REMOVED when the removed package is the one we
+            // read dictionary from?
+            mService.resetSuggestMainDict();
+        } else if (action.equals(NEW_DICTIONARY_INTENT_ACTION)) {
+            mService.resetSuggestMainDict();
+        }
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/EditingUtils.java b/java/src/com/android/inputmethod/latin/EditingUtils.java
index 0ca06dd..39e7e40 100644
--- a/java/src/com/android/inputmethod/latin/EditingUtils.java
+++ b/java/src/com/android/inputmethod/latin/EditingUtils.java
@@ -16,13 +16,13 @@
 
 package com.android.inputmethod.latin;
 
+import com.android.inputmethod.compat.InputConnectionCompatUtils;
+
 import android.text.TextUtils;
 import android.view.inputmethod.ExtractedText;
 import android.view.inputmethod.ExtractedTextRequest;
 import android.view.inputmethod.InputConnection;
 
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
 import java.util.regex.Pattern;
 
 /**
@@ -34,11 +34,6 @@
      */
     private static final int LOOKBACK_CHARACTER_NUM = 15;
 
-    // Cache Method pointers
-    private static boolean sMethodsInitialized;
-    private static Method sMethodGetSelectedText;
-    private static Method sMethodSetComposingRegion;
-
     private EditingUtils() {
         // Unintentional empty constructor for singleton.
     }
@@ -78,7 +73,7 @@
 
     /**
      * @param connection connection to the current text field.
-     * @param sep characters which may separate words
+     * @param separators characters which may separate words
      * @return the word that surrounds the cursor, including up to one trailing
      *   separator. For example, if the field contains "he|llo world", where |
      *   represents the cursor, then "hello " will be returned.
@@ -166,23 +161,62 @@
 
     private static final Pattern spaceRegex = Pattern.compile("\\s+");
 
+
     public static CharSequence getPreviousWord(InputConnection connection,
             String sentenceSeperators) {
         //TODO: Should fix this. This could be slow!
         CharSequence prev = connection.getTextBeforeCursor(LOOKBACK_CHARACTER_NUM, 0);
-        if (prev == null) {
-            return null;
-        }
+        return getPreviousWord(prev, sentenceSeperators);
+    }
+
+    // Get the word before the whitespace preceding the non-whitespace preceding the cursor.
+    // Also, it won't return words that end in a separator.
+    // Example :
+    // "abc def|" -> abc
+    // "abc def |" -> abc
+    // "abc def. |" -> abc
+    // "abc def . |" -> def
+    // "abc|" -> null
+    // "abc |" -> null
+    // "abc. def|" -> null
+    public static CharSequence getPreviousWord(CharSequence prev, String sentenceSeperators) {
+        if (prev == null) return null;
         String[] w = spaceRegex.split(prev);
-        if (w.length >= 2 && w[w.length-2].length() > 0) {
-            char lastChar = w[w.length-2].charAt(w[w.length-2].length() -1);
-            if (sentenceSeperators.contains(String.valueOf(lastChar))) {
-                return null;
-            }
-            return w[w.length-2];
-        } else {
-            return null;
-        }
+
+        // If we can't find two words, or we found an empty word, return null.
+        if (w.length < 2 || w[w.length - 2].length() <= 0) return null;
+
+        // If ends in a separator, return null
+        char lastChar = w[w.length - 2].charAt(w[w.length - 2].length() - 1);
+        if (sentenceSeperators.contains(String.valueOf(lastChar))) return null;
+
+        return w[w.length - 2];
+    }
+
+    public static CharSequence getThisWord(InputConnection connection, String sentenceSeperators) {
+        final CharSequence prev = connection.getTextBeforeCursor(LOOKBACK_CHARACTER_NUM, 0);
+        return getThisWord(prev, sentenceSeperators);
+    }
+
+    // Get the word immediately before the cursor, even if there is whitespace between it and
+    // the cursor - but not if there is punctuation.
+    // Example :
+    // "abc def|" -> def
+    // "abc def |" -> def
+    // "abc def. |" -> null
+    // "abc def . |" -> null
+    public static CharSequence getThisWord(CharSequence prev, String sentenceSeperators) {
+        if (prev == null) return null;
+        String[] w = spaceRegex.split(prev);
+
+        // No word : return null
+        if (w.length < 1 || w[w.length - 1].length() <= 0) return null;
+
+        // If ends in a separator, return null
+        char lastChar = w[w.length - 1].charAt(w[w.length - 1].length() - 1);
+        if (sentenceSeperators.contains(String.valueOf(lastChar))) return null;
+
+        return w[w.length - 1];
     }
 
     public static class SelectedWord {
@@ -241,7 +275,8 @@
             }
 
             // Extract the selection alone
-            CharSequence touching = getSelectedText(ic, selStart, selEnd);
+            CharSequence touching = InputConnectionCompatUtils.getSelectedText(
+                    ic, selStart, selEnd);
             if (TextUtils.isEmpty(touching)) return null;
             // Is any part of the selection a separator? If so, return null.
             final int length = touching.length();
@@ -255,74 +290,4 @@
         }
         return null;
     }
-
-    /**
-     * Cache method pointers for performance
-     */
-    private static void initializeMethodsForReflection() {
-        try {
-            // These will either both exist or not, so no need for separate try/catch blocks.
-            // If other methods are added later, use separate try/catch blocks.
-            sMethodGetSelectedText = InputConnection.class.getMethod("getSelectedText", int.class);
-            sMethodSetComposingRegion = InputConnection.class.getMethod("setComposingRegion",
-                    int.class, int.class);
-        } catch (NoSuchMethodException exc) {
-            // Ignore
-        }
-        sMethodsInitialized = true;
-    }
-
-    /**
-     * Returns the selected text between the selStart and selEnd positions.
-     */
-    private static CharSequence getSelectedText(InputConnection ic, int selStart, int selEnd) {
-        // Use reflection, for backward compatibility
-        CharSequence result = null;
-        if (!sMethodsInitialized) {
-            initializeMethodsForReflection();
-        }
-        if (sMethodGetSelectedText != null) {
-            try {
-                result = (CharSequence) sMethodGetSelectedText.invoke(ic, 0);
-                return result;
-            } catch (InvocationTargetException exc) {
-                // Ignore
-            } catch (IllegalArgumentException e) {
-                // Ignore
-            } catch (IllegalAccessException e) {
-                // Ignore
-            }
-        }
-        // Reflection didn't work, try it the poor way, by moving the cursor to the start,
-        // getting the text after the cursor and moving the text back to selected mode.
-        // TODO: Verify that this works properly in conjunction with 
-        // LatinIME#onUpdateSelection
-        ic.setSelection(selStart, selEnd);
-        result = ic.getTextAfterCursor(selEnd - selStart, 0);
-        ic.setSelection(selStart, selEnd);
-        return result;
-    }
-
-    /**
-     * Tries to set the text into composition mode if there is support for it in the framework.
-     */
-    public static void underlineWord(InputConnection ic, SelectedWord word) {
-        // Use reflection, for backward compatibility
-        // If method not found, there's nothing we can do. It still works but just wont underline
-        // the word.
-        if (!sMethodsInitialized) {
-            initializeMethodsForReflection();
-        }
-        if (sMethodSetComposingRegion != null) {
-            try {
-                sMethodSetComposingRegion.invoke(ic, word.mStart, word.mEnd);
-            } catch (InvocationTargetException exc) {
-                // Ignore
-            } catch (IllegalArgumentException e) {
-                // Ignore
-            } catch (IllegalAccessException e) {
-                // Ignore
-            }
-        }
-    }
 }
diff --git a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
index 0318175..26391fe 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
@@ -19,6 +19,8 @@
 import android.content.Context;
 import android.os.AsyncTask;
 
+import com.android.inputmethod.keyboard.Keyboard;
+
 import java.util.LinkedList;
 
 /**
@@ -32,14 +34,14 @@
      */
     protected static final int MAX_WORD_LENGTH = 32;
 
+    // Bigram frequency is a fixed point number with 1 meaning 1.2 and 255 meaning 1.8.
+    protected static final int BIGRAM_MAX_FREQUENCY = 255;
+
     private Context mContext;
     private char[] mWordBuilder = new char[MAX_WORD_LENGTH];
     private int mDicTypeId;
     private int mMaxDepth;
     private int mInputLength;
-    private StringBuilder sb = new StringBuilder(MAX_WORD_LENGTH);
-
-    private static final char QUOTE = '\'';
 
     private boolean mRequiresReload;
 
@@ -98,6 +100,7 @@
 
         public int addFrequency(int add) {
             mFrequency += add;
+            if (mFrequency > BIGRAM_MAX_FREQUENCY) mFrequency = BIGRAM_MAX_FREQUENCY;
             return mFrequency;
         }
     }
@@ -301,7 +304,8 @@
                     getWordsRec(children, codes, word, depth + 1, completion, snr, inputIndex,
                             skipPos, callback);
                 }
-            } else if ((c == QUOTE && currentChars[0] != QUOTE) || depth == skipPos) {
+            } else if ((c == Keyboard.CODE_SINGLE_QUOTE
+                    && currentChars[0] != Keyboard.CODE_SINGLE_QUOTE) || depth == skipPos) {
                 // Skip the ' and continue deeper
                 word[depth] = c;
                 if (children != null) {
@@ -327,7 +331,7 @@
                                     final int finalFreq;
                                     if (skipPos < 0) {
                                         finalFreq = freq * snr * addedAttenuation
-                                                * FULL_WORD_FREQ_MULTIPLIER;
+                                                * FULL_WORD_SCORE_MULTIPLIER;
                                     } else {
                                         finalFreq = computeSkippedWordFinalFreq(freq,
                                                 snr * addedAttenuation, mInputLength);
@@ -462,6 +466,9 @@
         }
     }
 
+    // Local to reverseLookUp, but do not allocate each time.
+    private final char[] mLookedUpString = new char[MAX_WORD_LENGTH];
+
     /**
      * reverseLookUp retrieves the full word given a list of terminal nodes and adds those words
      * through callback.
@@ -474,18 +481,15 @@
         for (NextWord nextWord : terminalNodes) {
             node = nextWord.mWord;
             freq = nextWord.getFrequency();
-            // TODO Not the best way to limit suggestion threshold
-            if (freq >= UserBigramDictionary.SUGGEST_THRESHOLD) {
-                sb.setLength(0);
-                do {
-                    sb.insert(0, node.mCode);
-                    node = node.mParent;
-                } while(node != null);
+            int index = MAX_WORD_LENGTH;
+            do {
+                --index;
+                mLookedUpString[index] = node.mCode;
+                node = node.mParent;
+            } while (node != null);
 
-                // TODO better way to feed char array?
-                callback.addWord(sb.toString().toCharArray(), 0, sb.length(), freq, mDicTypeId,
-                        DataType.BIGRAM);
-            }
+            callback.addWord(mLookedUpString, index, MAX_WORD_LENGTH - index, freq, mDicTypeId,
+                    DataType.BIGRAM);
         }
     }
 
diff --git a/java/src/com/android/inputmethod/latin/Flag.java b/java/src/com/android/inputmethod/latin/Flag.java
new file mode 100644
index 0000000..3cb8f7e
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/Flag.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import android.content.Context;
+import android.content.res.Resources;
+
+public class Flag {
+    public final String mName;
+    public final int mResource;
+    public final int mMask;
+    public final int mSource;
+
+    static private final int SOURCE_CONFIG = 1;
+    static private final int SOURCE_EXTRAVALUE = 2;
+
+    public Flag(int resourceId, int mask) {
+        mName = null;
+        mResource = resourceId;
+        mSource = SOURCE_CONFIG;
+        mMask = mask;
+    }
+
+    public Flag(String name, int mask) {
+        mName = name;
+        mResource = 0;
+        mSource = SOURCE_EXTRAVALUE;
+        mMask = mask;
+    }
+
+    // If context/switcher are null, set all related flags in flagArray to on.
+    public static int initFlags(Flag[] flagArray, Context context, SubtypeSwitcher switcher) {
+        int flags = 0;
+        final Resources res = null == context ? null : context.getResources();
+        for (Flag entry : flagArray) {
+            switch (entry.mSource) {
+                case Flag.SOURCE_CONFIG:
+                    if (res == null || res.getBoolean(entry.mResource))
+                        flags |= entry.mMask;
+                    break;
+                case Flag.SOURCE_EXTRAVALUE:
+                    if (switcher == null ||
+                            switcher.currentSubtypeContainsExtraValueKey(entry.mName))
+                        flags |= entry.mMask;
+                    break;
+            }
+        }
+        return flags;
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/InputLanguageSelection.java b/java/src/com/android/inputmethod/latin/InputLanguageSelection.java
deleted file mode 100644
index 5587c68..0000000
--- a/java/src/com/android/inputmethod/latin/InputLanguageSelection.java
+++ /dev/null
@@ -1,211 +0,0 @@
-/*
- * Copyright (C) 2008-2009 Google Inc.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.android.inputmethod.latin;
-
-import android.content.SharedPreferences;
-import android.content.SharedPreferences.Editor;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.os.Bundle;
-import android.preference.CheckBoxPreference;
-import android.preference.PreferenceActivity;
-import android.preference.PreferenceGroup;
-import android.preference.PreferenceManager;
-import android.text.TextUtils;
-
-import java.text.Collator;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Locale;
-
-public class InputLanguageSelection extends PreferenceActivity {
-
-    private SharedPreferences mPrefs;
-    private String mSelectedLanguages;
-    private ArrayList<Loc> mAvailableLanguages = new ArrayList<Loc>();
-    private static final String[] BLACKLIST_LANGUAGES = {
-        "ko", "ja", "zh", "el", "zz"
-    };
-
-    private static class Loc implements Comparable<Object> {
-        private static Collator sCollator = Collator.getInstance();
-
-        private String mLabel;
-        public final Locale mLocale;
-
-        public Loc(String label, Locale locale) {
-            this.mLabel = label;
-            this.mLocale = locale;
-        }
-
-        public void setLabel(String label) {
-            this.mLabel = label;
-        }
-
-        @Override
-        public String toString() {
-            return this.mLabel;
-        }
-
-        @Override
-        public int compareTo(Object o) {
-            return sCollator.compare(this.mLabel, ((Loc) o).mLabel);
-        }
-    }
-
-    @Override
-    protected void onCreate(Bundle icicle) {
-        super.onCreate(icicle);
-        addPreferencesFromResource(R.xml.language_prefs);
-        // Get the settings preferences
-        mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
-        mSelectedLanguages = mPrefs.getString(Settings.PREF_SELECTED_LANGUAGES, "");
-        String[] languageList = mSelectedLanguages.split(",");
-        mAvailableLanguages = getUniqueLocales();
-        PreferenceGroup parent = getPreferenceScreen();
-        for (int i = 0; i < mAvailableLanguages.size(); i++) {
-            CheckBoxPreference pref = new CheckBoxPreference(this);
-            Locale locale = mAvailableLanguages.get(i).mLocale;
-            pref.setTitle(SubtypeSwitcher.getFullDisplayName(locale, true));
-            boolean checked = isLocaleIn(locale, languageList);
-            pref.setChecked(checked);
-            if (hasDictionary(locale)) {
-                pref.setSummary(R.string.has_dictionary);
-            }
-            parent.addPreference(pref);
-        }
-    }
-
-    private boolean isLocaleIn(Locale locale, String[] list) {
-        String lang = get5Code(locale);
-        for (int i = 0; i < list.length; i++) {
-            if (lang.equalsIgnoreCase(list[i])) return true;
-        }
-        return false;
-    }
-
-    private boolean hasDictionary(Locale locale) {
-        final Resources res = getResources();
-        final Configuration conf = res.getConfiguration();
-        final Locale saveLocale = conf.locale;
-        boolean haveDictionary = false;
-        conf.locale = locale;
-        res.updateConfiguration(conf, res.getDisplayMetrics());
-
-        int mainDicResId = Utils.getMainDictionaryResourceId(res);
-        BinaryDictionary bd = BinaryDictionary.initDictionary(this, mainDicResId, Suggest.DIC_MAIN);
-
-        // Is the dictionary larger than a placeholder? Arbitrarily chose a lower limit of
-        // 4000-5000 words, whereas the LARGE_DICTIONARY is about 20000+ words.
-        if (bd.getSize() > Suggest.LARGE_DICTIONARY_THRESHOLD / 4) {
-            haveDictionary = true;
-        }
-        bd.close();
-        conf.locale = saveLocale;
-        res.updateConfiguration(conf, res.getDisplayMetrics());
-        return haveDictionary;
-    }
-
-    private String get5Code(Locale locale) {
-        String country = locale.getCountry();
-        return locale.getLanguage()
-                + (TextUtils.isEmpty(country) ? "" : "_" + country);
-    }
-
-    @Override
-    protected void onResume() {
-        super.onResume();
-    }
-
-    @Override
-    protected void onPause() {
-        super.onPause();
-        // Save the selected languages
-        String checkedLanguages = "";
-        PreferenceGroup parent = getPreferenceScreen();
-        int count = parent.getPreferenceCount();
-        for (int i = 0; i < count; i++) {
-            CheckBoxPreference pref = (CheckBoxPreference) parent.getPreference(i);
-            if (pref.isChecked()) {
-                Locale locale = mAvailableLanguages.get(i).mLocale;
-                checkedLanguages += get5Code(locale) + ",";
-            }
-        }
-        if (checkedLanguages.length() < 1) checkedLanguages = null; // Save null
-        Editor editor = mPrefs.edit();
-        editor.putString(Settings.PREF_SELECTED_LANGUAGES, checkedLanguages);
-        SharedPreferencesCompat.apply(editor);
-    }
-
-    public ArrayList<Loc> getUniqueLocales() {
-        String[] locales = getAssets().getLocales();
-        Arrays.sort(locales);
-        ArrayList<Loc> uniqueLocales = new ArrayList<Loc>();
-
-        final int origSize = locales.length;
-        Loc[] preprocess = new Loc[origSize];
-        int finalSize = 0;
-        for (int i = 0 ; i < origSize; i++ ) {
-            String s = locales[i];
-            int len = s.length();
-            if (len == 5) {
-                String language = s.substring(0, 2);
-                String country = s.substring(3, 5);
-                Locale l = new Locale(language, country);
-
-                // Exclude languages that are not relevant to LatinIME
-                if (arrayContains(BLACKLIST_LANGUAGES, language)) continue;
-
-                if (finalSize == 0) {
-                    preprocess[finalSize++] =
-                            new Loc(SubtypeSwitcher.getFullDisplayName(l, true), l);
-                } else {
-                    // check previous entry:
-                    //  same lang and a country -> upgrade to full name and
-                    //    insert ours with full name
-                    //  diff lang -> insert ours with lang-only name
-                    if (preprocess[finalSize-1].mLocale.getLanguage().equals(
-                            language)) {
-                        preprocess[finalSize-1].setLabel(SubtypeSwitcher.getFullDisplayName(
-                                preprocess[finalSize-1].mLocale, false));
-                        preprocess[finalSize++] =
-                                new Loc(SubtypeSwitcher.getFullDisplayName(l, false), l);
-                    } else {
-                        String displayName;
-                        if (s.equals("zz_ZZ")) {
-                            // ignore this locale
-                        } else {
-                            displayName = SubtypeSwitcher.getFullDisplayName(l, true);
-                            preprocess[finalSize++] = new Loc(displayName, l);
-                        }
-                    }
-                }
-            }
-        }
-        for (int i = 0; i < finalSize ; i++) {
-            uniqueLocales.add(preprocess[i]);
-        }
-        return uniqueLocales;
-    }
-
-    private boolean arrayContains(String[] array, String value) {
-        for (int i = 0; i < array.length; i++) {
-            if (array[i].equalsIgnoreCase(value)) return true;
-        }
-        return false;
-    }
-}
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 6e76cad..8b130ae 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -16,14 +16,20 @@
 
 package com.android.inputmethod.latin;
 
+import com.android.inputmethod.compat.CompatUtils;
+import com.android.inputmethod.compat.EditorInfoCompatUtils;
+import com.android.inputmethod.compat.InputConnectionCompatUtils;
+import com.android.inputmethod.compat.InputMethodManagerCompatWrapper;
+import com.android.inputmethod.compat.InputMethodServiceCompatWrapper;
+import com.android.inputmethod.compat.InputTypeCompatUtils;
+import com.android.inputmethod.deprecated.LanguageSwitcherProxy;
+import com.android.inputmethod.deprecated.VoiceProxy;
 import com.android.inputmethod.keyboard.Keyboard;
 import com.android.inputmethod.keyboard.KeyboardActionListener;
 import com.android.inputmethod.keyboard.KeyboardSwitcher;
 import com.android.inputmethod.keyboard.KeyboardView;
 import com.android.inputmethod.keyboard.LatinKeyboard;
 import com.android.inputmethod.keyboard.LatinKeyboardView;
-import com.android.inputmethod.latin.Utils.RingCharBuffer;
-import com.android.inputmethod.voice.VoiceIMEConnector;
 
 import android.app.AlertDialog;
 import android.content.BroadcastReceiver;
@@ -42,7 +48,6 @@
 import android.os.IBinder;
 import android.os.Message;
 import android.os.SystemClock;
-import android.os.Vibrator;
 import android.preference.PreferenceActivity;
 import android.preference.PreferenceManager;
 import android.text.InputType;
@@ -51,7 +56,6 @@
 import android.util.Log;
 import android.util.PrintWriterPrinter;
 import android.util.Printer;
-import android.view.Gravity;
 import android.view.HapticFeedbackConstants;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
@@ -61,27 +65,19 @@
 import android.view.Window;
 import android.view.WindowManager;
 import android.view.inputmethod.CompletionInfo;
-import android.view.inputmethod.CorrectionInfo;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.ExtractedText;
-import android.view.inputmethod.ExtractedTextRequest;
 import android.view.inputmethod.InputConnection;
-import android.view.inputmethod.InputMethodManager;
-import android.view.inputmethod.InputMethodSubtype;
-import android.widget.FrameLayout;
-import android.widget.HorizontalScrollView;
 import android.widget.LinearLayout;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Locale;
 
 /**
  * Input method implementation for Qwerty'ish keyboard.
  */
-public class LatinIME extends InputMethodService implements KeyboardActionListener {
+public class LatinIME extends InputMethodServiceCompatWrapper implements KeyboardActionListener {
     private static final String TAG = LatinIME.class.getSimpleName();
     private static final boolean PERF_DEBUG = false;
     private static final boolean TRACE = false;
@@ -94,6 +90,7 @@
      *
      * @deprecated Use {@link LatinIME#IME_OPTION_NO_MICROPHONE} with package name prefixed.
      */
+    @SuppressWarnings("dep-ann")
     public static final String IME_OPTION_NO_MICROPHONE_COMPAT = "nm";
 
     /**
@@ -109,9 +106,6 @@
      */
     public static final String IME_OPTION_NO_SETTINGS_KEY = "noSettingsKey";
 
-    private static final int DELAY_UPDATE_SUGGESTIONS = 180;
-    private static final int DELAY_UPDATE_OLD_SUGGESTIONS = 300;
-    private static final int DELAY_UPDATE_SHIFT_STATE = 300;
     private static final int EXTENDED_TOUCHABLE_REGION_HEIGHT = 100;
 
     // How many continuous deletes at which to start deleting at a higher speed.
@@ -119,6 +113,12 @@
     // Key events coming any faster than this are long-presses.
     private static final int QUICK_PRESS = 200;
 
+    /**
+     * The name of the scheme used by the Package Manager to warn of a new package installation,
+     * replacement or removal.
+     */
+    private static final String SCHEME_PACKAGE = "package";
+
     private int mSuggestionVisibility;
     private static final int SUGGESTION_VISIBILILTY_SHOW_VALUE
             = R.string.prefs_suggestion_visibility_show_value;
@@ -133,55 +133,45 @@
         SUGGESTION_VISIBILILTY_HIDE_VALUE
     };
 
+    private Settings.Values mSettingsValues;
+
     private View mCandidateViewContainer;
+    private int mCandidateStripHeight;
     private CandidateView mCandidateView;
     private Suggest mSuggest;
     private CompletionInfo[] mApplicationSpecifiedCompletions;
 
     private AlertDialog mOptionsDialog;
 
-    private InputMethodManager mImm;
+    private InputMethodManagerCompatWrapper mImm;
     private Resources mResources;
     private SharedPreferences mPrefs;
     private String mInputMethodId;
     private KeyboardSwitcher mKeyboardSwitcher;
     private SubtypeSwitcher mSubtypeSwitcher;
-    private VoiceIMEConnector mVoiceConnector;
+    private VoiceProxy mVoiceProxy;
+    private Recorrection mRecorrection;
 
     private UserDictionary mUserDictionary;
     private UserBigramDictionary mUserBigramDictionary;
     private ContactsDictionary mContactsDictionary;
     private AutoDictionary mAutoDictionary;
 
+    // TODO: Create an inner class to group options and pseudo-options to improve readability.
     // These variables are initialized according to the {@link EditorInfo#inputType}.
-    private boolean mAutoSpace;
+    private boolean mShouldInsertMagicSpace;
     private boolean mInputTypeNoAutoCorrect;
     private boolean mIsSettingsSuggestionStripOn;
     private boolean mApplicationSpecifiedCompletionOn;
 
-    private AccessibilityUtils mAccessibilityUtils;
-
     private final StringBuilder mComposing = new StringBuilder();
     private WordComposer mWord = new WordComposer();
     private CharSequence mBestWord;
-    private boolean mHasValidSuggestions;
+    private boolean mHasUncommittedTypedChars;
     private boolean mHasDictionary;
-    private boolean mJustAddedAutoSpace;
-    private boolean mAutoCorrectEnabled;
-    private boolean mRecorrectionEnabled;
-    private boolean mBigramSuggestionEnabled;
-    private boolean mAutoCorrectOn;
-    private boolean mVibrateOn;
-    private boolean mSoundOn;
-    private boolean mPopupOn;
-    private boolean mAutoCap;
-    private boolean mQuickFixes;
-    private boolean mConfigEnableShowSubtypeSettings;
-    private boolean mConfigSwipeDownDismissKeyboardEnabled;
-    private int mConfigDelayBeforeFadeoutLanguageOnSpacebar;
-    private int mConfigDurationOfFadeoutLanguageOnSpacebar;
-    private float mConfigFinalFadeoutFactorOfLanguageOnSpacebar;
-    private long mConfigDoubleSpacesTurnIntoPeriodTimeout;
+    // Magic space: a space that should disappear on space/apostrophe insertion, move after the
+    // punctuation on punctuation insertion, and become a real space on alpha char insertion.
+    private boolean mJustAddedMagicSpace; // This indicates whether the last char is a magic space.
 
     private int mCorrectionMode;
     private int mCommittedLength;
@@ -189,7 +179,6 @@
     // Keep track of the last selection range to decide if we need to show word alternatives
     private int mLastSelectionStart;
     private int mLastSelectionEnd;
-    private SuggestedWords mSuggestPuncList;
 
     // Indicates whether the suggestion strip is to be on in landscape
     private boolean mJustAccepted;
@@ -199,66 +188,18 @@
     private AudioManager mAudioManager;
     // Align sound effect volume on music volume
     private static final float FX_VOLUME = -1.0f;
-    private boolean mSilentMode;
+    private boolean mSilentModeOn; // System-wide current configuration
 
-    /* package */ String mWordSeparators;
-    private String mSentenceSeparators;
-    private String mSuggestPuncs;
-    // TODO: Move this flag to VoiceIMEConnector
+    // TODO: Move this flag to VoiceProxy
     private boolean mConfigurationChanging;
 
+    // Object for reacting to adding/removing a dictionary pack.
+    private BroadcastReceiver mDictionaryPackInstallReceiver =
+            new DictionaryPackInstallBroadcastReceiver(this);
+
     // Keeps track of most recently inserted text (multi-character key) for reverting
     private CharSequence mEnteredText;
 
-    private final ArrayList<WordAlternatives> mWordHistory = new ArrayList<WordAlternatives>();
-
-    public abstract static class WordAlternatives {
-        protected CharSequence mChosenWord;
-
-        public WordAlternatives() {
-            // Nothing
-        }
-
-        public WordAlternatives(CharSequence chosenWord) {
-            mChosenWord = chosenWord;
-        }
-
-        @Override
-        public int hashCode() {
-            return mChosenWord.hashCode();
-        }
-
-        public abstract CharSequence getOriginalWord();
-
-        public CharSequence getChosenWord() {
-            return mChosenWord;
-        }
-
-        public abstract SuggestedWords.Builder getAlternatives();
-    }
-
-    public class TypedWordAlternatives extends WordAlternatives {
-        private WordComposer word;
-
-        public TypedWordAlternatives() {
-            // Nothing
-        }
-
-        public TypedWordAlternatives(CharSequence chosenWord, WordComposer wordComposer) {
-            super(chosenWord);
-            word = wordComposer;
-        }
-
-        @Override
-        public CharSequence getOriginalWord() {
-            return word.getTypedWord();
-        }
-
-        @Override
-        public SuggestedWords.Builder getAlternatives() {
-            return getTypedSuggestions(word);
-        }
-    }
 
     public final UIHandler mHandler = new UIHandler();
 
@@ -270,6 +211,7 @@
         private static final int MSG_FADEOUT_LANGUAGE_ON_SPACEBAR = 4;
         private static final int MSG_DISMISS_LANGUAGE_ON_SPACEBAR = 5;
         private static final int MSG_SPACE_TYPED = 6;
+        private static final int MSG_SET_BIGRAM_PREDICTIONS = 7;
 
         @Override
         public void handleMessage(Message msg) {
@@ -280,34 +222,43 @@
                 updateSuggestions();
                 break;
             case MSG_UPDATE_OLD_SUGGESTIONS:
-                setOldSuggestions();
+                mRecorrection.setRecorrectionSuggestions(mVoiceProxy, mCandidateView, mSuggest,
+                        mKeyboardSwitcher, mWord, mHasUncommittedTypedChars, mLastSelectionStart,
+                        mLastSelectionEnd, mSettingsValues.mWordSeparators);
                 break;
             case MSG_UPDATE_SHIFT_STATE:
                 switcher.updateShiftState();
                 break;
+            case MSG_SET_BIGRAM_PREDICTIONS:
+                updateBigramPredictions();
+                break;
             case MSG_VOICE_RESULTS:
-                mVoiceConnector.handleVoiceResults(preferCapitalization()
+                mVoiceProxy.handleVoiceResults(preferCapitalization()
                         || (switcher.isAlphabetMode() && switcher.isShiftedOrShiftLocked()));
                 break;
             case MSG_FADEOUT_LANGUAGE_ON_SPACEBAR:
-                if (inputView != null)
+                if (inputView != null) {
                     inputView.setSpacebarTextFadeFactor(
-                            (1.0f + mConfigFinalFadeoutFactorOfLanguageOnSpacebar) / 2,
+                            (1.0f + mSettingsValues.mFinalFadeoutFactorOfLanguageOnSpacebar) / 2,
                             (LatinKeyboard)msg.obj);
+                }
                 sendMessageDelayed(obtainMessage(MSG_DISMISS_LANGUAGE_ON_SPACEBAR, msg.obj),
-                        mConfigDurationOfFadeoutLanguageOnSpacebar);
+                        mSettingsValues.mDurationOfFadeoutLanguageOnSpacebar);
                 break;
             case MSG_DISMISS_LANGUAGE_ON_SPACEBAR:
-                if (inputView != null)
+                if (inputView != null) {
                     inputView.setSpacebarTextFadeFactor(
-                            mConfigFinalFadeoutFactorOfLanguageOnSpacebar, (LatinKeyboard)msg.obj);
+                            mSettingsValues.mFinalFadeoutFactorOfLanguageOnSpacebar,
+                            (LatinKeyboard)msg.obj);
+                }
                 break;
             }
         }
 
         public void postUpdateSuggestions() {
             removeMessages(MSG_UPDATE_SUGGESTIONS);
-            sendMessageDelayed(obtainMessage(MSG_UPDATE_SUGGESTIONS), DELAY_UPDATE_SUGGESTIONS);
+            sendMessageDelayed(obtainMessage(MSG_UPDATE_SUGGESTIONS),
+                    mSettingsValues.mDelayUpdateSuggestions);
         }
 
         public void cancelUpdateSuggestions() {
@@ -321,7 +272,7 @@
         public void postUpdateOldSuggestions() {
             removeMessages(MSG_UPDATE_OLD_SUGGESTIONS);
             sendMessageDelayed(obtainMessage(MSG_UPDATE_OLD_SUGGESTIONS),
-                    DELAY_UPDATE_OLD_SUGGESTIONS);
+                    mSettingsValues.mDelayUpdateOldSuggestions);
         }
 
         public void cancelUpdateOldSuggestions() {
@@ -330,13 +281,24 @@
 
         public void postUpdateShiftKeyState() {
             removeMessages(MSG_UPDATE_SHIFT_STATE);
-            sendMessageDelayed(obtainMessage(MSG_UPDATE_SHIFT_STATE), DELAY_UPDATE_SHIFT_STATE);
+            sendMessageDelayed(obtainMessage(MSG_UPDATE_SHIFT_STATE),
+                    mSettingsValues.mDelayUpdateShiftState);
         }
 
         public void cancelUpdateShiftState() {
             removeMessages(MSG_UPDATE_SHIFT_STATE);
         }
 
+        public void postUpdateBigramPredictions() {
+            removeMessages(MSG_SET_BIGRAM_PREDICTIONS);
+            sendMessageDelayed(obtainMessage(MSG_SET_BIGRAM_PREDICTIONS),
+                    mSettingsValues.mDelayUpdateSuggestions);
+        }
+
+        public void cancelUpdateBigramPredictions() {
+            removeMessages(MSG_SET_BIGRAM_PREDICTIONS);
+        }
+
         public void updateVoiceResults() {
             sendMessage(obtainMessage(MSG_VOICE_RESULTS));
         }
@@ -347,14 +309,18 @@
             final LatinKeyboardView inputView = mKeyboardSwitcher.getInputView();
             if (inputView != null) {
                 final LatinKeyboard keyboard = mKeyboardSwitcher.getLatinKeyboard();
-                // The language is never displayed when the delay is zero.
-                if (mConfigDelayBeforeFadeoutLanguageOnSpacebar != 0)
-                    inputView.setSpacebarTextFadeFactor(localeChanged ? 1.0f
-                            : mConfigFinalFadeoutFactorOfLanguageOnSpacebar, keyboard);
                 // The language is always displayed when the delay is negative.
-                if (localeChanged && mConfigDelayBeforeFadeoutLanguageOnSpacebar > 0) {
+                final boolean needsToDisplayLanguage = localeChanged
+                        || mSettingsValues.mDelayBeforeFadeoutLanguageOnSpacebar < 0;
+                // The language is never displayed when the delay is zero.
+                if (mSettingsValues.mDelayBeforeFadeoutLanguageOnSpacebar != 0) {
+                    inputView.setSpacebarTextFadeFactor(needsToDisplayLanguage ? 1.0f
+                            : mSettingsValues.mFinalFadeoutFactorOfLanguageOnSpacebar, keyboard);
+                }
+                // The fadeout animation will start when the delay is positive.
+                if (localeChanged && mSettingsValues.mDelayBeforeFadeoutLanguageOnSpacebar > 0) {
                     sendMessageDelayed(obtainMessage(MSG_FADEOUT_LANGUAGE_ON_SPACEBAR, keyboard),
-                            mConfigDelayBeforeFadeoutLanguageOnSpacebar);
+                            mSettingsValues.mDelayBeforeFadeoutLanguageOnSpacebar);
                 }
             }
         }
@@ -362,7 +328,7 @@
         public void startDoubleSpacesTimer() {
             removeMessages(MSG_SPACE_TYPED);
             sendMessageDelayed(obtainMessage(MSG_SPACE_TYPED),
-                    mConfigDoubleSpacesTurnIntoPeriodTimeout);
+                    mSettingsValues.mDoubleSpacesTurnIntoPeriodTimeout);
         }
 
         public void cancelDoubleSpacesTimer() {
@@ -379,43 +345,24 @@
         final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
         mPrefs = prefs;
         LatinImeLogger.init(this, prefs);
+        LanguageSwitcherProxy.init(this, prefs);
         SubtypeSwitcher.init(this, prefs);
         KeyboardSwitcher.init(this, prefs);
-        AccessibilityUtils.init(this, prefs);
+        Recorrection.init(this, prefs);
 
         super.onCreate();
 
-        mImm = ((InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE));
+        mImm = InputMethodManagerCompatWrapper.getInstance(this);
         mInputMethodId = Utils.getInputMethodId(mImm, getPackageName());
         mSubtypeSwitcher = SubtypeSwitcher.getInstance();
         mKeyboardSwitcher = KeyboardSwitcher.getInstance();
-        mAccessibilityUtils = AccessibilityUtils.getInstance();
+        mRecorrection = Recorrection.getInstance();
+
+        loadSettings();
 
         final Resources res = getResources();
         mResources = res;
 
-        // If the option should not be shown, do not read the recorrection preference
-        // but always use the default setting defined in the resources.
-        if (res.getBoolean(R.bool.config_enable_show_recorrection_option)) {
-            mRecorrectionEnabled = prefs.getBoolean(Settings.PREF_RECORRECTION_ENABLED,
-                    res.getBoolean(R.bool.config_default_recorrection_enabled));
-        } else {
-            mRecorrectionEnabled = res.getBoolean(R.bool.config_default_recorrection_enabled);
-        }
-
-        mConfigEnableShowSubtypeSettings = res.getBoolean(
-                R.bool.config_enable_show_subtype_settings);
-        mConfigSwipeDownDismissKeyboardEnabled = res.getBoolean(
-                R.bool.config_swipe_down_dismiss_keyboard_enabled);
-        mConfigDelayBeforeFadeoutLanguageOnSpacebar = res.getInteger(
-                R.integer.config_delay_before_fadeout_language_on_spacebar);
-        mConfigDurationOfFadeoutLanguageOnSpacebar = res.getInteger(
-                R.integer.config_duration_of_fadeout_language_on_spacebar);
-        mConfigFinalFadeoutFactorOfLanguageOnSpacebar = res.getInteger(
-                R.integer.config_final_fadeout_percentage_of_language_on_spacebar) / 100.0f;
-        mConfigDoubleSpacesTurnIntoPeriodTimeout = res.getInteger(
-                R.integer.config_double_spaces_turn_into_period_timeout);
-
         Utils.GCUtils.getInstance().reset();
         boolean tryGC = true;
         for (int i = 0; i < Utils.GCUtils.GC_TRY_LOOP_MAX && tryGC; ++i) {
@@ -428,49 +375,73 @@
         }
 
         mOrientation = res.getConfiguration().orientation;
-        initSuggestPuncList();
 
-        // register to receive ringer mode change and network state change.
+        // Register to receive ringer mode change and network state change.
+        // Also receive installation and removal of a dictionary pack.
         final IntentFilter filter = new IntentFilter();
         filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION);
         filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
         registerReceiver(mReceiver, filter);
-        mVoiceConnector = VoiceIMEConnector.init(this, prefs, mHandler);
+        mVoiceProxy = VoiceProxy.init(this, prefs, mHandler);
+
+        final IntentFilter packageFilter = new IntentFilter();
+        packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
+        packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+        packageFilter.addDataScheme(SCHEME_PACKAGE);
+        registerReceiver(mDictionaryPackInstallReceiver, packageFilter);
+
+        final IntentFilter newDictFilter = new IntentFilter();
+        newDictFilter.addAction(
+                DictionaryPackInstallBroadcastReceiver.NEW_DICTIONARY_INTENT_ACTION);
+        registerReceiver(mDictionaryPackInstallReceiver, newDictFilter);
+    }
+
+    // Has to be package-visible for unit tests
+    /* package */ void loadSettings() {
+        if (null == mPrefs) mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
+        if (null == mSubtypeSwitcher) mSubtypeSwitcher = SubtypeSwitcher.getInstance();
+        mSettingsValues = new Settings.Values(mPrefs, this, mSubtypeSwitcher.getInputLocaleStr());
     }
 
     private void initSuggest() {
-        String locale = mSubtypeSwitcher.getInputLocaleStr();
+        final String localeStr = mSubtypeSwitcher.getInputLocaleStr();
+        final Locale keyboardLocale = new Locale(localeStr);
 
-        Locale savedLocale = mSubtypeSwitcher.changeSystemLocale(new Locale(locale));
+        final Resources res = mResources;
+        final Locale savedLocale = Utils.setSystemLocale(res, keyboardLocale);
         if (mSuggest != null) {
             mSuggest.close();
         }
-        final SharedPreferences prefs = mPrefs;
-        mQuickFixes = isQuickFixesEnabled(prefs);
 
-        final Resources res = mResources;
         int mainDicResId = Utils.getMainDictionaryResourceId(res);
-        mSuggest = new Suggest(this, mainDicResId);
-        loadAndSetAutoCorrectionThreshold(prefs);
+        mSuggest = new Suggest(this, mainDicResId, keyboardLocale);
+        if (mSettingsValues.mAutoCorrectEnabled) {
+            mSuggest.setAutoCorrectionThreshold(mSettingsValues.mAutoCorrectionThreshold);
+        }
         updateAutoTextEnabled();
 
-        mUserDictionary = new UserDictionary(this, locale);
+        mUserDictionary = new UserDictionary(this, localeStr);
         mSuggest.setUserDictionary(mUserDictionary);
 
         mContactsDictionary = new ContactsDictionary(this, Suggest.DIC_CONTACTS);
         mSuggest.setContactsDictionary(mContactsDictionary);
 
-        mAutoDictionary = new AutoDictionary(this, this, locale, Suggest.DIC_AUTO);
+        mAutoDictionary = new AutoDictionary(this, this, localeStr, Suggest.DIC_AUTO);
         mSuggest.setAutoDictionary(mAutoDictionary);
 
-        mUserBigramDictionary = new UserBigramDictionary(this, this, locale, Suggest.DIC_USER);
+        mUserBigramDictionary = new UserBigramDictionary(this, this, localeStr, Suggest.DIC_USER);
         mSuggest.setUserBigramDictionary(mUserBigramDictionary);
 
         updateCorrectionMode();
-        mWordSeparators = res.getString(R.string.word_separators);
-        mSentenceSeparators = res.getString(R.string.sentence_separators);
 
-        mSubtypeSwitcher.changeSystemLocale(savedLocale);
+        Utils.setSystemLocale(res, savedLocale);
+    }
+
+    /* package private */ void resetSuggestMainDict() {
+        final String localeStr = mSubtypeSwitcher.getInputLocaleStr();
+        final Locale keyboardLocale = new Locale(localeStr);
+        int mainDicResId = Utils.getMainDictionaryResourceId(mResources);
+        mSuggest.resetMainDict(this, mainDicResId, keyboardLocale);
     }
 
     @Override
@@ -480,7 +451,8 @@
             mSuggest = null;
         }
         unregisterReceiver(mReceiver);
-        mVoiceConnector.destroy();
+        unregisterReceiver(mDictionaryPackInstallReceiver);
+        mVoiceProxy.destroy();
         LatinImeLogger.commit();
         LatinImeLogger.onDestroy();
         super.onDestroy();
@@ -501,8 +473,11 @@
 
         mConfigurationChanging = true;
         super.onConfigurationChanged(conf);
-        mVoiceConnector.onConfigurationChanged(conf);
+        mVoiceProxy.onConfigurationChanged(conf);
         mConfigurationChanging = false;
+
+        // This will work only when the subtype is not supported.
+        LanguageSwitcherProxy.onConfigurationChanged(conf);
     }
 
     @Override
@@ -515,12 +490,7 @@
         LayoutInflater inflater = getLayoutInflater();
         LinearLayout container = (LinearLayout)inflater.inflate(R.layout.candidates, null);
         mCandidateViewContainer = container;
-        if (container.getPaddingRight() != 0) {
-            HorizontalScrollView scrollView =
-                    (HorizontalScrollView) container.findViewById(R.id.candidates_scroll_view);
-            scrollView.setOverScrollMode(View.OVER_SCROLL_NEVER);
-            container.setGravity(Gravity.CENTER_HORIZONTAL);
-        }
+        mCandidateStripHeight = (int)mResources.getDimension(R.dimen.candidate_strip_height);
         mCandidateView = (CandidateView) container.findViewById(R.id.candidates);
         mCandidateView.setService(this);
         setCandidatesViewShown(true);
@@ -532,7 +502,7 @@
         final KeyboardSwitcher switcher = mKeyboardSwitcher;
         LatinKeyboardView inputView = switcher.getInputView();
 
-        if(DEBUG) {
+        if (DEBUG) {
             Log.d(TAG, "onStartInputView: " + inputView);
         }
         // In landscape mode, this method gets called without the input view being created.
@@ -547,20 +517,31 @@
         // Most such things we decide below in initializeInputAttributesAndGetMode, but we need to
         // know now whether this is a password text field, because we need to know now whether we
         // want to enable the voice button.
-        final VoiceIMEConnector voiceIme = mVoiceConnector;
-        voiceIme.resetVoiceStates(Utils.isPasswordInputType(attribute.inputType)
-                || Utils.isVisiblePasswordInputType(attribute.inputType));
+        final VoiceProxy voiceIme = mVoiceProxy;
+        voiceIme.resetVoiceStates(InputTypeCompatUtils.isPasswordInputType(attribute.inputType)
+                || InputTypeCompatUtils.isVisiblePasswordInputType(attribute.inputType));
 
         initializeInputAttributes(attribute);
 
         inputView.closing();
         mEnteredText = null;
         mComposing.setLength(0);
-        mHasValidSuggestions = false;
+        mHasUncommittedTypedChars = false;
         mDeleteCount = 0;
-        mJustAddedAutoSpace = false;
+        mJustAddedMagicSpace = false;
 
-        loadSettings(attribute);
+        loadSettings();
+        updateCorrectionMode();
+        updateAutoTextEnabled();
+        updateSuggestionVisibility(mPrefs, mResources);
+
+        if (mSuggest != null && mSettingsValues.mAutoCorrectEnabled) {
+            mSuggest.setAutoCorrectionThreshold(mSettingsValues.mAutoCorrectionThreshold);
+         }
+        mVoiceProxy.loadSettings(attribute, mPrefs);
+        // This will work only when the subtype is not supported.
+        LanguageSwitcherProxy.loadSettings();
+
         if (mSubtypeSwitcher.isKeyboardMode()) {
             switcher.loadKeyboard(attribute,
                     mSubtypeSwitcher.isShortcutImeEnabled() && voiceIme.isVoiceButtonEnabled(),
@@ -568,20 +549,16 @@
             switcher.updateShiftState();
         }
 
-        setCandidatesViewShownInternal(isCandidateStripVisible(),
-                false /* needsInputViewShown */ );
+        setCandidatesViewShownInternal(isCandidateStripVisible(), false /* needsInputViewShown */ );
         // Delay updating suggestions because keyboard input view may not be shown at this point.
         mHandler.postUpdateSuggestions();
 
         updateCorrectionMode();
 
-        final boolean accessibilityEnabled = mAccessibilityUtils.isAccessibilityEnabled();
-
-        inputView.setPreviewEnabled(mPopupOn);
+        inputView.setKeyPreviewEnabled(mSettingsValues.mPopupOn);
         inputView.setProximityCorrectionEnabled(true);
-        inputView.setAccessibilityEnabled(accessibilityEnabled);
         // If we just entered a text field, maybe it has some old text that requires correction
-        checkRecorrectionOnStart();
+        mRecorrection.checkRecorrectionOnStart();
         inputView.setForeground(true);
 
         voiceIme.onStartInputView(inputView.getWindowToken());
@@ -594,7 +571,7 @@
             return;
         final int inputType = attribute.inputType;
         final int variation = inputType & InputType.TYPE_MASK_VARIATION;
-        mAutoSpace = false;
+        mShouldInsertMagicSpace = false;
         mInputTypeNoAutoCorrect = false;
         mIsSettingsSuggestionStripOn = false;
         mApplicationSpecifiedCompletionOn = false;
@@ -603,17 +580,17 @@
         if ((inputType & InputType.TYPE_MASK_CLASS) == InputType.TYPE_CLASS_TEXT) {
             mIsSettingsSuggestionStripOn = true;
             // Make sure that passwords are not displayed in candidate view
-            if (Utils.isPasswordInputType(inputType)
-                    || Utils.isVisiblePasswordInputType(inputType)) {
+            if (InputTypeCompatUtils.isPasswordInputType(inputType)
+                    || InputTypeCompatUtils.isVisiblePasswordInputType(inputType)) {
                 mIsSettingsSuggestionStripOn = false;
             }
-            if (Utils.isEmailVariation(variation)
+            if (InputTypeCompatUtils.isEmailVariation(variation)
                     || variation == InputType.TYPE_TEXT_VARIATION_PERSON_NAME) {
-                mAutoSpace = false;
+                mShouldInsertMagicSpace = false;
             } else {
-                mAutoSpace = true;
+                mShouldInsertMagicSpace = true;
             }
-            if (Utils.isEmailVariation(variation)) {
+            if (InputTypeCompatUtils.isEmailVariation(variation)) {
                 mIsSettingsSuggestionStripOn = false;
             } else if (variation == InputType.TYPE_TEXT_VARIATION_URI) {
                 mIsSettingsSuggestionStripOn = false;
@@ -644,34 +621,6 @@
         }
     }
 
-    private void checkRecorrectionOnStart() {
-        if (!mRecorrectionEnabled) return;
-
-        final InputConnection ic = getCurrentInputConnection();
-        if (ic == null) return;
-        // There could be a pending composing span.  Clean it up first.
-        ic.finishComposingText();
-
-        if (isShowingSuggestionsStrip() && isSuggestionsRequested()) {
-            // First get the cursor position. This is required by setOldSuggestions(), so that
-            // it can pass the correct range to setComposingRegion(). At this point, we don't
-            // have valid values for mLastSelectionStart/End because onUpdateSelection() has
-            // not been called yet.
-            ExtractedTextRequest etr = new ExtractedTextRequest();
-            etr.token = 0; // anything is fine here
-            ExtractedText et = ic.getExtractedText(etr, 0);
-            if (et == null) return;
-
-            mLastSelectionStart = et.startOffset + et.selectionStart;
-            mLastSelectionEnd = et.startOffset + et.selectionEnd;
-
-            // Then look for possible corrections in a delayed fashion
-            if (!TextUtils.isEmpty(et.text) && isCursorTouchingWord()) {
-                mHandler.postUpdateOldSuggestions();
-            }
-        }
-    }
-
     @Override
     public void onFinishInput() {
         super.onFinishInput();
@@ -679,7 +628,7 @@
         LatinImeLogger.commit();
         mKeyboardSwitcher.onAutoCorrectionStateChanged(false);
 
-        mVoiceConnector.flushVoiceInputLogs(mConfigurationChanging);
+        mVoiceProxy.flushVoiceInputLogs(mConfigurationChanging);
 
         KeyboardView inputView = mKeyboardSwitcher.getInputView();
         if (inputView != null) inputView.closing();
@@ -700,7 +649,7 @@
     @Override
     public void onUpdateExtractedText(int token, ExtractedText text) {
         super.onUpdateExtractedText(token, text);
-        mVoiceConnector.showPunctuationHintIfNecessary();
+        mVoiceProxy.showPunctuationHintIfNecessary();
     }
 
     @Override
@@ -721,36 +670,41 @@
                     + ", ce=" + candidatesEnd);
         }
 
-        mVoiceConnector.setCursorAndSelection(newSelEnd, newSelStart);
+        mVoiceProxy.setCursorAndSelection(newSelEnd, newSelStart);
 
         // If the current selection in the text view changes, we should
         // clear whatever candidate text we have.
         final boolean selectionChanged = (newSelStart != candidatesEnd
                 || newSelEnd != candidatesEnd) && mLastSelectionStart != newSelStart;
         final boolean candidatesCleared = candidatesStart == -1 && candidatesEnd == -1;
-        if (((mComposing.length() > 0 && mHasValidSuggestions)
-                || mVoiceConnector.isVoiceInputHighlighted())
+        if (((mComposing.length() > 0 && mHasUncommittedTypedChars)
+                || mVoiceProxy.isVoiceInputHighlighted())
                 && (selectionChanged || candidatesCleared)) {
             if (candidatesCleared) {
                 // If the composing span has been cleared, save the typed word in the history for
                 // recorrection before we reset the candidate strip.  Then, we'll be able to show
                 // suggestions for recorrection right away.
-                saveWordInHistory(mComposing);
+                mRecorrection.saveWordInHistory(mWord, mComposing);
             }
             mComposing.setLength(0);
-            mHasValidSuggestions = false;
-            mHandler.postUpdateSuggestions();
+            mHasUncommittedTypedChars = false;
+            if (isCursorTouchingWord()) {
+                mHandler.cancelUpdateBigramPredictions();
+                mHandler.postUpdateSuggestions();
+            } else {
+                setPunctuationSuggestions();
+            }
             TextEntryState.reset();
             InputConnection ic = getCurrentInputConnection();
             if (ic != null) {
                 ic.finishComposingText();
             }
-            mVoiceConnector.setVoiceInputHighlighted(false);
-        } else if (!mHasValidSuggestions && !mJustAccepted) {
+            mVoiceProxy.setVoiceInputHighlighted(false);
+        } else if (!mHasUncommittedTypedChars && !mJustAccepted) {
             if (TextEntryState.isAcceptedDefault() || TextEntryState.isSpaceAfterPicked()) {
                 if (TextEntryState.isAcceptedDefault())
                     TextEntryState.reset();
-                mJustAddedAutoSpace = false; // The user moved the cursor.
+                mJustAddedMagicSpace = false; // The user moved the cursor.
             }
         }
         mJustAccepted = false;
@@ -760,28 +714,15 @@
         mLastSelectionStart = newSelStart;
         mLastSelectionEnd = newSelEnd;
 
-        if (mRecorrectionEnabled && isShowingSuggestionsStrip()) {
-            // Don't look for corrections if the keyboard is not visible
-            if (mKeyboardSwitcher.isInputViewShown()) {
-                // Check if we should go in or out of correction mode.
-                if (isSuggestionsRequested()
-                        && (candidatesStart == candidatesEnd || newSelStart != oldSelStart
-                                || TextEntryState.isRecorrecting())
-                                && (newSelStart < newSelEnd - 1 || !mHasValidSuggestions)) {
-                    if (isCursorTouchingWord() || mLastSelectionStart < mLastSelectionEnd) {
-                        mHandler.postUpdateOldSuggestions();
-                    } else {
-                        abortRecorrection(false);
-                        // Show the punctuation suggestions list if the current one is not
-                        // and if not showing "Touch again to save".
-                        if (mCandidateView != null && !isShowingPunctuationList()
-                                && !mCandidateView.isShowingAddToDictionaryHint()) {
-                            setPunctuationSuggestions();
-                        }
-                    }
-                }
-            }
-        }
+        mRecorrection.updateRecorrectionSelection(mKeyboardSwitcher,
+                mCandidateView, candidatesStart, candidatesEnd, newSelStart,
+                newSelEnd, oldSelStart, mLastSelectionStart,
+                mLastSelectionEnd, mHasUncommittedTypedChars);
+    }
+
+    public void setLastSelection(int start, int end) {
+        mLastSelectionStart = start;
+        mLastSelectionEnd = end;
     }
 
     /**
@@ -794,7 +735,7 @@
      */
     @Override
     public void onExtractedTextClicked() {
-        if (mRecorrectionEnabled && isSuggestionsRequested()) return;
+        if (mRecorrection.isRecorrectionEnabled() && isSuggestionsRequested()) return;
 
         super.onExtractedTextClicked();
     }
@@ -810,7 +751,7 @@
      */
     @Override
     public void onExtractedCursorMovement(int dx, int dy) {
-        if (mRecorrectionEnabled && isSuggestionsRequested()) return;
+        if (mRecorrection.isRecorrectionEnabled() && isSuggestionsRequested()) return;
 
         super.onExtractedCursorMovement(dx, dy);
     }
@@ -825,8 +766,8 @@
             mOptionsDialog.dismiss();
             mOptionsDialog = null;
         }
-        mVoiceConnector.hideVoiceWindow(mConfigurationChanging);
-        mWordHistory.clear();
+        mVoiceProxy.hideVoiceWindow(mConfigurationChanging);
+        mRecorrection.clearWordsInHistory();
         super.hideWindow();
     }
 
@@ -859,10 +800,21 @@
     }
 
     private void setCandidatesViewShownInternal(boolean shown, boolean needsInputViewShown) {
-        // TODO: Remove this if we support candidates with hard keyboard
+        // TODO: Modify this if we support candidates with hard keyboard
         if (onEvaluateInputViewShown()) {
-            super.setCandidatesViewShown(shown
-                    && (needsInputViewShown ? mKeyboardSwitcher.isInputViewShown() : true));
+            final boolean shouldShowCandidates = shown
+                    && (needsInputViewShown ? mKeyboardSwitcher.isInputViewShown() : true);
+            if (isExtractViewShown()) {
+                // No need to have extra space to show the key preview.
+                mCandidateViewContainer.setMinimumHeight(0);
+                super.setCandidatesViewShown(shown);
+            } else {
+                // We must control the visibility of the suggestion strip in order to avoid clipped
+                // key previews, even when we don't show the suggestion strip.
+                mCandidateViewContainer.setVisibility(
+                        shouldShowCandidates ? View.VISIBLE : View.INVISIBLE);
+                super.setCandidatesViewShown(true);
+            }
         }
     }
 
@@ -874,37 +826,28 @@
     @Override
     public void onComputeInsets(InputMethodService.Insets outInsets) {
         super.onComputeInsets(outInsets);
-        if (!isFullscreenMode()) {
-            outInsets.contentTopInsets = outInsets.visibleTopInsets;
-        }
-        KeyboardView inputView = mKeyboardSwitcher.getInputView();
+        final KeyboardView inputView = mKeyboardSwitcher.getInputView();
+        if (inputView == null)
+            return;
+        final int containerHeight = mCandidateViewContainer.getHeight();
+        int touchY = containerHeight;
         // Need to set touchable region only if input view is being shown
-        if (inputView != null && mKeyboardSwitcher.isInputViewShown()) {
-            final int x = 0;
-            int y = 0;
-            final int width = inputView.getWidth();
-            int height = inputView.getHeight() + EXTENDED_TOUCHABLE_REGION_HEIGHT;
-            if (mCandidateViewContainer != null) {
-                ViewParent candidateParent = mCandidateViewContainer.getParent();
-                if (candidateParent instanceof FrameLayout) {
-                    FrameLayout fl = (FrameLayout) candidateParent;
-                    if (fl != null) {
-                        // Check frame layout's visibility
-                        if (fl.getVisibility() == View.INVISIBLE) {
-                            y = fl.getHeight();
-                            height += y;
-                        } else if (fl.getVisibility() == View.VISIBLE) {
-                            height += fl.getHeight();
-                        }
-                    }
-                }
+        if (mKeyboardSwitcher.isInputViewShown()) {
+            if (mCandidateViewContainer.getVisibility() == View.VISIBLE) {
+                touchY -= mCandidateStripHeight;
             }
+            final int touchWidth = inputView.getWidth();
+            final int touchHeight = inputView.getHeight() + containerHeight
+                    // Extend touchable region below the keyboard.
+                    + EXTENDED_TOUCHABLE_REGION_HEIGHT;
             if (DEBUG) {
-                Log.d(TAG, "Touchable region " + x + ", " + y + ", " + width + ", " + height);
+                Log.d(TAG, "Touchable region: y=" + touchY + " width=" + touchWidth
+                        + " height=" + touchHeight);
             }
-            outInsets.touchableInsets = InputMethodService.Insets.TOUCHABLE_INSETS_REGION;
-            outInsets.touchableRegion.set(x, y, width, height);
+            setTouchableRegionCompat(outInsets, 0, touchY, touchWidth, touchHeight);
         }
+        outInsets.contentTopInsets = touchY;
+        outInsets.visibleTopInsets = touchY;
     }
 
     @Override
@@ -960,8 +903,8 @@
     }
 
     public void commitTyped(InputConnection inputConnection) {
-        if (mHasValidSuggestions) {
-            mHasValidSuggestions = false;
+        if (mHasUncommittedTypedChars) {
+            mHasUncommittedTypedChars = false;
             if (mComposing.length() > 0) {
                 if (inputConnection != null) {
                     inputConnection.commitText(mComposing, 1);
@@ -977,45 +920,29 @@
     public boolean getCurrentAutoCapsState() {
         InputConnection ic = getCurrentInputConnection();
         EditorInfo ei = getCurrentInputEditorInfo();
-        if (mAutoCap && ic != null && ei != null && ei.inputType != InputType.TYPE_NULL) {
+        if (mSettingsValues.mAutoCap && ic != null && ei != null
+                && ei.inputType != InputType.TYPE_NULL) {
             return ic.getCursorCapsMode(ei.inputType) != 0;
         }
         return false;
     }
 
-    private void swapPunctuationAndSpace() {
+    private void swapSwapperAndSpace() {
         final InputConnection ic = getCurrentInputConnection();
         if (ic == null) return;
         CharSequence lastTwo = ic.getTextBeforeCursor(2, 0);
+        // It is guaranteed lastTwo.charAt(1) is a swapper - else this method is not called.
         if (lastTwo != null && lastTwo.length() == 2
-                && lastTwo.charAt(0) == Keyboard.CODE_SPACE
-                && isSentenceSeparator(lastTwo.charAt(1))) {
+                && lastTwo.charAt(0) == Keyboard.CODE_SPACE) {
             ic.beginBatchEdit();
             ic.deleteSurroundingText(2, 0);
             ic.commitText(lastTwo.charAt(1) + " ", 1);
             ic.endBatchEdit();
             mKeyboardSwitcher.updateShiftState();
-            mJustAddedAutoSpace = true;
         }
     }
 
-    private void reswapPeriodAndSpace() {
-        final InputConnection ic = getCurrentInputConnection();
-        if (ic == null) return;
-        CharSequence lastThree = ic.getTextBeforeCursor(3, 0);
-        if (lastThree != null && lastThree.length() == 3
-                && lastThree.charAt(0) == Keyboard.CODE_PERIOD
-                && lastThree.charAt(1) == Keyboard.CODE_SPACE
-                && lastThree.charAt(2) == Keyboard.CODE_PERIOD) {
-            ic.beginBatchEdit();
-            ic.deleteSurroundingText(3, 0);
-            ic.commitText(" ..", 1);
-            ic.endBatchEdit();
-            mKeyboardSwitcher.updateShiftState();
-        }
-    }
-
-    private void doubleSpace() {
+    private void maybeDoubleSpace() {
         if (mCorrectionMode == Suggest.CORRECTION_NONE) return;
         final InputConnection ic = getCurrentInputConnection();
         if (ic == null) return;
@@ -1031,7 +958,6 @@
             ic.commitText(". ", 1);
             ic.endBatchEdit();
             mKeyboardSwitcher.updateShiftState();
-            mJustAddedAutoSpace = true;
         } else {
             mHandler.startDoubleSpacesTimer();
         }
@@ -1080,7 +1006,7 @@
 
     private void onSettingsKeyPressed() {
         if (!isShowingOptionDialog()) {
-            if (!mConfigEnableShowSubtypeSettings) {
+            if (!mSettingsValues.mEnableShowSubtypeSettings) {
                 showSubtypeSelectorAndSettings();
             } else if (Utils.hasMultipleEnabledIMEsOrSubtypes(mImm)) {
                 showOptionsMenu();
@@ -1113,7 +1039,6 @@
         }
         mLastKeyTime = when;
         KeyboardSwitcher switcher = mKeyboardSwitcher;
-        final boolean accessibilityEnabled = switcher.isAccessibilityEnabled();
         final boolean distinctMultiTouch = switcher.hasDistinctMultitouch();
         switch (primaryCode) {
         case Keyboard.CODE_DELETE:
@@ -1123,12 +1048,12 @@
             break;
         case Keyboard.CODE_SHIFT:
             // Shift key is handled in onPress() when device has distinct multi-touch panel.
-            if (!distinctMultiTouch || accessibilityEnabled)
+            if (!distinctMultiTouch)
                 switcher.toggleShift();
             break;
         case Keyboard.CODE_SWITCH_ALPHA_SYMBOL:
             // Symbol key is handled in onPress() when device has distinct multi-touch panel.
-            if (!distinctMultiTouch || accessibilityEnabled)
+            if (!distinctMultiTouch)
                 switcher.changeKeyboardMode();
             break;
         case Keyboard.CODE_CANCEL:
@@ -1142,29 +1067,24 @@
         case Keyboard.CODE_SETTINGS_LONGPRESS:
             onSettingsKeyLongPressed();
             break;
-        case Keyboard.CODE_NEXT_LANGUAGE:
-            toggleLanguage(false, true);
+        case LatinKeyboard.CODE_NEXT_LANGUAGE:
+            toggleLanguage(true);
             break;
-        case Keyboard.CODE_PREV_LANGUAGE:
-            toggleLanguage(false, false);
+        case LatinKeyboard.CODE_PREV_LANGUAGE:
+            toggleLanguage(false);
             break;
         case Keyboard.CODE_CAPSLOCK:
             switcher.toggleCapsLock();
             break;
-        case Keyboard.CODE_VOICE:
+        case Keyboard.CODE_SHORTCUT:
             mSubtypeSwitcher.switchToShortcutIME();
             break;
         case Keyboard.CODE_TAB:
             handleTab();
             break;
         default:
-            if (primaryCode != Keyboard.CODE_ENTER) {
-                mJustAddedAutoSpace = false;
-            }
-            RingCharBuffer.getInstance().push((char)primaryCode, x, y);
-            LatinImeLogger.logOnInputChar();
-            if (isWordSeparator(primaryCode)) {
-                handleSeparator(primaryCode);
+            if (mSettingsValues.isWordSeparator(primaryCode)) {
+                handleSeparator(primaryCode, x, y);
             } else {
                 handleCharacter(primaryCode, keyCodes, x, y);
             }
@@ -1176,10 +1096,10 @@
 
     @Override
     public void onTextInput(CharSequence text) {
-        mVoiceConnector.commitVoiceInput();
+        mVoiceProxy.commitVoiceInput();
         InputConnection ic = getCurrentInputConnection();
         if (ic == null) return;
-        abortRecorrection(false);
+        mRecorrection.abortRecorrection(false);
         ic.beginBatchEdit();
         commitTyped(ic);
         maybeRemovePreviousPeriod(text);
@@ -1187,7 +1107,7 @@
         ic.endBatchEdit();
         mKeyboardSwitcher.updateShiftState();
         mKeyboardSwitcher.onKey(Keyboard.CODE_DUMMY);
-        mJustAddedAutoSpace = false;
+        mJustAddedMagicSpace = false;
         mEnteredText = text;
     }
 
@@ -1198,25 +1118,32 @@
     }
 
     private void handleBackspace() {
-        if (mVoiceConnector.logAndRevertVoiceInput()) return;
+        if (mVoiceProxy.logAndRevertVoiceInput()) return;
 
         final InputConnection ic = getCurrentInputConnection();
         if (ic == null) return;
         ic.beginBatchEdit();
 
-        mVoiceConnector.handleBackspace();
+        mVoiceProxy.handleBackspace();
 
         boolean deleteChar = false;
-        if (mHasValidSuggestions) {
+        if (mHasUncommittedTypedChars) {
             final int length = mComposing.length();
             if (length > 0) {
                 mComposing.delete(length - 1, length);
                 mWord.deleteLast();
                 ic.setComposingText(mComposing, 1);
                 if (mComposing.length() == 0) {
-                    mHasValidSuggestions = false;
+                    mHasUncommittedTypedChars = false;
                 }
-                mHandler.postUpdateSuggestions();
+                if (1 == length) {
+                    // 1 == length means we are about to erase the last character of the word,
+                    // so we can show bigrams.
+                    mHandler.postUpdateBigramPredictions();
+                } else {
+                    // length > 1, so we still have letters to deduce a suggestion from.
+                    mHandler.postUpdateSuggestions();
+                }
             } else {
                 ic.deleteSurroundingText(1, 0);
             }
@@ -1256,9 +1183,8 @@
 
     private void handleTab() {
         final int imeOptions = getCurrentInputEditorInfo().imeOptions;
-        final int navigationFlags =
-                EditorInfo.IME_FLAG_NAVIGATE_NEXT | EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS;
-        if ((imeOptions & navigationFlags) == 0) {
+        if (!EditorInfoCompatUtils.hasFlagNavigateNext(imeOptions)
+                && !EditorInfoCompatUtils.hasFlagNavigatePrevious(imeOptions)) {
             sendDownUpKeyEvents(KeyEvent.KEYCODE_TAB);
             return;
         }
@@ -1269,37 +1195,33 @@
 
         // True if keyboard is in either chording shift or manual temporary upper case mode.
         final boolean isManualTemporaryUpperCase = mKeyboardSwitcher.isManualTemporaryUpperCase();
-        if ((imeOptions & EditorInfo.IME_FLAG_NAVIGATE_NEXT) != 0
+        if (EditorInfoCompatUtils.hasFlagNavigateNext(imeOptions)
                 && !isManualTemporaryUpperCase) {
+            EditorInfoCompatUtils.performEditorActionNext(ic);
             ic.performEditorAction(EditorInfo.IME_ACTION_NEXT);
-        } else if ((imeOptions & EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS) != 0
+        } else if (EditorInfoCompatUtils.hasFlagNavigatePrevious(imeOptions)
                 && isManualTemporaryUpperCase) {
-            ic.performEditorAction(EditorInfo.IME_ACTION_PREVIOUS);
-        }
-    }
-
-    private void abortRecorrection(boolean force) {
-        if (force || TextEntryState.isRecorrecting()) {
-            TextEntryState.onAbortRecorrection();
-            setCandidatesViewShown(isCandidateStripVisible());
-            getCurrentInputConnection().finishComposingText();
-            clearSuggestions();
+            EditorInfoCompatUtils.performEditorActionPrevious(ic);
         }
     }
 
     private void handleCharacter(int primaryCode, int[] keyCodes, int x, int y) {
-        mVoiceConnector.handleCharacter();
+        mVoiceProxy.handleCharacter();
 
-        if (mLastSelectionStart == mLastSelectionEnd && TextEntryState.isRecorrecting()) {
-            abortRecorrection(false);
+        if (mJustAddedMagicSpace && mSettingsValues.isMagicSpaceStripper(primaryCode)) {
+            removeTrailingSpace();
+        }
+
+        if (mLastSelectionStart == mLastSelectionEnd) {
+            mRecorrection.abortRecorrection(false);
         }
 
         int code = primaryCode;
         if (isAlphabet(code) && isSuggestionsRequested() && !isCursorTouchingWord()) {
-            if (!mHasValidSuggestions) {
-                mHasValidSuggestions = true;
+            if (!mHasUncommittedTypedChars) {
+                mHasUncommittedTypedChars = true;
                 mComposing.setLength(0);
-                saveWordInHistory(mBestWord);
+                mRecorrection.saveWordInHistory(mWord, mBestWord);
                 mWord.reset();
                 clearSuggestions();
             }
@@ -1323,7 +1245,7 @@
                 }
             }
         }
-        if (mHasValidSuggestions) {
+        if (mHasUncommittedTypedChars) {
             if (mComposing.length() == 0 && switcher.isAlphabetMode()
                     && switcher.isShiftedOrShiftLocked()) {
                 mWord.setFirstCharCapitalized(true);
@@ -1342,16 +1264,23 @@
         } else {
             sendKeyChar((char)code);
         }
+        if (mJustAddedMagicSpace && mSettingsValues.isMagicSpaceSwapper(primaryCode)) {
+            swapSwapperAndSpace();
+        } else {
+            mJustAddedMagicSpace = false;
+        }
+
         switcher.updateShiftState();
         if (LatinIME.PERF_DEBUG) measureCps();
-        TextEntryState.typedCharacter((char) code, isWordSeparator(code));
+        TextEntryState.typedCharacter((char) code, mSettingsValues.isWordSeparator(code), x, y);
     }
 
-    private void handleSeparator(int primaryCode) {
-        mVoiceConnector.handleSeparator();
+    private void handleSeparator(int primaryCode, int x, int y) {
+        mVoiceProxy.handleSeparator();
 
         // Should dismiss the "Touch again to save" message when handling separator
         if (mCandidateView != null && mCandidateView.dismissAddToDictionaryHint()) {
+            mHandler.cancelUpdateBigramPredictions();
             mHandler.postUpdateSuggestions();
         }
 
@@ -1360,54 +1289,61 @@
         final InputConnection ic = getCurrentInputConnection();
         if (ic != null) {
             ic.beginBatchEdit();
-            abortRecorrection(false);
+            mRecorrection.abortRecorrection(false);
         }
-        if (mHasValidSuggestions) {
+        if (mHasUncommittedTypedChars) {
             // In certain languages where single quote is a separator, it's better
             // not to auto correct, but accept the typed word. For instance,
             // in Italian dov' should not be expanded to dove' because the elision
             // requires the last vowel to be removed.
-            if (mAutoCorrectOn && primaryCode != '\'') {
-                pickedDefault = pickDefaultSuggestion();
-                // Picked the suggestion by the space key.  We consider this
-                // as "added an auto space".
-                if (primaryCode == Keyboard.CODE_SPACE) {
-                    mJustAddedAutoSpace = true;
-                }
+            final boolean shouldAutoCorrect =
+                    (mSettingsValues.mAutoCorrectEnabled || mSettingsValues.mQuickFixes)
+                    && !mInputTypeNoAutoCorrect && mHasDictionary;
+            if (shouldAutoCorrect && primaryCode != Keyboard.CODE_SINGLE_QUOTE) {
+                pickedDefault = pickDefaultSuggestion(primaryCode);
             } else {
                 commitTyped(ic);
             }
         }
-        if (mJustAddedAutoSpace && primaryCode == Keyboard.CODE_ENTER) {
-            removeTrailingSpace();
-            mJustAddedAutoSpace = false;
-        }
-        sendKeyChar((char)primaryCode);
 
-        // Handle the case of ". ." -> " .." with auto-space if necessary
-        // before changing the TextEntryState.
-        if (TextEntryState.isPunctuationAfterAccepted() && primaryCode == Keyboard.CODE_PERIOD) {
-            reswapPeriodAndSpace();
+        if (mJustAddedMagicSpace) {
+            if (mSettingsValues.isMagicSpaceSwapper(primaryCode)) {
+                sendKeyChar((char)primaryCode);
+                swapSwapperAndSpace();
+            } else {
+                if (mSettingsValues.isMagicSpaceStripper(primaryCode)) removeTrailingSpace();
+                sendKeyChar((char)primaryCode);
+                mJustAddedMagicSpace = false;
+            }
+        } else {
+            sendKeyChar((char)primaryCode);
         }
 
-        TextEntryState.typedCharacter((char) primaryCode, true);
-        if (TextEntryState.isPunctuationAfterAccepted() && primaryCode != Keyboard.CODE_ENTER) {
-            swapPunctuationAndSpace();
-        } else if (isSuggestionsRequested() && primaryCode == Keyboard.CODE_SPACE) {
-            doubleSpace();
+        if (isSuggestionsRequested() && primaryCode == Keyboard.CODE_SPACE) {
+            maybeDoubleSpace();
         }
+
+        TextEntryState.typedCharacter((char) primaryCode, true, x, y);
+
         if (pickedDefault) {
             CharSequence typedWord = mWord.getTypedWord();
             TextEntryState.backToAcceptedDefault(typedWord);
             if (!TextUtils.isEmpty(typedWord) && !typedWord.equals(mBestWord)) {
-                if (ic != null) {
-                    CorrectionInfo correctionInfo = new CorrectionInfo(
-                            mLastSelectionEnd - typedWord.length(), typedWord, mBestWord);
-                    ic.commitCorrection(correctionInfo);
-                }
+                InputConnectionCompatUtils.commitCorrection(
+                        ic, mLastSelectionEnd - typedWord.length(), typedWord, mBestWord);
                 if (mCandidateView != null)
                     mCandidateView.onAutoCorrectionInverted(mBestWord);
             }
+        }
+        if (Keyboard.CODE_SPACE == primaryCode) {
+            if (!isCursorTouchingWord()) {
+                mHandler.cancelUpdateSuggestions();
+                mHandler.cancelUpdateOldSuggestions();
+                mHandler.postUpdateBigramPredictions();
+            }
+        } else {
+            // Set punctuation right away. onUpdateSelection will fire but tests whether it is
+            // already displayed or not, so it's okay.
             setPunctuationSuggestions();
         }
         mKeyboardSwitcher.updateShiftState();
@@ -1418,45 +1354,29 @@
 
     private void handleClose() {
         commitTyped(getCurrentInputConnection());
-        mVoiceConnector.handleClose();
+        mVoiceProxy.handleClose();
         requestHideSelf(0);
         LatinKeyboardView inputView = mKeyboardSwitcher.getInputView();
         if (inputView != null)
             inputView.closing();
     }
 
-    private void saveWordInHistory(CharSequence result) {
-        if (mWord.size() <= 1) {
-            return;
-        }
-        // Skip if result is null. It happens in some edge case.
-        if (TextUtils.isEmpty(result)) {
-            return;
-        }
-
-        // Make a copy of the CharSequence, since it is/could be a mutable CharSequence
-        final String resultCopy = result.toString();
-        TypedWordAlternatives entry = new TypedWordAlternatives(resultCopy,
-                new WordComposer(mWord));
-        mWordHistory.add(entry);
-    }
-
-    private boolean isSuggestionsRequested() {
+    public boolean isSuggestionsRequested() {
         return mIsSettingsSuggestionStripOn
                 && (mCorrectionMode > 0 || isShowingSuggestionsStrip());
     }
 
-    private boolean isShowingPunctuationList() {
-        return mSuggestPuncList == mCandidateView.getSuggestions();
+    public boolean isShowingPunctuationList() {
+        return mSettingsValues.mSuggestPuncList == mCandidateView.getSuggestions();
     }
 
-    private boolean isShowingSuggestionsStrip() {
+    public boolean isShowingSuggestionsStrip() {
         return (mSuggestionVisibility == SUGGESTION_VISIBILILTY_SHOW_VALUE)
                 || (mSuggestionVisibility == SUGGESTION_VISIBILILTY_SHOW_ONLY_PORTRAIT_VALUE
                         && mOrientation == Configuration.ORIENTATION_PORTRAIT);
     }
 
-    private boolean isCandidateStripVisible() {
+    public boolean isCandidateStripVisible() {
         if (mCandidateView == null)
             return false;
         if (mCandidateView.isShowingAddToDictionaryHint() || TextEntryState.isRecorrecting())
@@ -1491,7 +1411,7 @@
     }
 
     public void setSuggestions(SuggestedWords words) {
-        if (mVoiceConnector.getAndResetIsShowingHint()) {
+        if (mVoiceProxy.getAndResetIsShowingHint()) {
              setCandidatesView(mCandidateViewContainer);
         }
 
@@ -1507,31 +1427,21 @@
     public void updateSuggestions() {
         // Check if we have a suggestion engine attached.
         if ((mSuggest == null || !isSuggestionsRequested())
-                && !mVoiceConnector.isVoiceInputHighlighted()) {
+                && !mVoiceProxy.isVoiceInputHighlighted()) {
             return;
         }
 
-        if (!mHasValidSuggestions) {
+        if (!mHasUncommittedTypedChars) {
             setPunctuationSuggestions();
             return;
         }
         showSuggestions(mWord);
     }
 
-    private SuggestedWords.Builder getTypedSuggestions(WordComposer word) {
-        return mSuggest.getSuggestedWordBuilder(mKeyboardSwitcher.getInputView(), word, null);
-    }
-
-    private void showCorrections(WordAlternatives alternatives) {
-        SuggestedWords.Builder builder = alternatives.getAlternatives();
-        builder.setTypedWordValid(false).setHasMinimalSuggestion(false);
-        showSuggestions(builder.build(), alternatives.getOriginalWord());
-    }
-
     private void showSuggestions(WordComposer word) {
         // TODO: May need a better way of retrieving previous word
         CharSequence prevWord = EditingUtils.getPreviousWord(getCurrentInputConnection(),
-                mWordSeparators);
+                mSettingsValues.mWordSeparators);
         SuggestedWords.Builder builder = mSuggest.getSuggestedWordBuilder(
                 mKeyboardSwitcher.getInputView(), word, prevWord);
 
@@ -1559,14 +1469,14 @@
             builder.setTypedWordValid(typedWordValid).setHasMinimalSuggestion(correctionAvailable);
         } else {
             final SuggestedWords previousSuggestions = mCandidateView.getSuggestions();
-            if (previousSuggestions == mSuggestPuncList)
+            if (previousSuggestions == mSettingsValues.mSuggestPuncList)
                 return;
             builder.addTypedWordAndPreviousSuggestions(typedWord, previousSuggestions);
         }
         showSuggestions(builder.build(), typedWord);
     }
 
-    private void showSuggestions(SuggestedWords suggestedWords, CharSequence typedWord) {
+    public void showSuggestions(SuggestedWords suggestedWords, CharSequence typedWord) {
         setSuggestions(suggestedWords);
         if (suggestedWords.size() > 0) {
             if (Utils.shouldBlockedBySafetyNetForAutoCorrection(suggestedWords, mSuggest)) {
@@ -1582,27 +1492,27 @@
         setCandidatesViewShown(isCandidateStripVisible());
     }
 
-    private boolean pickDefaultSuggestion() {
+    private boolean pickDefaultSuggestion(int separatorCode) {
         // Complete any pending candidate query first
         if (mHandler.hasPendingUpdateSuggestions()) {
             mHandler.cancelUpdateSuggestions();
             updateSuggestions();
         }
         if (mBestWord != null && mBestWord.length() > 0) {
-            TextEntryState.acceptedDefault(mWord.getTypedWord(), mBestWord);
+            TextEntryState.acceptedDefault(mWord.getTypedWord(), mBestWord, separatorCode);
             mJustAccepted = true;
             pickSuggestion(mBestWord);
             // Add the word to the auto dictionary if it's not a known word
             addToAutoAndUserBigramDictionaries(mBestWord, AutoDictionary.FREQUENCY_FOR_TYPED);
             return true;
-
         }
         return false;
     }
 
     public void pickSuggestionManually(int index, CharSequence suggestion) {
         SuggestedWords suggestions = mCandidateView.getSuggestions();
-        mVoiceConnector.flushAndLogAllTextModificationCounters(index, suggestion, mWordSeparators);
+        mVoiceProxy.flushAndLogAllTextModificationCounters(index, suggestion,
+                mSettingsValues.mWordSeparators);
 
         final boolean recorrecting = TextEntryState.isRecorrecting();
         InputConnection ic = getCurrentInputConnection();
@@ -1627,21 +1537,34 @@
         }
 
         // If this is a punctuation, apply it through the normal key press
-        if (suggestion.length() == 1 && (isWordSeparator(suggestion.charAt(0))
-                || isSuggestedPunctuation(suggestion.charAt(0)))) {
+        if (suggestion.length() == 1 && (mSettingsValues.isWordSeparator(suggestion.charAt(0))
+                || mSettingsValues.isSuggestedPunctuation(suggestion.charAt(0)))) {
             // Word separators are suggested before the user inputs something.
             // So, LatinImeLogger logs "" as a user's input.
             LatinImeLogger.logOnManualSuggestion(
                     "", suggestion.toString(), index, suggestions.mWords);
+            // Find out whether the previous character is a space. If it is, as a special case
+            // for punctuation entered through the suggestion strip, it should be considered
+            // a magic space even if it was a normal space. This is meant to help in case the user
+            // pressed space on purpose of displaying the suggestion strip punctuation.
             final char primaryCode = suggestion.charAt(0);
+            final int toLeft = (ic == null) ? 0 : ic.getTextBeforeCursor(1, 0).charAt(0);
+            final boolean oldMagicSpace = mJustAddedMagicSpace;
+            if (Keyboard.CODE_SPACE == toLeft) mJustAddedMagicSpace = true;
             onCodeInput(primaryCode, new int[] { primaryCode },
                     KeyboardActionListener.NOT_A_TOUCH_COORDINATE,
                     KeyboardActionListener.NOT_A_TOUCH_COORDINATE);
+            mJustAddedMagicSpace = oldMagicSpace;
             if (ic != null) {
                 ic.endBatchEdit();
             }
             return;
         }
+        if (!mHasUncommittedTypedChars) {
+            // If we are not composing a word, then it was a suggestion inferred from
+            // context - no user input. We should reset the word composer.
+            mWord.reset();
+        }
         mJustAccepted = true;
         pickSuggestion(suggestion);
         // Add the word to the auto dictionary if it's not a known word
@@ -1654,9 +1577,8 @@
                 index, suggestions.mWords);
         TextEntryState.acceptedSuggestion(mComposing.toString(), suggestion);
         // Follow it with a space
-        if (mAutoSpace && !recorrecting) {
-            sendSpace();
-            mJustAddedAutoSpace = true;
+        if (mShouldInsertMagicSpace && !recorrecting) {
+            sendMagicSpace();
         }
 
         // We should show the hint if the user pressed the first entry AND either:
@@ -1678,8 +1600,9 @@
             // Fool the state watcher so that a subsequent backspace will not do a revert, unless
             // we just did a correction, in which case we need to stay in
             // TextEntryState.State.PICKED_SUGGESTION state.
-            TextEntryState.typedCharacter((char) Keyboard.CODE_SPACE, true);
-            setPunctuationSuggestions();
+            TextEntryState.typedCharacter((char) Keyboard.CODE_SPACE, true,
+                    WordComposer.NOT_A_COORDINATE, WordComposer.NOT_A_COORDINATE);
+            // From there on onUpdateSelection() will fire so suggestions will be updated
         } else if (!showingAddToDictionaryHint) {
             // If we're not showing the "Touch again to save", then show corrections again.
             // In case the cursor position doesn't change, make sure we show the suggestions again.
@@ -1706,97 +1629,40 @@
             return;
         InputConnection ic = getCurrentInputConnection();
         if (ic != null) {
-            mVoiceConnector.rememberReplacedWord(suggestion, mWordSeparators);
+            mVoiceProxy.rememberReplacedWord(suggestion, mSettingsValues.mWordSeparators);
             ic.commitText(suggestion, 1);
         }
-        saveWordInHistory(suggestion);
-        mHasValidSuggestions = false;
+        mRecorrection.saveWordInHistory(mWord, suggestion);
+        mHasUncommittedTypedChars = false;
         mCommittedLength = suggestion.length();
     }
 
-    /**
-     * Tries to apply any typed alternatives for the word if we have any cached alternatives,
-     * otherwise tries to find new corrections and completions for the word.
-     * @param touching The word that the cursor is touching, with position information
-     * @return true if an alternative was found, false otherwise.
-     */
-    private boolean applyTypedAlternatives(EditingUtils.SelectedWord touching) {
-        // If we didn't find a match, search for result in typed word history
-        WordComposer foundWord = null;
-        WordAlternatives alternatives = null;
-        // Search old suggestions to suggest re-corrected suggestions.
-        for (WordAlternatives entry : mWordHistory) {
-            if (TextUtils.equals(entry.getChosenWord(), touching.mWord)) {
-                if (entry instanceof TypedWordAlternatives) {
-                    foundWord = ((TypedWordAlternatives) entry).word;
-                }
-                alternatives = entry;
-                break;
-            }
-        }
-        // If we didn't find a match, at least suggest corrections as re-corrected suggestions.
-        if (foundWord == null
-                && (AutoCorrection.isValidWord(
-                        mSuggest.getUnigramDictionaries(), touching.mWord, true))) {
-            foundWord = new WordComposer();
-            for (int i = 0; i < touching.mWord.length(); i++) {
-                foundWord.add(touching.mWord.charAt(i), new int[] {
-                    touching.mWord.charAt(i)
-                }, WordComposer.NOT_A_COORDINATE, WordComposer.NOT_A_COORDINATE);
-            }
-            foundWord.setFirstCharCapitalized(Character.isUpperCase(touching.mWord.charAt(0)));
-        }
-        // Found a match, show suggestions
-        if (foundWord != null || alternatives != null) {
-            if (alternatives == null) {
-                alternatives = new TypedWordAlternatives(touching.mWord, foundWord);
-            }
-            showCorrections(alternatives);
-            if (foundWord != null) {
-                mWord = new WordComposer(foundWord);
-            } else {
-                mWord.reset();
-            }
-            return true;
-        }
-        return false;
-    }
+    private static final WordComposer sEmptyWordComposer = new WordComposer();
+    private void updateBigramPredictions() {
+        if (mSuggest == null || !isSuggestionsRequested())
+            return;
 
-    private void setOldSuggestions() {
-        mVoiceConnector.setShowingVoiceSuggestions(false);
-        if (mCandidateView != null && mCandidateView.isShowingAddToDictionaryHint()) {
+        if (!mSettingsValues.mBigramPredictionEnabled) {
+            setPunctuationSuggestions();
             return;
         }
-        InputConnection ic = getCurrentInputConnection();
-        if (ic == null) return;
-        if (!mHasValidSuggestions) {
-            // Extract the selected or touching text
-            EditingUtils.SelectedWord touching = EditingUtils.getWordAtCursorOrSelection(ic,
-                    mLastSelectionStart, mLastSelectionEnd, mWordSeparators);
 
-            if (touching != null && touching.mWord.length() > 1) {
-                ic.beginBatchEdit();
+        final CharSequence prevWord = EditingUtils.getThisWord(getCurrentInputConnection(),
+                mSettingsValues.mWordSeparators);
+        SuggestedWords.Builder builder = mSuggest.getSuggestedWordBuilder(
+                mKeyboardSwitcher.getInputView(), sEmptyWordComposer, prevWord);
 
-                if (!mVoiceConnector.applyVoiceAlternatives(touching)
-                        && !applyTypedAlternatives(touching)) {
-                    abortRecorrection(true);
-                } else {
-                    TextEntryState.selectedForRecorrection();
-                    EditingUtils.underlineWord(ic, touching);
-                }
-
-                ic.endBatchEdit();
-            } else {
-                abortRecorrection(true);
-                setPunctuationSuggestions();  // Show the punctuation suggestions list
-            }
+        if (builder.size() > 0) {
+            // Explicitly supply an empty typed word (the no-second-arg version of
+            // showSuggestions will retrieve the word near the cursor, we don't want that here)
+            showSuggestions(builder.build(), "");
         } else {
-            abortRecorrection(true);
+            if (!isShowingPunctuationList()) setPunctuationSuggestions();
         }
     }
 
-    private void setPunctuationSuggestions() {
-        setSuggestions(mSuggestPuncList);
+    public void setPunctuationSuggestions() {
+        setSuggestions(mSettingsValues.mSuggestPuncList);
         setCandidatesViewShown(isCandidateStripVisible());
     }
 
@@ -1835,27 +1701,30 @@
         }
 
         if (mUserBigramDictionary != null) {
+            // We don't want to register as bigrams words separated by a separator.
+            // For example "I will, and you too" : we don't want the pair ("will" "and") to be
+            // a bigram.
             CharSequence prevWord = EditingUtils.getPreviousWord(getCurrentInputConnection(),
-                    mSentenceSeparators);
+                    mSettingsValues.mWordSeparators);
             if (!TextUtils.isEmpty(prevWord)) {
                 mUserBigramDictionary.addBigrams(prevWord.toString(), suggestion.toString());
             }
         }
     }
 
-    private boolean isCursorTouchingWord() {
+    public boolean isCursorTouchingWord() {
         InputConnection ic = getCurrentInputConnection();
         if (ic == null) return false;
         CharSequence toLeft = ic.getTextBeforeCursor(1, 0);
         CharSequence toRight = ic.getTextAfterCursor(1, 0);
         if (!TextUtils.isEmpty(toLeft)
-                && !isWordSeparator(toLeft.charAt(0))
-                && !isSuggestedPunctuation(toLeft.charAt(0))) {
+                && !mSettingsValues.isWordSeparator(toLeft.charAt(0))
+                && !mSettingsValues.isSuggestedPunctuation(toLeft.charAt(0))) {
             return true;
         }
         if (!TextUtils.isEmpty(toRight)
-                && !isWordSeparator(toRight.charAt(0))
-                && !isSuggestedPunctuation(toRight.charAt(0))) {
+                && !mSettingsValues.isWordSeparator(toRight.charAt(0))
+                && !mSettingsValues.isSuggestedPunctuation(toRight.charAt(0))) {
             return true;
         }
         return false;
@@ -1868,53 +1737,49 @@
 
     public void revertLastWord(boolean deleteChar) {
         final int length = mComposing.length();
-        if (!mHasValidSuggestions && length > 0) {
+        if (!mHasUncommittedTypedChars && length > 0) {
             final InputConnection ic = getCurrentInputConnection();
             final CharSequence punctuation = ic.getTextBeforeCursor(1, 0);
             if (deleteChar) ic.deleteSurroundingText(1, 0);
             int toDelete = mCommittedLength;
             final CharSequence toTheLeft = ic.getTextBeforeCursor(mCommittedLength, 0);
-            if (!TextUtils.isEmpty(toTheLeft) && isWordSeparator(toTheLeft.charAt(0))) {
+            if (!TextUtils.isEmpty(toTheLeft)
+                    && mSettingsValues.isWordSeparator(toTheLeft.charAt(0))) {
                 toDelete--;
             }
             ic.deleteSurroundingText(toDelete, 0);
             // Re-insert punctuation only when the deleted character was word separator and the
             // composing text wasn't equal to the auto-corrected text.
             if (deleteChar
-                    && !TextUtils.isEmpty(punctuation) && isWordSeparator(punctuation.charAt(0))
+                    && !TextUtils.isEmpty(punctuation)
+                    && mSettingsValues.isWordSeparator(punctuation.charAt(0))
                     && !TextUtils.equals(mComposing, toTheLeft)) {
                 ic.commitText(mComposing, 1);
                 TextEntryState.acceptedTyped(mComposing);
                 ic.commitText(punctuation, 1);
-                TextEntryState.typedCharacter(punctuation.charAt(0), true);
+                TextEntryState.typedCharacter(punctuation.charAt(0), true,
+                        WordComposer.NOT_A_COORDINATE, WordComposer.NOT_A_COORDINATE);
                 // Clear composing text
                 mComposing.setLength(0);
             } else {
-                mHasValidSuggestions = true;
+                mHasUncommittedTypedChars = true;
                 ic.setComposingText(mComposing, 1);
                 TextEntryState.backspace();
             }
+            mHandler.cancelUpdateBigramPredictions();
             mHandler.postUpdateSuggestions();
         } else {
             sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL);
         }
     }
 
-    protected String getWordSeparators() {
-        return mWordSeparators;
-    }
-
     public boolean isWordSeparator(int code) {
-        String separators = getWordSeparators();
-        return separators.contains(String.valueOf((char)code));
+        return mSettingsValues.isWordSeparator(code);
     }
 
-    private boolean isSentenceSeparator(int code) {
-        return mSentenceSeparators.contains(String.valueOf((char)code));
-    }
-
-    private void sendSpace() {
+    private void sendMagicSpace() {
         sendKeyChar((char)Keyboard.CODE_SPACE);
+        mJustAddedMagicSpace = true;
         mKeyboardSwitcher.updateShiftState();
     }
 
@@ -1922,28 +1787,31 @@
         return mWord.isFirstCharCapitalized();
     }
 
-    // Notify that language or mode have been changed and toggleLanguage will update KeyboaredID
+    // Notify that language or mode have been changed and toggleLanguage will update KeyboardID
     // according to new language or mode.
     public void onRefreshKeyboard() {
-        toggleLanguage(true, true);
-    }
-
-    // "reset" and "next" are used only for USE_SPACEBAR_LANGUAGE_SWITCHER.
-    private void toggleLanguage(boolean reset, boolean next) {
-        if (mSubtypeSwitcher.useSpacebarLanguageSwitcher()) {
-            mSubtypeSwitcher.toggleLanguage(reset, next);
-        }
         // Reload keyboard because the current language has been changed.
         mKeyboardSwitcher.loadKeyboard(getCurrentInputEditorInfo(),
-                mSubtypeSwitcher.isShortcutImeEnabled() && mVoiceConnector.isVoiceButtonEnabled(),
-                mVoiceConnector.isVoiceButtonOnPrimary());
+                mSubtypeSwitcher.isShortcutImeEnabled() && mVoiceProxy.isVoiceButtonEnabled(),
+                mVoiceProxy.isVoiceButtonOnPrimary());
         initSuggest();
+        loadSettings();
         mKeyboardSwitcher.updateShiftState();
     }
 
+    // "reset" and "next" are used only for USE_SPACEBAR_LANGUAGE_SWITCHER.
+    private void toggleLanguage(boolean next) {
+        if (mSubtypeSwitcher.useSpacebarLanguageSwitcher()) {
+            mSubtypeSwitcher.toggleLanguage(next);
+        }
+        // The following is necessary because on API levels < 10, we don't get notified when
+        // subtype changes.
+        onRefreshKeyboard();
+     }
+
     @Override
     public void onSwipeDown() {
-        if (mConfigSwipeDownDismissKeyboardEnabled)
+        if (mSettingsValues.mSwipeDownDismissKeyboardEnabled)
             handleClose();
     }
 
@@ -1962,21 +1830,18 @@
         } else {
             switcher.onOtherKeyPressed();
         }
-        mAccessibilityUtils.onPress(primaryCode, switcher);
     }
 
     @Override
     public void onRelease(int primaryCode, boolean withSliding) {
         KeyboardSwitcher switcher = mKeyboardSwitcher;
         // Reset any drag flags in the keyboard
-        switcher.keyReleased();
         final boolean distinctMultiTouch = switcher.hasDistinctMultitouch();
         if (distinctMultiTouch && primaryCode == Keyboard.CODE_SHIFT) {
             switcher.onReleaseShift(withSliding);
         } else if (distinctMultiTouch && primaryCode == Keyboard.CODE_SWITCH_ALPHA_SYMBOL) {
             switcher.onReleaseSymbol();
         }
-        mAccessibilityUtils.onRelease(primaryCode, switcher);
     }
 
 
@@ -1999,7 +1864,7 @@
             mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
         }
         if (mAudioManager != null) {
-            mSilentMode = (mAudioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL);
+            mSilentModeOn = (mAudioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL);
         }
     }
 
@@ -2011,7 +1876,7 @@
                 updateRingerMode();
             }
         }
-        if (mSoundOn && !mSilentMode) {
+        if (isSoundOn()) {
             // FIXME: Volume and enable should come from UI settings
             // FIXME: These should be triggered after auto-repeat logic
             int sound = AudioManager.FX_KEYPRESS_STANDARD;
@@ -2031,7 +1896,7 @@
     }
 
     public void vibrate() {
-        if (!mVibrateOn) {
+        if (!mSettingsValues.mVibrateOn) {
             return;
         }
         LatinKeyboardView inputView = mKeyboardSwitcher.getInputView();
@@ -2052,18 +1917,22 @@
     }
 
     public boolean getPopupOn() {
-        return mPopupOn;
+        return mSettingsValues.mPopupOn;
+    }
+    boolean isSoundOn() {
+        return mSettingsValues.mSoundOn && !mSilentModeOn;
     }
 
     private void updateCorrectionMode() {
         // TODO: cleanup messy flags
         mHasDictionary = mSuggest != null ? mSuggest.hasMainDictionary() : false;
-        mAutoCorrectOn = (mAutoCorrectEnabled || mQuickFixes)
-                && !mInputTypeNoAutoCorrect && mHasDictionary;
-        mCorrectionMode = (mAutoCorrectOn && mAutoCorrectEnabled)
+        final boolean shouldAutoCorrect = (mSettingsValues.mAutoCorrectEnabled
+                || mSettingsValues.mQuickFixes) && !mInputTypeNoAutoCorrect && mHasDictionary;
+        mCorrectionMode = (shouldAutoCorrect && mSettingsValues.mAutoCorrectEnabled)
                 ? Suggest.CORRECTION_FULL
-                : (mAutoCorrectOn ? Suggest.CORRECTION_BASIC : Suggest.CORRECTION_NONE);
-        mCorrectionMode = (mBigramSuggestionEnabled && mAutoCorrectOn && mAutoCorrectEnabled)
+                : (shouldAutoCorrect ? Suggest.CORRECTION_BASIC : Suggest.CORRECTION_NONE);
+        mCorrectionMode = (mSettingsValues.mBigramSuggestionEnabled && shouldAutoCorrect
+                && mSettingsValues.mAutoCorrectEnabled)
                 ? Suggest.CORRECTION_FULL_BIGRAM : mCorrectionMode;
         if (mSuggest != null) {
             mSuggest.setCorrectionMode(mCorrectionMode);
@@ -2072,12 +1941,11 @@
 
     private void updateAutoTextEnabled() {
         if (mSuggest == null) return;
-        mSuggest.setQuickFixesEnabled(mQuickFixes
+        mSuggest.setQuickFixesEnabled(mSettingsValues.mQuickFixes
                 && SubtypeSwitcher.getInstance().isSystemLanguageSameAsInputLanguage());
     }
 
-    private void updateSuggestionVisibility(SharedPreferences prefs) {
-        final Resources res = mResources;
+    private void updateSuggestionVisibility(final SharedPreferences prefs, final Resources res) {
         final String suggestionVisiblityStr = prefs.getString(
                 Settings.PREF_SHOW_SUGGESTIONS_SETTING,
                 res.getString(R.string.prefs_suggestion_visibility_default_value));
@@ -2105,121 +1973,6 @@
         startActivity(intent);
     }
 
-    private void loadSettings(EditorInfo attribute) {
-        // Get the settings preferences
-        final SharedPreferences prefs = mPrefs;
-        Vibrator vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
-        mVibrateOn = vibrator != null && vibrator.hasVibrator()
-                && prefs.getBoolean(Settings.PREF_VIBRATE_ON, false);
-        mSoundOn = prefs.getBoolean(Settings.PREF_SOUND_ON,
-                mResources.getBoolean(R.bool.config_default_sound_enabled));
-
-        mPopupOn = isPopupEnabled(prefs);
-        mAutoCap = prefs.getBoolean(Settings.PREF_AUTO_CAP, true);
-        mQuickFixes = isQuickFixesEnabled(prefs);
-
-        mAutoCorrectEnabled = isAutoCorrectEnabled(prefs);
-        mBigramSuggestionEnabled = mAutoCorrectEnabled && isBigramSuggestionEnabled(prefs);
-        loadAndSetAutoCorrectionThreshold(prefs);
-
-        mVoiceConnector.loadSettings(attribute, prefs);
-
-        updateCorrectionMode();
-        updateAutoTextEnabled();
-        updateSuggestionVisibility(prefs);
-        SubtypeSwitcher.getInstance().loadSettings();
-    }
-
-    /**
-     *  Load Auto correction threshold from SharedPreferences, and modify mSuggest's threshold.
-     */
-    private void loadAndSetAutoCorrectionThreshold(SharedPreferences sp) {
-        // When mSuggest is not initialized, cannnot modify mSuggest's threshold.
-        if (mSuggest == null) return;
-        // When auto correction setting is turned off, the threshold is ignored.
-        if (!isAutoCorrectEnabled(sp)) return;
-
-        final String currentAutoCorrectionSetting = sp.getString(
-                Settings.PREF_AUTO_CORRECTION_THRESHOLD,
-                mResources.getString(R.string.auto_correction_threshold_mode_index_modest));
-        final String[] autoCorrectionThresholdValues = mResources.getStringArray(
-                R.array.auto_correction_threshold_values);
-        // When autoCrrectionThreshold is greater than 1.0, auto correction is virtually turned off.
-        double autoCorrectionThreshold = Double.MAX_VALUE;
-        try {
-            final int arrayIndex = Integer.valueOf(currentAutoCorrectionSetting);
-            if (arrayIndex >= 0 && arrayIndex < autoCorrectionThresholdValues.length) {
-                autoCorrectionThreshold = Double.parseDouble(
-                        autoCorrectionThresholdValues[arrayIndex]);
-            }
-        } catch (NumberFormatException e) {
-            // Whenever the threshold settings are correct, never come here.
-            autoCorrectionThreshold = Double.MAX_VALUE;
-            Log.w(TAG, "Cannot load auto correction threshold setting."
-                    + " currentAutoCorrectionSetting: " + currentAutoCorrectionSetting
-                    + ", autoCorrectionThresholdValues: "
-                    + Arrays.toString(autoCorrectionThresholdValues));
-        }
-        // TODO: This should be refactored :
-        //           setAutoCorrectionThreshold should be called outside of this method.
-        mSuggest.setAutoCorrectionThreshold(autoCorrectionThreshold);
-    }
-
-    private boolean isPopupEnabled(SharedPreferences sp) {
-        final boolean showPopupOption = getResources().getBoolean(
-                R.bool.config_enable_show_popup_on_keypress_option);
-        if (!showPopupOption) return mResources.getBoolean(R.bool.config_default_popup_preview);
-        return sp.getBoolean(Settings.PREF_POPUP_ON,
-                mResources.getBoolean(R.bool.config_default_popup_preview));
-    }
-
-    private boolean isQuickFixesEnabled(SharedPreferences sp) {
-        final boolean showQuickFixesOption = mResources.getBoolean(
-                R.bool.config_enable_quick_fixes_option);
-        if (!showQuickFixesOption) {
-            return isAutoCorrectEnabled(sp);
-        }
-        return sp.getBoolean(Settings.PREF_QUICK_FIXES, mResources.getBoolean(
-                R.bool.config_default_quick_fixes));
-    }
-
-    private boolean isAutoCorrectEnabled(SharedPreferences sp) {
-        final String currentAutoCorrectionSetting = sp.getString(
-                Settings.PREF_AUTO_CORRECTION_THRESHOLD,
-                mResources.getString(R.string.auto_correction_threshold_mode_index_modest));
-        final String autoCorrectionOff = mResources.getString(
-                R.string.auto_correction_threshold_mode_index_off);
-        return !currentAutoCorrectionSetting.equals(autoCorrectionOff);
-    }
-
-    private boolean isBigramSuggestionEnabled(SharedPreferences sp) {
-        final boolean showBigramSuggestionsOption = mResources.getBoolean(
-                R.bool.config_enable_bigram_suggestions_option);
-        if (!showBigramSuggestionsOption) {
-            return isAutoCorrectEnabled(sp);
-        }
-        return sp.getBoolean(Settings.PREF_BIGRAM_SUGGESTIONS, mResources.getBoolean(
-                R.bool.config_default_bigram_suggestions));
-    }
-
-    private void initSuggestPuncList() {
-        if (mSuggestPuncs != null || mSuggestPuncList != null)
-            return;
-        SuggestedWords.Builder builder = new SuggestedWords.Builder();
-        String puncs = mResources.getString(R.string.suggested_punctuations);
-        if (puncs != null) {
-            for (int i = 0; i < puncs.length(); i++) {
-                builder.addWord(puncs.subSequence(i, i + 1));
-            }
-        }
-        mSuggestPuncList = builder.build();
-        mSuggestPuncs = puncs;
-    }
-
-    private boolean isSuggestedPunctuation(int code) {
-        return mSuggestPuncs.contains(String.valueOf((char)code));
-    }
-
     private void showSubtypeSelectorAndSettings() {
         final CharSequence title = getString(R.string.english_ime_input_options);
         final CharSequence[] items = new CharSequence[] {
@@ -2233,13 +1986,10 @@
                 di.dismiss();
                 switch (position) {
                 case 0:
-                    Intent intent = new Intent(
-                            android.provider.Settings.ACTION_INPUT_METHOD_SUBTYPE_SETTINGS);
-                    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+                    Intent intent = CompatUtils.getInputLanguageSelectionIntent(
+                            mInputMethodId, Intent.FLAG_ACTIVITY_NEW_TASK
                             | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
                             | Intent.FLAG_ACTIVITY_CLEAR_TOP);
-                    intent.putExtra(android.provider.Settings.EXTRA_INPUT_METHOD_ID,
-                            mInputMethodId);
                     startActivity(intent);
                     break;
                 case 1:
@@ -2305,14 +2055,14 @@
         p.println("  mComposing=" + mComposing.toString());
         p.println("  mIsSuggestionsRequested=" + mIsSettingsSuggestionStripOn);
         p.println("  mCorrectionMode=" + mCorrectionMode);
-        p.println("  mHasValidSuggestions=" + mHasValidSuggestions);
-        p.println("  mAutoCorrectOn=" + mAutoCorrectOn);
-        p.println("  mAutoSpace=" + mAutoSpace);
+        p.println("  mHasUncommittedTypedChars=" + mHasUncommittedTypedChars);
+        p.println("  mAutoCorrectEnabled=" + mSettingsValues.mAutoCorrectEnabled);
+        p.println("  mShouldInsertMagicSpace=" + mShouldInsertMagicSpace);
         p.println("  mApplicationSpecifiedCompletionOn=" + mApplicationSpecifiedCompletionOn);
         p.println("  TextEntryState.state=" + TextEntryState.getState());
-        p.println("  mSoundOn=" + mSoundOn);
-        p.println("  mVibrateOn=" + mVibrateOn);
-        p.println("  mPopupOn=" + mPopupOn);
+        p.println("  mSoundOn=" + mSettingsValues.mSoundOn);
+        p.println("  mVibrateOn=" + mSettingsValues.mVibrateOn);
+        p.println("  mPopupOn=" + mSettingsValues.mPopupOn);
     }
 
     // Characters per second measurement
@@ -2332,9 +2082,4 @@
         for (int i = 0; i < CPS_BUFFER_SIZE; i++) total += mCpsIntervals[i];
         System.out.println("CPS = " + ((CPS_BUFFER_SIZE * 1000f) / total));
     }
-
-    @Override
-    public void onCurrentInputMethodSubtypeChanged(InputMethodSubtype subtype) {
-        SubtypeSwitcher.getInstance().updateSubtype(subtype);
-    }
 }
diff --git a/java/src/com/android/inputmethod/latin/LatinImeLogger.java b/java/src/com/android/inputmethod/latin/LatinImeLogger.java
index aaecfff..e460471 100644
--- a/java/src/com/android/inputmethod/latin/LatinImeLogger.java
+++ b/java/src/com/android/inputmethod/latin/LatinImeLogger.java
@@ -45,10 +45,10 @@
             String before, String after, int position, List<CharSequence> suggestions) {
    }
 
-    public static void logOnAutoSuggestion(String before, String after) {
+    public static void logOnAutoCorrection(String before, String after, int separatorCode) {
     }
 
-    public static void logOnAutoSuggestionCanceled() {
+    public static void logOnAutoCorrectionCancelled() {
     }
 
     public static void logOnDelete() {
@@ -57,6 +57,9 @@
     public static void logOnInputChar() {
     }
 
+    public static void logOnInputSeparator() {
+    }
+
     public static void logOnException(String metaData, Throwable e) {
     }
 
diff --git a/java/src/com/android/inputmethod/latin/PrivateBinaryDictionaryGetter.java b/java/src/com/android/inputmethod/latin/PrivateBinaryDictionaryGetter.java
new file mode 100644
index 0000000..eb740e1
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/PrivateBinaryDictionaryGetter.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import android.content.Context;
+
+import java.util.List;
+import java.util.Locale;
+
+class PrivateBinaryDictionaryGetter {
+    private PrivateBinaryDictionaryGetter() {}
+    public static List<AssetFileAddress> getDictionaryFiles(Locale locale, Context context) {
+        return null;
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/Recorrection.java b/java/src/com/android/inputmethod/latin/Recorrection.java
new file mode 100644
index 0000000..3fa6292
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/Recorrection.java
@@ -0,0 +1,255 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import com.android.inputmethod.compat.InputConnectionCompatUtils;
+import com.android.inputmethod.deprecated.VoiceProxy;
+import com.android.inputmethod.keyboard.KeyboardSwitcher;
+
+import android.content.SharedPreferences;
+import android.content.res.Resources;
+import android.text.TextUtils;
+import android.view.inputmethod.ExtractedText;
+import android.view.inputmethod.ExtractedTextRequest;
+import android.view.inputmethod.InputConnection;
+
+import java.util.ArrayList;
+
+/**
+ * Manager of re-correction functionalities
+ */
+public class Recorrection {
+    private static final Recorrection sInstance = new Recorrection();
+
+    private LatinIME mService;
+    private boolean mRecorrectionEnabled = false;
+    private final ArrayList<WordAlternatives> mWordHistory = new ArrayList<WordAlternatives>();
+
+    public static Recorrection getInstance() {
+        return sInstance;
+    }
+
+    public static void init(LatinIME context, SharedPreferences prefs) {
+        if (context == null || prefs == null) {
+            return;
+        }
+        sInstance.initInternal(context, prefs);
+    }
+
+    private Recorrection() {
+    }
+
+    public boolean isRecorrectionEnabled() {
+        return mRecorrectionEnabled;
+    }
+
+    private void initInternal(LatinIME context, SharedPreferences prefs) {
+        final Resources res = context.getResources();
+        // If the option should not be shown, do not read the re-correction preference
+        // but always use the default setting defined in the resources.
+        if (res.getBoolean(R.bool.config_enable_show_recorrection_option)) {
+            mRecorrectionEnabled = prefs.getBoolean(Settings.PREF_RECORRECTION_ENABLED,
+                    res.getBoolean(R.bool.config_default_recorrection_enabled));
+        } else {
+            mRecorrectionEnabled = res.getBoolean(R.bool.config_default_recorrection_enabled);
+        }
+        mService = context;
+    }
+
+    public void checkRecorrectionOnStart() {
+        if (!mRecorrectionEnabled) return;
+
+        final InputConnection ic = mService.getCurrentInputConnection();
+        if (ic == null) return;
+        // There could be a pending composing span.  Clean it up first.
+        ic.finishComposingText();
+
+        if (mService.isShowingSuggestionsStrip() && mService.isSuggestionsRequested()) {
+            // First get the cursor position. This is required by setOldSuggestions(), so that
+            // it can pass the correct range to setComposingRegion(). At this point, we don't
+            // have valid values for mLastSelectionStart/End because onUpdateSelection() has
+            // not been called yet.
+            ExtractedTextRequest etr = new ExtractedTextRequest();
+            etr.token = 0; // anything is fine here
+            ExtractedText et = ic.getExtractedText(etr, 0);
+            if (et == null) return;
+            mService.setLastSelection(
+                    et.startOffset + et.selectionStart, et.startOffset + et.selectionEnd);
+
+            // Then look for possible corrections in a delayed fashion
+            if (!TextUtils.isEmpty(et.text) && mService.isCursorTouchingWord()) {
+                mService.mHandler.postUpdateOldSuggestions();
+            }
+        }
+    }
+
+    public void updateRecorrectionSelection(KeyboardSwitcher keyboardSwitcher,
+            CandidateView candidateView, int candidatesStart, int candidatesEnd, int newSelStart,
+            int newSelEnd, int oldSelStart, int lastSelectionStart,
+            int lastSelectionEnd, boolean hasUncommittedTypedChars) {
+        if (mRecorrectionEnabled && mService.isShowingSuggestionsStrip()) {
+            // Don't look for corrections if the keyboard is not visible
+            if (keyboardSwitcher.isInputViewShown()) {
+                // Check if we should go in or out of correction mode.
+                if (mService.isSuggestionsRequested()
+                        && (candidatesStart == candidatesEnd || newSelStart != oldSelStart
+                                || TextEntryState.isRecorrecting())
+                                && (newSelStart < newSelEnd - 1 || !hasUncommittedTypedChars)) {
+                    if (mService.isCursorTouchingWord() || lastSelectionStart < lastSelectionEnd) {
+                        mService.mHandler.cancelUpdateBigramPredictions();
+                        mService.mHandler.postUpdateOldSuggestions();
+                    } else {
+                        abortRecorrection(false);
+                        // If showing the "touch again to save" hint, do not replace it. Else,
+                        // show the bigrams if we are at the end of the text, punctuation otherwise.
+                        if (candidateView != null
+                                && !candidateView.isShowingAddToDictionaryHint()) {
+                            InputConnection ic = mService.getCurrentInputConnection();
+                            if (null == ic || !TextUtils.isEmpty(ic.getTextAfterCursor(1, 0))) {
+                                if (!mService.isShowingPunctuationList()) {
+                                    mService.setPunctuationSuggestions();
+                                }
+                            } else {
+                                mService.mHandler.postUpdateBigramPredictions();
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    public void saveWordInHistory(WordComposer word, CharSequence result) {
+        if (word.size() <= 1) {
+            return;
+        }
+        // Skip if result is null. It happens in some edge case.
+        if (TextUtils.isEmpty(result)) {
+            return;
+        }
+
+        // Make a copy of the CharSequence, since it is/could be a mutable CharSequence
+        final String resultCopy = result.toString();
+        WordAlternatives entry = new WordAlternatives(resultCopy, new WordComposer(word));
+        mWordHistory.add(entry);
+    }
+
+    public void clearWordsInHistory() {
+        mWordHistory.clear();
+    }
+
+    /**
+     * Tries to apply any typed alternatives for the word if we have any cached alternatives,
+     * otherwise tries to find new corrections and completions for the word.
+     * @param touching The word that the cursor is touching, with position information
+     * @return true if an alternative was found, false otherwise.
+     */
+    public boolean applyTypedAlternatives(WordComposer word, Suggest suggest,
+            KeyboardSwitcher keyboardSwitcher, EditingUtils.SelectedWord touching) {
+        // If we didn't find a match, search for result in typed word history
+        WordComposer foundWord = null;
+        WordAlternatives alternatives = null;
+        // Search old suggestions to suggest re-corrected suggestions.
+        for (WordAlternatives entry : mWordHistory) {
+            if (TextUtils.equals(entry.getChosenWord(), touching.mWord)) {
+                foundWord = entry.mWordComposer;
+                alternatives = entry;
+                break;
+            }
+        }
+        // If we didn't find a match, at least suggest corrections as re-corrected suggestions.
+        if (foundWord == null
+                && (AutoCorrection.isValidWord(suggest.getUnigramDictionaries(),
+                        touching.mWord, true))) {
+            foundWord = new WordComposer();
+            for (int i = 0; i < touching.mWord.length(); i++) {
+                foundWord.add(touching.mWord.charAt(i),
+                        new int[] { touching.mWord.charAt(i) }, WordComposer.NOT_A_COORDINATE,
+                        WordComposer.NOT_A_COORDINATE);
+            }
+            foundWord.setFirstCharCapitalized(Character.isUpperCase(touching.mWord.charAt(0)));
+        }
+        // Found a match, show suggestions
+        if (foundWord != null || alternatives != null) {
+            if (alternatives == null) {
+                alternatives = new WordAlternatives(touching.mWord, foundWord);
+            }
+            showRecorrections(suggest, keyboardSwitcher, alternatives);
+            if (foundWord != null) {
+                word.init(foundWord);
+            } else {
+                word.reset();
+            }
+            return true;
+        }
+        return false;
+    }
+
+
+    private void showRecorrections(Suggest suggest, KeyboardSwitcher keyboardSwitcher,
+            WordAlternatives alternatives) {
+        SuggestedWords.Builder builder = alternatives.getAlternatives(suggest, keyboardSwitcher);
+        builder.setTypedWordValid(false).setHasMinimalSuggestion(false);
+        mService.showSuggestions(builder.build(), alternatives.getOriginalWord());
+    }
+
+    public void setRecorrectionSuggestions(VoiceProxy voiceProxy, CandidateView candidateView,
+            Suggest suggest, KeyboardSwitcher keyboardSwitcher, WordComposer word,
+            boolean hasUncommittedTypedChars, int lastSelectionStart, int lastSelectionEnd,
+            String wordSeparators) {
+        if (!InputConnectionCompatUtils.RECORRECTION_SUPPORTED) return;
+        voiceProxy.setShowingVoiceSuggestions(false);
+        if (candidateView != null && candidateView.isShowingAddToDictionaryHint()) {
+            return;
+        }
+        InputConnection ic = mService.getCurrentInputConnection();
+        if (ic == null) return;
+        if (!hasUncommittedTypedChars) {
+            // Extract the selected or touching text
+            EditingUtils.SelectedWord touching = EditingUtils.getWordAtCursorOrSelection(ic,
+                    lastSelectionStart, lastSelectionEnd, wordSeparators);
+
+            if (touching != null && touching.mWord.length() > 1) {
+                ic.beginBatchEdit();
+
+                if (applyTypedAlternatives(word, suggest, keyboardSwitcher, touching)
+                        || voiceProxy.applyVoiceAlternatives(touching)) {
+                    TextEntryState.selectedForRecorrection();
+                    InputConnectionCompatUtils.underlineWord(ic, touching);
+                } else {
+                    abortRecorrection(true);
+                }
+
+                ic.endBatchEdit();
+            } else {
+                abortRecorrection(true);
+                mService.setPunctuationSuggestions();  // Show the punctuation suggestions list
+            }
+        } else {
+            abortRecorrection(true);
+        }
+    }
+
+    public void abortRecorrection(boolean force) {
+        if (force || TextEntryState.isRecorrecting()) {
+            TextEntryState.onAbortRecorrection();
+            mService.setCandidatesViewShown(mService.isCandidateStripVisible());
+            mService.getCurrentInputConnection().finishComposingText();
+            mService.clearSuggestions();
+        }
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/Settings.java b/java/src/com/android/inputmethod/latin/Settings.java
index 341d5ad..5eb3657 100644
--- a/java/src/com/android/inputmethod/latin/Settings.java
+++ b/java/src/com/android/inputmethod/latin/Settings.java
@@ -16,17 +16,21 @@
 
 package com.android.inputmethod.latin;
 
-import com.android.inputmethod.voice.VoiceIMEConnector;
-import com.android.inputmethod.voice.VoiceInputLogger;
+import com.android.inputmethod.compat.CompatUtils;
+import com.android.inputmethod.compat.InputMethodManagerCompatWrapper;
+import com.android.inputmethod.compat.InputMethodServiceCompatWrapper;
+import com.android.inputmethod.deprecated.VoiceProxy;
+import com.android.inputmethod.compat.VibratorCompatWrapper;
 
 import android.app.AlertDialog;
 import android.app.Dialog;
 import android.app.backup.BackupManager;
+import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.SharedPreferences;
+import android.content.res.Resources;
 import android.os.Bundle;
-import android.os.Vibrator;
 import android.preference.CheckBoxPreference;
 import android.preference.ListPreference;
 import android.preference.Preference;
@@ -41,6 +45,7 @@
 import android.util.Log;
 import android.widget.TextView;
 
+import java.util.Arrays;
 import java.util.Locale;
 
 public class Settings extends PreferenceActivity
@@ -60,29 +65,223 @@
     public static final String PREF_SELECTED_LANGUAGES = "selected_languages";
     public static final String PREF_SUBTYPES = "subtype_settings";
 
-    public static final String PREF_PREDICTION_SETTINGS_KEY = "prediction_settings";
+    public static final String PREF_CORRECTION_SETTINGS_KEY = "correction_settings";
     public static final String PREF_QUICK_FIXES = "quick_fixes";
     public static final String PREF_SHOW_SUGGESTIONS_SETTING = "show_suggestions_setting";
     public static final String PREF_AUTO_CORRECTION_THRESHOLD = "auto_correction_threshold";
+    public static final String PREF_DEBUG_SETTINGS = "debug_settings";
+
+    public static final String PREF_NGRAM_SETTINGS_KEY = "ngram_settings";
     public static final String PREF_BIGRAM_SUGGESTIONS = "bigram_suggestion";
+    public static final String PREF_BIGRAM_PREDICTIONS = "bigram_prediction";
+
+    public static final String PREF_MISC_SETTINGS_KEY = "misc_settings";
 
     public static final String PREF_USABILITY_STUDY_MODE = "usability_study_mode";
 
     // Dialog ids
     private static final int VOICE_INPUT_CONFIRM_DIALOG = 0;
 
+    public static class Values {
+        // From resources:
+        public final boolean mEnableShowSubtypeSettings;
+        public final boolean mSwipeDownDismissKeyboardEnabled;
+        public final int mDelayBeforeFadeoutLanguageOnSpacebar;
+        public final int mDelayUpdateSuggestions;
+        public final int mDelayUpdateOldSuggestions;
+        public final int mDelayUpdateShiftState;
+        public final int mDurationOfFadeoutLanguageOnSpacebar;
+        public final float mFinalFadeoutFactorOfLanguageOnSpacebar;
+        public final long mDoubleSpacesTurnIntoPeriodTimeout;
+        public final String mWordSeparators;
+        public final String mMagicSpaceStrippers;
+        public final String mMagicSpaceSwappers;
+        public final String mSuggestPuncs;
+        public final SuggestedWords mSuggestPuncList;
+
+        // From preferences:
+        public final boolean mSoundOn; // Sound setting private to Latin IME (see mSilentModeOn)
+        public final boolean mVibrateOn;
+        public final boolean mPopupOn; // Warning : this escapes through LatinIME#isPopupOn
+        public final boolean mAutoCap;
+        public final boolean mQuickFixes;
+        public final boolean mAutoCorrectEnabled;
+        public final double mAutoCorrectionThreshold;
+        // Suggestion: use bigrams to adjust scores of suggestions obtained from unigram dictionary
+        public final boolean mBigramSuggestionEnabled;
+        // Prediction: use bigrams to predict the next word when there is no input for it yet
+        public final boolean mBigramPredictionEnabled;
+
+        public Values(final SharedPreferences prefs, final Context context,
+                final String localeStr) {
+            final Resources res = context.getResources();
+            final Locale savedLocale;
+            if (null != localeStr) {
+                final Locale keyboardLocale = new Locale(localeStr);
+                savedLocale = Utils.setSystemLocale(res, keyboardLocale);
+            } else {
+                savedLocale = null;
+            }
+
+            // Get the resources
+            mEnableShowSubtypeSettings = res.getBoolean(
+                    R.bool.config_enable_show_subtype_settings);
+            mSwipeDownDismissKeyboardEnabled = res.getBoolean(
+                    R.bool.config_swipe_down_dismiss_keyboard_enabled);
+            mDelayBeforeFadeoutLanguageOnSpacebar = res.getInteger(
+                    R.integer.config_delay_before_fadeout_language_on_spacebar);
+            mDelayUpdateSuggestions =
+                    res.getInteger(R.integer.config_delay_update_suggestions);
+            mDelayUpdateOldSuggestions = res.getInteger(
+                    R.integer.config_delay_update_old_suggestions);
+            mDelayUpdateShiftState =
+                    res.getInteger(R.integer.config_delay_update_shift_state);
+            mDurationOfFadeoutLanguageOnSpacebar = res.getInteger(
+                    R.integer.config_duration_of_fadeout_language_on_spacebar);
+            mFinalFadeoutFactorOfLanguageOnSpacebar = res.getInteger(
+                    R.integer.config_final_fadeout_percentage_of_language_on_spacebar) / 100.0f;
+            mDoubleSpacesTurnIntoPeriodTimeout = res.getInteger(
+                    R.integer.config_double_spaces_turn_into_period_timeout);
+            mMagicSpaceStrippers = res.getString(R.string.magic_space_stripping_symbols);
+            mMagicSpaceSwappers = res.getString(R.string.magic_space_swapping_symbols);
+            String wordSeparators = mMagicSpaceStrippers + mMagicSpaceSwappers
+                    + res.getString(R.string.magic_space_promoting_symbols);
+            final String notWordSeparators = res.getString(R.string.non_word_separator_symbols);
+            for (int i = notWordSeparators.length() - 1; i >= 0; --i) {
+                wordSeparators = wordSeparators.replace(notWordSeparators.substring(i, i + 1), "");
+            }
+            mWordSeparators = wordSeparators;
+            mSuggestPuncs = res.getString(R.string.suggested_punctuations);
+            // TODO: it would be nice not to recreate this each time we change the configuration
+            mSuggestPuncList = createSuggestPuncList(mSuggestPuncs);
+
+            // Get the settings preferences
+            final boolean hasVibrator = VibratorCompatWrapper.getInstance(context).hasVibrator();
+            mVibrateOn = hasVibrator && prefs.getBoolean(Settings.PREF_VIBRATE_ON, false);
+            mSoundOn = prefs.getBoolean(Settings.PREF_SOUND_ON,
+                    res.getBoolean(R.bool.config_default_sound_enabled));
+
+            mPopupOn = isPopupEnabled(prefs, res);
+            mAutoCap = prefs.getBoolean(Settings.PREF_AUTO_CAP, true);
+            mQuickFixes = isQuickFixesEnabled(prefs, res);
+
+            mAutoCorrectEnabled = isAutoCorrectEnabled(prefs, res);
+            mBigramSuggestionEnabled = mAutoCorrectEnabled
+                    && isBigramSuggestionEnabled(prefs, res, mAutoCorrectEnabled);
+            mBigramPredictionEnabled = mBigramSuggestionEnabled
+                    && isBigramPredictionEnabled(prefs, res);
+
+            mAutoCorrectionThreshold = getAutoCorrectionThreshold(prefs, res);
+        }
+
+        public boolean isSuggestedPunctuation(int code) {
+            return mSuggestPuncs.contains(String.valueOf((char)code));
+        }
+
+        public boolean isWordSeparator(int code) {
+            return mWordSeparators.contains(String.valueOf((char)code));
+        }
+
+        public boolean isMagicSpaceStripper(int code) {
+            return mMagicSpaceStrippers.contains(String.valueOf((char)code));
+        }
+
+        public boolean isMagicSpaceSwapper(int code) {
+            return mMagicSpaceSwappers.contains(String.valueOf((char)code));
+        }
+
+        // Helper methods
+        private static boolean isQuickFixesEnabled(SharedPreferences sp, Resources resources) {
+            final boolean showQuickFixesOption = resources.getBoolean(
+                    R.bool.config_enable_quick_fixes_option);
+            if (!showQuickFixesOption) {
+                return isAutoCorrectEnabled(sp, resources);
+            }
+            return sp.getBoolean(Settings.PREF_QUICK_FIXES, resources.getBoolean(
+                    R.bool.config_default_quick_fixes));
+        }
+        private static boolean isAutoCorrectEnabled(SharedPreferences sp, Resources resources) {
+            final String currentAutoCorrectionSetting = sp.getString(
+                    Settings.PREF_AUTO_CORRECTION_THRESHOLD,
+                    resources.getString(R.string.auto_correction_threshold_mode_index_modest));
+            final String autoCorrectionOff = resources.getString(
+                    R.string.auto_correction_threshold_mode_index_off);
+            return !currentAutoCorrectionSetting.equals(autoCorrectionOff);
+        }
+        private static boolean isPopupEnabled(SharedPreferences sp, Resources resources) {
+            final boolean showPopupOption = resources.getBoolean(
+                    R.bool.config_enable_show_popup_on_keypress_option);
+            if (!showPopupOption) return resources.getBoolean(R.bool.config_default_popup_preview);
+            return sp.getBoolean(Settings.PREF_POPUP_ON,
+                    resources.getBoolean(R.bool.config_default_popup_preview));
+        }
+        private static boolean isBigramSuggestionEnabled(SharedPreferences sp, Resources resources,
+                boolean autoCorrectEnabled) {
+            final boolean showBigramSuggestionsOption = resources.getBoolean(
+                    R.bool.config_enable_bigram_suggestions_option);
+            if (!showBigramSuggestionsOption) {
+                return autoCorrectEnabled;
+            }
+            return sp.getBoolean(Settings.PREF_BIGRAM_SUGGESTIONS, resources.getBoolean(
+                    R.bool.config_default_bigram_suggestions));
+        }
+        private static boolean isBigramPredictionEnabled(SharedPreferences sp,
+                Resources resources) {
+            return sp.getBoolean(Settings.PREF_BIGRAM_PREDICTIONS, resources.getBoolean(
+                    R.bool.config_default_bigram_prediction));
+        }
+        private static double getAutoCorrectionThreshold(SharedPreferences sp,
+                Resources resources) {
+            final String currentAutoCorrectionSetting = sp.getString(
+                    Settings.PREF_AUTO_CORRECTION_THRESHOLD,
+                    resources.getString(R.string.auto_correction_threshold_mode_index_modest));
+            final String[] autoCorrectionThresholdValues = resources.getStringArray(
+                    R.array.auto_correction_threshold_values);
+            // When autoCorrectionThreshold is greater than 1.0, it's like auto correction is off.
+            double autoCorrectionThreshold = Double.MAX_VALUE;
+            try {
+                final int arrayIndex = Integer.valueOf(currentAutoCorrectionSetting);
+                if (arrayIndex >= 0 && arrayIndex < autoCorrectionThresholdValues.length) {
+                    autoCorrectionThreshold = Double.parseDouble(
+                            autoCorrectionThresholdValues[arrayIndex]);
+                }
+            } catch (NumberFormatException e) {
+                // Whenever the threshold settings are correct, never come here.
+                autoCorrectionThreshold = Double.MAX_VALUE;
+                Log.w(TAG, "Cannot load auto correction threshold setting."
+                        + " currentAutoCorrectionSetting: " + currentAutoCorrectionSetting
+                        + ", autoCorrectionThresholdValues: "
+                        + Arrays.toString(autoCorrectionThresholdValues));
+            }
+            return autoCorrectionThreshold;
+        }
+        private static SuggestedWords createSuggestPuncList(final String puncs) {
+            SuggestedWords.Builder builder = new SuggestedWords.Builder();
+            if (puncs != null) {
+                for (int i = 0; i < puncs.length(); i++) {
+                    builder.addWord(puncs.subSequence(i, i + 1));
+                }
+            }
+            return builder.build();
+        }
+    }
+
     private PreferenceScreen mInputLanguageSelection;
     private CheckBoxPreference mQuickFixes;
     private ListPreference mVoicePreference;
     private ListPreference mSettingsKeyPreference;
     private ListPreference mShowCorrectionSuggestionsPreference;
     private ListPreference mAutoCorrectionThreshold;
+    // Suggestion: use bigrams to adjust scores of suggestions obtained from unigram dictionary
     private CheckBoxPreference mBigramSuggestion;
+    // Prediction: use bigrams to predict the next word when there is no input for it yet
+    private CheckBoxPreference mBigramPrediction;
+    private Preference mDebugSettingsPreference;
     private boolean mVoiceOn;
 
     private AlertDialog mDialog;
 
-    private VoiceInputLogger mLogger;
+    private VoiceProxy.VoiceLoggerWrapper mVoiceLogger;
 
     private boolean mOkClicked = false;
     private String mVoiceModeOff;
@@ -92,6 +291,7 @@
                 R.string.auto_correction_threshold_mode_index_off);
         final String currentSetting = mAutoCorrectionThreshold.getValue();
         mBigramSuggestion.setEnabled(!currentSetting.equals(autoCorrectionOff));
+        mBigramPrediction.setEnabled(!currentSetting.equals(autoCorrectionOff));
     }
 
     @Override
@@ -111,16 +311,26 @@
         mVoiceModeOff = getString(R.string.voice_mode_off);
         mVoiceOn = !(prefs.getString(PREF_VOICE_SETTINGS_KEY, mVoiceModeOff)
                 .equals(mVoiceModeOff));
-        mLogger = VoiceInputLogger.getLogger(this);
+        mVoiceLogger = VoiceProxy.VoiceLoggerWrapper.getInstance(this);
 
         mAutoCorrectionThreshold = (ListPreference) findPreference(PREF_AUTO_CORRECTION_THRESHOLD);
         mBigramSuggestion = (CheckBoxPreference) findPreference(PREF_BIGRAM_SUGGESTIONS);
+        mBigramPrediction = (CheckBoxPreference) findPreference(PREF_BIGRAM_PREDICTIONS);
+        mDebugSettingsPreference = findPreference(PREF_DEBUG_SETTINGS);
+        if (mDebugSettingsPreference != null) {
+            final Intent debugSettingsIntent = new Intent(Intent.ACTION_MAIN);
+            debugSettingsIntent.setClassName(getPackageName(), DebugSettings.class.getName());
+            mDebugSettingsPreference.setIntent(debugSettingsIntent);
+        }
+
         ensureConsistencyOfAutoCorrectionSettings();
 
         final PreferenceGroup generalSettings =
                 (PreferenceGroup) findPreference(PREF_GENERAL_SETTINGS_KEY);
         final PreferenceGroup textCorrectionGroup =
-                (PreferenceGroup) findPreference(PREF_PREDICTION_SETTINGS_KEY);
+                (PreferenceGroup) findPreference(PREF_CORRECTION_SETTINGS_KEY);
+        final PreferenceGroup bigramGroup =
+                (PreferenceGroup) findPreference(PREF_NGRAM_SETTINGS_KEY);
 
         final boolean showSettingsKeyOption = getResources().getBoolean(
                 R.bool.config_enable_show_settings_key_option);
@@ -134,14 +344,14 @@
             generalSettings.removePreference(mVoicePreference);
         }
 
-        Vibrator vibrator = (Vibrator) getSystemService(VIBRATOR_SERVICE);
-        if (vibrator == null || !vibrator.hasVibrator()) {
+        if (!VibratorCompatWrapper.getInstance(this).hasVibrator()) {
             generalSettings.removePreference(findPreference(PREF_VIBRATE_ON));
         }
 
         final boolean showSubtypeSettings = getResources().getBoolean(
                 R.bool.config_enable_show_subtype_settings);
-        if (!showSubtypeSettings) {
+        if (InputMethodServiceCompatWrapper.CAN_HANDLE_ON_CURRENT_INPUT_METHOD_SUBTYPE_CHANGED
+                && !showSubtypeSettings) {
             generalSettings.removePreference(findPreference(PREF_SUBTYPES));
         }
 
@@ -167,6 +377,7 @@
                 R.bool.config_enable_bigram_suggestions_option);
         if (!showBigramSuggestionsOption) {
             textCorrectionGroup.removePreference(findPreference(PREF_BIGRAM_SUGGESTIONS));
+            textCorrectionGroup.removePreference(findPreference(PREF_BIGRAM_PREDICTIONS));
         }
 
         final boolean showUsabilityModeStudyOption = getResources().getBoolean(
@@ -181,10 +392,10 @@
         super.onResume();
         int autoTextSize = AutoText.getSize(getListView());
         if (autoTextSize < 1) {
-            ((PreferenceGroup) findPreference(PREF_PREDICTION_SETTINGS_KEY))
+            ((PreferenceGroup) findPreference(PREF_CORRECTION_SETTINGS_KEY))
                     .removePreference(mQuickFixes);
         }
-        if (!VoiceIMEConnector.VOICE_INSTALLED
+        if (!VoiceProxy.VOICE_INSTALLED
                 || !SpeechRecognizer.isRecognitionAvailable(this)) {
             getPreferenceScreen().removePreference(mVoicePreference);
         } else {
@@ -222,16 +433,9 @@
     @Override
     public boolean onPreferenceClick(Preference pref) {
         if (pref == mInputLanguageSelection) {
-            final String action;
-            if (android.os.Build.VERSION.SDK_INT
-                    >= /* android.os.Build.VERSION_CODES.HONEYCOMB */ 11) {
-                // Refer to android.provider.Settings.ACTION_INPUT_METHOD_SUBTYPE_SETTINGS
-                // TODO: Can this be a constant instead of literal String constant?
-                action = "android.settings.INPUT_METHOD_SUBTYPE_SETTINGS";
-            } else {
-                action = "com.android.inputmethod.latin.INPUT_LANGUAGE_SELECTION";
-            }
-            startActivity(new Intent(action));
+            startActivity(CompatUtils.getInputLanguageSelectionIntent(
+                    Utils.getInputMethodId(InputMethodManagerCompatWrapper.getInstance(this),
+                            getApplicationInfo().packageName), 0));
             return true;
         }
         return false;
@@ -277,10 +481,10 @@
                     public void onClick(DialogInterface dialog, int whichButton) {
                         if (whichButton == DialogInterface.BUTTON_NEGATIVE) {
                             mVoicePreference.setValue(mVoiceModeOff);
-                            mLogger.settingsWarningDialogCancel();
+                            mVoiceLogger.settingsWarningDialogCancel();
                         } else if (whichButton == DialogInterface.BUTTON_POSITIVE) {
                             mOkClicked = true;
-                            mLogger.settingsWarningDialogOk();
+                            mVoiceLogger.settingsWarningDialogOk();
                         }
                         updateVoicePreference();
                     }
@@ -311,7 +515,7 @@
                 AlertDialog dialog = builder.create();
                 mDialog = dialog;
                 dialog.setOnDismissListener(this);
-                mLogger.settingsWarningDialogShown();
+                mVoiceLogger.settingsWarningDialogShown();
                 return dialog;
             default:
                 Log.e(TAG, "unknown dialog " + id);
@@ -321,7 +525,7 @@
 
     @Override
     public void onDismiss(DialogInterface dialog) {
-        mLogger.settingsWarningDialogDismissed();
+        mVoiceLogger.settingsWarningDialogDismissed();
         if (!mOkClicked) {
             // This assumes that onPreferenceClick gets called first, and this if the user
             // agreed after the warning, we set the mOkClicked value to true.
@@ -331,10 +535,6 @@
 
     private void updateVoicePreference() {
         boolean isChecked = !mVoicePreference.getValue().equals(mVoiceModeOff);
-        if (isChecked) {
-            mLogger.voiceInputSettingEnabled();
-        } else {
-            mLogger.voiceInputSettingDisabled();
-        }
+        mVoiceLogger.voiceInputSettingEnabled(isChecked);
     }
 }
diff --git a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
index dc14d77..d801208 100644
--- a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
+++ b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
@@ -16,11 +16,12 @@
 
 package com.android.inputmethod.latin;
 
+import com.android.inputmethod.compat.InputMethodInfoCompatWrapper;
+import com.android.inputmethod.compat.InputMethodManagerCompatWrapper;
+import com.android.inputmethod.compat.InputMethodSubtypeCompatWrapper;
+import com.android.inputmethod.deprecated.VoiceProxy;
 import com.android.inputmethod.keyboard.KeyboardSwitcher;
 import com.android.inputmethod.keyboard.LatinKeyboard;
-import com.android.inputmethod.voice.SettingsUtil;
-import com.android.inputmethod.voice.VoiceIMEConnector;
-import com.android.inputmethod.voice.VoiceInput;
 
 import android.content.Context;
 import android.content.Intent;
@@ -31,12 +32,10 @@
 import android.graphics.drawable.Drawable;
 import android.net.ConnectivityManager;
 import android.net.NetworkInfo;
+import android.os.AsyncTask;
 import android.os.IBinder;
 import android.text.TextUtils;
 import android.util.Log;
-import android.view.inputmethod.InputMethodInfo;
-import android.view.inputmethod.InputMethodManager;
-import android.view.inputmethod.InputMethodSubtype;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -53,32 +52,37 @@
     private static final String VOICE_MODE = "voice";
     private static final String SUBTYPE_EXTRAVALUE_REQUIRE_NETWORK_CONNECTIVITY =
             "requireNetworkConnectivity";
+    public static final String USE_SPACEBAR_LANGUAGE_SWITCH_KEY = "use_spacebar_language_switch";
+
     private final TextUtils.SimpleStringSplitter mLocaleSplitter =
             new TextUtils.SimpleStringSplitter(LOCALE_SEPARATER);
 
     private static final SubtypeSwitcher sInstance = new SubtypeSwitcher();
     private /* final */ LatinIME mService;
-    private /* final */ SharedPreferences mPrefs;
-    private /* final */ InputMethodManager mImm;
+    private /* final */ InputMethodManagerCompatWrapper mImm;
     private /* final */ Resources mResources;
     private /* final */ ConnectivityManager mConnectivityManager;
     private /* final */ boolean mConfigUseSpacebarLanguageSwitcher;
-    private final ArrayList<InputMethodSubtype> mEnabledKeyboardSubtypesOfCurrentInputMethod =
-            new ArrayList<InputMethodSubtype>();
+    private /* final */ SharedPreferences mPrefs;
+    private final ArrayList<InputMethodSubtypeCompatWrapper>
+            mEnabledKeyboardSubtypesOfCurrentInputMethod =
+                    new ArrayList<InputMethodSubtypeCompatWrapper>();
     private final ArrayList<String> mEnabledLanguagesOfCurrentInputMethod = new ArrayList<String>();
+    private final LanguageBarInfo mLanguageBarInfo = new LanguageBarInfo();
 
     /*-----------------------------------------------------------*/
     // Variants which should be changed only by reload functions.
     private boolean mNeedsToDisplayLanguage;
     private boolean mIsSystemLanguageSameAsInputLanguage;
-    private InputMethodInfo mShortcutInputMethodInfo;
-    private InputMethodSubtype mShortcutSubtype;
-    private List<InputMethodSubtype> mAllEnabledSubtypesOfCurrentInputMethod;
-    private InputMethodSubtype mCurrentSubtype;
+    private InputMethodInfoCompatWrapper mShortcutInputMethodInfo;
+    private InputMethodSubtypeCompatWrapper mShortcutSubtype;
+    private List<InputMethodSubtypeCompatWrapper> mAllEnabledSubtypesOfCurrentInputMethod;
+    private InputMethodSubtypeCompatWrapper mCurrentSubtype;
     private Locale mSystemLocale;
     private Locale mInputLocale;
     private String mInputLocaleStr;
-    private VoiceInput mVoiceInput;
+    private String mInputMethodId;
+    private VoiceProxy.VoiceInputWrapper mVoiceInputWrapper;
     /*-----------------------------------------------------------*/
 
     private boolean mIsNetworkConnected;
@@ -100,9 +104,8 @@
 
     private void initialize(LatinIME service, SharedPreferences prefs) {
         mService = service;
-        mPrefs = prefs;
         mResources = service.getResources();
-        mImm = (InputMethodManager) service.getSystemService(Context.INPUT_METHOD_SERVICE);
+        mImm = InputMethodManagerCompatWrapper.getInstance(service);
         mConnectivityManager = (ConnectivityManager) service.getSystemService(
                 Context.CONNECTIVITY_SERVICE);
         mEnabledKeyboardSubtypesOfCurrentInputMethod.clear();
@@ -112,15 +115,12 @@
         mInputLocaleStr = null;
         mCurrentSubtype = null;
         mAllEnabledSubtypesOfCurrentInputMethod = null;
-        // TODO: Voice input should be created here
-        mVoiceInput = null;
-        mConfigUseSpacebarLanguageSwitcher = mResources.getBoolean(
-                R.bool.config_use_spacebar_language_switcher);
-        if (mConfigUseSpacebarLanguageSwitcher)
-            initLanguageSwitcher(service);
+        mVoiceInputWrapper = null;
+        mPrefs = prefs;
 
         final NetworkInfo info = mConnectivityManager.getActiveNetworkInfo();
         mIsNetworkConnected = (info != null && info.isConnected());
+        mInputMethodId = Utils.getInputMethodId(mImm, service.getPackageName());
     }
 
     // Update all parameters stored in SubtypeSwitcher.
@@ -134,11 +134,10 @@
     // Update parameters which are changed outside LatinIME. This parameters affect UI so they
     // should be updated every time onStartInputview.
     public void updateParametersOnStartInputView() {
-        if (mConfigUseSpacebarLanguageSwitcher) {
-            updateForSpacebarLanguageSwitch();
-        } else {
-            updateEnabledSubtypes();
-        }
+        mConfigUseSpacebarLanguageSwitcher = mPrefs.getBoolean(USE_SPACEBAR_LANGUAGE_SWITCH_KEY,
+                mService.getResources().getBoolean(
+                        R.bool.config_use_spacebar_language_switcher));
+        updateEnabledSubtypes();
         updateShortcutIME();
     }
 
@@ -150,7 +149,7 @@
                 null, true);
         mEnabledLanguagesOfCurrentInputMethod.clear();
         mEnabledKeyboardSubtypesOfCurrentInputMethod.clear();
-        for (InputMethodSubtype ims: mAllEnabledSubtypesOfCurrentInputMethod) {
+        for (InputMethodSubtypeCompatWrapper ims : mAllEnabledSubtypesOfCurrentInputMethod) {
             final String locale = ims.getLocale();
             final String mode = ims.getMode();
             mLocaleSplitter.setString(locale);
@@ -172,6 +171,10 @@
                 Log.w(TAG, "Last subtype was disabled. Update to the current one.");
             }
             updateSubtype(mImm.getCurrentInputMethodSubtype());
+        } else {
+            // mLanguageBarInfo.update() will be called in updateSubtype so there is no need
+            // to call this in the if-clause above.
+            mLanguageBarInfo.update();
         }
     }
 
@@ -184,10 +187,10 @@
                             + ", " + mShortcutSubtype.getMode())));
         }
         // TODO: Update an icon for shortcut IME
-        Map<InputMethodInfo, List<InputMethodSubtype>> shortcuts =
+        final Map<InputMethodInfoCompatWrapper, List<InputMethodSubtypeCompatWrapper>> shortcuts =
                 mImm.getShortcutInputMethodsAndSubtypes();
-        for (InputMethodInfo imi: shortcuts.keySet()) {
-            List<InputMethodSubtype> subtypes = shortcuts.get(imi);
+        for (InputMethodInfoCompatWrapper imi : shortcuts.keySet()) {
+            List<InputMethodSubtypeCompatWrapper> subtypes = shortcuts.get(imi);
             // TODO: Returns the first found IMI for now. Should handle all shortcuts as
             // appropriate.
             mShortcutInputMethodInfo = imi;
@@ -206,7 +209,7 @@
     }
 
     // Update the current subtype. LatinIME.onCurrentInputMethodSubtypeChanged calls this function.
-    public void updateSubtype(InputMethodSubtype newSubtype) {
+    public void updateSubtype(InputMethodSubtypeCompatWrapper newSubtype) {
         final String newLocale;
         final String newMode;
         final String oldMode = getCurrentSubtypeMode();
@@ -243,32 +246,33 @@
         // We cancel its status when we change mode, while we reset otherwise.
         if (isKeyboardMode()) {
             if (modeChanged) {
-                if (VOICE_MODE.equals(oldMode) && mVoiceInput != null) {
-                    mVoiceInput.cancel();
+                if (VOICE_MODE.equals(oldMode) && mVoiceInputWrapper != null) {
+                    mVoiceInputWrapper.cancel();
                 }
             }
             if (modeChanged || languageChanged) {
                 updateShortcutIME();
                 mService.onRefreshKeyboard();
             }
-        } else if (isVoiceMode() && mVoiceInput != null) {
+        } else if (isVoiceMode() && mVoiceInputWrapper != null) {
             if (VOICE_MODE.equals(oldMode)) {
-                mVoiceInput.reset();
+                mVoiceInputWrapper.reset();
             }
             // If needsToShowWarningDialog is true, voice input need to show warning before
             // show recognition view.
             if (languageChanged || modeChanged
-                    || VoiceIMEConnector.getInstance().needsToShowWarningDialog()) {
+                    || VoiceProxy.getInstance().needsToShowWarningDialog()) {
                 triggerVoiceIME();
             }
         } else {
             Log.w(TAG, "Unknown subtype mode: " + newMode);
-            if (VOICE_MODE.equals(oldMode) && mVoiceInput != null) {
+            if (VOICE_MODE.equals(oldMode) && mVoiceInputWrapper != null) {
                 // We need to reset the voice input to release the resources and to reset its status
                 // as it is not the current input mode.
-                mVoiceInput.reset();
+                mVoiceInputWrapper.reset();
             }
         }
+        mLanguageBarInfo.update();
     }
 
     // Update the current input locale from Locale string.
@@ -303,25 +307,47 @@
     ////////////////////////////
 
     public void switchToShortcutIME() {
-        final IBinder token = mService.getWindow().getWindow().getAttributes().token;
-        if (token == null || mShortcutInputMethodInfo == null) {
+        if (mShortcutInputMethodInfo == null) {
             return;
         }
+
         final String imiId = mShortcutInputMethodInfo.getId();
-        final InputMethodSubtype subtype = mShortcutSubtype;
-        new Thread("SwitchToShortcutIME") {
+        final InputMethodSubtypeCompatWrapper subtype = mShortcutSubtype;
+        switchToTargetIME(imiId, subtype);
+    }
+
+    private void switchToTargetIME(
+            final String imiId, final InputMethodSubtypeCompatWrapper subtype) {
+        final IBinder token = mService.getWindow().getWindow().getAttributes().token;
+        if (token == null) {
+            return;
+        }
+        new AsyncTask<Void, Void, Void>() {
             @Override
-            public void run() {
+            protected Void doInBackground(Void... params) {
                 mImm.setInputMethodAndSubtype(token, imiId, subtype);
+                return null;
             }
-        }.start();
+
+            @Override
+            protected void onPostExecute(Void result) {
+                // Calls in this method need to be done in the same thread as the thread which
+                // called switchToShortcutIME().
+
+                // Notify an event that the current subtype was changed. This event will be
+                // handled if "onCurrentInputMethodSubtypeChanged" can't be implemented
+                // when the API level is 10 or previous.
+                mService.notifyOnCurrentInputMethodSubtypeChanged(subtype);
+            }
+        }.execute();
     }
 
     public Drawable getShortcutIcon() {
         return getSubtypeIcon(mShortcutInputMethodInfo, mShortcutSubtype);
     }
 
-    private Drawable getSubtypeIcon(InputMethodInfo imi, InputMethodSubtype subtype) {
+    private Drawable getSubtypeIcon(
+            InputMethodInfoCompatWrapper imi, InputMethodSubtypeCompatWrapper subtype) {
         final PackageManager pm = mService.getPackageManager();
         if (imi != null) {
             final String imiPackageName = imi.getPackageName();
@@ -360,11 +386,16 @@
             return false;
         if (mShortcutSubtype == null)
             return true;
+        // For compatibility, if the shortcut subtype is dummy, we assume the shortcut IME
+        // (built-in voice dummy subtype) is available.
+        if (!mShortcutSubtype.hasOriginalObject()) return true;
         final boolean allowsImplicitlySelectedSubtypes = true;
-        for (final InputMethodSubtype enabledSubtype : mImm.getEnabledInputMethodSubtypeList(
-                mShortcutInputMethodInfo, allowsImplicitlySelectedSubtypes)) {
-            if (enabledSubtype.equals(mShortcutSubtype))
+        for (final InputMethodSubtypeCompatWrapper enabledSubtype :
+                mImm.getEnabledInputMethodSubtypeList(
+                        mShortcutInputMethodInfo, allowsImplicitlySelectedSubtypes)) {
+            if (enabledSubtype.equals(mShortcutSubtype)) {
                 return true;
+            }
         }
         return false;
     }
@@ -398,11 +429,7 @@
     //////////////////////////////////
 
     public int getEnabledKeyboardLocaleCount() {
-        if (mConfigUseSpacebarLanguageSwitcher) {
-            return mLanguageSwitcher.getLocaleCount();
-        } else {
-            return mEnabledKeyboardSubtypesOfCurrentInputMethod.size();
-        }
+        return mEnabledKeyboardSubtypesOfCurrentInputMethod.size();
     }
 
     public boolean useSpacebarLanguageSwitcher() {
@@ -414,90 +441,40 @@
     }
 
     public Locale getInputLocale() {
-        if (mConfigUseSpacebarLanguageSwitcher) {
-            return mLanguageSwitcher.getInputLocale();
-        } else {
-            return mInputLocale;
-        }
+        return mInputLocale;
     }
 
     public String getInputLocaleStr() {
-        if (mConfigUseSpacebarLanguageSwitcher) {
-            String inputLanguage = null;
-            inputLanguage = mLanguageSwitcher.getInputLanguage();
-            // Should return system locale if there is no Language available.
-            if (inputLanguage == null) {
-                inputLanguage = getSystemLocale().getLanguage();
-            }
-            return inputLanguage;
-        } else {
-            return mInputLocaleStr;
-        }
+        return mInputLocaleStr;
     }
 
     public String[] getEnabledLanguages() {
-        if (mConfigUseSpacebarLanguageSwitcher) {
-            return mLanguageSwitcher.getEnabledLanguages();
-        } else {
-            int enabledLanguageCount = mEnabledLanguagesOfCurrentInputMethod.size();
-            // Workaround for explicitly specifying the voice language
-            if (enabledLanguageCount == 1) {
-                mEnabledLanguagesOfCurrentInputMethod.add(
-                        mEnabledLanguagesOfCurrentInputMethod.get(0));
-                ++enabledLanguageCount;
-            }
-            return mEnabledLanguagesOfCurrentInputMethod.toArray(
-                    new String[enabledLanguageCount]);
+        int enabledLanguageCount = mEnabledLanguagesOfCurrentInputMethod.size();
+        // Workaround for explicitly specifying the voice language
+        if (enabledLanguageCount == 1) {
+            mEnabledLanguagesOfCurrentInputMethod.add(mEnabledLanguagesOfCurrentInputMethod
+                    .get(0));
+            ++enabledLanguageCount;
         }
+        return mEnabledLanguagesOfCurrentInputMethod.toArray(new String[enabledLanguageCount]);
     }
 
     public Locale getSystemLocale() {
-        if (mConfigUseSpacebarLanguageSwitcher) {
-            return mLanguageSwitcher.getSystemLocale();
-        } else {
-            return mSystemLocale;
-        }
+        return mSystemLocale;
     }
 
     public boolean isSystemLanguageSameAsInputLanguage() {
-        if (mConfigUseSpacebarLanguageSwitcher) {
-            return getSystemLocale().getLanguage().equalsIgnoreCase(
-                    getInputLocaleStr().substring(0, 2));
-        } else {
-            return mIsSystemLanguageSameAsInputLanguage;
-        }
+        return mIsSystemLanguageSameAsInputLanguage;
     }
 
     public void onConfigurationChanged(Configuration conf) {
         final Locale systemLocale = conf.locale;
         // If system configuration was changed, update all parameters.
         if (!TextUtils.equals(systemLocale.toString(), mSystemLocale.toString())) {
-            if (mConfigUseSpacebarLanguageSwitcher) {
-                // If the system locale changes and is different from the saved
-                // locale (mSystemLocale), then reload the input locale list from the
-                // latin ime settings (shared prefs) and reset the input locale
-                // to the first one.
-                mLanguageSwitcher.loadLocales(mPrefs);
-                mLanguageSwitcher.setSystemLocale(systemLocale);
-            } else {
-                updateAllParameters();
-            }
+            updateAllParameters();
         }
     }
 
-    /**
-     * Change system locale for this application
-     * @param newLocale
-     * @return oldLocale
-     */
-    public Locale changeSystemLocale(Locale newLocale) {
-        Configuration conf = mResources.getConfiguration();
-        Locale oldLocale = conf.locale;
-        conf.locale = newLocale;
-        mResources.updateConfiguration(conf, mResources.getDisplayMetrics());
-        return oldLocale;
-    }
-
     public boolean isKeyboardMode() {
         return KEYBOARD_MODE.equals(getCurrentSubtypeMode());
     }
@@ -507,9 +484,9 @@
     // Voice Input functions //
     ///////////////////////////
 
-    public boolean setVoiceInput(VoiceInput vi) {
-        if (mVoiceInput == null && vi != null) {
-            mVoiceInput = vi;
+    public boolean setVoiceInputWrapper(VoiceProxy.VoiceInputWrapper vi) {
+        if (mVoiceInputWrapper == null && vi != null) {
+            mVoiceInputWrapper = vi;
             if (isVoiceMode()) {
                 if (DBG) {
                     Log.d(TAG, "Set and call voice input.: " + getInputLocaleStr());
@@ -525,9 +502,14 @@
         return null == mCurrentSubtype ? false : VOICE_MODE.equals(getCurrentSubtypeMode());
     }
 
+    public boolean isDummyVoiceMode() {
+        return mCurrentSubtype != null && mCurrentSubtype.getOriginalObject() == null
+                && VOICE_MODE.equals(getCurrentSubtypeMode());
+    }
+
     private void triggerVoiceIME() {
         if (!mService.isInputViewShown()) return;
-        VoiceIMEConnector.getInstance().startListening(false,
+        VoiceProxy.getInstance().startListening(false,
                 KeyboardSwitcher.getInstance().getInputView().getWindowToken());
     }
 
@@ -535,7 +517,70 @@
     // Spacebar Language Switch support //
     //////////////////////////////////////
 
-    private LanguageSwitcher mLanguageSwitcher;
+    private class LanguageBarInfo {
+        private int mCurrentKeyboardSubtypeIndex;
+        private InputMethodSubtypeCompatWrapper mNextKeyboardSubtype;
+        private InputMethodSubtypeCompatWrapper mPreviousKeyboardSubtype;
+        private String mNextLanguage;
+        private String mPreviousLanguage;
+        public LanguageBarInfo() {
+            update();
+        }
+
+        private String getNextLanguage() {
+            return mNextLanguage;
+        }
+
+        private String getPreviousLanguage() {
+            return mPreviousLanguage;
+        }
+
+        public InputMethodSubtypeCompatWrapper getNextKeyboardSubtype() {
+            return mNextKeyboardSubtype;
+        }
+
+        public InputMethodSubtypeCompatWrapper getPreviousKeyboardSubtype() {
+            return mPreviousKeyboardSubtype;
+        }
+
+        public void update() {
+            if (!mConfigUseSpacebarLanguageSwitcher
+                    || mEnabledKeyboardSubtypesOfCurrentInputMethod == null
+                    || mEnabledKeyboardSubtypesOfCurrentInputMethod.size() == 0) return;
+            mCurrentKeyboardSubtypeIndex = getCurrentIndex();
+            mNextKeyboardSubtype = getNextKeyboardSubtypeInternal(mCurrentKeyboardSubtypeIndex);
+            Locale locale = new Locale(mNextKeyboardSubtype.getLocale());
+            mNextLanguage = getDisplayLanguage(locale);
+            mPreviousKeyboardSubtype = getPreviousKeyboardSubtypeInternal(
+                    mCurrentKeyboardSubtypeIndex);
+            locale = new Locale(mPreviousKeyboardSubtype.getLocale());
+            mPreviousLanguage = getDisplayLanguage(locale);
+        }
+
+        private int normalize(int index) {
+            final int N = mEnabledKeyboardSubtypesOfCurrentInputMethod.size();
+            final int ret = index % N;
+            return ret < 0 ? ret + N : ret;
+        }
+
+        private int getCurrentIndex() {
+            final int N = mEnabledKeyboardSubtypesOfCurrentInputMethod.size();
+            for (int i = 0; i < N; ++i) {
+                if (mEnabledKeyboardSubtypesOfCurrentInputMethod.get(i).equals(mCurrentSubtype)) {
+                    return i;
+                }
+            }
+            return 0;
+        }
+
+        private InputMethodSubtypeCompatWrapper getNextKeyboardSubtypeInternal(int index) {
+            return mEnabledKeyboardSubtypesOfCurrentInputMethod.get(normalize(index + 1));
+        }
+
+        private InputMethodSubtypeCompatWrapper getPreviousKeyboardSubtypeInternal(int index) {
+            return mEnabledKeyboardSubtypesOfCurrentInputMethod.get(normalize(index - 1));
+        }
+    }
 
     public static String getFullDisplayName(Locale locale, boolean returnsNameInThisLocale) {
         if (returnsNameInThisLocale) {
@@ -549,6 +594,10 @@
         return toTitleCase(locale.getDisplayLanguage(locale));
     }
 
+    public static String getMiddleDisplayLanguage(Locale locale) {
+        return toTitleCase((new Locale(locale.getLanguage()).getDisplayLanguage(locale)));
+    }
+
     public static String getShortDisplayLanguage(Locale locale) {
         return toTitleCase(locale.getLanguage());
     }
@@ -560,32 +609,16 @@
         return Character.toUpperCase(s.charAt(0)) + s.substring(1);
     }
 
-    private void updateForSpacebarLanguageSwitch() {
-        // We need to update mNeedsToDisplayLanguage in onStartInputView because
-        // getEnabledKeyboardLocaleCount could have been changed.
-        mNeedsToDisplayLanguage = !(getEnabledKeyboardLocaleCount() <= 1
-                && getSystemLocale().getLanguage().equalsIgnoreCase(
-                        getInputLocale().getLanguage()));
-    }
-
     public String getInputLanguageName() {
         return getDisplayLanguage(getInputLocale());
     }
 
     public String getNextInputLanguageName() {
-        if (mConfigUseSpacebarLanguageSwitcher) {
-            return getDisplayLanguage(mLanguageSwitcher.getNextInputLocale());
-        } else {
-            return "";
-        }
+        return mLanguageBarInfo.getNextLanguage();
     }
 
     public String getPreviousInputLanguageName() {
-        if (mConfigUseSpacebarLanguageSwitcher) {
-            return getDisplayLanguage(mLanguageSwitcher.getPrevInputLocale());
-        } else {
-            return "";
-        }
+        return mLanguageBarInfo.getPreviousLanguage();
     }
 
     /////////////////////////////
@@ -612,60 +645,36 @@
     }
 
 
-    // A list of locales which are supported by default for voice input, unless we get a
-    // different list from Gservices.
-    private static final String DEFAULT_VOICE_INPUT_SUPPORTED_LOCALES =
-            "en " +
-            "en_US " +
-            "en_GB " +
-            "en_AU " +
-            "en_CA " +
-            "en_IE " +
-            "en_IN " +
-            "en_NZ " +
-            "en_SG " +
-            "en_ZA ";
-
     public boolean isVoiceSupported(String locale) {
         // Get the current list of supported locales and check the current locale against that
         // list. We cache this value so as not to check it every time the user starts a voice
         // input. Because this method is called by onStartInputView, this should mean that as
         // long as the locale doesn't change while the user is keeping the IME open, the
         // value should never be stale.
-        String supportedLocalesString = SettingsUtil.getSettingsString(
-                mService.getContentResolver(),
-                SettingsUtil.LATIN_IME_VOICE_INPUT_SUPPORTED_LOCALES,
-                DEFAULT_VOICE_INPUT_SUPPORTED_LOCALES);
+        String supportedLocalesString = VoiceProxy.getSupportedLocalesString(
+                mService.getContentResolver());
         List<String> voiceInputSupportedLocales = Arrays.asList(
                 supportedLocalesString.split("\\s+"));
         return voiceInputSupportedLocales.contains(locale);
     }
 
-    public void loadSettings() {
-        if (mConfigUseSpacebarLanguageSwitcher) {
-            mLanguageSwitcher.loadLocales(mPrefs);
-        }
+    private void changeToNextSubtype() {
+        final InputMethodSubtypeCompatWrapper subtype =
+                mLanguageBarInfo.getNextKeyboardSubtype();
+        switchToTargetIME(mInputMethodId, subtype);
     }
 
-    public void toggleLanguage(boolean reset, boolean next) {
-        if (mConfigUseSpacebarLanguageSwitcher) {
-            if (reset) {
-                mLanguageSwitcher.reset();
-            } else {
-                if (next) {
-                    mLanguageSwitcher.next();
-                } else {
-                    mLanguageSwitcher.prev();
-                }
-            }
-            mLanguageSwitcher.persist(mPrefs);
-        }
+    private void changeToPreviousSubtype() {
+        final InputMethodSubtypeCompatWrapper subtype =
+                mLanguageBarInfo.getPreviousKeyboardSubtype();
+        switchToTargetIME(mInputMethodId, subtype);
     }
 
-    private void initLanguageSwitcher(LatinIME service) {
-        final Configuration conf = service.getResources().getConfiguration();
-        mLanguageSwitcher = new LanguageSwitcher(service);
-        mLanguageSwitcher.loadLocales(mPrefs);
-        mLanguageSwitcher.setSystemLocale(conf.locale);
+    public void toggleLanguage(boolean next) {
+        if (next) {
+            changeToNextSubtype();
+        } else {
+            changeToPreviousSubtype();
+        }
     }
 }
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index 0de474e..ca75866 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -27,6 +27,7 @@
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
 
@@ -47,7 +48,7 @@
 
     /**
      * Words that appear in both bigram and unigram data gets multiplier ranging from
-     * BIGRAM_MULTIPLIER_MIN to BIGRAM_MULTIPLIER_MAX depending on the frequency score from
+     * BIGRAM_MULTIPLIER_MIN to BIGRAM_MULTIPLIER_MAX depending on the score from
      * bigram data.
      */
     public static final double BIGRAM_MULTIPLIER_MIN = 1.2;
@@ -55,7 +56,7 @@
 
     /**
      * Maximum possible bigram frequency. Will depend on how many bits are being used in data
-     * structure. Maximum bigram freqeuncy will get the BIGRAM_MULTIPLIER_MAX as the multiplier.
+     * structure. Maximum bigram frequency will get the BIGRAM_MULTIPLIER_MAX as the multiplier.
      */
     public static final int MAXIMUM_BIGRAM_FREQUENCY = 127;
 
@@ -74,13 +75,11 @@
     public static final String DICT_KEY_USER_BIGRAM = "user_bigram";
     public static final String DICT_KEY_WHITELIST ="whitelist";
 
-    static final int LARGE_DICTIONARY_THRESHOLD = 200 * 1000;
-
     private static final boolean DBG = LatinImeLogger.sDBG;
 
     private AutoCorrection mAutoCorrection;
 
-    private BinaryDictionary mMainDict;
+    private Dictionary mMainDict;
     private WhitelistDictionary mWhiteListDictionary;
     private final Map<String, Dictionary> mUnigramDictionaries = new HashMap<String, Dictionary>();
     private final Map<String, Dictionary> mBigramDictionaries = new HashMap<String, Dictionary>();
@@ -92,13 +91,13 @@
     private boolean mQuickFixesEnabled;
 
     private double mAutoCorrectionThreshold;
-    private int[] mPriorities = new int[mPrefMaxSuggestions];
-    private int[] mBigramPriorities = new int[PREF_MAX_BIGRAMS];
+    private int[] mScores = new int[mPrefMaxSuggestions];
+    private int[] mBigramScores = new int[PREF_MAX_BIGRAMS];
 
     private ArrayList<CharSequence> mSuggestions = new ArrayList<CharSequence>();
     ArrayList<CharSequence> mBigramSuggestions  = new ArrayList<CharSequence>();
     private ArrayList<CharSequence> mStringPool = new ArrayList<CharSequence>();
-    private String mLowerOriginalWord;
+    private CharSequence mTypedWord;
 
     // TODO: Remove these member variables by passing more context to addWord() callback method
     private boolean mIsFirstCharCapitalized;
@@ -106,15 +105,18 @@
 
     private int mCorrectionMode = CORRECTION_BASIC;
 
-    public Suggest(Context context, int dictionaryResId) {
-        init(context, BinaryDictionary.initDictionary(context, dictionaryResId, DIC_MAIN));
+    public Suggest(Context context, int dictionaryResId, Locale locale) {
+        init(context, DictionaryFactory.createDictionaryFromManager(context, locale,
+                dictionaryResId));
     }
 
-    /* package for test */ Suggest(File dictionary, long startOffset, long length) {
-        init(null, BinaryDictionary.initDictionary(dictionary, startOffset, length, DIC_MAIN));
+    /* package for test */ Suggest(Context context, File dictionary, long startOffset, long length,
+            Flag[] flagArray) {
+        init(null, DictionaryFactory.createDictionaryForTest(context, dictionary, startOffset,
+                length, flagArray));
     }
 
-    private void init(Context context, BinaryDictionary mainDict) {
+    private void init(Context context, Dictionary mainDict) {
         if (mainDict != null) {
             mMainDict = mainDict;
             mUnigramDictionaries.put(DICT_KEY_MAIN, mainDict);
@@ -128,6 +130,19 @@
         initPool();
     }
 
+    public void resetMainDict(Context context, int dictionaryResId, Locale locale) {
+        final Dictionary newMainDict = DictionaryFactory.createDictionaryFromManager(
+                context, locale, dictionaryResId);
+        mMainDict = newMainDict;
+        if (null == newMainDict) {
+            mUnigramDictionaries.remove(DICT_KEY_MAIN);
+            mBigramDictionaries.remove(DICT_KEY_MAIN);
+        } else {
+            mUnigramDictionaries.put(DICT_KEY_MAIN, newMainDict);
+            mBigramDictionaries.put(DICT_KEY_MAIN, newMainDict);
+        }
+    }
+
     private void initPool() {
         for (int i = 0; i < mPrefMaxSuggestions; i++) {
             StringBuilder sb = new StringBuilder(getApproxMaxWordLength());
@@ -148,7 +163,7 @@
     }
 
     public boolean hasMainDictionary() {
-        return mMainDict != null && mMainDict.getSize() > LARGE_DICTIONARY_THRESHOLD;
+        return mMainDict != null;
     }
 
     public Map<String, Dictionary> getUnigramDictionaries() {
@@ -207,8 +222,8 @@
             throw new IllegalArgumentException("maxSuggestions must be between 1 and 100");
         }
         mPrefMaxSuggestions = maxSuggestions;
-        mPriorities = new int[mPrefMaxSuggestions];
-        mBigramPriorities = new int[PREF_MAX_BIGRAMS];
+        mScores = new int[mPrefMaxSuggestions];
+        mBigramScores = new int[PREF_MAX_BIGRAMS];
         collectGarbage(mSuggestions, mPrefMaxSuggestions);
         while (mStringPool.size() < mPrefMaxSuggestions) {
             StringBuilder sb = new StringBuilder(getApproxMaxWordLength());
@@ -248,6 +263,16 @@
         return sb;
     }
 
+    protected void addBigramToSuggestions(CharSequence bigram) {
+        final int poolSize = mStringPool.size();
+        final StringBuilder sb = poolSize > 0 ?
+                (StringBuilder) mStringPool.remove(poolSize - 1)
+                        : new StringBuilder(getApproxMaxWordLength());
+        sb.setLength(0);
+        sb.append(bigram);
+        mSuggestions.add(sb);
+    }
+
     // TODO: cleanup dictionaries looking up and suggestions building with SuggestedWords.Builder
     public SuggestedWords.Builder getSuggestedWordBuilder(View view, WordComposer wordComposer,
             CharSequence prevWordForBigram) {
@@ -256,25 +281,23 @@
         mIsFirstCharCapitalized = wordComposer.isFirstCharCapitalized();
         mIsAllUpperCase = wordComposer.isAllUpperCase();
         collectGarbage(mSuggestions, mPrefMaxSuggestions);
-        Arrays.fill(mPriorities, 0);
+        Arrays.fill(mScores, 0);
 
         // Save a lowercase version of the original word
         CharSequence typedWord = wordComposer.getTypedWord();
         if (typedWord != null) {
             final String typedWordString = typedWord.toString();
             typedWord = typedWordString;
-            mLowerOriginalWord = typedWordString.toLowerCase();
             // Treating USER_TYPED as UNIGRAM suggestion for logging now.
             LatinImeLogger.onAddSuggestedWord(typedWordString, Suggest.DIC_USER_TYPED,
                     Dictionary.DataType.UNIGRAM);
-        } else {
-            mLowerOriginalWord = "";
         }
+        mTypedWord = typedWord;
 
-        if (wordComposer.size() == 1 && (mCorrectionMode == CORRECTION_FULL_BIGRAM
+        if (wordComposer.size() <= 1 && (mCorrectionMode == CORRECTION_FULL_BIGRAM
                 || mCorrectionMode == CORRECTION_BASIC)) {
             // At first character typed, search only the bigrams
-            Arrays.fill(mBigramPriorities, 0);
+            Arrays.fill(mBigramScores, 0);
             collectGarbage(mBigramSuggestions, PREF_MAX_BIGRAMS);
 
             if (!TextUtils.isEmpty(prevWordForBigram)) {
@@ -285,21 +308,26 @@
                 for (final Dictionary dictionary : mBigramDictionaries.values()) {
                     dictionary.getBigrams(wordComposer, prevWordForBigram, this);
                 }
-                char currentChar = wordComposer.getTypedWord().charAt(0);
-                char currentCharUpper = Character.toUpperCase(currentChar);
-                int count = 0;
-                int bigramSuggestionSize = mBigramSuggestions.size();
-                for (int i = 0; i < bigramSuggestionSize; i++) {
-                    if (mBigramSuggestions.get(i).charAt(0) == currentChar
-                            || mBigramSuggestions.get(i).charAt(0) == currentCharUpper) {
-                        int poolSize = mStringPool.size();
-                        StringBuilder sb = poolSize > 0 ?
-                                (StringBuilder) mStringPool.remove(poolSize - 1)
-                                : new StringBuilder(getApproxMaxWordLength());
-                        sb.setLength(0);
-                        sb.append(mBigramSuggestions.get(i));
-                        mSuggestions.add(count++, sb);
-                        if (count > mPrefMaxSuggestions) break;
+                if (TextUtils.isEmpty(typedWord)) {
+                    // Nothing entered: return all bigrams for the previous word
+                    int insertCount = Math.min(mBigramSuggestions.size(), mPrefMaxSuggestions);
+                    for (int i = 0; i < insertCount; ++i) {
+                        addBigramToSuggestions(mBigramSuggestions.get(i));
+                    }
+                } else {
+                    // Word entered: return only bigrams that match the first char of the typed word
+                    final char currentChar = typedWord.charAt(0);
+                    final char currentCharUpper = Character.toUpperCase(currentChar);
+                    int count = 0;
+                    final int bigramSuggestionSize = mBigramSuggestions.size();
+                    for (int i = 0; i < bigramSuggestionSize; i++) {
+                        final CharSequence bigramSuggestion = mBigramSuggestions.get(i);
+                        final char bigramSuggestionFirstChar = bigramSuggestion.charAt(0);
+                        if (bigramSuggestionFirstChar == currentChar
+                                || bigramSuggestionFirstChar == currentCharUpper) {
+                            addBigramToSuggestions(bigramSuggestion);
+                            if (++count > mPrefMaxSuggestions) break;
+                        }
                     }
                 }
             }
@@ -346,7 +374,7 @@
                 mWhiteListDictionary.getWhiteListedWord(typedWordString));
 
         mAutoCorrection.updateAutoCorrectionStatus(mUnigramDictionaries, wordComposer,
-                mSuggestions, mPriorities, typedWord, mAutoCorrectionThreshold, mCorrectionMode,
+                mSuggestions, mScores, typedWord, mAutoCorrectionThreshold, mCorrectionMode,
                 autoText, whitelistedWord);
 
         if (autoText != null) {
@@ -364,26 +392,25 @@
 
         if (DBG) {
             double normalizedScore = mAutoCorrection.getNormalizedScore();
-            ArrayList<SuggestedWords.SuggestedWordInfo> frequencyInfoList =
+            ArrayList<SuggestedWords.SuggestedWordInfo> scoreInfoList =
                     new ArrayList<SuggestedWords.SuggestedWordInfo>();
-            frequencyInfoList.add(new SuggestedWords.SuggestedWordInfo("+", false));
-            final int priorityLength = mPriorities.length;
-            for (int i = 0; i < priorityLength; ++i) {
+            scoreInfoList.add(new SuggestedWords.SuggestedWordInfo("+", false));
+            for (int i = 0; i < mScores.length; ++i) {
                 if (normalizedScore > 0) {
-                    final String priorityThreshold = Integer.toString(mPriorities[i]) + " (" +
-                            normalizedScore + ")";
-                    frequencyInfoList.add(
-                            new SuggestedWords.SuggestedWordInfo(priorityThreshold, false));
+                    final String scoreThreshold = String.format("%d (%4.2f)", mScores[i],
+                            normalizedScore);
+                    scoreInfoList.add(
+                            new SuggestedWords.SuggestedWordInfo(scoreThreshold, false));
                     normalizedScore = 0.0;
                 } else {
-                    final String priority = Integer.toString(mPriorities[i]);
-                    frequencyInfoList.add(new SuggestedWords.SuggestedWordInfo(priority, false));
+                    final String score = Integer.toString(mScores[i]);
+                    scoreInfoList.add(new SuggestedWords.SuggestedWordInfo(score, false));
                 }
             }
-            for (int i = priorityLength; i < mSuggestions.size(); ++i) {
-                frequencyInfoList.add(new SuggestedWords.SuggestedWordInfo("--", false));
+            for (int i = mScores.length; i < mSuggestions.size(); ++i) {
+                scoreInfoList.add(new SuggestedWords.SuggestedWordInfo("--", false));
             }
-            return new SuggestedWords.Builder().addWords(mSuggestions, frequencyInfoList);
+            return new SuggestedWords.Builder().addWords(mSuggestions, scoreInfoList);
         }
         return new SuggestedWords.Builder().addWords(mSuggestions, null);
     }
@@ -419,52 +446,37 @@
         return mAutoCorrection.hasAutoCorrection();
     }
 
-    private static boolean compareCaseInsensitive(final String lowerOriginalWord,
-            final char[] word, final int offset, final int length) {
-        final int originalLength = lowerOriginalWord.length();
-        if (originalLength == length && Character.isUpperCase(word[offset])) {
-            for (int i = 0; i < originalLength; i++) {
-                if (lowerOriginalWord.charAt(i) != Character.toLowerCase(word[offset+i])) {
-                    return false;
-                }
-            }
-            return true;
-        }
-        return false;
-    }
-
     @Override
-    public boolean addWord(final char[] word, final int offset, final int length, int freq,
+    public boolean addWord(final char[] word, final int offset, final int length, int score,
             final int dicTypeId, final Dictionary.DataType dataType) {
         Dictionary.DataType dataTypeForLog = dataType;
-        ArrayList<CharSequence> suggestions;
-        int[] priorities;
-        int prefMaxSuggestions;
+        final ArrayList<CharSequence> suggestions;
+        final int[] sortedScores;
+        final int prefMaxSuggestions;
         if(dataType == Dictionary.DataType.BIGRAM) {
             suggestions = mBigramSuggestions;
-            priorities = mBigramPriorities;
+            sortedScores = mBigramScores;
             prefMaxSuggestions = PREF_MAX_BIGRAMS;
         } else {
             suggestions = mSuggestions;
-            priorities = mPriorities;
+            sortedScores = mScores;
             prefMaxSuggestions = mPrefMaxSuggestions;
         }
 
         int pos = 0;
 
         // Check if it's the same word, only caps are different
-        if (compareCaseInsensitive(mLowerOriginalWord, word, offset, length)) {
+        if (Utils.equalsIgnoreCase(mTypedWord, word, offset, length)) {
             // TODO: remove this surrounding if clause and move this logic to
             // getSuggestedWordBuilder.
             if (suggestions.size() > 0) {
-                final String currentHighestWordLowerCase =
-                        suggestions.get(0).toString().toLowerCase();
+                final String currentHighestWord = suggestions.get(0).toString();
                 // If the current highest word is also equal to typed word, we need to compare
                 // frequency to determine the insertion position. This does not ensure strictly
                 // correct ordering, but ensures the top score is on top which is enough for
                 // removing duplicates correctly.
-                if (compareCaseInsensitive(currentHighestWordLowerCase, word, offset, length)
-                        && freq <= priorities[0]) {
+                if (Utils.equalsIgnoreCase(currentHighestWord, word, offset, length)
+                        && score <= sortedScores[0]) {
                     pos = 1;
                 }
             }
@@ -475,24 +487,24 @@
                 if(bigramSuggestion >= 0) {
                     dataTypeForLog = Dictionary.DataType.BIGRAM;
                     // turn freq from bigram into multiplier specified above
-                    double multiplier = (((double) mBigramPriorities[bigramSuggestion])
+                    double multiplier = (((double) mBigramScores[bigramSuggestion])
                             / MAXIMUM_BIGRAM_FREQUENCY)
                             * (BIGRAM_MULTIPLIER_MAX - BIGRAM_MULTIPLIER_MIN)
                             + BIGRAM_MULTIPLIER_MIN;
                     /* Log.d(TAG,"bigram num: " + bigramSuggestion
                             + "  wordB: " + mBigramSuggestions.get(bigramSuggestion).toString()
-                            + "  currentPriority: " + freq + "  bigramPriority: "
-                            + mBigramPriorities[bigramSuggestion]
+                            + "  currentScore: " + score + "  bigramScore: "
+                            + mBigramScores[bigramSuggestion]
                             + "  multiplier: " + multiplier); */
-                    freq = (int)Math.round((freq * multiplier));
+                    score = (int)Math.round((score * multiplier));
                 }
             }
 
-            // Check the last one's priority and bail
-            if (priorities[prefMaxSuggestions - 1] >= freq) return true;
+            // Check the last one's score and bail
+            if (sortedScores[prefMaxSuggestions - 1] >= score) return true;
             while (pos < prefMaxSuggestions) {
-                if (priorities[pos] < freq
-                        || (priorities[pos] == freq && length < suggestions.get(pos).length())) {
+                if (sortedScores[pos] < score
+                        || (sortedScores[pos] == score && length < suggestions.get(pos).length())) {
                     break;
                 }
                 pos++;
@@ -502,8 +514,8 @@
             return true;
         }
 
-        System.arraycopy(priorities, pos, priorities, pos + 1, prefMaxSuggestions - pos - 1);
-        priorities[pos] = freq;
+        System.arraycopy(sortedScores, pos, sortedScores, pos + 1, prefMaxSuggestions - pos - 1);
+        sortedScores[pos] = score;
         int poolSize = mStringPool.size();
         StringBuilder sb = poolSize > 0 ? (StringBuilder) mStringPool.remove(poolSize - 1)
                 : new StringBuilder(getApproxMaxWordLength());
diff --git a/java/src/com/android/inputmethod/latin/SuggestedWords.java b/java/src/com/android/inputmethod/latin/SuggestedWords.java
index fe7aac7..a8cdfc0 100644
--- a/java/src/com/android/inputmethod/latin/SuggestedWords.java
+++ b/java/src/com/android/inputmethod/latin/SuggestedWords.java
@@ -32,14 +32,14 @@
     public final List<SuggestedWordInfo> mSuggestedWordInfoList;
 
     private SuggestedWords(List<CharSequence> words, boolean typedWordValid,
-            boolean hasMinamlSuggestion, List<SuggestedWordInfo> suggestedWordInfoList) {
+            boolean hasMinimalSuggestion, List<SuggestedWordInfo> suggestedWordInfoList) {
         if (words != null) {
             mWords = words;
         } else {
             mWords = Collections.emptyList();
         }
         mTypedWordValid = typedWordValid;
-        mHasMinimalSuggestion = hasMinamlSuggestion;
+        mHasMinimalSuggestion = hasMinimalSuggestion;
         mSuggestedWordInfoList = suggestedWordInfoList;
     }
 
@@ -113,8 +113,8 @@
             return this;
         }
 
-        public Builder setHasMinimalSuggestion(boolean hasMinamlSuggestion) {
-            mHasMinimalSuggestion = hasMinamlSuggestion;
+        public Builder setHasMinimalSuggestion(boolean hasMinimalSuggestion) {
+            mHasMinimalSuggestion = hasMinimalSuggestion;
             return this;
         }
 
diff --git a/java/src/com/android/inputmethod/latin/TextEntryState.java b/java/src/com/android/inputmethod/latin/TextEntryState.java
index 6319643..de13f3a 100644
--- a/java/src/com/android/inputmethod/latin/TextEntryState.java
+++ b/java/src/com/android/inputmethod/latin/TextEntryState.java
@@ -16,6 +16,8 @@
 
 package com.android.inputmethod.latin;
 
+import com.android.inputmethod.latin.Utils.RingCharBuffer;
+
 import android.util.Log;
 
 public class TextEntryState {
@@ -43,10 +45,12 @@
         sState = newState;
     }
 
-    public static void acceptedDefault(CharSequence typedWord, CharSequence actualWord) {
+    public static void acceptedDefault(CharSequence typedWord, CharSequence actualWord,
+            int separatorCode) {
         if (typedWord == null) return;
         setState(ACCEPTED_DEFAULT);
-        LatinImeLogger.logOnAutoSuggestion(typedWord.toString(), actualWord.toString());
+        LatinImeLogger.logOnAutoCorrection(
+                typedWord.toString(), actualWord.toString(), separatorCode);
         if (DEBUG)
             displayState("acceptedDefault", "typedWord", typedWord, "actualWord", actualWord);
     }
@@ -95,7 +99,7 @@
         if (DEBUG) displayState("onAbortRecorrection");
     }
 
-    public static void typedCharacter(char c, boolean isSeparator) {
+    public static void typedCharacter(char c, boolean isSeparator, int x, int y) {
         final boolean isSpace = (c == ' ');
         switch (sState) {
         case IN_WORD:
@@ -149,13 +153,19 @@
             setState(START);
             break;
         }
+        RingCharBuffer.getInstance().push(c, x, y);
+        if (isSeparator) {
+            LatinImeLogger.logOnInputSeparator();
+        } else {
+            LatinImeLogger.logOnInputChar();
+        }
         if (DEBUG) displayState("typedCharacter", "char", c, "isSeparator", isSeparator);
     }
     
     public static void backspace() {
         if (sState == ACCEPTED_DEFAULT) {
             setState(UNDO_COMMIT);
-            LatinImeLogger.logOnAutoSuggestionCanceled();
+            LatinImeLogger.logOnAutoCorrectionCancelled();
         } else if (sState == UNDO_COMMIT) {
             setState(IN_WORD);
         }
diff --git a/java/src/com/android/inputmethod/latin/UserBigramDictionary.java b/java/src/com/android/inputmethod/latin/UserBigramDictionary.java
index 4750fb9..a32a646 100644
--- a/java/src/com/android/inputmethod/latin/UserBigramDictionary.java
+++ b/java/src/com/android/inputmethod/latin/UserBigramDictionary.java
@@ -44,12 +44,6 @@
     /** Maximum frequency for all pairs */
     private static final int FREQUENCY_MAX = 127;
 
-    /**
-     * If this pair is typed 6 times, it would be suggested.
-     * Should be smaller than ContactsDictionary.FREQUENCY_FOR_CONTACTS_BIGRAM
-     */
-    protected static final int SUGGEST_THRESHOLD = 6 * FREQUENCY_FOR_TYPED;
-
     /** Maximum number of pairs. Pruning will start when databases goes above this number. */
     private static int sMaxUserBigrams = 10000;
 
@@ -168,6 +162,10 @@
         if (mIme != null && mIme.getCurrentWord().isAutoCapitalized()) {
             word2 = Character.toLowerCase(word2.charAt(0)) + word2.substring(1);
         }
+        // Do not insert a word as a bigram of itself
+        if (word1.equals(word2)) {
+            return 0;
+        }
 
         int freq = super.addBigram(word1, word2, FREQUENCY_FOR_TYPED);
         if (freq > FREQUENCY_MAX) freq = FREQUENCY_MAX;
diff --git a/java/src/com/android/inputmethod/latin/Utils.java b/java/src/com/android/inputmethod/latin/Utils.java
index 727e3f1..d165de3 100644
--- a/java/src/com/android/inputmethod/latin/Utils.java
+++ b/java/src/com/android/inputmethod/latin/Utils.java
@@ -16,8 +16,14 @@
 
 package com.android.inputmethod.latin;
 
+import com.android.inputmethod.compat.InputMethodInfoCompatWrapper;
+import com.android.inputmethod.compat.InputMethodManagerCompatWrapper;
+import com.android.inputmethod.compat.InputTypeCompatUtils;
+import com.android.inputmethod.keyboard.Keyboard;
 import com.android.inputmethod.keyboard.KeyboardId;
 
+import android.content.Context;
+import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.inputmethodservice.InputMethodService;
 import android.os.AsyncTask;
@@ -28,8 +34,6 @@
 import android.text.format.DateUtils;
 import android.util.Log;
 import android.view.inputmethod.EditorInfo;
-import android.view.inputmethod.InputMethodInfo;
-import android.view.inputmethod.InputMethodManager;
 
 import java.io.BufferedReader;
 import java.io.File;
@@ -40,11 +44,13 @@
 import java.io.PrintWriter;
 import java.text.SimpleDateFormat;
 import java.util.Date;
+import java.util.Locale;
 
 public class Utils {
     private static final String TAG = Utils.class.getSimpleName();
     private static final int MINIMUM_SAFETY_NET_CHAR_LENGTH = 4;
     private static boolean DBG = LatinImeLogger.sDBG;
+    private static boolean DBG_EDIT_DISTANCE = false;
 
     private Utils() {
         // Intentional empty constructor for utility class.
@@ -101,17 +107,22 @@
         }
     }
 
-    public static boolean hasMultipleEnabledIMEsOrSubtypes(InputMethodManager imm) {
+    public static boolean hasMultipleEnabledIMEsOrSubtypes(InputMethodManagerCompatWrapper imm) {
         return imm.getEnabledInputMethodList().size() > 1
         // imm.getEnabledInputMethodSubtypeList(null, false) will return the current IME's enabled
         // input method subtype (The current IME should be LatinIME.)
                 || imm.getEnabledInputMethodSubtypeList(null, false).size() > 1;
     }
 
-    public static String getInputMethodId(InputMethodManager imm, String packageName) {
-        for (final InputMethodInfo imi : imm.getEnabledInputMethodList()) {
+    public static String getInputMethodId(InputMethodManagerCompatWrapper imm, String packageName) {
+        return getInputMethodInfo(imm, packageName).getId();
+    }
+
+    public static InputMethodInfoCompatWrapper getInputMethodInfo(
+            InputMethodManagerCompatWrapper imm, String packageName) {
+        for (final InputMethodInfoCompatWrapper imi : imm.getEnabledInputMethodList()) {
             if (imi.getPackageName().equals(packageName))
-                return imi.getId();
+                return imi;
         }
         throw new RuntimeException("Can not find input method id for " + packageName);
     }
@@ -202,11 +213,11 @@
                 return mCharBuf[mEnd];
             }
         }
-        public char getLastChar() {
-            if (mLength < 1) {
+        public char getBackwardNthChar(int n) {
+            if (mLength <= n || n < 0) {
                 return PLACEHOLDER_DELIMITER_CHAR;
             } else {
-                return mCharBuf[normalize(mEnd - 1)];
+                return mCharBuf[normalize(mEnd - n - 1)];
             }
         }
         public int getPreviousX(char c, int back) {
@@ -227,9 +238,16 @@
                 return mYBuf[index];
             }
         }
-        public String getLastString() {
+        public String getLastWord(int ignoreCharCount) {
             StringBuilder sb = new StringBuilder();
-            for (int i = 0; i < mLength; ++i) {
+            int i = ignoreCharCount;
+            for (; i < mLength; ++i) {
+                char c = mCharBuf[normalize(mEnd - 1 - i)];
+                if (!((LatinIME)mContext).isWordSeparator(c)) {
+                    break;
+                }
+            }
+            for (; i < mLength; ++i) {
                 char c = mCharBuf[normalize(mEnd - 1 - i)];
                 if (!((LatinIME)mContext).isWordSeparator(c)) {
                     sb.append(c);
@@ -244,6 +262,8 @@
         }
     }
 
+
+    /* Damerau-Levenshtein distance */
     public static int editDistance(CharSequence s, CharSequence t) {
         if (s == null || t == null) {
             throw new IllegalArgumentException("editDistance: Arguments should not be null.");
@@ -259,14 +279,29 @@
         }
         for (int i = 0; i < sl; ++i) {
             for (int j = 0; j < tl; ++j) {
-                if (Character.toLowerCase(s.charAt(i)) == Character.toLowerCase(t.charAt(j))) {
-                    dp[i + 1][j + 1] = dp[i][j];
-                } else {
-                    dp[i + 1][j + 1] = 1 + Math.min(dp[i][j],
-                            Math.min(dp[i + 1][j], dp[i][j + 1]));
+                final char sc = Character.toLowerCase(s.charAt(i));
+                final char tc = Character.toLowerCase(t.charAt(j));
+                final int cost = sc == tc ? 0 : 1;
+                dp[i + 1][j + 1] = Math.min(
+                        dp[i][j + 1] + 1, Math.min(dp[i + 1][j] + 1, dp[i][j] + cost));
+                // Overwrite for transposition cases
+                if (i > 0 && j > 0
+                        && sc == Character.toLowerCase(t.charAt(j - 1))
+                        && tc == Character.toLowerCase(s.charAt(i - 1))) {
+                    dp[i + 1][j + 1] = Math.min(dp[i + 1][j + 1], dp[i - 1][j - 1] + cost);
                 }
             }
         }
+        if (DBG_EDIT_DISTANCE) {
+            Log.d(TAG, "editDistance:" + s + "," + t);
+            for (int i = 0; i < dp.length; ++i) {
+                StringBuffer sb = new StringBuffer();
+                for (int j = 0; j < dp[i].length; ++j) {
+                    sb.append(dp[i][j]).append(',');
+                }
+                Log.d(TAG, i + ":" + sb.toString());
+            }
+        }
         return dp[sl][tl];
     }
 
@@ -285,7 +320,7 @@
 
     // In dictionary.cpp, getSuggestion() method,
     // suggestion scores are computed using the below formula.
-    // original score (called 'frequency')
+    // original score
     //  := pow(mTypedLetterMultiplier (this is defined 2),
     //         (the number of matched characters between typed word and suggested word))
     //     * (individual word's score which defined in the unigram dictionary,
@@ -295,7 +330,7 @@
     //       (full match up to min(before.length(), after.length())
     //       => Then multiply by FULL_MATCHED_WORDS_PROMOTION_RATE (this is defined 1.2)
     //     - If the word is a true full match except for differences in accents or
-    //       capitalization, then treat it as if the frequency was 255.
+    //       capitalization, then treat it as if the score was 255.
     //     - If before.length() == after.length()
     //       => multiply by mFullWordMultiplier (this is defined 2))
     // So, maximum original score is pow(2, min(before.length(), after.length())) * 255 * 2 * 1.2
@@ -306,6 +341,7 @@
     private static final int MAX_INITIAL_SCORE = 255;
     private static final int TYPED_LETTER_MULTIPLIER = 2;
     private static final int FULL_WORD_MULTIPLIER = 2;
+    private static final int S_INT_MAX = 2147483647;
     public static double calcNormalizedScore(CharSequence before, CharSequence after, int score) {
         final int beforeLength = before.length();
         final int afterLength = after.length();
@@ -313,8 +349,16 @@
         final int distance = editDistance(before, after);
         // If afterLength < beforeLength, the algorithm is suggesting a word by excessive character
         // correction.
-        final double maximumScore = MAX_INITIAL_SCORE
-                * Math.pow(TYPED_LETTER_MULTIPLIER, Math.min(beforeLength, afterLength))
+        int spaceCount = 0;
+        for (int i = 0; i < afterLength; ++i) {
+            if (after.charAt(i) == Keyboard.CODE_SPACE) {
+                ++spaceCount;
+            }
+        }
+        if (spaceCount == afterLength) return 0;
+        final double maximumScore = score == S_INT_MAX ? S_INT_MAX : MAX_INITIAL_SCORE
+                * Math.pow(
+                        TYPED_LETTER_MULTIPLIER, Math.min(beforeLength, afterLength - spaceCount))
                 * FULL_WORD_MULTIPLIER;
         // add a weight based on edit distance.
         // distance <= max(afterLength, beforeLength) == afterLength,
@@ -485,7 +529,7 @@
         case InputType.TYPE_CLASS_PHONE:
             return KeyboardId.MODE_PHONE;
         case InputType.TYPE_CLASS_TEXT:
-            if (Utils.isEmailVariation(variation)) {
+            if (InputTypeCompatUtils.isEmailVariation(variation)) {
                 return KeyboardId.MODE_EMAIL;
             } else if (variation == InputType.TYPE_TEXT_VARIATION_URI) {
                 return KeyboardId.MODE_URL;
@@ -503,31 +547,6 @@
         }
     }
 
-    public static boolean isEmailVariation(int variation) {
-        return variation == InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
-                || variation == InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS;
-    }
-
-    // Please refer to TextView.isPasswordInputType
-    public static boolean isPasswordInputType(int inputType) {
-        final int variation =
-                inputType & (InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION);
-        return (variation
-                == (InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD))
-                || (variation
-                == (InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD))
-                || (variation
-                == (InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD));
-    }
-
-    // Please refer to TextView.isVisiblePasswordInputType
-    public static boolean isVisiblePasswordInputType(int inputType) {
-        final int variation =
-                inputType & (InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION);
-        return variation
-                == (InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
-    }
-
     public static boolean containsInCsv(String key, String csv) {
         if (csv == null)
             return false;
@@ -551,7 +570,9 @@
      * @return main dictionary resource id
      */
     public static int getMainDictionaryResourceId(Resources res) {
-        return res.getIdentifier("main", "raw", LatinIME.class.getPackage().getName());
+        final String MAIN_DIC_NAME = "main";
+        String packageName = LatinIME.class.getPackage().getName();
+        return res.getIdentifier(MAIN_DIC_NAME, "raw", packageName);
     }
 
     public static void loadNativeLibrary() {
@@ -561,4 +582,84 @@
             Log.e(TAG, "Could not load native library jni_latinime");
         }
     }
+
+    /**
+     * Returns true if a and b are equal ignoring the case of the character.
+     * @param a first character to check
+     * @param b second character to check
+     * @return {@code true} if a and b are equal, {@code false} otherwise.
+     */
+    public static boolean equalsIgnoreCase(char a, char b) {
+        // Some language, such as Turkish, need testing both cases.
+        return a == b
+                || Character.toLowerCase(a) == Character.toLowerCase(b)
+                || Character.toUpperCase(a) == Character.toUpperCase(b);
+    }
+
+    /**
+     * Returns true if a and b are equal ignoring the case of the characters, including if they are
+     * both null.
+     * @param a first CharSequence to check
+     * @param b second CharSequence to check
+     * @return {@code true} if a and b are equal, {@code false} otherwise.
+     */
+    public static boolean equalsIgnoreCase(CharSequence a, CharSequence b) {
+        if (a == b)
+            return true;  // including both a and b are null.
+        if (a == null || b == null)
+            return false;
+        final int length = a.length();
+        if (length != b.length())
+            return false;
+        for (int i = 0; i < length; i++) {
+            if (!equalsIgnoreCase(a.charAt(i), b.charAt(i)))
+                return false;
+        }
+        return true;
+    }
+
+    /**
+     * Returns true if a and b are equal ignoring the case of the characters, including if a is null
+     * and b is zero length.
+     * @param a CharSequence to check
+     * @param b character array to check
+     * @param offset start offset of array b
+     * @param length length of characters in array b
+     * @return {@code true} if a and b are equal, {@code false} otherwise.
+     * @throws IndexOutOfBoundsException
+     *   if {@code offset < 0 || length < 0 || offset + length > data.length}.
+     * @throws NullPointerException if {@code b == null}.
+     */
+    public static boolean equalsIgnoreCase(CharSequence a, char[] b, int offset, int length) {
+        if (offset < 0 || length < 0 || length > b.length - offset)
+            throw new IndexOutOfBoundsException("array.length=" + b.length + " offset=" + offset
+                    + " length=" + length);
+        if (a == null)
+            return length == 0;  // including a is null and b is zero length.
+        if (a.length() != length)
+            return false;
+        for (int i = 0; i < length; i++) {
+            if (!equalsIgnoreCase(a.charAt(i), b[offset + i]))
+                return false;
+        }
+        return true;
+    }
+
+    public static float getDipScale(Context context) {
+        final float scale = context.getResources().getDisplayMetrics().density;
+        return scale;
+    }
+
+    /** Convert pixel to DIP */
+    public static int dipToPixel(float scale, int dip) {
+        return (int) (dip * scale + 0.5);
+    }
+
+    public static Locale setSystemLocale(Resources res, Locale newLocale) {
+        final Configuration conf = res.getConfiguration();
+        final Locale saveLocale = conf.locale;
+        conf.locale = newLocale;
+        res.updateConfiguration(conf, res.getDisplayMetrics());
+        return saveLocale;
+    }
 }
diff --git a/java/src/com/android/inputmethod/latin/WordAlternatives.java b/java/src/com/android/inputmethod/latin/WordAlternatives.java
new file mode 100644
index 0000000..0e99144
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/WordAlternatives.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import com.android.inputmethod.keyboard.KeyboardSwitcher;
+
+import android.text.TextUtils;
+
+public class WordAlternatives {
+    public final CharSequence mChosenWord;
+    public final WordComposer mWordComposer;
+
+    public WordAlternatives(CharSequence chosenWord, WordComposer wordComposer) {
+        mChosenWord = chosenWord;
+        mWordComposer = wordComposer;
+    }
+
+    public CharSequence getChosenWord() {
+        return mChosenWord;
+    }
+
+    public CharSequence getOriginalWord() {
+        return mWordComposer.getTypedWord();
+    }
+
+    public SuggestedWords.Builder getAlternatives(
+            Suggest suggest, KeyboardSwitcher keyboardSwitcher) {
+        return getTypedSuggestions(suggest, keyboardSwitcher, mWordComposer);
+    }
+
+    @Override
+    public int hashCode() {
+        return mChosenWord.hashCode();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        return o instanceof CharSequence && TextUtils.equals(mChosenWord, (CharSequence)o);
+    }
+
+    private static SuggestedWords.Builder getTypedSuggestions(
+            Suggest suggest, KeyboardSwitcher keyboardSwitcher, WordComposer word) {
+        return suggest.getSuggestedWordBuilder(keyboardSwitcher.getInputView(), word, null);
+    }
+}
\ No newline at end of file
diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java
index 0258389..cf05929 100644
--- a/java/src/com/android/inputmethod/latin/WordComposer.java
+++ b/java/src/com/android/inputmethod/latin/WordComposer.java
@@ -31,18 +31,18 @@
     /**
      * The list of unicode values for each keystroke (including surrounding keys)
      */
-    private final ArrayList<int[]> mCodes;
+    private ArrayList<int[]> mCodes;
 
     private int mTypedLength;
-    private final int[] mXCoordinates;
-    private final int[] mYCoordinates;
+    private int[] mXCoordinates;
+    private int[] mYCoordinates;
 
     /**
      * The word chosen from the candidate list, until it is committed.
      */
     private String mPreferredWord;
 
-    private final StringBuilder mTypedWord;
+    private StringBuilder mTypedWord;
 
     private int mCapsCount;
 
@@ -63,6 +63,10 @@
     }
 
     WordComposer(WordComposer source) {
+        init(source);
+    }
+
+    public void init(WordComposer source) {
         mCodes = new ArrayList<int[]>(source.mCodes);
         mPreferredWord = source.mPreferredWord;
         mTypedWord = new StringBuilder(source.mTypedWord);
diff --git a/native/Android.mk b/native/Android.mk
index c8342e3..4727b1e 100644
--- a/native/Android.mk
+++ b/native/Android.mk
@@ -3,6 +3,11 @@
 
 LOCAL_C_INCLUDES += $(LOCAL_PATH)/src
 
+LOCAL_CFLAGS += -Werror -Wall
+
+# To suppress compiler warnings for unused variables/functions used for debug features etc.
+LOCAL_CFLAGS += -Wno-unused-parameter -Wno-unused-function
+
 LOCAL_SRC_FILES := \
     jni/com_android_inputmethod_keyboard_ProximityInfo.cpp \
     jni/com_android_inputmethod_latin_BinaryDictionary.cpp \
diff --git a/native/src/bigram_dictionary.cpp b/native/src/bigram_dictionary.cpp
index 5ec310f..36761b8 100644
--- a/native/src/bigram_dictionary.cpp
+++ b/native/src/bigram_dictionary.cpp
@@ -30,8 +30,10 @@
     : DICT(dict), MAX_WORD_LENGTH(maxWordLength),
     MAX_ALTERNATIVES(maxAlternatives), IS_LATEST_DICT_VERSION(isLatestDictVersion),
     HAS_BIGRAM(hasBigram), mParentDictionary(parentDictionary) {
-    if (DEBUG_DICT) LOGI("BigramDictionary - constructor");
-    if (DEBUG_DICT) LOGI("Has Bigram : %d", hasBigram);
+    if (DEBUG_DICT) {
+        LOGI("BigramDictionary - constructor");
+        LOGI("Has Bigram : %d", hasBigram);
+    }
 }
 
 BigramDictionary::~BigramDictionary() {
@@ -54,7 +56,9 @@
         }
         insertAt++;
     }
-    if (DEBUG_DICT) LOGI("Bigram: InsertAt -> %d maxBigrams: %d", insertAt, mMaxBigrams);
+    if (DEBUG_DICT) {
+        LOGI("Bigram: InsertAt -> %d maxBigrams: %d", insertAt, mMaxBigrams);
+    }
     if (insertAt < mMaxBigrams) {
         memmove((char*) mBigramFreq + (insertAt + 1) * sizeof(mBigramFreq[0]),
                (char*) mBigramFreq + insertAt * sizeof(mBigramFreq[0]),
@@ -68,7 +72,9 @@
             *dest++ = *word++;
         }
         *dest = 0; // NULL terminate
-        if (DEBUG_DICT) LOGI("Bigram: Added word at %d", insertAt);
+        if (DEBUG_DICT) {
+            LOGI("Bigram: Added word at %d", insertAt);
+        }
         return true;
     }
     return false;
@@ -107,7 +113,9 @@
     if (HAS_BIGRAM && IS_LATEST_DICT_VERSION) {
         int pos = mParentDictionary->isValidWordRec(
                 DICTIONARY_HEADER_SIZE, prevWord, 0, prevWordLength);
-        if (DEBUG_DICT) LOGI("Pos -> %d", pos);
+        if (DEBUG_DICT) {
+            LOGI("Pos -> %d", pos);
+        }
         if (pos < 0) {
             return 0;
         }
@@ -151,7 +159,9 @@
         }
         pos = followDownBranchAddress; // pos start at count
         int count = DICT[pos] & 0xFF;
-        if (DEBUG_DICT) LOGI("count - %d",count);
+        if (DEBUG_DICT) {
+            LOGI("count - %d",count);
+        }
         pos++;
         for (int i = 0; i < count; i++) {
             // pos at data
@@ -225,7 +235,9 @@
         }
         depth++;
         if (followDownBranchAddress == 0) {
-            if (DEBUG_DICT) LOGI("ERROR!!! Cannot find bigram!!");
+            if (DEBUG_DICT) {
+                LOGI("ERROR!!! Cannot find bigram!!");
+            }
             break;
         }
     }
diff --git a/native/src/defines.h b/native/src/defines.h
index 00cbb6c..bdab19f 100644
--- a/native/src/defines.h
+++ b/native/src/defines.h
@@ -77,8 +77,8 @@
 }
 
 #else // FLAG_DBG
-#define LOGE
-#define LOGI
+#define LOGE(fmt, ...)
+#define LOGI(fmt, ...)
 #define DEBUG_DICT false
 #define DEBUG_DICT_FULL false
 #define DEBUG_SHOW_FOUND_WORD false
@@ -138,12 +138,14 @@
 #define SUGGEST_WORDS_WITH_SPACE_PROXIMITY true
 
 // The following "rate"s are used as a multiplier before dividing by 100, so they are in percent.
-#define WORDS_WITH_MISSING_CHARACTER_DEMOTION_RATE 70
+#define WORDS_WITH_MISSING_CHARACTER_DEMOTION_RATE 80
+#define WORDS_WITH_MISSING_CHARACTER_DEMOTION_START_POS_10X 12
 #define WORDS_WITH_MISSING_SPACE_CHARACTER_DEMOTION_RATE 80
 #define WORDS_WITH_EXCESSIVE_CHARACTER_DEMOTION_RATE 75
 #define WORDS_WITH_EXCESSIVE_CHARACTER_OUT_OF_PROXIMITY_DEMOTION_RATE 75
 #define WORDS_WITH_TRANSPOSED_CHARACTERS_DEMOTION_RATE 60
 #define FULL_MATCHED_WORDS_PROMOTION_RATE 120
+#define WORDS_WITH_PROXIMITY_CHARACTER_DEMOTION_RATE 90
 
 // This should be greater than or equal to MAX_WORD_LENGTH defined in BinaryDictionary.java
 // This is only used for the size of array. Not to be used in c functions.
diff --git a/native/src/proximity_info.h b/native/src/proximity_info.h
index 0f12018..c2062e8 100644
--- a/native/src/proximity_info.h
+++ b/native/src/proximity_info.h
@@ -32,13 +32,13 @@
     bool hasSpaceProximity(const int x, const int y) const;
 private:
     int getStartIndexFromCoordinates(const int x, const int y) const;
-    const int CELL_WIDTH;
-    const int CELL_HEIGHT;
+    const int MAX_PROXIMITY_CHARS_SIZE;
     const int KEYBOARD_WIDTH;
     const int KEYBOARD_HEIGHT;
     const int GRID_WIDTH;
     const int GRID_HEIGHT;
-    const int MAX_PROXIMITY_CHARS_SIZE;
+    const int CELL_WIDTH;
+    const int CELL_HEIGHT;
     uint32_t *mProximityCharsArray;
 };
 }; // namespace latinime
diff --git a/native/src/unigram_dictionary.cpp b/native/src/unigram_dictionary.cpp
index 30fbaea..20a1852 100644
--- a/native/src/unigram_dictionary.cpp
+++ b/native/src/unigram_dictionary.cpp
@@ -43,7 +43,9 @@
     ROOT_POS(isLatestDictVersion ? DICTIONARY_HEADER_SIZE : 0),
     BYTES_IN_ONE_CHAR(MAX_PROXIMITY_CHARS * sizeof(*mInputCodes)),
     MAX_UMLAUT_SEARCH_DEPTH(DEFAULT_MAX_UMLAUT_SEARCH_DEPTH) {
-    if (DEBUG_DICT) LOGI("UnigramDictionary - constructor");
+    if (DEBUG_DICT) {
+        LOGI("UnigramDictionary - constructor");
+    }
 }
 
 UnigramDictionary::~UnigramDictionary() {}
@@ -183,7 +185,9 @@
     // Suggestion with missing character
     if (SUGGEST_WORDS_WITH_MISSING_CHARACTER) {
         for (int i = 0; i < codesSize; ++i) {
-            if (DEBUG_DICT) LOGI("--- Suggest missing characters %d", i);
+            if (DEBUG_DICT) {
+                LOGI("--- Suggest missing characters %d", i);
+            }
             getSuggestionCandidates(i, -1, -1, NULL, 0, MAX_DEPTH);
         }
     }
@@ -194,7 +198,9 @@
     if (SUGGEST_WORDS_WITH_EXCESSIVE_CHARACTER
             && mInputLength >= MIN_USER_TYPED_LENGTH_FOR_EXCESSIVE_CHARACTER_SUGGESTION) {
         for (int i = 0; i < codesSize; ++i) {
-            if (DEBUG_DICT) LOGI("--- Suggest excessive characters %d", i);
+            if (DEBUG_DICT) {
+                LOGI("--- Suggest excessive characters %d", i);
+            }
             getSuggestionCandidates(-1, i, -1, NULL, 0, MAX_DEPTH);
         }
     }
@@ -205,7 +211,9 @@
     // Only suggest words that length is mInputLength
     if (SUGGEST_WORDS_WITH_TRANSPOSED_CHARACTERS) {
         for (int i = 0; i < codesSize; ++i) {
-            if (DEBUG_DICT) LOGI("--- Suggest transposed characters %d", i);
+            if (DEBUG_DICT) {
+                LOGI("--- Suggest transposed characters %d", i);
+            }
             getSuggestionCandidates(-1, -1, i, NULL, 0, mInputLength - 1);
         }
     }
@@ -216,7 +224,9 @@
     if (SUGGEST_WORDS_WITH_MISSING_SPACE_CHARACTER
             && mInputLength >= MIN_USER_TYPED_LENGTH_FOR_MISSING_SPACE_SUGGESTION) {
         for (int i = 1; i < codesSize; ++i) {
-            if (DEBUG_DICT) LOGI("--- Suggest missing space characters %d", i);
+            if (DEBUG_DICT) {
+                LOGI("--- Suggest missing space characters %d", i);
+            }
             getMissingSpaceWords(mInputLength, i);
         }
     }
@@ -226,12 +236,15 @@
     if (SUGGEST_WORDS_WITH_SPACE_PROXIMITY) {
         // The first and last "mistyped spaces" are taken care of by excessive character handling
         for (int i = 1; i < codesSize - 1; ++i) {
-            if (DEBUG_DICT) LOGI("--- Suggest words with proximity space %d", i);
+            if (DEBUG_DICT) {
+                LOGI("--- Suggest words with proximity space %d", i);
+            }
             const int x = xcoordinates[i];
             const int y = ycoordinates[i];
-            if (DEBUG_PROXIMITY_INFO)
+            if (DEBUG_PROXIMITY_INFO) {
                 LOGI("Input[%d] x = %d, y = %d, has space proximity = %d",
                         i, x, y, proximityInfo->hasSpaceProximity(x, y));
+            }
             if (proximityInfo->hasSpaceProximity(x, y)) {
                 getMistypedSpaceWords(mInputLength, i);
             }
@@ -242,7 +255,9 @@
 
 void UnigramDictionary::initSuggestions(const int *codes, const int codesSize,
         unsigned short *outWords, int *frequencies) {
-    if (DEBUG_DICT) LOGI("initSuggest");
+    if (DEBUG_DICT) {
+        LOGI("initSuggest");
+    }
     mFrequencies = frequencies;
     mOutputChars = outWords;
     mInputCodes = codes;
@@ -266,7 +281,9 @@
         LOGI("Found word = %s, freq = %d", s, frequency);
     }
     if (length > MAX_WORD_LENGTH) {
-        if (DEBUG_DICT) LOGI("Exceeded max word length.");
+        if (DEBUG_DICT) {
+            LOGI("Exceeded max word length.");
+        }
         return false;
     }
 
@@ -283,7 +300,7 @@
         if (DEBUG_DICT) {
             char s[length + 1];
             for (int i = 0; i <= length; i++) s[i] = word[i];
-            LOGI("Added word = %s, freq = %d", s, frequency);
+            LOGI("Added word = %s, freq = %d, %d", s, frequency, S_INT_MAX);
         }
         memmove((char*) mFrequencies + (insertAt + 1) * sizeof(mFrequencies[0]),
                (char*) mFrequencies + insertAt * sizeof(mFrequencies[0]),
@@ -297,7 +314,9 @@
             *dest++ = *word++;
         }
         *dest = 0; // NULL terminate
-        if (DEBUG_DICT) LOGI("Added word at %d", insertAt);
+        if (DEBUG_DICT) {
+            LOGI("Added word at %d", insertAt);
+        }
         return true;
     }
     return false;
@@ -390,14 +409,93 @@
     }
 }
 
-inline static void multiplyRate(const int rate, int *freq) {
-    if (rate > 1000000) {
-        *freq = (*freq / 100) * rate;
-    } else {
-        *freq = *freq * rate / 100;
+static const int TWO_31ST_DIV_255 = S_INT_MAX / 255;
+static inline int capped255MultForFullMatchAccentsOrCapitalizationDifference(const int num) {
+    return (num < TWO_31ST_DIV_255 ? 255 * num : S_INT_MAX);
+}
+
+static const int TWO_31ST_DIV_2 = S_INT_MAX / 2;
+inline static void multiplyIntCapped(const int multiplier, int *base) {
+    const int temp = *base;
+    if (temp != S_INT_MAX) {
+        // Branch if multiplier == 2 for the optimization
+        if (multiplier == 2) {
+            *base = TWO_31ST_DIV_2 >= temp ? temp << 1 : S_INT_MAX;
+        } else {
+            const int tempRetval = temp * multiplier;
+            *base = tempRetval >= temp ? tempRetval : S_INT_MAX;
+        }
     }
 }
 
+inline static int powerIntCapped(const int base, const int n) {
+    if (base == 2) {
+        return n < 31 ? 1 << n : S_INT_MAX;
+    } else {
+        int ret = base;
+        for (int i = 1; i < n; ++i) multiplyIntCapped(base, &ret);
+        return ret;
+    }
+}
+
+inline static void multiplyRate(const int rate, int *freq) {
+    if (*freq != S_INT_MAX) {
+        if (*freq > 1000000) {
+            *freq /= 100;
+            multiplyIntCapped(rate, freq);
+        } else {
+            multiplyIntCapped(rate, freq);
+            *freq /= 100;
+        }
+    }
+}
+
+inline static int calcFreqForSplitTwoWords(
+        const int typedLetterMultiplier, const int firstWordLength,
+        const int secondWordLength, const int firstFreq, const int secondFreq) {
+    if (firstWordLength == 0 || secondWordLength == 0) {
+        return 0;
+    }
+    const int firstDemotionRate = 100 - 100 / (firstWordLength + 1);
+    int tempFirstFreq = firstFreq;
+    multiplyRate(firstDemotionRate, &tempFirstFreq);
+
+    const int secondDemotionRate = 100 - 100 / (secondWordLength + 1);
+    int tempSecondFreq = secondFreq;
+    multiplyRate(secondDemotionRate, &tempSecondFreq);
+
+    const int totalLength = firstWordLength + secondWordLength;
+
+    // Promote pairFreq with multiplying by 2, because the word length is the same as the typed
+    // length.
+    int totalFreq = tempFirstFreq + tempSecondFreq;
+
+    // This is a workaround to try offsetting the not-enough-demotion which will be done in
+    // calcNormalizedScore in Utils.java.
+    // In calcNormalizedScore the score will be demoted by (1 - 1 / length)
+    // but we demoted only (1 - 1 / (length + 1)) so we will additionally adjust freq by
+    // (1 - 1 / length) / (1 - 1 / (length + 1)) = (1 - 1 / (length * length))
+    const int normalizedScoreNotEnoughDemotionAdjustment = 100 - 100 / (totalLength * totalLength);
+    multiplyRate(normalizedScoreNotEnoughDemotionAdjustment, &totalFreq);
+
+    // At this moment, totalFreq is calculated by the following formula:
+    // (firstFreq * (1 - 1 / (firstWordLength + 1)) + secondFreq * (1 - 1 / (secondWordLength + 1)))
+    //        * (1 - 1 / totalLength) / (1 - 1 / (totalLength + 1))
+
+    multiplyIntCapped(powerIntCapped(typedLetterMultiplier, totalLength), &totalFreq);
+
+    // This is another workaround to offset the demotion which will be done in
+    // calcNormalizedScore in Utils.java.
+    // In calcNormalizedScore the score will be demoted by (1 - 1 / length) so we have to promote
+    // the same amount because we already have adjusted the synthetic freq of this "missing or
+    // mistyped space" suggestion candidate above in this method.
+    const int normalizedScoreDemotionRateOffset = (100 + 100 / totalLength);
+    multiplyRate(normalizedScoreDemotionRateOffset, &totalFreq);
+
+    multiplyRate(WORDS_WITH_MISSING_SPACE_CHARACTER_DEMOTION_RATE, &totalFreq);
+    return totalFreq;
+}
+
 bool UnigramDictionary::getSplitTwoWordsSuggestion(const int inputLength,
         const int firstWordStartPos, const int firstWordLength, const int secondWordStartPos,
         const int secondWordLength) {
@@ -409,7 +507,9 @@
     // Allocating variable length array on stack
     unsigned short word[newWordLength];
     const int firstFreq = getBestWordFreq(firstWordStartPos, firstWordLength, mWord);
-    if (DEBUG_DICT) LOGI("First freq: %d", firstFreq);
+    if (DEBUG_DICT) {
+        LOGI("First freq: %d", firstFreq);
+    }
     if (firstFreq <= 0) return false;
 
     for (int i = 0; i < firstWordLength; ++i) {
@@ -417,7 +517,9 @@
     }
 
     const int secondFreq = getBestWordFreq(secondWordStartPos, secondWordLength, mWord);
-    if (DEBUG_DICT) LOGI("Second  freq:  %d", secondFreq);
+    if (DEBUG_DICT) {
+        LOGI("Second  freq:  %d", secondFreq);
+    }
     if (secondFreq <= 0) return false;
 
     word[firstWordLength] = SPACE;
@@ -425,9 +527,12 @@
         word[i] = mWord[i - firstWordLength - 1];
     }
 
-    int pairFreq = ((firstFreq + secondFreq) / 2);
-    for (int i = 0; i < inputLength; ++i) pairFreq *= TYPED_LETTER_MULTIPLIER;
-    multiplyRate(WORDS_WITH_MISSING_SPACE_CHARACTER_DEMOTION_RATE, &pairFreq);
+    int pairFreq = calcFreqForSplitTwoWords(
+            TYPED_LETTER_MULTIPLIER, firstWordLength, secondWordLength, firstFreq, secondFreq);
+    if (DEBUG_DICT) {
+        LOGI("Split two words:  %d, %d, %d, %d, %d", firstFreq, secondFreq, pairFreq, inputLength,
+                TYPED_LETTER_MULTIPLIER);
+    }
     addWord(word, newWordLength, pairFreq);
     return true;
 }
@@ -485,19 +590,21 @@
     }
 }
 
-static const int TWO_31ST_DIV_255 = S_INT_MAX / 255;
-static inline int capped255MultForFullMatchAccentsOrCapitalizationDifference(const int num) {
-    return (num < TWO_31ST_DIV_255 ? 255 * num : S_INT_MAX);
-}
 inline int UnigramDictionary::calculateFinalFreq(const int inputIndex, const int depth,
         const int matchWeight, const int skipPos, const int excessivePos, const int transposedPos,
         const int freq, const bool sameLength) const {
     // TODO: Demote by edit distance
     int finalFreq = freq * matchWeight;
     if (skipPos >= 0) {
-        if (mInputLength >= 3) {
-            multiplyRate(WORDS_WITH_MISSING_CHARACTER_DEMOTION_RATE *
-                    (mInputLength - 2) / (mInputLength - 1), &finalFreq);
+        if (mInputLength >= 2) {
+            const int demotionRate = WORDS_WITH_MISSING_CHARACTER_DEMOTION_RATE
+                    * (10 * mInputLength - WORDS_WITH_MISSING_CHARACTER_DEMOTION_START_POS_10X)
+                    / (10 * mInputLength
+                            - WORDS_WITH_MISSING_CHARACTER_DEMOTION_START_POS_10X + 10);
+            if (DEBUG_DICT_FULL) {
+                LOGI("Demotion rate for missing character is %d.", demotionRate);
+            }
+            multiplyRate(demotionRate, &finalFreq);
         } else {
             finalFreq = 0;
         }
@@ -511,17 +618,30 @@
         }
     }
     int lengthFreq = TYPED_LETTER_MULTIPLIER;
-    for (int i = 0; i < depth; ++i) lengthFreq *= TYPED_LETTER_MULTIPLIER;
+    multiplyIntCapped(powerIntCapped(TYPED_LETTER_MULTIPLIER, depth), &lengthFreq);
     if (lengthFreq == matchWeight) {
+        // Full exact match
         if (depth > 1) {
-            if (DEBUG_DICT) LOGI("Found full matched word.");
+            if (DEBUG_DICT) {
+                LOGI("Found full matched word.");
+            }
             multiplyRate(FULL_MATCHED_WORDS_PROMOTION_RATE, &finalFreq);
         }
         if (sameLength && transposedPos < 0 && skipPos < 0 && excessivePos < 0) {
             finalFreq = capped255MultForFullMatchAccentsOrCapitalizationDifference(finalFreq);
         }
+    } else if (sameLength && transposedPos < 0 && skipPos < 0 && excessivePos < 0 && depth > 0) {
+        // A word with proximity corrections
+        if (DEBUG_DICT) {
+            LOGI("Found one proximity correction.");
+        }
+        multiplyIntCapped(TYPED_LETTER_MULTIPLIER, &finalFreq);
+        multiplyRate(WORDS_WITH_PROXIMITY_CHARACTER_DEMOTION_RATE, &finalFreq);
     }
-    if (sameLength) finalFreq *= FULL_WORD_MULTIPLIER;
+    if (DEBUG_DICT) {
+        LOGI("calc: %d, %d", depth, sameLength);
+    }
+    if (sameLength) multiplyIntCapped(FULL_WORD_MULTIPLIER, &finalFreq);
     return finalFreq;
 }
 
@@ -674,7 +794,7 @@
         // If inputIndex is greater than mInputLength, that means there is no
         // proximity chars. So, we don't need to check proximity.
         if (SAME_OR_ACCENTED_OR_CAPITALIZED_CHAR == matchedProximityCharId) {
-            matchWeight = matchWeight * TYPED_LETTER_MULTIPLIER;
+            multiplyIntCapped(TYPED_LETTER_MULTIPLIER, &matchWeight);
         }
         bool isSameAsUserTypedLength = mInputLength == inputIndex + 1
                 || (excessivePos == mInputLength - 1 && inputIndex == mInputLength - 2);
@@ -768,7 +888,9 @@
     *siblingPos = Dictionary::setDictionaryValues(DICT, IS_LATEST_DICT_VERSION, firstChildPos, &c,
             newChildPosition, newTerminal, newFreq);
     const unsigned int inputC = currentChars[0];
-    if (DEBUG_DICT) assert(inputC <= U_SHORT_MAX);
+    if (DEBUG_DICT) {
+        assert(inputC <= U_SHORT_MAX);
+    }
     const unsigned short baseLowerC = toBaseLowerCase(c);
     const bool matched = (inputC == baseLowerC || inputC == c);
     const bool hasChild = *newChildPosition != 0;
@@ -776,7 +898,9 @@
         word[depth] = c;
         if (DEBUG_DICT && DEBUG_NODE) {
             LOGI("Node(%c, %c)<%d>, %d, %d", inputC, c, matched, hasChild, *newFreq);
-            if (*newTerminal) LOGI("Terminal %d", *newFreq);
+            if (*newTerminal) {
+                LOGI("Terminal %d", *newFreq);
+            }
         }
         if (hasChild) {
             *newCount = Dictionary::getCount(DICT, newChildPosition);
diff --git a/tests/src/com/android/inputmethod/keyboard/MiniKeyboardBuilderTests.java b/tests/src/com/android/inputmethod/keyboard/MiniKeyboardBuilderTests.java
index 7e3106d..600342a 100644
--- a/tests/src/com/android/inputmethod/keyboard/MiniKeyboardBuilderTests.java
+++ b/tests/src/com/android/inputmethod/keyboard/MiniKeyboardBuilderTests.java
@@ -25,6 +25,16 @@
     private static final int WIDTH = 10;
     private static final int HEIGHT = 10;
 
+    private static final int KEYBOARD_WIDTH = WIDTH * 10;
+    private static final int XPOS_L0 = WIDTH * 0;
+    private static final int XPOS_L1 = WIDTH * 1;
+    private static final int XPOS_L2 = WIDTH * 2;
+    private static final int XPOS_M0 = WIDTH * 5;
+    private static final int XPOS_R3 = WIDTH * 6;
+    private static final int XPOS_R2 = WIDTH * 7;
+    private static final int XPOS_R1 = WIDTH * 8;
+    private static final int XPOS_R0 = WIDTH * 9;
+
     @Override
     protected void setUp() throws Exception {
         super.setUp();
@@ -33,9 +43,8 @@
     public void testLayoutError() {
         MiniKeyboardLayoutParams params = null;
         try {
-            params = new MiniKeyboardLayoutParams(
-                    10, MAX_COLUMNS + 1, WIDTH, HEIGHT,
-                    WIDTH * 2, WIDTH * MAX_COLUMNS);
+            params = new MiniKeyboardLayoutParams(10, MAX_COLUMNS + 1, WIDTH, HEIGHT, WIDTH * 2,
+                    WIDTH * MAX_COLUMNS);
             fail("Should throw IllegalArgumentException");
         } catch (IllegalArgumentException e) {
             // Too small keyboard to hold mini keyboard.
@@ -48,39 +57,198 @@
     // "[1]" is the default key.
 
     // [1]
-    public void testLayout1Key() {
-        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(
-                1, MAX_COLUMNS, WIDTH, HEIGHT,
-                WIDTH * 5, WIDTH * 10);
-        assertEquals("1 key columns", 1, params.mNumColumns);
-        assertEquals("1 key rows", 1, params.mNumRows);
-        assertEquals("1 key left", 0, params.mLeftKeys);
-        assertEquals("1 key right", 1, params.mRightKeys);
-        assertEquals("1 key [1]", 0, params.getColumnPos(0));
-        assertEquals("1 key centering", false, params.mTopRowNeedsCentering);
-        assertEquals("1 key default", 0, params.getDefaultKeyCoordX());
+    public void testLayout1KeyM0() {
+        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(1, MAX_COLUMNS, WIDTH,
+                HEIGHT, XPOS_M0, KEYBOARD_WIDTH);
+        assertEquals("1 key M0 columns", 1, params.mNumColumns);
+        assertEquals("1 key M0 rows", 1, params.mNumRows);
+        assertEquals("1 key M0 left", 0, params.mLeftKeys);
+        assertEquals("1 key M0 right", 1, params.mRightKeys);
+        assertEquals("1 key M0 [1]", 0, params.getColumnPos(0));
+        assertEquals("1 key M0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("1 key M0 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |[1]
+    public void testLayout1KeyL0() {
+        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(1, MAX_COLUMNS, WIDTH,
+                HEIGHT, XPOS_L0, KEYBOARD_WIDTH);
+        assertEquals("1 key L0 columns", 1, params.mNumColumns);
+        assertEquals("1 key L0 rows", 1, params.mNumRows);
+        assertEquals("1 key L0 left", 0, params.mLeftKeys);
+        assertEquals("1 key L0 right", 1, params.mRightKeys);
+        assertEquals("1 key L0 [1]", 0, params.getColumnPos(0));
+        assertEquals("1 key L0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("1 key L0 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___ [1]
+    public void testLayout1KeyL1() {
+        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(1, MAX_COLUMNS, WIDTH,
+                HEIGHT, XPOS_L1, KEYBOARD_WIDTH);
+        assertEquals("1 key L1 columns", 1, params.mNumColumns);
+        assertEquals("1 key L1 rows", 1, params.mNumRows);
+        assertEquals("1 key L1 left", 0, params.mLeftKeys);
+        assertEquals("1 key L1 right", 1, params.mRightKeys);
+        assertEquals("1 key L1 [1]", 0, params.getColumnPos(0));
+        assertEquals("1 key L1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("1 key L1 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___ ___ [1]
+    public void testLayout1KeyL2() {
+        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(1, MAX_COLUMNS, WIDTH,
+                HEIGHT, XPOS_L2, KEYBOARD_WIDTH);
+        assertEquals("1 key L2 columns", 1, params.mNumColumns);
+        assertEquals("1 key L2 rows", 1, params.mNumRows);
+        assertEquals("1 key L2 left", 0, params.mLeftKeys);
+        assertEquals("1 key L2 right", 1, params.mRightKeys);
+        assertEquals("1 key L2 [1]", 0, params.getColumnPos(0));
+        assertEquals("1 key L2 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("1 key L2 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // [1]|
+    public void testLayout1KeyR0() {
+        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(1, MAX_COLUMNS, WIDTH,
+                HEIGHT, XPOS_R0, KEYBOARD_WIDTH);
+        assertEquals("1 key R0 columns", 1, params.mNumColumns);
+        assertEquals("1 key R0 rows", 1, params.mNumRows);
+        assertEquals("1 key R0 left", 0, params.mLeftKeys);
+        assertEquals("1 key R0 right", 1, params.mRightKeys);
+        assertEquals("1 key R0 [1]", 0, params.getColumnPos(0));
+        assertEquals("1 key R0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("1 key R0 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // [1] ___|
+    public void testLayout1KeyR1() {
+        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(1, MAX_COLUMNS, WIDTH,
+                HEIGHT, XPOS_R1, KEYBOARD_WIDTH);
+        assertEquals("1 key R1 columns", 1, params.mNumColumns);
+        assertEquals("1 key R1 rows", 1, params.mNumRows);
+        assertEquals("1 key R1 left", 0, params.mLeftKeys);
+        assertEquals("1 key R1 right", 1, params.mRightKeys);
+        assertEquals("1 key R1 [1]", 0, params.getColumnPos(0));
+        assertEquals("1 key R1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("1 key R1 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // [1] ___ ___|
+    public void testLayout1KeyR2() {
+        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(1, MAX_COLUMNS, WIDTH,
+                HEIGHT, XPOS_R2, KEYBOARD_WIDTH);
+        assertEquals("1 key R2 columns", 1, params.mNumColumns);
+        assertEquals("1 key R2 rows", 1, params.mNumRows);
+        assertEquals("1 key R2 left", 0, params.mLeftKeys);
+        assertEquals("1 key R2 right", 1, params.mRightKeys);
+        assertEquals("1 key R2 [1]", 0, params.getColumnPos(0));
+        assertEquals("1 key R2 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("1 key R2 default", WIDTH * 0, params.getDefaultKeyCoordX());
     }
 
     // [1] [2]
-    public void testLayout2Key() {
-        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(
-                2, MAX_COLUMNS, WIDTH, HEIGHT,
-                WIDTH * 5, WIDTH * 10);
-        assertEquals("2 key columns", 2, params.mNumColumns);
-        assertEquals("2 key rows", 1, params.mNumRows);
-        assertEquals("2 key left", 0, params.mLeftKeys);
-        assertEquals("2 key right", 2, params.mRightKeys);
-        assertEquals("2 key [1]", 0, params.getColumnPos(0));
-        assertEquals("2 key [2]", 1, params.getColumnPos(1));
-        assertEquals("2 key centering", false, params.mTopRowNeedsCentering);
-        assertEquals("2 key default", 0, params.getDefaultKeyCoordX());
+    public void testLayout2KeyM0() {
+        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(2, MAX_COLUMNS, WIDTH,
+                HEIGHT, XPOS_M0, KEYBOARD_WIDTH);
+        assertEquals("2 key M0 columns", 2, params.mNumColumns);
+        assertEquals("2 key M0 rows", 1, params.mNumRows);
+        assertEquals("2 key M0 left", 0, params.mLeftKeys);
+        assertEquals("2 key M0 right", 2, params.mRightKeys);
+        assertEquals("2 key M0 [1]", 0, params.getColumnPos(0));
+        assertEquals("2 key M0 [2]", 1, params.getColumnPos(1));
+        assertEquals("2 key M0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("2 key M0 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |[1] [2]
+    public void testLayout2KeyL0() {
+        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(2, MAX_COLUMNS, WIDTH,
+                HEIGHT, XPOS_L0, KEYBOARD_WIDTH);
+        assertEquals("2 key L0 columns", 2, params.mNumColumns);
+        assertEquals("2 key L0 rows", 1, params.mNumRows);
+        assertEquals("2 key L0 left", 0, params.mLeftKeys);
+        assertEquals("2 key L0 right", 2, params.mRightKeys);
+        assertEquals("2 key L0 [1]", 0, params.getColumnPos(0));
+        assertEquals("2 key L0 [2]", 1, params.getColumnPos(1));
+        assertEquals("2 key L0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("2 key L0 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___ [1] [2]
+    public void testLayout2KeyL1() {
+        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(2, MAX_COLUMNS, WIDTH,
+                HEIGHT, XPOS_L1, KEYBOARD_WIDTH);
+        assertEquals("2 key L1 columns", 2, params.mNumColumns);
+        assertEquals("2 key L1 rows", 1, params.mNumRows);
+        assertEquals("2 key L1 left", 0, params.mLeftKeys);
+        assertEquals("2 key L1 right", 2, params.mRightKeys);
+        assertEquals("2 key L1 [1]", 0, params.getColumnPos(0));
+        assertEquals("2 key L1 [2]", 1, params.getColumnPos(1));
+        assertEquals("2 key L1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("2 key L1 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___ ___ [1] [2]
+    public void testLayout2KeyL2() {
+        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(2, MAX_COLUMNS, WIDTH,
+                HEIGHT, XPOS_L2, KEYBOARD_WIDTH);
+        assertEquals("2 key L2 columns", 2, params.mNumColumns);
+        assertEquals("2 key L2 rows", 1, params.mNumRows);
+        assertEquals("2 key L2 left", 0, params.mLeftKeys);
+        assertEquals("2 key L2 right", 2, params.mRightKeys);
+        assertEquals("2 key L2 [1]", 0, params.getColumnPos(0));
+        assertEquals("2 key L2 [2]", 1, params.getColumnPos(1));
+        assertEquals("2 key L2 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("2 key L2 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // [2] [1]|
+    public void testLayout2KeyR0() {
+        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(2, MAX_COLUMNS, WIDTH,
+                HEIGHT, XPOS_R0, KEYBOARD_WIDTH);
+        assertEquals("2 key R0 columns", 2, params.mNumColumns);
+        assertEquals("2 key R0 rows", 1, params.mNumRows);
+        assertEquals("2 key R0 left", 1, params.mLeftKeys);
+        assertEquals("2 key R0 right", 1, params.mRightKeys);
+        assertEquals("2 key R0 [1]", 0, params.getColumnPos(0));
+        assertEquals("2 key R0 [2]", -1, params.getColumnPos(1));
+        assertEquals("2 key R0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("2 key R0 default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    // [2] [1] ___|
+    public void testLayout2KeyR1() {
+        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(2, MAX_COLUMNS, WIDTH,
+                HEIGHT, XPOS_R1, KEYBOARD_WIDTH);
+        assertEquals("2 key R1 columns", 2, params.mNumColumns);
+        assertEquals("2 key R1 rows", 1, params.mNumRows);
+        assertEquals("2 key R1 left", 1, params.mLeftKeys);
+        assertEquals("2 key R1 right", 1, params.mRightKeys);
+        assertEquals("2 key R1 [1]", 0, params.getColumnPos(0));
+        assertEquals("2 key R1 [2]", -1, params.getColumnPos(1));
+        assertEquals("2 key R1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("2 key R1 default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    // [1] [2] ___ ___|
+    public void testLayout2KeyR2() {
+        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(2, MAX_COLUMNS, WIDTH,
+                HEIGHT, XPOS_R2, KEYBOARD_WIDTH);
+        assertEquals("2 key R2 columns", 2, params.mNumColumns);
+        assertEquals("2 key R2 rows", 1, params.mNumRows);
+        assertEquals("2 key R2 left", 0, params.mLeftKeys);
+        assertEquals("2 key R2 right", 2, params.mRightKeys);
+        assertEquals("2 key R2 [1]", 0, params.getColumnPos(0));
+        assertEquals("2 key R2 [2]", 1, params.getColumnPos(1));
+        assertEquals("2 key R2 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("2 key R2 default", WIDTH * 0, params.getDefaultKeyCoordX());
     }
 
     // [3] [1] [2]
-    public void testLayout3Key() {
-        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(
-                3, MAX_COLUMNS, WIDTH, HEIGHT,
-                WIDTH * 5, WIDTH * 10);
+    public void testLayout3KeyM0() {
+        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(3, MAX_COLUMNS, WIDTH,
+                HEIGHT, XPOS_M0, KEYBOARD_WIDTH);
         assertEquals("3 key columns", 3, params.mNumColumns);
         assertEquals("3 key rows", 1, params.mNumRows);
         assertEquals("3 key left", 1, params.mLeftKeys);
@@ -88,15 +256,104 @@
         assertEquals("3 key [1]", 0, params.getColumnPos(0));
         assertEquals("3 key [2]", 1, params.getColumnPos(1));
         assertEquals("3 key [3]", -1, params.getColumnPos(2));
-        assertEquals("3 key centering", false, params.mTopRowNeedsCentering);
-        assertEquals("3 key default", WIDTH, params.getDefaultKeyCoordX());
+        assertEquals("3 key adjust", 0, params.mTopRowAdjustment);
+        assertEquals("3 key default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    // |[1] [2] [3]
+    public void testLayout3KeyL0() {
+        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(3, MAX_COLUMNS, WIDTH,
+                HEIGHT, XPOS_L0, KEYBOARD_WIDTH);
+        assertEquals("3 key L0 columns", 3, params.mNumColumns);
+        assertEquals("3 key L0 rows", 1, params.mNumRows);
+        assertEquals("3 key L0 left", 0, params.mLeftKeys);
+        assertEquals("3 key L0 right", 3, params.mRightKeys);
+        assertEquals("3 key L0 [1]", 0, params.getColumnPos(0));
+        assertEquals("3 key L0 [2]", 1, params.getColumnPos(1));
+        assertEquals("3 key L0 [3]", 2, params.getColumnPos(2));
+        assertEquals("3 key L0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("3 key L0 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___ [1] [2] [3]
+    public void testLayout3KeyL1() {
+        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(3, MAX_COLUMNS, WIDTH,
+                HEIGHT, XPOS_L1, KEYBOARD_WIDTH);
+        assertEquals("3 key L1 columns", 3, params.mNumColumns);
+        assertEquals("3 key L1 rows", 1, params.mNumRows);
+        assertEquals("3 key L1 left", 0, params.mLeftKeys);
+        assertEquals("3 key L1 right", 3, params.mRightKeys);
+        assertEquals("3 key L1 [1]", 0, params.getColumnPos(0));
+        assertEquals("3 key L1 [2]", 1, params.getColumnPos(1));
+        assertEquals("3 key L1 [3]", 2, params.getColumnPos(2));
+        assertEquals("3 key L1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("3 key L1 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___ ___ [3] [1] [2]
+    public void testLayout3KeyL2() {
+        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(3, MAX_COLUMNS, WIDTH,
+                HEIGHT, XPOS_L2, KEYBOARD_WIDTH);
+        assertEquals("3 key L2 columns", 3, params.mNumColumns);
+        assertEquals("3 key L2 rows", 1, params.mNumRows);
+        assertEquals("3 key L2 left", 1, params.mLeftKeys);
+        assertEquals("3 key L2 right", 2, params.mRightKeys);
+        assertEquals("3 key L2 [1]", 0, params.getColumnPos(0));
+        assertEquals("3 key L2 [2]", 1, params.getColumnPos(1));
+        assertEquals("3 key L2 [3]", -1, params.getColumnPos(2));
+        assertEquals("3 key L2 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("3 key L2 default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    // [3] [2] [1]|
+    public void testLayout3KeyR0() {
+        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(3, MAX_COLUMNS, WIDTH,
+                HEIGHT, XPOS_R0, KEYBOARD_WIDTH);
+        assertEquals("3 key R0 columns", 3, params.mNumColumns);
+        assertEquals("3 key R0 rows", 1, params.mNumRows);
+        assertEquals("3 key R0 left", 2, params.mLeftKeys);
+        assertEquals("3 key R0 right", 1, params.mRightKeys);
+        assertEquals("3 key R0 [1]", 0, params.getColumnPos(0));
+        assertEquals("3 key R0 [2]", -1, params.getColumnPos(1));
+        assertEquals("3 key R0 [3]", -2, params.getColumnPos(2));
+        assertEquals("3 key R0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("3 key R0 default", WIDTH * 2, params.getDefaultKeyCoordX());
+    }
+
+    // [3] [2] [1] ___|
+    public void testLayout3KeyR1() {
+        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(3, MAX_COLUMNS, WIDTH,
+                HEIGHT, XPOS_R1, KEYBOARD_WIDTH);
+        assertEquals("3 key R1 columns", 3, params.mNumColumns);
+        assertEquals("3 key R1 rows", 1, params.mNumRows);
+        assertEquals("3 key R1 left", 2, params.mLeftKeys);
+        assertEquals("3 key R1 right", 1, params.mRightKeys);
+        assertEquals("3 key R1 [1]", 0, params.getColumnPos(0));
+        assertEquals("3 key R1 [2]", -1, params.getColumnPos(1));
+        assertEquals("3 key R1 [3]", -2, params.getColumnPos(2));
+        assertEquals("3 key R1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("3 key R1 default", WIDTH * 2, params.getDefaultKeyCoordX());
+    }
+
+    // [3] [1] [2] ___ ___|
+    public void testLayout3KeyR2() {
+        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(3, MAX_COLUMNS, WIDTH,
+                HEIGHT, XPOS_R2, KEYBOARD_WIDTH);
+        assertEquals("3 key R2 columns", 3, params.mNumColumns);
+        assertEquals("3 key R2 rows", 1, params.mNumRows);
+        assertEquals("3 key R2 left", 1, params.mLeftKeys);
+        assertEquals("3 key R2 right", 2, params.mRightKeys);
+        assertEquals("3 key R2 [1]", 0, params.getColumnPos(0));
+        assertEquals("3 key R2 [2]", 1, params.getColumnPos(1));
+        assertEquals("3 key R2 [3]", -1, params.getColumnPos(2));
+        assertEquals("3 key R2 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("3 key R2 default", WIDTH * 1, params.getDefaultKeyCoordX());
     }
 
     // [3] [1] [2] [4]
-    public void testLayout4Key() {
-        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(
-                4, MAX_COLUMNS, WIDTH, HEIGHT,
-                WIDTH * 5, WIDTH * 10);
+    public void testLayout4KeyM0() {
+        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(4, MAX_COLUMNS, WIDTH,
+                HEIGHT, XPOS_M0, KEYBOARD_WIDTH);
         assertEquals("4 key columns", 4, params.mNumColumns);
         assertEquals("4 key rows", 1, params.mNumRows);
         assertEquals("4 key left", 1, params.mLeftKeys);
@@ -105,15 +362,110 @@
         assertEquals("4 key [2]", 1, params.getColumnPos(1));
         assertEquals("4 key [3]", -1, params.getColumnPos(2));
         assertEquals("4 key [4]", 2, params.getColumnPos(3));
-        assertEquals("4 key centering", false, params.mTopRowNeedsCentering);
-        assertEquals("4 key default", WIDTH, params.getDefaultKeyCoordX());
+        assertEquals("4 key adjust", 0, params.mTopRowAdjustment);
+        assertEquals("4 key default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    // |[1] [2] [3] [4]
+    public void testLayout4KeyL0() {
+        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(4, MAX_COLUMNS, WIDTH,
+                HEIGHT, XPOS_L0, KEYBOARD_WIDTH);
+        assertEquals("4 key L0 columns", 4, params.mNumColumns);
+        assertEquals("4 key L0 rows", 1, params.mNumRows);
+        assertEquals("4 key L0 left", 0, params.mLeftKeys);
+        assertEquals("4 key L0 right", 4, params.mRightKeys);
+        assertEquals("4 key L0 [1]", 0, params.getColumnPos(0));
+        assertEquals("4 key L0 [2]", 1, params.getColumnPos(1));
+        assertEquals("4 key L0 [3]", 2, params.getColumnPos(2));
+        assertEquals("4 key L0 [4]", 3, params.getColumnPos(3));
+        assertEquals("4 key L0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("4 key L0 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___ [1] [2] [3] [4]
+    public void testLayout4KeyL1() {
+        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(4, MAX_COLUMNS, WIDTH,
+                HEIGHT, XPOS_L1, KEYBOARD_WIDTH);
+        assertEquals("4 key L1 columns", 4, params.mNumColumns);
+        assertEquals("4 key L1 rows", 1, params.mNumRows);
+        assertEquals("4 key L1 left", 0, params.mLeftKeys);
+        assertEquals("4 key L1 right", 4, params.mRightKeys);
+        assertEquals("4 key L1 [1]", 0, params.getColumnPos(0));
+        assertEquals("4 key L1 [2]", 1, params.getColumnPos(1));
+        assertEquals("4 key L1 [3]", 2, params.getColumnPos(2));
+        assertEquals("4 key L1 [4]", 3, params.getColumnPos(3));
+        assertEquals("4 key L1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("4 key L1 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___ ___ [3] [1] [2] [4]
+    public void testLayout4KeyL2() {
+        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(4, MAX_COLUMNS, WIDTH,
+                HEIGHT, XPOS_L2, KEYBOARD_WIDTH);
+        assertEquals("4 key L2 columns", 4, params.mNumColumns);
+        assertEquals("4 key L2 rows", 1, params.mNumRows);
+        assertEquals("4 key L2 left", 1, params.mLeftKeys);
+        assertEquals("4 key L2 right", 3, params.mRightKeys);
+        assertEquals("4 key L2 [1]", 0, params.getColumnPos(0));
+        assertEquals("4 key L2 [2]", 1, params.getColumnPos(1));
+        assertEquals("4 key L2 [3]", -1, params.getColumnPos(2));
+        assertEquals("4 key L2 [4]", 2, params.getColumnPos(3));
+        assertEquals("4 key L2 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("4 key L2 default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    // [4] [3] [2] [1]|
+    public void testLayout4KeyR0() {
+        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(4, MAX_COLUMNS, WIDTH,
+                HEIGHT, XPOS_R0, KEYBOARD_WIDTH);
+        assertEquals("4 key R0 columns", 4, params.mNumColumns);
+        assertEquals("4 key R0 rows", 1, params.mNumRows);
+        assertEquals("4 key R0 left", 3, params.mLeftKeys);
+        assertEquals("4 key R0 right", 1, params.mRightKeys);
+        assertEquals("4 key R0 [1]", 0, params.getColumnPos(0));
+        assertEquals("4 key R0 [2]", -1, params.getColumnPos(1));
+        assertEquals("4 key R0 [3]", -2, params.getColumnPos(2));
+        assertEquals("4 key R0 [4]", -3, params.getColumnPos(3));
+        assertEquals("4 key R0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("4 key R0 default", WIDTH * 3, params.getDefaultKeyCoordX());
+    }
+
+    // [4] [3] [2] [1] ___|
+    public void testLayout4KeyR1() {
+        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(4, MAX_COLUMNS, WIDTH,
+                HEIGHT, XPOS_R1, KEYBOARD_WIDTH);
+        assertEquals("4 key R1 columns", 4, params.mNumColumns);
+        assertEquals("4 key R1 rows", 1, params.mNumRows);
+        assertEquals("4 key R1 left", 3, params.mLeftKeys);
+        assertEquals("4 key R1 right", 1, params.mRightKeys);
+        assertEquals("4 key R1 [1]", 0, params.getColumnPos(0));
+        assertEquals("4 key R1 [2]", -1, params.getColumnPos(1));
+        assertEquals("4 key R1 [3]", -2, params.getColumnPos(2));
+        assertEquals("4 key R1 [4]", -3, params.getColumnPos(3));
+        assertEquals("4 key R1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("4 key R1 default", WIDTH * 3, params.getDefaultKeyCoordX());
+    }
+
+    // [4] [3] [1] [2] ___ ___|
+    public void testLayout4KeyR2() {
+        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(4, MAX_COLUMNS, WIDTH,
+                HEIGHT, XPOS_R2, KEYBOARD_WIDTH);
+        assertEquals("4 key R2 columns", 4, params.mNumColumns);
+        assertEquals("4 key R2 rows", 1, params.mNumRows);
+        assertEquals("4 key R2 left", 2, params.mLeftKeys);
+        assertEquals("4 key R2 right", 2, params.mRightKeys);
+        assertEquals("4 key R2 [1]", 0, params.getColumnPos(0));
+        assertEquals("4 key R2 [2]", 1, params.getColumnPos(1));
+        assertEquals("4 key R2 [3]", -1, params.getColumnPos(2));
+        assertEquals("4 key R2 [4]", -2, params.getColumnPos(3));
+        assertEquals("4 key R2 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("4 key R2 default", WIDTH * 2, params.getDefaultKeyCoordX());
     }
 
     // [5] [3] [1] [2] [4]
-    public void testLayout5Key() {
-        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(
-                5, MAX_COLUMNS, WIDTH, HEIGHT,
-                WIDTH * 5, WIDTH * 10);
+    public void testLayout5KeyM0() {
+        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(5, MAX_COLUMNS, WIDTH,
+                HEIGHT, XPOS_M0, KEYBOARD_WIDTH);
         assertEquals("5 key columns", 5, params.mNumColumns);
         assertEquals("5 key rows", 1, params.mNumRows);
         assertEquals("5 key left", 2, params.mLeftKeys);
@@ -123,190 +475,942 @@
         assertEquals("5 key [3]", -1, params.getColumnPos(2));
         assertEquals("5 key [4]", 2, params.getColumnPos(3));
         assertEquals("5 key [5]", -2, params.getColumnPos(4));
-        assertEquals("5 key centering", false, params.mTopRowNeedsCentering);
+        assertEquals("5 key adjust", 0, params.mTopRowAdjustment);
         assertEquals("5 key default", WIDTH * 2, params.getDefaultKeyCoordX());
     }
 
-    //         [6]
-    // [5] [3] [1] [2] [4]
-    public void testLayout6Key() {
-        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(
-                6, MAX_COLUMNS, WIDTH, HEIGHT,
-                WIDTH * 5, WIDTH * 10);
-        assertEquals("6 key columns", 5, params.mNumColumns);
+    // |[1] [2] [3] [4] [5]
+    public void testLayout5KeyL0() {
+        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(5, MAX_COLUMNS, WIDTH,
+                HEIGHT, XPOS_L0, KEYBOARD_WIDTH);
+        assertEquals("5 key L0 columns", 5, params.mNumColumns);
+        assertEquals("5 key L0 rows", 1, params.mNumRows);
+        assertEquals("5 key L0 left", 0, params.mLeftKeys);
+        assertEquals("5 key L0 right", 5, params.mRightKeys);
+        assertEquals("5 key L0 [1]", 0, params.getColumnPos(0));
+        assertEquals("5 key L0 [2]", 1, params.getColumnPos(1));
+        assertEquals("5 key L0 [3]", 2, params.getColumnPos(2));
+        assertEquals("5 key L0 [4]", 3, params.getColumnPos(3));
+        assertEquals("5 key L0 [5]", 4, params.getColumnPos(4));
+        assertEquals("5 key L0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("5 key L0 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___ [1] [2] [3] [4] [5]
+    public void testLayout5KeyL1() {
+        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(5, MAX_COLUMNS, WIDTH,
+                HEIGHT, XPOS_L1, KEYBOARD_WIDTH);
+        assertEquals("5 key L1 columns", 5, params.mNumColumns);
+        assertEquals("5 key L1 rows", 1, params.mNumRows);
+        assertEquals("5 key L1 left", 0, params.mLeftKeys);
+        assertEquals("5 key L1 right", 5, params.mRightKeys);
+        assertEquals("5 key L1 [1]", 0, params.getColumnPos(0));
+        assertEquals("5 key L1 [2]", 1, params.getColumnPos(1));
+        assertEquals("5 key L1 [3]", 2, params.getColumnPos(2));
+        assertEquals("5 key L1 [4]", 3, params.getColumnPos(3));
+        assertEquals("5 key L1 [5]", 4, params.getColumnPos(4));
+        assertEquals("5 key L1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("5 key L1 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___ ___ [3] [1] [2] [4] [5]
+    public void testLayout5KeyL2() {
+        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(5, MAX_COLUMNS, WIDTH,
+                HEIGHT, XPOS_L2, KEYBOARD_WIDTH);
+        assertEquals("5 key L2 columns", 5, params.mNumColumns);
+        assertEquals("5 key L2 rows", 1, params.mNumRows);
+        assertEquals("5 key L2 left", 1, params.mLeftKeys);
+        assertEquals("5 key L2 right", 4, params.mRightKeys);
+        assertEquals("5 key L2 [1]", 0, params.getColumnPos(0));
+        assertEquals("5 key L2 [2]", 1, params.getColumnPos(1));
+        assertEquals("5 key L2 [3]", -1, params.getColumnPos(2));
+        assertEquals("5 key L2 [4]", 2, params.getColumnPos(3));
+        assertEquals("5 key L2 [5]", 3, params.getColumnPos(4));
+        assertEquals("5 key L2 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("5 key L2 default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    // [5] [4] [3] [2] [1]|
+    public void testLayout5KeyR0() {
+        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(5, MAX_COLUMNS, WIDTH,
+                HEIGHT, XPOS_R0, KEYBOARD_WIDTH);
+        assertEquals("5 key R0 columns", 5, params.mNumColumns);
+        assertEquals("5 key R0 rows", 1, params.mNumRows);
+        assertEquals("5 key R0 left", 4, params.mLeftKeys);
+        assertEquals("5 key R0 right", 1, params.mRightKeys);
+        assertEquals("5 key R0 [1]", 0, params.getColumnPos(0));
+        assertEquals("5 key R0 [2]", -1, params.getColumnPos(1));
+        assertEquals("5 key R0 [3]", -2, params.getColumnPos(2));
+        assertEquals("5 key R0 [4]", -3, params.getColumnPos(3));
+        assertEquals("5 key R0 [5]", -4, params.getColumnPos(4));
+        assertEquals("5 key R0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("5 key R0 default", WIDTH * 4, params.getDefaultKeyCoordX());
+    }
+
+    // [5] [4] [3] [2] [1] ___|
+    public void testLayout5KeyR1() {
+        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(5, MAX_COLUMNS, WIDTH,
+                HEIGHT, XPOS_R1, KEYBOARD_WIDTH);
+        assertEquals("5 key R1 columns", 5, params.mNumColumns);
+        assertEquals("5 key R1 rows", 1, params.mNumRows);
+        assertEquals("5 key R1 left", 4, params.mLeftKeys);
+        assertEquals("5 key R1 right", 1, params.mRightKeys);
+        assertEquals("5 key R1 [1]", 0, params.getColumnPos(0));
+        assertEquals("5 key R1 [2]", -1, params.getColumnPos(1));
+        assertEquals("5 key R1 [3]", -2, params.getColumnPos(2));
+        assertEquals("5 key R1 [4]", -3, params.getColumnPos(3));
+        assertEquals("5 key R1 [5]", -4, params.getColumnPos(4));
+        assertEquals("5 key R1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("5 key R1 default", WIDTH * 4, params.getDefaultKeyCoordX());
+    }
+
+    // [5] [4] [3] [1] [2] ___ ___|
+    public void testLayout5KeyR2() {
+        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(5, MAX_COLUMNS, WIDTH,
+                HEIGHT, XPOS_R2, KEYBOARD_WIDTH);
+        assertEquals("5 key R2 columns", 5, params.mNumColumns);
+        assertEquals("5 key R2 rows", 1, params.mNumRows);
+        assertEquals("5 key R2 left", 3, params.mLeftKeys);
+        assertEquals("5 key R2 right", 2, params.mRightKeys);
+        assertEquals("5 key R2 [1]", 0, params.getColumnPos(0));
+        assertEquals("5 key R2 [2]", 1, params.getColumnPos(1));
+        assertEquals("5 key R2 [3]", -1, params.getColumnPos(2));
+        assertEquals("5 key R2 [4]", -2, params.getColumnPos(3));
+        assertEquals("5 key R2 [5]", -3, params.getColumnPos(4));
+        assertEquals("5 key R2 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("5 key R2 default", WIDTH * 3, params.getDefaultKeyCoordX());
+    }
+
+    // [6] [4] [5]
+    // [3] [1] [2]
+    public void testLayout6KeyM0() {
+        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(6, MAX_COLUMNS, WIDTH,
+                HEIGHT, XPOS_M0, KEYBOARD_WIDTH);
+        assertEquals("6 key columns", 3, params.mNumColumns);
         assertEquals("6 key rows", 2, params.mNumRows);
-        assertEquals("6 key left", 2, params.mLeftKeys);
-        assertEquals("6 key right", 3, params.mRightKeys);
+        assertEquals("6 key left", 1, params.mLeftKeys);
+        assertEquals("6 key right", 2, params.mRightKeys);
         assertEquals("6 key [1]", 0, params.getColumnPos(0));
         assertEquals("6 key [2]", 1, params.getColumnPos(1));
         assertEquals("6 key [3]", -1, params.getColumnPos(2));
-        assertEquals("6 key [4]", 2, params.getColumnPos(3));
-        assertEquals("6 key [5]", -2, params.getColumnPos(4));
-        assertEquals("6 key [6]", 0, params.getColumnPos(5));
-        assertEquals("6 key centering", false, params.mTopRowNeedsCentering);
-        assertEquals("6 key default", WIDTH * 2, params.getDefaultKeyCoordX());
+        assertEquals("6 key [4]", 0, params.getColumnPos(3));
+        assertEquals("6 key [5]", 1, params.getColumnPos(4));
+        assertEquals("6 key [6]", -1, params.getColumnPos(5));
+        assertEquals("6 key adjust", 0, params.mTopRowAdjustment);
+        assertEquals("6 key default", WIDTH * 1, params.getDefaultKeyCoordX());
     }
 
-    //       [6] [7]
-    // [5] [3] [1] [2] [4]
-    public void testLayout7Key() {
-        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(
-                7, MAX_COLUMNS, WIDTH, HEIGHT,
-                WIDTH * 5, WIDTH * 10);
-        assertEquals("7 key columns", 5, params.mNumColumns);
+    // |[4] [5] [6]
+    // |[1] [2] [3]
+    public void testLayout6KeyL0() {
+        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(6, MAX_COLUMNS, WIDTH,
+                HEIGHT, XPOS_L0, KEYBOARD_WIDTH);
+        assertEquals("6 key L0 columns", 3, params.mNumColumns);
+        assertEquals("6 key L0 rows", 2, params.mNumRows);
+        assertEquals("6 key L0 left", 0, params.mLeftKeys);
+        assertEquals("6 key L0 right", 3, params.mRightKeys);
+        assertEquals("6 key L0 [1]", 0, params.getColumnPos(0));
+        assertEquals("6 key L0 [2]", 1, params.getColumnPos(1));
+        assertEquals("6 key L0 [3]", 2, params.getColumnPos(2));
+        assertEquals("6 key L0 [4]", 0, params.getColumnPos(3));
+        assertEquals("6 key L0 [5]", 1, params.getColumnPos(4));
+        assertEquals("6 key L0 [6]", 2, params.getColumnPos(5));
+        assertEquals("6 key L0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("6 key L0 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___ [4] [5] [6]
+    // |___ [1] [2] [3]
+    public void testLayout6KeyL1() {
+        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(6, MAX_COLUMNS, WIDTH,
+                HEIGHT, XPOS_L1, KEYBOARD_WIDTH);
+        assertEquals("6 key L1 columns", 3, params.mNumColumns);
+        assertEquals("6 key L1 rows", 2, params.mNumRows);
+        assertEquals("6 key L1 left", 0, params.mLeftKeys);
+        assertEquals("6 key L1 right", 3, params.mRightKeys);
+        assertEquals("6 key L1 [1]", 0, params.getColumnPos(0));
+        assertEquals("6 key L1 [2]", 1, params.getColumnPos(1));
+        assertEquals("6 key L1 [3]", 2, params.getColumnPos(2));
+        assertEquals("6 key L1 [4]", 0, params.getColumnPos(3));
+        assertEquals("6 key L1 [5]", 1, params.getColumnPos(4));
+        assertEquals("6 key L1 [6]", 2, params.getColumnPos(5));
+        assertEquals("6 key L1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("6 key L1 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___ ___ [6] [4] [5]
+    // |___ ___ [3] [1] [2]
+    public void testLayout6KeyL2() {
+        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(6, MAX_COLUMNS, WIDTH,
+                HEIGHT, XPOS_L2, KEYBOARD_WIDTH);
+        assertEquals("6 key L2 columns", 3, params.mNumColumns);
+        assertEquals("6 key L2 rows", 2, params.mNumRows);
+        assertEquals("6 key L2 left", 1, params.mLeftKeys);
+        assertEquals("6 key L2 right", 2, params.mRightKeys);
+        assertEquals("6 key L2 [1]", 0, params.getColumnPos(0));
+        assertEquals("6 key L2 [2]", 1, params.getColumnPos(1));
+        assertEquals("6 key L2 [3]", -1, params.getColumnPos(2));
+        assertEquals("6 key L2 [4]", 0, params.getColumnPos(3));
+        assertEquals("6 key L2 [5]", 1, params.getColumnPos(4));
+        assertEquals("6 key L2 [6]", -1, params.getColumnPos(5));
+        assertEquals("6 key L2 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("6 key L2 default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    // [6] [5] [4]|
+    // [3] [2] [1]|
+    public void testLayout6KeyR0() {
+        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(6, MAX_COLUMNS, WIDTH,
+                HEIGHT, XPOS_R0, KEYBOARD_WIDTH);
+        assertEquals("6 key R0 columns", 3, params.mNumColumns);
+        assertEquals("6 key R0 rows", 2, params.mNumRows);
+        assertEquals("6 key R0 left", 2, params.mLeftKeys);
+        assertEquals("6 key R0 right", 1, params.mRightKeys);
+        assertEquals("6 key R0 [1]", 0, params.getColumnPos(0));
+        assertEquals("6 key R0 [2]", -1, params.getColumnPos(1));
+        assertEquals("6 key R0 [3]", -2, params.getColumnPos(2));
+        assertEquals("6 key R0 [4]", 0, params.getColumnPos(3));
+        assertEquals("6 key R0 [5]", -1, params.getColumnPos(4));
+        assertEquals("6 key R0 [6]", -2, params.getColumnPos(5));
+        assertEquals("6 key R0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("6 key R0 default", WIDTH * 2, params.getDefaultKeyCoordX());
+    }
+
+    // [6] [5] [4] ___|
+    // [3] [2] [1] ___|
+    public void testLayout6KeyR1() {
+        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(6, MAX_COLUMNS, WIDTH,
+                HEIGHT, XPOS_R1, KEYBOARD_WIDTH);
+        assertEquals("6 key R1 columns", 3, params.mNumColumns);
+        assertEquals("6 key R1 rows", 2, params.mNumRows);
+        assertEquals("6 key R1 left", 2, params.mLeftKeys);
+        assertEquals("6 key R1 right", 1, params.mRightKeys);
+        assertEquals("6 key R1 [1]", 0, params.getColumnPos(0));
+        assertEquals("6 key R1 [2]", -1, params.getColumnPos(1));
+        assertEquals("6 key R1 [3]", -2, params.getColumnPos(2));
+        assertEquals("6 key R1 [4]", 0, params.getColumnPos(3));
+        assertEquals("6 key R1 [5]", -1, params.getColumnPos(4));
+        assertEquals("6 key R1 [6]", -2, params.getColumnPos(5));
+        assertEquals("6 key R1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("6 key R1 default", WIDTH * 2, params.getDefaultKeyCoordX());
+    }
+
+    // [6] [4] [5] ___ ___|
+    // [3] [1] [2] ___ ___|
+    public void testLayout6KeyR2() {
+        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(6, MAX_COLUMNS, WIDTH,
+                HEIGHT, XPOS_R2, KEYBOARD_WIDTH);
+        assertEquals("6 key R2 columns", 3, params.mNumColumns);
+        assertEquals("6 key R2 rows", 2, params.mNumRows);
+        assertEquals("6 key R2 left", 1, params.mLeftKeys);
+        assertEquals("6 key R2 right", 2, params.mRightKeys);
+        assertEquals("6 key R2 [1]", 0, params.getColumnPos(0));
+        assertEquals("6 key R2 [2]", 1, params.getColumnPos(1));
+        assertEquals("6 key R2 [3]", -1, params.getColumnPos(2));
+        assertEquals("6 key R2 [4]", 0, params.getColumnPos(3));
+        assertEquals("6 key R2 [5]", 1, params.getColumnPos(4));
+        assertEquals("6 key R2 [6]", -1, params.getColumnPos(5));
+        assertEquals("6 key R2 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("6 key R2 default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    //   [7] [5] [6]
+    // [3] [1] [2] [4]
+    public void testLayout7KeyM0() {
+        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(7, MAX_COLUMNS, WIDTH,
+                HEIGHT, XPOS_M0, KEYBOARD_WIDTH);
+        assertEquals("7 key columns", 4, params.mNumColumns);
         assertEquals("7 key rows", 2, params.mNumRows);
-        assertEquals("7 key left", 2, params.mLeftKeys);
+        assertEquals("7 key left", 1, params.mLeftKeys);
         assertEquals("7 key right", 3, params.mRightKeys);
         assertEquals("7 key [1]", 0, params.getColumnPos(0));
         assertEquals("7 key [2]", 1, params.getColumnPos(1));
         assertEquals("7 key [3]", -1, params.getColumnPos(2));
         assertEquals("7 key [4]", 2, params.getColumnPos(3));
-        assertEquals("7 key [5]", -2, params.getColumnPos(4));
-        assertEquals("7 key [6]", 0, params.getColumnPos(5));
-        assertEquals("7 key [7]", 1, params.getColumnPos(6));
-        assertEquals("7 key centering", true, params.mTopRowNeedsCentering);
-        assertEquals("7 key default", WIDTH * 2, params.getDefaultKeyCoordX());
+        assertEquals("7 key [5]", 0, params.getColumnPos(4));
+        assertEquals("7 key [6]", 1, params.getColumnPos(5));
+        assertEquals("7 key [7]", -1, params.getColumnPos(6));
+        assertEquals("7 key adjust", 1, params.mTopRowAdjustment);
+        assertEquals("7 key default", WIDTH * 1, params.getDefaultKeyCoordX());
     }
 
-    //     [8] [6] [7]
-    // [5] [3] [1] [2] [4]
-    public void testLayout8Key() {
-        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(
-                8, MAX_COLUMNS, WIDTH, HEIGHT,
-                WIDTH * 5, WIDTH * 10);
-        assertEquals("8 key columns", 5, params.mNumColumns);
-        assertEquals("8 key rows", 2, params.mNumRows);
-        assertEquals("8 key left", 2, params.mLeftKeys);
-        assertEquals("8 key right", 3, params.mRightKeys);
-        assertEquals("8 key [1]", 0, params.getColumnPos(0));
-        assertEquals("8 key [2]", 1, params.getColumnPos(1));
-        assertEquals("8 key [3]", -1, params.getColumnPos(2));
-        assertEquals("8 key [4]", 2, params.getColumnPos(3));
-        assertEquals("8 key [5]", -2, params.getColumnPos(4));
-        assertEquals("8 key [6]", 0, params.getColumnPos(5));
-        assertEquals("8 key [7]", 1, params.getColumnPos(6));
-        assertEquals("8 key [8]", -1, params.getColumnPos(7));
-        assertEquals("8 key centering", false, params.mTopRowNeedsCentering);
-        assertEquals("8 key default", WIDTH * 2, params.getDefaultKeyCoordX());
+    // |[5] [6] [7]
+    // |[1] [2] [3] [4]
+    public void testLayout7KeyL0() {
+        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(7, MAX_COLUMNS, WIDTH,
+                HEIGHT, XPOS_L0, KEYBOARD_WIDTH);
+        assertEquals("7 key L0 columns", 4, params.mNumColumns);
+        assertEquals("7 key L0 rows", 2, params.mNumRows);
+        assertEquals("7 key L0 left", 0, params.mLeftKeys);
+        assertEquals("7 key L0 right", 4, params.mRightKeys);
+        assertEquals("7 key L0 [1]", 0, params.getColumnPos(0));
+        assertEquals("7 key L0 [2]", 1, params.getColumnPos(1));
+        assertEquals("7 key L0 [3]", 2, params.getColumnPos(2));
+        assertEquals("7 key L0 [4]", 3, params.getColumnPos(3));
+        assertEquals("7 key L0 [5]", 0, params.getColumnPos(4));
+        assertEquals("7 key L0 [6]", 1, params.getColumnPos(5));
+        assertEquals("7 key L0 [7]", 2, params.getColumnPos(6));
+        assertEquals("7 key L0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("7 key L0 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___ [5] [6] [7]
+    // |___ [1] [2] [3] [4]
+    public void testLayout7KeyL1() {
+        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(7, MAX_COLUMNS, WIDTH,
+                HEIGHT, XPOS_L1, KEYBOARD_WIDTH);
+        assertEquals("7 key L1 columns", 4, params.mNumColumns);
+        assertEquals("7 key L1 rows", 2, params.mNumRows);
+        assertEquals("7 key L1 left", 0, params.mLeftKeys);
+        assertEquals("7 key L1 right", 4, params.mRightKeys);
+        assertEquals("7 key L1 [1]", 0, params.getColumnPos(0));
+        assertEquals("7 key L1 [2]", 1, params.getColumnPos(1));
+        assertEquals("7 key L1 [3]", 2, params.getColumnPos(2));
+        assertEquals("7 key L1 [4]", 3, params.getColumnPos(3));
+        assertEquals("7 key L1 [5]", 0, params.getColumnPos(4));
+        assertEquals("7 key L1 [6]", 1, params.getColumnPos(5));
+        assertEquals("7 key L1 [7]", 2, params.getColumnPos(6));
+        assertEquals("7 key L1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("7 key L1 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___ ___   [7] [5] [6]
+    // |___ ___ [3] [1] [2] [4]
+    public void testLayout7KeyL2() {
+        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(7, MAX_COLUMNS, WIDTH,
+                HEIGHT, XPOS_L2, KEYBOARD_WIDTH);
+        assertEquals("7 key L2 columns", 4, params.mNumColumns);
+        assertEquals("7 key L2 rows", 2, params.mNumRows);
+        assertEquals("7 key L2 left", 1, params.mLeftKeys);
+        assertEquals("7 key L2 right", 3, params.mRightKeys);
+        assertEquals("7 key L2 [1]", 0, params.getColumnPos(0));
+        assertEquals("7 key L2 [2]", 1, params.getColumnPos(1));
+        assertEquals("7 key L2 [3]", -1, params.getColumnPos(2));
+        assertEquals("7 key L2 [4]", 2, params.getColumnPos(3));
+        assertEquals("7 key L2 [5]", 0, params.getColumnPos(4));
+        assertEquals("7 key L2 [6]", 1, params.getColumnPos(5));
+        assertEquals("7 key L2 [7]", -1, params.getColumnPos(6));
+        assertEquals("7 key L2 adjust", 1, params.mTopRowAdjustment);
+        assertEquals("7 key L2 default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    //     [7] [6] [5]|
+    // [4] [3] [2] [1]|
+    public void testLayout7KeyR0() {
+        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(7, MAX_COLUMNS, WIDTH,
+                HEIGHT, XPOS_R0, KEYBOARD_WIDTH);
+        assertEquals("7 key R0 columns", 4, params.mNumColumns);
+        assertEquals("7 key R0 rows", 2, params.mNumRows);
+        assertEquals("7 key R0 left", 3, params.mLeftKeys);
+        assertEquals("7 key R0 right", 1, params.mRightKeys);
+        assertEquals("7 key R0 [1]", 0, params.getColumnPos(0));
+        assertEquals("7 key R0 [2]", -1, params.getColumnPos(1));
+        assertEquals("7 key R0 [3]", -2, params.getColumnPos(2));
+        assertEquals("7 key R0 [4]", -3, params.getColumnPos(3));
+        assertEquals("7 key R0 [5]", 0, params.getColumnPos(4));
+        assertEquals("7 key R0 [6]", -1, params.getColumnPos(5));
+        assertEquals("7 key R0 [7]", -2, params.getColumnPos(6));
+        assertEquals("7 key R0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("7 key R0 default", WIDTH * 3, params.getDefaultKeyCoordX());
+    }
+
+    //     [7] [6] [5] ___|
+    // [4] [3] [2] [1] ___|
+    public void testLayout7KeyR1() {
+        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(7, MAX_COLUMNS, WIDTH,
+                HEIGHT, XPOS_R1, KEYBOARD_WIDTH);
+        assertEquals("7 key R1 columns", 4, params.mNumColumns);
+        assertEquals("7 key R1 rows", 2, params.mNumRows);
+        assertEquals("7 key R1 left", 3, params.mLeftKeys);
+        assertEquals("7 key R1 right", 1, params.mRightKeys);
+        assertEquals("7 key R1 [1]", 0, params.getColumnPos(0));
+        assertEquals("7 key R1 [2]", -1, params.getColumnPos(1));
+        assertEquals("7 key R1 [3]", -2, params.getColumnPos(2));
+        assertEquals("7 key R1 [4]", -3, params.getColumnPos(3));
+        assertEquals("7 key R1 [5]", 0, params.getColumnPos(4));
+        assertEquals("7 key R1 [6]", -1, params.getColumnPos(5));
+        assertEquals("7 key R1 [7]", -2, params.getColumnPos(6));
+        assertEquals("7 key R1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("7 key R1 default", WIDTH * 3, params.getDefaultKeyCoordX());
+    }
+
+    //   [7] [5] [6]   ___ ___|
+    // [4] [3] [1] [2] ___ ___|
+    public void testLayout7KeyR2() {
+        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(7, MAX_COLUMNS, WIDTH,
+                HEIGHT, XPOS_R2, KEYBOARD_WIDTH);
+        assertEquals("7 key R2 columns", 4, params.mNumColumns);
+        assertEquals("7 key R2 rows", 2, params.mNumRows);
+        assertEquals("7 key R2 left", 2, params.mLeftKeys);
+        assertEquals("7 key R2 right", 2, params.mRightKeys);
+        assertEquals("7 key R2 [1]", 0, params.getColumnPos(0));
+        assertEquals("7 key R2 [2]", 1, params.getColumnPos(1));
+        assertEquals("7 key R2 [3]", -1, params.getColumnPos(2));
+        assertEquals("7 key R2 [4]", -2, params.getColumnPos(3));
+        assertEquals("7 key R2 [5]", 0, params.getColumnPos(4));
+        assertEquals("7 key R2 [6]", 1, params.getColumnPos(5));
+        assertEquals("7 key R2 [7]", -1, params.getColumnPos(6));
+        assertEquals("7 key R2 adjust", -1, params.mTopRowAdjustment);
+        assertEquals("7 key R2 default", WIDTH * 2, params.getDefaultKeyCoordX());
+    }
+
+    // [7] [6] [5] [3] [1] [2] [4] ___|
+    public void testLayout7KeyR3Max7() {
+        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(7, 7, WIDTH,
+                HEIGHT, XPOS_R3, KEYBOARD_WIDTH);
+        assertEquals("7 key R2 columns", 7, params.mNumColumns);
+        assertEquals("7 key R2 rows", 1, params.mNumRows);
+        assertEquals("7 key R2 left", 4, params.mLeftKeys);
+        assertEquals("7 key R2 right", 3, params.mRightKeys);
+        assertEquals("7 key R2 [1]", 0, params.getColumnPos(0));
+        assertEquals("7 key R2 [2]", 1, params.getColumnPos(1));
+        assertEquals("7 key R2 [3]", -1, params.getColumnPos(2));
+        assertEquals("7 key R2 [4]", 2, params.getColumnPos(3));
+        assertEquals("7 key R2 [5]", -2, params.getColumnPos(4));
+        assertEquals("7 key R2 [6]", -3, params.getColumnPos(5));
+        assertEquals("7 key R2 [7]", -4, params.getColumnPos(6));
+        assertEquals("7 key R2 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("7 key R2 default", WIDTH * 4, params.getDefaultKeyCoordX());
+    }
+
+    // [7] [5] [6] [8]
+    // [3] [1] [2] [4]
+    public void testLayout8KeyM0() {
+        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(8, MAX_COLUMNS, WIDTH,
+                HEIGHT, XPOS_M0, KEYBOARD_WIDTH);
+        assertEquals("8 key M0 columns", 4, params.mNumColumns);
+        assertEquals("8 key M0 rows", 2, params.mNumRows);
+        assertEquals("8 key M0 left", 1, params.mLeftKeys);
+        assertEquals("8 key M0 right", 3, params.mRightKeys);
+        assertEquals("8 key M0 [1]", 0, params.getColumnPos(0));
+        assertEquals("8 key M0 [2]", 1, params.getColumnPos(1));
+        assertEquals("8 key M0 [3]", -1, params.getColumnPos(2));
+        assertEquals("8 key M0 [4]", 2, params.getColumnPos(3));
+        assertEquals("8 key M0 [5]", 0, params.getColumnPos(4));
+        assertEquals("8 key M0 [6]", 1, params.getColumnPos(5));
+        assertEquals("8 key M0 [7]", -1, params.getColumnPos(6));
+        assertEquals("8 key M0 [8]", 2, params.getColumnPos(7));
+        assertEquals("8 key M0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("8 key M0 default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    // |[5] [6] [7] [8]
+    // |[1] [2] [3] [4]
+    public void testLayout8KeyL0() {
+        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(8, MAX_COLUMNS, WIDTH,
+                HEIGHT, XPOS_L0, KEYBOARD_WIDTH);
+        assertEquals("8 key L0 columns", 4, params.mNumColumns);
+        assertEquals("8 key L0 rows", 2, params.mNumRows);
+        assertEquals("8 key L0 left", 0, params.mLeftKeys);
+        assertEquals("8 key L0 right", 4, params.mRightKeys);
+        assertEquals("8 key L0 [1]", 0, params.getColumnPos(0));
+        assertEquals("8 key L0 [2]", 1, params.getColumnPos(1));
+        assertEquals("8 key L0 [3]", 2, params.getColumnPos(2));
+        assertEquals("8 key L0 [4]", 3, params.getColumnPos(3));
+        assertEquals("8 key L0 [5]", 0, params.getColumnPos(4));
+        assertEquals("8 key L0 [6]", 1, params.getColumnPos(5));
+        assertEquals("8 key L0 [7]", 2, params.getColumnPos(6));
+        assertEquals("8 key L0 [8]", 3, params.getColumnPos(7));
+        assertEquals("8 key L0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("8 key L0 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___ [5] [6] [7] [8]
+    // |___ [1] [2] [3] [4]
+    public void testLayout8KeyL1() {
+        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(8, MAX_COLUMNS, WIDTH,
+                HEIGHT, XPOS_L1, KEYBOARD_WIDTH);
+        assertEquals("8 key L1 columns", 4, params.mNumColumns);
+        assertEquals("8 key L1 rows", 2, params.mNumRows);
+        assertEquals("8 key L1 left", 0, params.mLeftKeys);
+        assertEquals("8 key L1 right", 4, params.mRightKeys);
+        assertEquals("8 key L1 [1]", 0, params.getColumnPos(0));
+        assertEquals("8 key L1 [2]", 1, params.getColumnPos(1));
+        assertEquals("8 key L1 [3]", 2, params.getColumnPos(2));
+        assertEquals("8 key L1 [4]", 3, params.getColumnPos(3));
+        assertEquals("8 key L1 [5]", 0, params.getColumnPos(4));
+        assertEquals("8 key L1 [6]", 1, params.getColumnPos(5));
+        assertEquals("8 key L1 [7]", 2, params.getColumnPos(6));
+        assertEquals("8 key L1 [8]", 3, params.getColumnPos(7));
+        assertEquals("8 key L1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("8 key L1 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___ ___ [7] [5] [6] [8]
+    // |___ ___ [3] [1] [2] [4]
+    public void testLayout8KeyL2() {
+        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(8, MAX_COLUMNS, WIDTH,
+                HEIGHT, XPOS_L2, KEYBOARD_WIDTH);
+        assertEquals("8 key L2 columns", 4, params.mNumColumns);
+        assertEquals("8 key L2 rows", 2, params.mNumRows);
+        assertEquals("8 key L2 left", 1, params.mLeftKeys);
+        assertEquals("8 key L2 right", 3, params.mRightKeys);
+        assertEquals("8 key L2 [1]", 0, params.getColumnPos(0));
+        assertEquals("8 key L2 [2]", 1, params.getColumnPos(1));
+        assertEquals("8 key L2 [3]", -1, params.getColumnPos(2));
+        assertEquals("8 key L2 [4]", 2, params.getColumnPos(3));
+        assertEquals("8 key L2 [5]", 0, params.getColumnPos(4));
+        assertEquals("8 key L2 [6]", 1, params.getColumnPos(5));
+        assertEquals("8 key L2 [7]", -1, params.getColumnPos(6));
+        assertEquals("8 key L2 [8]", 2, params.getColumnPos(7));
+        assertEquals("8 key L2 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("8 key L2 default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    // [8] [7] [6] [5]|
+    // [4] [3] [2] [1]|
+    public void testLayout8KeyR0() {
+        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(8, MAX_COLUMNS, WIDTH,
+                HEIGHT, XPOS_R0, KEYBOARD_WIDTH);
+        assertEquals("8 key R0 columns", 4, params.mNumColumns);
+        assertEquals("8 key R0 rows", 2, params.mNumRows);
+        assertEquals("8 key R0 left", 3, params.mLeftKeys);
+        assertEquals("8 key R0 right", 1, params.mRightKeys);
+        assertEquals("8 key R0 [1]", 0, params.getColumnPos(0));
+        assertEquals("8 key R0 [2]", -1, params.getColumnPos(1));
+        assertEquals("8 key R0 [3]", -2, params.getColumnPos(2));
+        assertEquals("8 key R0 [4]", -3, params.getColumnPos(3));
+        assertEquals("8 key R0 [5]", 0, params.getColumnPos(4));
+        assertEquals("8 key R0 [6]", -1, params.getColumnPos(5));
+        assertEquals("8 key R0 [7]", -2, params.getColumnPos(6));
+        assertEquals("8 key R0 [8]", -3, params.getColumnPos(7));
+        assertEquals("8 key R0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("8 key R0 default", WIDTH * 3, params.getDefaultKeyCoordX());
+    }
+
+    // [8] [7] [6] [5] ___|
+    // [4] [3] [2] [1] ___|
+    public void testLayout8KeyR1() {
+        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(8, MAX_COLUMNS, WIDTH,
+                HEIGHT, XPOS_R1, KEYBOARD_WIDTH);
+        assertEquals("8 key R1 columns", 4, params.mNumColumns);
+        assertEquals("8 key R1 rows", 2, params.mNumRows);
+        assertEquals("8 key R1 left", 3, params.mLeftKeys);
+        assertEquals("8 key R1 right", 1, params.mRightKeys);
+        assertEquals("8 key R1 [1]", 0, params.getColumnPos(0));
+        assertEquals("8 key R1 [2]", -1, params.getColumnPos(1));
+        assertEquals("8 key R1 [3]", -2, params.getColumnPos(2));
+        assertEquals("8 key R1 [4]", -3, params.getColumnPos(3));
+        assertEquals("8 key R1 [5]", 0, params.getColumnPos(4));
+        assertEquals("8 key R1 [6]", -1, params.getColumnPos(5));
+        assertEquals("8 key R1 [7]", -2, params.getColumnPos(6));
+        assertEquals("8 key R1 [8]", -3, params.getColumnPos(7));
+        assertEquals("8 key R1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("8 key R1 default", WIDTH * 3, params.getDefaultKeyCoordX());
+    }
+
+    // [8] [7] [5] [6] ___ ___|
+    // [4] [3] [1] [2] ___ ___|
+    public void testLayout8KeyR2() {
+        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(8, MAX_COLUMNS, WIDTH,
+                HEIGHT, XPOS_R2, KEYBOARD_WIDTH);
+        assertEquals("8 key R2 columns", 4, params.mNumColumns);
+        assertEquals("8 key R2 rows", 2, params.mNumRows);
+        assertEquals("8 key R2 left", 2, params.mLeftKeys);
+        assertEquals("8 key R2 right", 2, params.mRightKeys);
+        assertEquals("8 key R2 [1]", 0, params.getColumnPos(0));
+        assertEquals("8 key R2 [2]", 1, params.getColumnPos(1));
+        assertEquals("8 key R2 [3]", -1, params.getColumnPos(2));
+        assertEquals("8 key R2 [4]", -2, params.getColumnPos(3));
+        assertEquals("8 key R2 [5]", 0, params.getColumnPos(4));
+        assertEquals("8 key R2 [6]", 1, params.getColumnPos(5));
+        assertEquals("8 key R2 [7]", -1, params.getColumnPos(6));
+        assertEquals("8 key R2 [8]", -2, params.getColumnPos(7));
+        assertEquals("8 key R2 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("8 key R2 default", WIDTH * 2, params.getDefaultKeyCoordX());
     }
 
     //   [8] [6] [7] [9]
     // [5] [3] [1] [2] [4]
-    public void testLayout9Key() {
-        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(
-                9, MAX_COLUMNS, WIDTH, HEIGHT,
-                WIDTH * 5, WIDTH * 10);
-        assertEquals("9 key columns", 5, params.mNumColumns);
-        assertEquals("9 key rows", 2, params.mNumRows);
-        assertEquals("9 key left", 2, params.mLeftKeys);
-        assertEquals("9 key right", 3, params.mRightKeys);
-        assertEquals("9 key [1]", 0, params.getColumnPos(0));
-        assertEquals("9 key [2]", 1, params.getColumnPos(1));
-        assertEquals("9 key [3]", -1, params.getColumnPos(2));
-        assertEquals("9 key [4]", 2, params.getColumnPos(3));
-        assertEquals("9 key [5]", -2, params.getColumnPos(4));
-        assertEquals("9 key [6]", 0, params.getColumnPos(5));
-        assertEquals("9 key [7]", 1, params.getColumnPos(6));
-        assertEquals("9 key [8]", -1, params.getColumnPos(7));
-        assertEquals("9 key [9]", 2, params.getColumnPos(8));
-        assertEquals("9 key centering", true, params.mTopRowNeedsCentering);
-        assertEquals("9 key default", WIDTH * 2, params.getDefaultKeyCoordX());
+    public void testLayout9KeyM0() {
+        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(9, MAX_COLUMNS, WIDTH,
+                HEIGHT, XPOS_M0, KEYBOARD_WIDTH);
+        assertEquals("9 key M0 columns", 5, params.mNumColumns);
+        assertEquals("9 key M0 rows", 2, params.mNumRows);
+        assertEquals("9 key M0 left", 2, params.mLeftKeys);
+        assertEquals("9 key M0 right", 3, params.mRightKeys);
+        assertEquals("9 key M0 [1]", 0, params.getColumnPos(0));
+        assertEquals("9 key M0 [2]", 1, params.getColumnPos(1));
+        assertEquals("9 key M0 [3]", -1, params.getColumnPos(2));
+        assertEquals("9 key M0 [4]", 2, params.getColumnPos(3));
+        assertEquals("9 key M0 [5]", -2, params.getColumnPos(4));
+        assertEquals("9 key M0 [6]", 0, params.getColumnPos(5));
+        assertEquals("9 key M0 [7]", 1, params.getColumnPos(6));
+        assertEquals("9 key M0 [8]", -1, params.getColumnPos(7));
+        assertEquals("9 key M0 [9]", 2, params.getColumnPos(8));
+        assertEquals("9 key M0 adjust", -1, params.mTopRowAdjustment);
+        assertEquals("9 key M0 default", WIDTH * 2, params.getDefaultKeyCoordX());
     }
 
-    // Nine keys test.  There is no key space for mini keyboard at left of the parent key.
-    //   [6] [7] [8] [9]
-    // [1] [2] [3] [4] [5]
-    public void testLayout9KeyLeft() {
-        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(
-                9, MAX_COLUMNS, WIDTH, HEIGHT,
-                0, WIDTH * 10);
-        assertEquals("9 key left columns", 5, params.mNumColumns);
-        assertEquals("9 key left rows", 2, params.mNumRows);
-        assertEquals("9 key left left", 0, params.mLeftKeys);
-        assertEquals("9 key left right", 5, params.mRightKeys);
-        assertEquals("9 key left [1]", 0, params.getColumnPos(0));
-        assertEquals("9 key left [2]", 1, params.getColumnPos(1));
-        assertEquals("9 key left [3]", 2, params.getColumnPos(2));
-        assertEquals("9 key left [4]", 3, params.getColumnPos(3));
-        assertEquals("9 key left [5]", 4, params.getColumnPos(4));
-        assertEquals("9 key left [6]", 0, params.getColumnPos(5));
-        assertEquals("9 key left [7]", 1, params.getColumnPos(6));
-        assertEquals("9 key left [8]", 2, params.getColumnPos(7));
-        assertEquals("9 key left [9]", 3, params.getColumnPos(8));
-        assertEquals("9 key left centering", true, params.mTopRowNeedsCentering);
-        assertEquals("9 key left default", 0, params.getDefaultKeyCoordX());
+    // |[6] [7] [8] [9]
+    // |[1] [2] [3] [4] [5]
+    public void testLayout9KeyL0() {
+        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(9, MAX_COLUMNS, WIDTH,
+                HEIGHT, XPOS_L0, KEYBOARD_WIDTH);
+        assertEquals("9 key L0 columns", 5, params.mNumColumns);
+        assertEquals("9 key L0 rows", 2, params.mNumRows);
+        assertEquals("9 key L0 left", 0, params.mLeftKeys);
+        assertEquals("9 key L0 right", 5, params.mRightKeys);
+        assertEquals("9 key L0 [1]", 0, params.getColumnPos(0));
+        assertEquals("9 key L0 [2]", 1, params.getColumnPos(1));
+        assertEquals("9 key L0 [3]", 2, params.getColumnPos(2));
+        assertEquals("9 key L0 [4]", 3, params.getColumnPos(3));
+        assertEquals("9 key L0 [5]", 4, params.getColumnPos(4));
+        assertEquals("9 key L0 [6]", 0, params.getColumnPos(5));
+        assertEquals("9 key L0 [7]", 1, params.getColumnPos(6));
+        assertEquals("9 key L0 [8]", 2, params.getColumnPos(7));
+        assertEquals("9 key L0 [9]", 3, params.getColumnPos(8));
+        assertEquals("9 key L0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("9 key L0 default", WIDTH * 0, params.getDefaultKeyCoordX());
     }
 
-    // Nine keys test.  There is only one key space for mini keyboard at left of the parent key.
-    //   [8] [6] [7] [9]
-    // [3] [1] [2] [4] [5]
-    public void testLayout9KeyNearLeft() {
-        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(
-                9, MAX_COLUMNS, WIDTH, HEIGHT,
-                WIDTH, WIDTH * 10);
-        assertEquals("9 key near left columns", 5, params.mNumColumns);
-        assertEquals("9 key near left rows", 2, params.mNumRows);
-        assertEquals("9 key near left left", 1, params.mLeftKeys);
-        assertEquals("9 key near left right", 4, params.mRightKeys);
-        assertEquals("9 key near left [1]", 0, params.getColumnPos(0));
-        assertEquals("9 key near left [2]", 1, params.getColumnPos(1));
-        assertEquals("9 key near left [3]", -1, params.getColumnPos(2));
-        assertEquals("9 key near left [4]", 2, params.getColumnPos(3));
-        assertEquals("9 key near left [5]", 3, params.getColumnPos(4));
-        assertEquals("9 key near left [6]", 0, params.getColumnPos(5));
-        assertEquals("9 key near left [7]", 1, params.getColumnPos(6));
-        assertEquals("9 key near left [8]", -1, params.getColumnPos(7));
-        assertEquals("9 key near left [9]", 2, params.getColumnPos(8));
-        assertEquals("9 key near left centering", true, params.mTopRowNeedsCentering);
-        assertEquals("9 key near left default", WIDTH, params.getDefaultKeyCoordX());
+    // |___ [6] [7] [8] [9]
+    // |___ [1] [2] [3] [4] [5]
+    public void testLayout9KeyL1() {
+        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(9, MAX_COLUMNS, WIDTH,
+                HEIGHT, XPOS_L1, KEYBOARD_WIDTH);
+        assertEquals("9 key L1 columns", 5, params.mNumColumns);
+        assertEquals("9 key L1 rows", 2, params.mNumRows);
+        assertEquals("9 key L1 left", 0, params.mLeftKeys);
+        assertEquals("9 key L1 right", 5, params.mRightKeys);
+        assertEquals("9 key L1 [1]", 0, params.getColumnPos(0));
+        assertEquals("9 key L1 [2]", 1, params.getColumnPos(1));
+        assertEquals("9 key L1 [3]", 2, params.getColumnPos(2));
+        assertEquals("9 key L1 [4]", 3, params.getColumnPos(3));
+        assertEquals("9 key L1 [5]", 4, params.getColumnPos(4));
+        assertEquals("9 key L1 [6]", 0, params.getColumnPos(5));
+        assertEquals("9 key L1 [7]", 1, params.getColumnPos(6));
+        assertEquals("9 key L1 [8]", 2, params.getColumnPos(7));
+        assertEquals("9 key L1 [9]", 3, params.getColumnPos(8));
+        assertEquals("9 key L1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("9 key L1 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___ ___   [8] [6] [7] [9]
+    // |___ ___ [3] [1] [2] [4] [5]
+    public void testLayout9KeyL2() {
+        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(9, MAX_COLUMNS, WIDTH,
+                HEIGHT, XPOS_L2, KEYBOARD_WIDTH);
+        assertEquals("9 key L2 columns", 5, params.mNumColumns);
+        assertEquals("9 key L2 rows", 2, params.mNumRows);
+        assertEquals("9 key L2 left", 1, params.mLeftKeys);
+        assertEquals("9 key L2 right", 4, params.mRightKeys);
+        assertEquals("9 key L2 [1]", 0, params.getColumnPos(0));
+        assertEquals("9 key L2 [2]", 1, params.getColumnPos(1));
+        assertEquals("9 key L2 [3]", -1, params.getColumnPos(2));
+        assertEquals("9 key L2 [4]", 2, params.getColumnPos(3));
+        assertEquals("9 key L2 [5]", 3, params.getColumnPos(4));
+        assertEquals("9 key L2 [6]", 0, params.getColumnPos(5));
+        assertEquals("9 key L2 [7]", 1, params.getColumnPos(6));
+        assertEquals("9 key L2 [8]", -1, params.getColumnPos(7));
+        assertEquals("9 key L2 [9]", 2, params.getColumnPos(8));
+        assertEquals("9 key L2 adjust", 1, params.mTopRowAdjustment);
+        assertEquals("9 key L2 default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    //     [9] [8] [7] [6]|
+    // [5] [4] [3] [2] [1]|
+    public void testLayout9KeyR0() {
+        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(9, MAX_COLUMNS, WIDTH,
+                HEIGHT, XPOS_R0, KEYBOARD_WIDTH);
+        assertEquals("9 key R0 columns", 5, params.mNumColumns);
+        assertEquals("9 key R0 rows", 2, params.mNumRows);
+        assertEquals("9 key R0 left", 4, params.mLeftKeys);
+        assertEquals("9 key R0 right", 1, params.mRightKeys);
+        assertEquals("9 key R0 [1]", 0, params.getColumnPos(0));
+        assertEquals("9 key R0 [2]", -1, params.getColumnPos(1));
+        assertEquals("9 key R0 [3]", -2, params.getColumnPos(2));
+        assertEquals("9 key R0 [4]", -3, params.getColumnPos(3));
+        assertEquals("9 key R0 [5]", -4, params.getColumnPos(4));
+        assertEquals("9 key R0 [6]", 0, params.getColumnPos(5));
+        assertEquals("9 key R0 [7]", -1, params.getColumnPos(6));
+        assertEquals("9 key R0 [8]", -2, params.getColumnPos(7));
+        assertEquals("9 key R0 [9]", -3, params.getColumnPos(8));
+        assertEquals("9 key R0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("9 key R0 default", WIDTH * 4, params.getDefaultKeyCoordX());
+    }
+
+    //     [9] [8] [7] [6] ___|
+    // [5] [4] [3] [2] [1] ___|
+    public void testLayout9KeyR1() {
+        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(9, MAX_COLUMNS, WIDTH,
+                HEIGHT, XPOS_R1, KEYBOARD_WIDTH);
+        assertEquals("9 key R1 columns", 5, params.mNumColumns);
+        assertEquals("9 key R1 rows", 2, params.mNumRows);
+        assertEquals("9 key R1 left", 4, params.mLeftKeys);
+        assertEquals("9 key R1 right", 1, params.mRightKeys);
+        assertEquals("9 key R1 [1]", 0, params.getColumnPos(0));
+        assertEquals("9 key R1 [2]", -1, params.getColumnPos(1));
+        assertEquals("9 key R1 [3]", -2, params.getColumnPos(2));
+        assertEquals("9 key R1 [4]", -3, params.getColumnPos(3));
+        assertEquals("9 key R1 [5]", -4, params.getColumnPos(4));
+        assertEquals("9 key R1 [6]", 0, params.getColumnPos(5));
+        assertEquals("9 key R1 [7]", -1, params.getColumnPos(6));
+        assertEquals("9 key R1 [8]", -2, params.getColumnPos(7));
+        assertEquals("9 key R1 [9]", -3, params.getColumnPos(8));
+        assertEquals("9 key R1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("9 key R1 default", WIDTH * 4, params.getDefaultKeyCoordX());
+    }
+
+    //   [9] [8] [6] [7]   ___ ___|
+    // [5] [4] [3] [1] [2] ___ ___|
+    public void testLayout9KeyR2() {
+        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(9, MAX_COLUMNS, WIDTH,
+                HEIGHT, XPOS_R2, KEYBOARD_WIDTH);
+        assertEquals("9 key R2 columns", 5, params.mNumColumns);
+        assertEquals("9 key R2 rows", 2, params.mNumRows);
+        assertEquals("9 key R2 left", 3, params.mLeftKeys);
+        assertEquals("9 key R2 right", 2, params.mRightKeys);
+        assertEquals("9 key R2 [1]", 0, params.getColumnPos(0));
+        assertEquals("9 key R2 [2]", 1, params.getColumnPos(1));
+        assertEquals("9 key R2 [3]", -1, params.getColumnPos(2));
+        assertEquals("9 key R2 [4]", -2, params.getColumnPos(3));
+        assertEquals("9 key R2 [5]", -3, params.getColumnPos(4));
+        assertEquals("9 key R2 [6]", 0, params.getColumnPos(5));
+        assertEquals("9 key R2 [7]", 1, params.getColumnPos(6));
+        assertEquals("9 key R2 [8]", -1, params.getColumnPos(7));
+        assertEquals("9 key R2 [9]", -2, params.getColumnPos(8));
+        assertEquals("9 key R2 adjust", -1, params.mTopRowAdjustment);
+        assertEquals("9 key R2 default", WIDTH * 3, params.getDefaultKeyCoordX());
+    }
+
+    // [A] [8] [6] [7] [9]
+    // [5] [3] [1] [2] [4]
+    public void testLayout10KeyM0() {
+        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(10, MAX_COLUMNS, WIDTH,
+                HEIGHT, XPOS_M0, KEYBOARD_WIDTH);
+        assertEquals("10 key M0 columns", 5, params.mNumColumns);
+        assertEquals("10 key M0 rows", 2, params.mNumRows);
+        assertEquals("10 key M0 left", 2, params.mLeftKeys);
+        assertEquals("10 key M0 right", 3, params.mRightKeys);
+        assertEquals("10 key M0 [1]", 0, params.getColumnPos(0));
+        assertEquals("10 key M0 [2]", 1, params.getColumnPos(1));
+        assertEquals("10 key M0 [3]", -1, params.getColumnPos(2));
+        assertEquals("10 key M0 [4]", 2, params.getColumnPos(3));
+        assertEquals("10 key M0 [5]", -2, params.getColumnPos(4));
+        assertEquals("10 key M0 [6]", 0, params.getColumnPos(5));
+        assertEquals("10 key M0 [7]", 1, params.getColumnPos(6));
+        assertEquals("10 key M0 [8]", -1, params.getColumnPos(7));
+        assertEquals("10 key M0 [9]", 2, params.getColumnPos(8));
+        assertEquals("10 key M0 [A]", -2, params.getColumnPos(9));
+        assertEquals("10 key M0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("10 key M0 default", WIDTH * 2, params.getDefaultKeyCoordX());
+    }
+
+    // |[6] [7] [8] [9] [A]
+    // |[1] [2] [3] [4] [5]
+    public void testLayout10KeyL0() {
+        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(10, MAX_COLUMNS, WIDTH,
+                HEIGHT, XPOS_L0, KEYBOARD_WIDTH);
+        assertEquals("10 key L0 columns", 5, params.mNumColumns);
+        assertEquals("10 key L0 rows", 2, params.mNumRows);
+        assertEquals("10 key L0 left", 0, params.mLeftKeys);
+        assertEquals("10 key L0 right", 5, params.mRightKeys);
+        assertEquals("10 key L0 [1]", 0, params.getColumnPos(0));
+        assertEquals("10 key L0 [2]", 1, params.getColumnPos(1));
+        assertEquals("10 key L0 [3]", 2, params.getColumnPos(2));
+        assertEquals("10 key L0 [4]", 3, params.getColumnPos(3));
+        assertEquals("10 key L0 [5]", 4, params.getColumnPos(4));
+        assertEquals("10 key L0 [6]", 0, params.getColumnPos(5));
+        assertEquals("10 key L0 [7]", 1, params.getColumnPos(6));
+        assertEquals("10 key L0 [8]", 2, params.getColumnPos(7));
+        assertEquals("10 key L0 [9]", 3, params.getColumnPos(8));
+        assertEquals("10 key L0 [A]", 4, params.getColumnPos(9));
+        assertEquals("10 key L0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("10 key L0 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___ [6] [7] [8] [9] [A]
+    // |___ [1] [2] [3] [4] [5]
+    public void testLayout10KeyL1() {
+        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(10, MAX_COLUMNS, WIDTH,
+                HEIGHT, XPOS_L1, KEYBOARD_WIDTH);
+        assertEquals("10 key L1 columns", 5, params.mNumColumns);
+        assertEquals("10 key L1 rows", 2, params.mNumRows);
+        assertEquals("10 key L1 left", 0, params.mLeftKeys);
+        assertEquals("10 key L1 right", 5, params.mRightKeys);
+        assertEquals("10 key L1 [1]", 0, params.getColumnPos(0));
+        assertEquals("10 key L1 [2]", 1, params.getColumnPos(1));
+        assertEquals("10 key L1 [3]", 2, params.getColumnPos(2));
+        assertEquals("10 key L1 [4]", 3, params.getColumnPos(3));
+        assertEquals("10 key L1 [5]", 4, params.getColumnPos(4));
+        assertEquals("10 key L1 [6]", 0, params.getColumnPos(5));
+        assertEquals("10 key L1 [7]", 1, params.getColumnPos(6));
+        assertEquals("10 key L1 [8]", 2, params.getColumnPos(7));
+        assertEquals("10 key L1 [9]", 3, params.getColumnPos(8));
+        assertEquals("10 key L1 [A]", 4, params.getColumnPos(9));
+        assertEquals("10 key L1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("10 key L1 default", WIDTH * 0, params.getDefaultKeyCoordX());
+    }
+
+    // |___ ___ [8] [6] [7] [9] [A]
+    // |___ ___ [3] [1] [2] [4] [5]
+    public void testLayout10KeyL2() {
+        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(10, MAX_COLUMNS, WIDTH,
+                HEIGHT, XPOS_L2, KEYBOARD_WIDTH);
+        assertEquals("10 key L2 columns", 5, params.mNumColumns);
+        assertEquals("10 key L2 rows", 2, params.mNumRows);
+        assertEquals("10 key L2 left", 1, params.mLeftKeys);
+        assertEquals("10 key L2 right", 4, params.mRightKeys);
+        assertEquals("10 key L2 [1]", 0, params.getColumnPos(0));
+        assertEquals("10 key L2 [2]", 1, params.getColumnPos(1));
+        assertEquals("10 key L2 [3]", -1, params.getColumnPos(2));
+        assertEquals("10 key L2 [4]", 2, params.getColumnPos(3));
+        assertEquals("10 key L2 [5]", 3, params.getColumnPos(4));
+        assertEquals("10 key L2 [6]", 0, params.getColumnPos(5));
+        assertEquals("10 key L2 [7]", 1, params.getColumnPos(6));
+        assertEquals("10 key L2 [8]", -1, params.getColumnPos(7));
+        assertEquals("10 key L2 [9]", 2, params.getColumnPos(8));
+        assertEquals("10 key L2 [A]", 3, params.getColumnPos(9));
+        assertEquals("10 key L2 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("10 key L2 default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    // [A] [9] [8] [7] [6]|
+    // [5] [4] [3] [2] [1]|
+    public void testLayout10KeyR0() {
+        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(10, MAX_COLUMNS, WIDTH,
+                HEIGHT, XPOS_R0, KEYBOARD_WIDTH);
+        assertEquals("10 key R0 columns", 5, params.mNumColumns);
+        assertEquals("10 key R0 rows", 2, params.mNumRows);
+        assertEquals("10 key R0 left", 4, params.mLeftKeys);
+        assertEquals("10 key R0 right", 1, params.mRightKeys);
+        assertEquals("10 key R0 [1]", 0, params.getColumnPos(0));
+        assertEquals("10 key R0 [2]", -1, params.getColumnPos(1));
+        assertEquals("10 key R0 [3]", -2, params.getColumnPos(2));
+        assertEquals("10 key R0 [4]", -3, params.getColumnPos(3));
+        assertEquals("10 key R0 [5]", -4, params.getColumnPos(4));
+        assertEquals("10 key R0 [6]", 0, params.getColumnPos(5));
+        assertEquals("10 key R0 [7]", -1, params.getColumnPos(6));
+        assertEquals("10 key R0 [8]", -2, params.getColumnPos(7));
+        assertEquals("10 key R0 [9]", -3, params.getColumnPos(8));
+        assertEquals("10 key R0 [A]", -4, params.getColumnPos(9));
+        assertEquals("10 key R0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("10 key R0 default", WIDTH * 4, params.getDefaultKeyCoordX());
+    }
+
+    // [A] [9] [8] [7] [6] ___|
+    // [5] [4] [3] [2] [1] ___|
+    public void testLayout10KeyR1() {
+        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(10, MAX_COLUMNS, WIDTH,
+                HEIGHT, XPOS_R1, KEYBOARD_WIDTH);
+        assertEquals("10 key R1 columns", 5, params.mNumColumns);
+        assertEquals("10 key R1 rows", 2, params.mNumRows);
+        assertEquals("10 key R1 left", 4, params.mLeftKeys);
+        assertEquals("10 key R1 right", 1, params.mRightKeys);
+        assertEquals("10 key R1 [1]", 0, params.getColumnPos(0));
+        assertEquals("10 key R1 [2]", -1, params.getColumnPos(1));
+        assertEquals("10 key R1 [3]", -2, params.getColumnPos(2));
+        assertEquals("10 key R1 [4]", -3, params.getColumnPos(3));
+        assertEquals("10 key R1 [5]", -4, params.getColumnPos(4));
+        assertEquals("10 key R1 [6]", 0, params.getColumnPos(5));
+        assertEquals("10 key R1 [7]", -1, params.getColumnPos(6));
+        assertEquals("10 key R1 [8]", -2, params.getColumnPos(7));
+        assertEquals("10 key R1 [9]", -3, params.getColumnPos(8));
+        assertEquals("10 key R1 [A]", -4, params.getColumnPos(9));
+        assertEquals("10 key R1 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("10 key R1 default", WIDTH * 4, params.getDefaultKeyCoordX());
+    }
+
+    // [A] [9] [8] [6] [7] ___ ___|
+    // [5] [4] [3] [1] [2] ___ ___|
+    public void testLayout10KeyR2() {
+        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(10, MAX_COLUMNS, WIDTH,
+                HEIGHT, XPOS_R2, KEYBOARD_WIDTH);
+        assertEquals("10 key R2 columns", 5, params.mNumColumns);
+        assertEquals("10 key R2 rows", 2, params.mNumRows);
+        assertEquals("10 key R2 left", 3, params.mLeftKeys);
+        assertEquals("10 key R2 right", 2, params.mRightKeys);
+        assertEquals("10 key R2 [1]", 0, params.getColumnPos(0));
+        assertEquals("10 key R2 [2]", 1, params.getColumnPos(1));
+        assertEquals("10 key R2 [3]", -1, params.getColumnPos(2));
+        assertEquals("10 key R2 [4]", -2, params.getColumnPos(3));
+        assertEquals("10 key R2 [5]", -3, params.getColumnPos(4));
+        assertEquals("10 key R2 [6]", 0, params.getColumnPos(5));
+        assertEquals("10 key R2 [7]", 1, params.getColumnPos(6));
+        assertEquals("10 key R2 [8]", -1, params.getColumnPos(7));
+        assertEquals("10 key R2 [9]", -2, params.getColumnPos(8));
+        assertEquals("10 key R2 [A]", -3, params.getColumnPos(9));
+        assertEquals("10 key R2 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("10 key R2 default", WIDTH * 3, params.getDefaultKeyCoordX());
+    }
+
+    //   [B] [9] [A]
+    // [7] [5] [6] [8]
+    // [3] [1] [2] [4]
+    public void testLayout11KeyM0() {
+        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(11, MAX_COLUMNS, WIDTH,
+                HEIGHT, XPOS_M0, KEYBOARD_WIDTH);
+        assertEquals("11 key M0 columns", 4, params.mNumColumns);
+        assertEquals("11 key M0 rows", 3, params.mNumRows);
+        assertEquals("11 key M0 left", 1, params.mLeftKeys);
+        assertEquals("11 key M0 right", 3, params.mRightKeys);
+        assertEquals("11 key M0 [1]", 0, params.getColumnPos(0));
+        assertEquals("11 key M0 [2]", 1, params.getColumnPos(1));
+        assertEquals("11 key M0 [3]", -1, params.getColumnPos(2));
+        assertEquals("11 key M0 [4]", 2, params.getColumnPos(3));
+        assertEquals("11 key M0 [5]", 0, params.getColumnPos(4));
+        assertEquals("11 key M0 [6]", 1, params.getColumnPos(5));
+        assertEquals("11 key M0 [7]", -1, params.getColumnPos(6));
+        assertEquals("11 key M0 [8]", 2, params.getColumnPos(7));
+        assertEquals("11 key M0 [9]", 0, params.getColumnPos(8));
+        assertEquals("11 key M0 [A]", 1, params.getColumnPos(9));
+        assertEquals("11 key M0 [B]", -1, params.getColumnPos(10));
+        assertEquals("11 key M0 adjust", 1, params.mTopRowAdjustment);
+        assertEquals("11 key M0 default", WIDTH * 1, params.getDefaultKeyCoordX());
+    }
+
+    // [B] [9] [A] [C]
+    // [7] [5] [6] [8]
+    // [3] [1] [2] [4]
+    public void testLayout12KeyM0() {
+        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(12, MAX_COLUMNS, WIDTH,
+                HEIGHT, XPOS_M0, KEYBOARD_WIDTH);
+        assertEquals("12 key M0 columns", 4, params.mNumColumns);
+        assertEquals("12 key M0 rows", 3, params.mNumRows);
+        assertEquals("12 key M0 left", 1, params.mLeftKeys);
+        assertEquals("12 key M0 right", 3, params.mRightKeys);
+        assertEquals("12 key M0 [1]", 0, params.getColumnPos(0));
+        assertEquals("12 key M0 [2]", 1, params.getColumnPos(1));
+        assertEquals("12 key M0 [3]", -1, params.getColumnPos(2));
+        assertEquals("12 key M0 [4]", 2, params.getColumnPos(3));
+        assertEquals("12 key M0 [5]", 0, params.getColumnPos(4));
+        assertEquals("12 key M0 [6]", 1, params.getColumnPos(5));
+        assertEquals("12 key M0 [7]", -1, params.getColumnPos(6));
+        assertEquals("12 key M0 [8]", 2, params.getColumnPos(7));
+        assertEquals("12 key M0 [9]", 0, params.getColumnPos(8));
+        assertEquals("12 key M0 [A]", 1, params.getColumnPos(9));
+        assertEquals("12 key M0 [B]", -1, params.getColumnPos(10));
+        assertEquals("12 key M0 [C]", 2, params.getColumnPos(11));
+        assertEquals("12 key M0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("12 key M0 default", WIDTH * 1, params.getDefaultKeyCoordX());
     }
 
 
-    // Nine keys test.  There is no key space for mini keyboard at right of the parent key.
-    //   [9] [8] [7] [6]
-    // [5] [4] [3] [2] [1]
-    public void testLayout9KeyRight() {
-        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(
-                9, MAX_COLUMNS, WIDTH, HEIGHT,
-                WIDTH * 9, WIDTH * 10);
-        assertEquals("9 key right columns", 5, params.mNumColumns);
-        assertEquals("9 key right rows", 2, params.mNumRows);
-        assertEquals("9 key right left", 4, params.mLeftKeys);
-        assertEquals("9 key right right", 1, params.mRightKeys);
-        assertEquals("9 key right [1]", 0, params.getColumnPos(0));
-        assertEquals("9 key right [2]", -1, params.getColumnPos(1));
-        assertEquals("9 key right [3]", -2, params.getColumnPos(2));
-        assertEquals("9 key right [4]", -3, params.getColumnPos(3));
-        assertEquals("9 key right [5]", -4, params.getColumnPos(4));
-        assertEquals("9 key right [6]", 0, params.getColumnPos(5));
-        assertEquals("9 key right [7]", -1, params.getColumnPos(6));
-        assertEquals("9 key right [8]", -2, params.getColumnPos(7));
-        assertEquals("9 key right [9]", -3, params.getColumnPos(8));
-        assertEquals("9 key right centering", true, params.mTopRowNeedsCentering);
-        assertEquals("9 key right default", WIDTH * 4, params.getDefaultKeyCoordX());
-    }
-
-    // Nine keys test.  There is only one key space for mini keyboard at right of the parent key.
-    //   [9] [8] [6] [7]
-    // [5] [4] [3] [1] [2]
-    public void testLayout9KeyNearRight() {
-        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(
-                9, MAX_COLUMNS, WIDTH, HEIGHT,
-                WIDTH * 8, WIDTH * 10);
-        assertEquals("9 key near right columns", 5, params.mNumColumns);
-        assertEquals("9 key near right rows", 2, params.mNumRows);
-        assertEquals("9 key near right left", 3, params.mLeftKeys);
-        assertEquals("9 key near right right", 2, params.mRightKeys);
-        assertEquals("9 key near right [1]", 0, params.getColumnPos(0));
-        assertEquals("9 key near right [2]", 1, params.getColumnPos(1));
-        assertEquals("9 key near right [3]", -1, params.getColumnPos(2));
-        assertEquals("9 key near right [4]", -2, params.getColumnPos(3));
-        assertEquals("9 key near right [5]", -3, params.getColumnPos(4));
-        assertEquals("9 key near right [6]", 0, params.getColumnPos(5));
-        assertEquals("9 key near right [7]", 1, params.getColumnPos(6));
-        assertEquals("9 key near right [8]", -1, params.getColumnPos(7));
-        assertEquals("9 key near right [9]", -2, params.getColumnPos(8));
-        assertEquals("9 key near right centering", true, params.mTopRowNeedsCentering);
-        assertEquals("9 key near right default", WIDTH * 3, params.getDefaultKeyCoordX());
+    //     [D] [B] [C]
+    // [A] [8] [6] [7] [9]
+    // [5] [3] [1] [2] [4]
+    public void testLayout13KeyM0() {
+        MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(13, MAX_COLUMNS, WIDTH,
+                HEIGHT, XPOS_M0, KEYBOARD_WIDTH);
+        assertEquals("13 key M0 columns", 5, params.mNumColumns);
+        assertEquals("13 key M0 rows", 3, params.mNumRows);
+        assertEquals("13 key M0 left", 2, params.mLeftKeys);
+        assertEquals("13 key M0 right", 3, params.mRightKeys);
+        assertEquals("13 key M0 [1]", 0, params.getColumnPos(0));
+        assertEquals("13 key M0 [2]", 1, params.getColumnPos(1));
+        assertEquals("13 key M0 [3]", -1, params.getColumnPos(2));
+        assertEquals("13 key M0 [4]", 2, params.getColumnPos(3));
+        assertEquals("13 key M0 [5]", -2, params.getColumnPos(4));
+        assertEquals("13 key M0 [6]", 0, params.getColumnPos(5));
+        assertEquals("13 key M0 [7]", 1, params.getColumnPos(6));
+        assertEquals("13 key M0 [8]", -1, params.getColumnPos(7));
+        assertEquals("13 key M0 [9]", 2, params.getColumnPos(8));
+        assertEquals("13 key M0 [A]", -2, params.getColumnPos(9));
+        assertEquals("13 key M0 [B]", 0, params.getColumnPos(10));
+        assertEquals("13 key M0 [C]", 1, params.getColumnPos(11));
+        assertEquals("13 key M0 [D]", -1, params.getColumnPos(12));
+        assertEquals("13 key M0 adjust", 0, params.mTopRowAdjustment);
+        assertEquals("13 key M0 default", WIDTH * 2, params.getDefaultKeyCoordX());
     }
 }
diff --git a/tests/src/com/android/inputmethod/latin/SuggestHelper.java b/tests/src/com/android/inputmethod/latin/SuggestHelper.java
index 5930ea3..8a11ff9 100644
--- a/tests/src/com/android/inputmethod/latin/SuggestHelper.java
+++ b/tests/src/com/android/inputmethod/latin/SuggestHelper.java
@@ -20,7 +20,6 @@
 import com.android.inputmethod.keyboard.KeyDetector;
 import com.android.inputmethod.keyboard.KeyboardId;
 import com.android.inputmethod.keyboard.LatinKeyboard;
-import com.android.inputmethod.keyboard.ProximityKeyDetector;
 
 import android.content.Context;
 import android.text.TextUtils;
@@ -34,17 +33,19 @@
     private final KeyDetector mKeyDetector;
 
     public SuggestHelper(Context context, int dictionaryId, KeyboardId keyboardId) {
-        mSuggest = new Suggest(context, dictionaryId);
+        // Use null as the locale for Suggest so as to force it to use the internal dictionary
+        // (and not try to find a dictionary provider for a specified locale)
+        mSuggest = new Suggest(context, dictionaryId, null);
         mKeyboard = new LatinKeyboard(context, keyboardId);
-        mKeyDetector = new ProximityKeyDetector();
+        mKeyDetector = new KeyDetector();
         init();
     }
 
     protected SuggestHelper(Context context, File dictionaryPath, long startOffset, long length,
             KeyboardId keyboardId) {
-        mSuggest = new Suggest(dictionaryPath, startOffset, length);
+        mSuggest = new Suggest(context, dictionaryPath, startOffset, length, null);
         mKeyboard = new LatinKeyboard(context, keyboardId);
-        mKeyDetector = new ProximityKeyDetector();
+        mKeyDetector = new KeyDetector();
         init();
     }
 
@@ -53,7 +54,7 @@
         mSuggest.setCorrectionMode(Suggest.CORRECTION_FULL);
         mKeyDetector.setKeyboard(mKeyboard, 0, 0);
         mKeyDetector.setProximityCorrectionEnabled(true);
-        mKeyDetector.setProximityThreshold(KeyDetector.getMostCommonKeyWidth(mKeyboard));
+        mKeyDetector.setProximityThreshold(mKeyboard.getMostCommonKeyWidth());
     }
 
     public void setCorrectionMode(int correctionMode) {
diff --git a/tests/src/com/android/inputmethod/latin/UtilsTests.java b/tests/src/com/android/inputmethod/latin/UtilsTests.java
new file mode 100644
index 0000000..5c0b03a
--- /dev/null
+++ b/tests/src/com/android/inputmethod/latin/UtilsTests.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2010,2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import android.test.AndroidTestCase;
+
+import com.android.inputmethod.latin.tests.R;
+
+public class UtilsTests extends AndroidTestCase {
+
+    // The following is meant to be a reasonable default for
+    // the "word_separators" resource.
+    private static final String sSeparators = ".,:;!?-";
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+    }
+
+    /************************** Tests ************************/
+
+    /**
+     * Test for getting previous word (for bigram suggestions)
+     */
+    public void testGetPreviousWord() {
+        // If one of the following cases breaks, the bigram suggestions won't work.
+        assertEquals(EditingUtils.getPreviousWord("abc def", sSeparators), "abc");
+        assertNull(EditingUtils.getPreviousWord("abc", sSeparators));
+        assertNull(EditingUtils.getPreviousWord("abc. def", sSeparators));
+
+        // The following tests reflect the current behavior of the function
+        // EditingUtils#getPreviousWord.
+        // TODO: However at this time, the code does never go
+        // into such a path, so it should be safe to change the behavior of
+        // this function if needed - especially since it does not seem very
+        // logical. These tests are just there to catch any unintentional
+        // changes in the behavior of the EditingUtils#getPreviousWord method.
+        assertEquals(EditingUtils.getPreviousWord("abc def ", sSeparators), "abc");
+        assertEquals(EditingUtils.getPreviousWord("abc def.", sSeparators), "abc");
+        assertEquals(EditingUtils.getPreviousWord("abc def .", sSeparators), "def");
+        assertNull(EditingUtils.getPreviousWord("abc ", sSeparators));
+    }
+
+    /**
+     * Test for getting the word before the cursor (for bigram)
+     */
+    public void testGetThisWord() {
+        assertEquals(EditingUtils.getThisWord("abc def", sSeparators), "def");
+        assertEquals(EditingUtils.getThisWord("abc def ", sSeparators), "def");
+        assertNull(EditingUtils.getThisWord("abc def.", sSeparators));
+        assertNull(EditingUtils.getThisWord("abc def .", sSeparators));
+    }
+}
diff --git a/tools/makedict/src/com/android/tools/dict/MakeBinaryDictionary.java b/tools/makedict/src/com/android/tools/dict/MakeBinaryDictionary.java
index 51e2038..4a285ff 100644
--- a/tools/makedict/src/com/android/tools/dict/MakeBinaryDictionary.java
+++ b/tools/makedict/src/com/android/tools/dict/MakeBinaryDictionary.java
@@ -41,18 +41,17 @@
  *  in the data. There is no need to increase the version when only the words in the data changes.
  */
 public class MakeBinaryDictionary {
-
     private static final int VERSION_NUM = 200;
 
-    public static final int ALPHA_SIZE = 256;
-
-    public static final String TAG_WORD = "w";
-    public static final String ATTR_FREQ = "f";
+    private static final String TAG_WORD = "w";
+    private static final String ATTR_FREQ = "f";
 
     private static final int FLAG_ADDRESS_MASK  = 0x400000;
     private static final int FLAG_TERMINAL_MASK = 0x800000;
     private static final int ADDRESS_MASK = 0x3FFFFF;
 
+    private static final int INITIAL_STRING_BUILDER_CAPACITY = 48;
+
     /**
      * Unit for this variable is in bytes
      * If destination file name is main.dict and file limit causes dictionary to be separated into
@@ -61,15 +60,15 @@
     private static int sOutputFileSize;
     private static boolean sSplitOutput;
 
-    public static final CharNode EMPTY_NODE = new CharNode();
+    private static final CharNode EMPTY_NODE = new CharNode();
 
-    List<CharNode> roots;
-    Map<String, Integer> mDictionary;
-    int mWordCount;
+    private List<CharNode> mRoots;
+    private Map<String, Integer> mDictionary;
+    private int mWordCount;
 
-    BigramDictionary bigramDict;
+    private BigramDictionary mBigramDict;
 
-    static class CharNode {
+    private static class CharNode {
         char data;
         int freq;
         boolean terminal;
@@ -81,7 +80,7 @@
         }
     }
 
-    public static void usage() {
+    private static void usage() {
         System.err.println("Usage: makedict -s <src_dict.xml> [-b <src_bigram.xml>] "
                 + "-d <dest.dict> [--size filesize]");
         System.exit(-1);
@@ -118,36 +117,37 @@
         }
     }
 
-    public MakeBinaryDictionary(String srcFilename, String bigramSrcFilename, String destFilename){
+    private MakeBinaryDictionary(String srcFilename, String bigramSrcFilename,
+            String destFilename) {
         System.out.println("Generating dictionary version " + VERSION_NUM);
-        bigramDict = new BigramDictionary(bigramSrcFilename, (bigramSrcFilename != null));
+        mBigramDict = new BigramDictionary(bigramSrcFilename, (bigramSrcFilename != null));
         populateDictionary(srcFilename);
         writeToDict(destFilename);
 
         // Enable the code below to verify that the generated tree is traversable
         // and bigram data is stored correctly.
         if (false) {
-            bigramDict.reverseLookupAll(mDictionary, dict);
+            mBigramDict.reverseLookupAll(mDictionary, mDict);
             traverseDict(2, new char[32], 0);
         }
     }
 
     private void populateDictionary(String filename) {
-        roots = new ArrayList<CharNode>();
+        mRoots = new ArrayList<CharNode>();
         mDictionary = new HashMap<String, Integer>();
         try {
             SAXParser parser = SAXParserFactory.newInstance().newSAXParser();
             parser.parse(new File(filename), new DefaultHandler() {
                 boolean inWord;
                 int freq;
-                StringBuilder wordBuilder = new StringBuilder(48);
+                StringBuilder wordBuilder = new StringBuilder(INITIAL_STRING_BUILDER_CAPACITY);
 
                 @Override
                 public void startElement(String uri, String localName,
                         String qName, Attributes attributes) {
-                    if (qName.equals("w")) {
+                    if (qName.equals(TAG_WORD)) {
                         inWord = true;
-                        freq = Integer.parseInt(attributes.getValue(0));
+                        freq = Integer.parseInt(attributes.getValue(ATTR_FREQ));
                         wordBuilder.setLength(0);
                     }
                 }
@@ -162,7 +162,7 @@
                 @Override
                 public void endElement(String uri, String localName,
                         String qName) {
-                    if (qName.equals("w")) {
+                    if (qName.equals(TAG_WORD)) {
                         if (wordBuilder.length() >= 1) {
                             addWordTop(wordBuilder.toString(), freq);
                             mWordCount++;
@@ -178,7 +178,7 @@
         System.out.println("Nodes = " + CharNode.sNodes);
     }
 
-    private int indexOf(List<CharNode> children, char c) {
+    private static int indexOf(List<CharNode> children, char c) {
         if (children == null) {
             return -1;
         }
@@ -190,27 +190,30 @@
         return -1;
     }
 
-    private void addWordTop(String word, int occur) {
-        if (occur > 255) occur = 255;
+    private void addWordTop(String word, int freq) {
+        if (freq < 0) {
+            freq = 0;
+        } else if (freq > 255) {
+            freq = 255;
+        }
         char firstChar = word.charAt(0);
-        int index = indexOf(roots, firstChar);
+        int index = indexOf(mRoots, firstChar);
         if (index == -1) {
             CharNode newNode = new CharNode();
             newNode.data = firstChar;
-            newNode.freq = occur;
-            index = roots.size();
-            roots.add(newNode);
-        } else {
-            roots.get(index).freq += occur;
+            index = mRoots.size();
+            mRoots.add(newNode);
         }
+        final CharNode node = mRoots.get(index);
         if (word.length() > 1) {
-            addWordRec(roots.get(index), word, 1, occur);
+            addWordRec(node, word, 1, freq);
         } else {
-            roots.get(index).terminal = true;
+            node.terminal = true;
+            node.freq = freq;
         }
     }
 
-    private void addWordRec(CharNode parent, String word, int charAt, int occur) {
+    private void addWordRec(CharNode parent, String word, int charAt, int freq) {
         CharNode child = null;
         char data = word.charAt(charAt);
         if (parent.children == null) {
@@ -229,89 +232,89 @@
             parent.children.add(child);
         }
         child.data = data;
-        if (child.freq == 0) child.freq = occur;
         if (word.length() > charAt + 1) {
-            addWordRec(child, word, charAt + 1, occur);
+            addWordRec(child, word, charAt + 1, freq);
         } else {
             child.terminal = true;
-            child.freq = occur;
+            child.freq = freq;
         }
     }
 
-    byte[] dict;
-    int dictSize;
-    static final int CHAR_WIDTH = 8;
-    static final int FLAGS_WIDTH = 1; // Terminal flag (word end)
-    static final int ADDR_WIDTH = 23; // Offset to children
-    static final int FREQ_WIDTH_BYTES = 1;
-    static final int COUNT_WIDTH_BYTES = 1;
+    private byte[] mDict;
+    private int mDictSize;
+    private static final int CHAR_WIDTH = 8;
+    private static final int FLAGS_WIDTH = 1; // Terminal flag (word end)
+    private static final int ADDR_WIDTH = 23; // Offset to children
+    private static final int FREQ_WIDTH_BYTES = 1;
+    private static final int COUNT_WIDTH_BYTES = 1;
 
     private void addCount(int count) {
-        dict[dictSize++] = (byte) (0xFF & count);
+        mDict[mDictSize++] = (byte) (0xFF & count);
     }
 
     private void addNode(CharNode node, String word1) {
-        if (node.terminal) { // store address of each word1
-            mDictionary.put(word1, dictSize);
+        if (node.terminal) { // store address of each word1 for bigram dic generation
+            mDictionary.put(word1, mDictSize);
         }
+
         int charData = 0xFFFF & node.data;
         if (charData > 254) {
-            dict[dictSize++] = (byte) 255;
-            dict[dictSize++] = (byte) ((node.data >> 8) & 0xFF);
-            dict[dictSize++] = (byte) (node.data & 0xFF);
+            mDict[mDictSize++] = (byte) 255;
+            mDict[mDictSize++] = (byte) ((node.data >> 8) & 0xFF);
+            mDict[mDictSize++] = (byte) (node.data & 0xFF);
         } else {
-            dict[dictSize++] = (byte) (0xFF & node.data);
+            mDict[mDictSize++] = (byte) (0xFF & node.data);
         }
         if (node.children != null) {
-            dictSize += 3; // Space for children address
+            mDictSize += 3; // Space for children address
         } else {
-            dictSize += 1; // Space for just the terminal/address flags
+            mDictSize += 1; // Space for just the terminal/address flags
         }
         if ((0xFFFFFF & node.freq) > 255) {
             node.freq = 255;
         }
         if (node.terminal) {
             byte freq = (byte) (0xFF & node.freq);
-            dict[dictSize++] = freq;
+            mDict[mDictSize++] = freq;
             // bigram
-            if (bigramDict.mBi.containsKey(word1)) {
-                int count = bigramDict.mBi.get(word1).count;
-                bigramDict.mBigramToFill.add(word1);
-                bigramDict.mBigramToFillAddress.add(dictSize);
-                dictSize += (4 * count);
+            if (mBigramDict.mBi.containsKey(word1)) {
+                int count = mBigramDict.mBi.get(word1).count;
+                mBigramDict.mBigramToFill.add(word1);
+                mBigramDict.mBigramToFillAddress.add(mDictSize);
+                mDictSize += (4 * count);
             } else {
-                dict[dictSize++] = (byte) (0x00);
+                mDict[mDictSize++] = (byte) (0x00);
             }
         }
     }
 
-    int nullChildrenCount = 0;
-    int notTerminalCount = 0;
+    private int mNullChildrenCount = 0;
+    private int mNotTerminalCount = 0;
 
     private void updateNodeAddress(int nodeAddress, CharNode node,
             int childrenAddress) {
-        if ((dict[nodeAddress] & 0xFF) == 0xFF) { // 3 byte character
+        if ((mDict[nodeAddress] & 0xFF) == 0xFF) { // 3 byte character
             nodeAddress += 2;
         }
         childrenAddress = ADDRESS_MASK & childrenAddress;
         if (childrenAddress == 0) {
-            nullChildrenCount++;
+            mNullChildrenCount++;
         } else {
             childrenAddress |= FLAG_ADDRESS_MASK;
         }
         if (node.terminal) {
             childrenAddress |= FLAG_TERMINAL_MASK;
         } else {
-            notTerminalCount++;
+            mNotTerminalCount++;
         }
-        dict[nodeAddress + 1] = (byte) (childrenAddress >> 16);
+        mDict[nodeAddress + 1] = (byte) (childrenAddress >> 16);
         if ((childrenAddress & FLAG_ADDRESS_MASK) != 0) {
-            dict[nodeAddress + 2] = (byte) ((childrenAddress & 0xFF00) >> 8);
-            dict[nodeAddress + 3] = (byte) ((childrenAddress & 0xFF));
+            mDict[nodeAddress + 2] = (byte) ((childrenAddress & 0xFF00) >> 8);
+            mDict[nodeAddress + 3] = (byte) ((childrenAddress & 0xFF));
         }
     }
 
-    void writeWordsRec(List<CharNode> children, StringBuilder word) {
+    private void writeWordsRec(List<CharNode> children, StringBuilder word) {
         if (children == null || children.size() == 0) {
             return;
         }
@@ -319,60 +322,59 @@
         addCount(childCount);
         int[] childrenAddresses = new int[childCount];
         for (int j = 0; j < childCount; j++) {
-            CharNode node = children.get(j);
-            childrenAddresses[j] = dictSize;
-            word.append(children.get(j).data);
-            addNode(node, word.toString());
-            word.deleteCharAt(word.length()-1);
+            CharNode child = children.get(j);
+            childrenAddresses[j] = mDictSize;
+            word.append(child.data);
+            addNode(child, word.toString());
+            word.setLength(word.length() - 1);
         }
         for (int j = 0; j < childCount; j++) {
-            CharNode node = children.get(j);
+            CharNode child = children.get(j);
             int nodeAddress = childrenAddresses[j];
-            int cacheDictSize = dictSize;
-            word.append(children.get(j).data);
-            writeWordsRec(node.children, word);
-            word.deleteCharAt(word.length()-1);
-            updateNodeAddress(nodeAddress, node, node.children != null
-                    ? cacheDictSize : 0);
+            int cacheDictSize = mDictSize;
+            word.append(child.data);
+            writeWordsRec(child.children, word);
+            word.setLength(word.length() - 1);
+            updateNodeAddress(nodeAddress, child, child.children != null ? cacheDictSize : 0);
         }
     }
 
-    void writeToDict(String dictFilename) {
+    private void writeToDict(String dictFilename) {
         // 4MB max, 22-bit offsets
-        dict = new byte[4 * 1024 * 1024]; // 4MB upper limit. Actual is probably
-                                          // < 1MB in most cases, as there is a limit in the
-                                          // resource size in apks.
-        dictSize = 0;
+        mDict = new byte[4 * 1024 * 1024]; // 4MB upper limit. Actual is probably
+                                           // < 1MB in most cases, as there is a limit in the
+                                           // resource size in apks.
+        mDictSize = 0;
 
-        dict[dictSize++] = (byte) (0xFF & VERSION_NUM); // version info
-        dict[dictSize++] = (byte) (0xFF & (bigramDict.mHasBigram ? 1 : 0));
+        mDict[mDictSize++] = (byte) (0xFF & VERSION_NUM); // version info
+        mDict[mDictSize++] = (byte) (0xFF & (mBigramDict.mHasBigram ? 1 : 0));
 
-        StringBuilder word = new StringBuilder(48);
-        writeWordsRec(roots, word);
-        dict = bigramDict.writeBigrams(dict, mDictionary);
-        System.out.println("Dict Size = " + dictSize);
+        final StringBuilder word = new StringBuilder(INITIAL_STRING_BUILDER_CAPACITY);
+        writeWordsRec(mRoots, word);
+        mDict = mBigramDict.writeBigrams(mDict, mDictionary);
+        System.out.println("Dict Size = " + mDictSize);
         if (!sSplitOutput) {
-            sOutputFileSize = dictSize;
+            sOutputFileSize = mDictSize;
         }
         try {
             int currentLoc = 0;
             int i = 0;
             int extension = dictFilename.indexOf(".dict");
             String filename = dictFilename.substring(0, extension);
-            while (dictSize > 0) {
+            while (mDictSize > 0) {
                 FileOutputStream fos;
                 if (sSplitOutput) {
                     fos = new FileOutputStream(filename + i + ".dict");
                 } else {
                     fos = new FileOutputStream(filename + ".dict");
                 }
-                if (dictSize > sOutputFileSize) {
-                    fos.write(dict, currentLoc, sOutputFileSize);
-                    dictSize -= sOutputFileSize;
+                if (mDictSize > sOutputFileSize) {
+                    fos.write(mDict, currentLoc, sOutputFileSize);
+                    mDictSize -= sOutputFileSize;
                     currentLoc += sOutputFileSize;
                 } else {
-                    fos.write(dict, currentLoc, dictSize);
-                    dictSize = 0;
+                    fos.write(mDict, currentLoc, mDictSize);
+                    mDictSize = 0;
                 }
                 fos.close();
                 i++;
@@ -382,36 +384,36 @@
         }
     }
 
-    void traverseDict(int pos, char[] word, int depth) {
-        int count = dict[pos++] & 0xFF;
+    private void traverseDict(int pos, char[] word, int depth) {
+        int count = mDict[pos++] & 0xFF;
         for (int i = 0; i < count; i++) {
-            char c = (char) (dict[pos++] & 0xFF);
+            char c = (char) (mDict[pos++] & 0xFF);
             if (c == 0xFF) { // two byte character
-                c = (char) (((dict[pos] & 0xFF) << 8) | (dict[pos+1] & 0xFF));
+                c = (char) (((mDict[pos] & 0xFF) << 8) | (mDict[pos+1] & 0xFF));
                 pos += 2;
             }
             word[depth] = c;
-            boolean terminal = getFirstBitOfByte(pos, dict);
+            boolean terminal = getFirstBitOfByte(pos, mDict);
             int address = 0;
-            if ((dict[pos] & (FLAG_ADDRESS_MASK >> 16)) > 0) { // address check
-                address = get22BitAddress(pos, dict);
+            if ((mDict[pos] & (FLAG_ADDRESS_MASK >> 16)) > 0) { // address check
+                address = get22BitAddress(pos, mDict);
                 pos += 3;
             } else {
                 pos += 1;
             }
             if (terminal) {
-                showWord(word, depth + 1, dict[pos] & 0xFF);
+                showWord(word, depth + 1, mDict[pos] & 0xFF);
                 pos++;
 
-                int bigramExist = (dict[pos] & bigramDict.FLAG_BIGRAM_READ);
+                int bigramExist = (mDict[pos] & mBigramDict.FLAG_BIGRAM_READ);
                 if (bigramExist > 0) {
                     int nextBigramExist = 1;
                     while (nextBigramExist > 0) {
-                        int bigramAddress = get22BitAddress(pos, dict);
+                        int bigramAddress = get22BitAddress(pos, mDict);
                         pos += 3;
-                        int frequency = (bigramDict.FLAG_BIGRAM_FREQ & dict[pos]);
-                        bigramDict.searchForTerminalNode(bigramAddress, frequency, dict);
-                        nextBigramExist = (dict[pos++] & bigramDict.FLAG_BIGRAM_CONTINUED);
+                        int frequency = (mBigramDict.FLAG_BIGRAM_FREQ & mDict[pos]);
+                        mBigramDict.searchForTerminalNode(bigramAddress, frequency, mDict);
+                        nextBigramExist = (mDict[pos++] & mBigramDict.FLAG_BIGRAM_CONTINUED);
                     }
                 } else {
                     pos++;
@@ -423,21 +425,21 @@
         }
     }
 
-    void showWord(char[] word, int size, int freq) {
+    private static void showWord(char[] word, int size, int freq) {
         System.out.print(new String(word, 0, size) + " " + freq + "\n");
     }
 
-    static int get22BitAddress(int pos, byte[] dict) {
+    /* package */ static int get22BitAddress(int pos, byte[] dict) {
         return ((dict[pos + 0] & 0x3F) << 16)
                 | ((dict[pos + 1] & 0xFF) << 8)
                 | ((dict[pos + 2] & 0xFF));
     }
 
-    static boolean getFirstBitOfByte(int pos, byte[] dict) {
+    /* package */ static boolean getFirstBitOfByte(int pos, byte[] dict) {
         return (dict[pos] & 0x80) > 0;
     }
 
-    static boolean getSecondBitOfByte(int pos, byte[] dict) {
+    /* package */ static boolean getSecondBitOfByte(int pos, byte[] dict) {
         return (dict[pos] & 0x40) > 0;
     }
 }