DO NOT MERGE. Backport LatinIME from master to Gingerbread

TODO: Cleanup will follow.

Change-Id: I4a68ba9f2f55760aa24187f1f13fdfa8a0b70963
diff --git a/java/AndroidManifest.xml b/java/AndroidManifest.xml
index e229bc7..642c717 100755
--- a/java/AndroidManifest.xml
+++ b/java/AndroidManifest.xml
@@ -1,8 +1,6 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
         package="com.android.inputmethod.latin">
 
-    <original-package android:name="com.android.inputmethod.latin" />
-
     <uses-permission android:name="android.permission.VIBRATE"/>
     <uses-permission android:name="android.permission.READ_USER_DICTIONARY" />
     <uses-permission android:name="android.permission.WRITE_USER_DICTIONARY" />
diff --git a/java/proguard.flags b/java/proguard.flags
index 0a5d2dd..829a096 100644
--- a/java/proguard.flags
+++ b/java/proguard.flags
@@ -1,3 +1,8 @@
 -keep class com.android.inputmethod.latin.BinaryDictionary {
   int mDictLength;
+  <init>(...); 
+}
+
+-keep class com.android.inputmethod.latin.Suggest {
+  <init>(...);
 }
diff --git a/java/res/drawable-en-hdpi/sym_keyboard_feedback_delete.png b/java/res/drawable-en-hdpi/sym_keyboard_feedback_delete.png
deleted file mode 100755
index ca76375..0000000
--- a/java/res/drawable-en-hdpi/sym_keyboard_feedback_delete.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-hdpi/btn_close_normal.png b/java/res/drawable-hdpi/btn_close_normal.png
new file mode 100644
index 0000000..38b49f1
--- /dev/null
+++ b/java/res/drawable-hdpi/btn_close_normal.png
Binary files differ
diff --git a/java/res/drawable-hdpi/btn_close_pressed.png b/java/res/drawable-hdpi/btn_close_pressed.png
new file mode 100644
index 0000000..aa9ea49
--- /dev/null
+++ b/java/res/drawable-hdpi/btn_close_pressed.png
Binary files differ
diff --git a/java/res/drawable-hdpi/btn_close_selected.png b/java/res/drawable-hdpi/btn_close_selected.png
new file mode 100644
index 0000000..870c670
--- /dev/null
+++ b/java/res/drawable-hdpi/btn_close_selected.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
new file mode 100644
index 0000000..b67732c
--- /dev/null
+++ 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_stone.9.png b/java/res/drawable-hdpi/btn_keyboard_key_normal_on_stone.9.png
new file mode 100644
index 0000000..534f1cd
--- /dev/null
+++ 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
new file mode 100644
index 0000000..fba10b8
--- /dev/null
+++ b/java/res/drawable-hdpi/btn_keyboard_key_normal_stone.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/btn_keyboard_normal_metal.9.png b/java/res/drawable-hdpi/btn_keyboard_normal_metal.9.png
new file mode 100644
index 0000000..b29d6d1
--- /dev/null
+++ b/java/res/drawable-hdpi/btn_keyboard_normal_metal.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/btn_keyboard_toggle_off.png b/java/res/drawable-hdpi/btn_keyboard_toggle_off.png
new file mode 100644
index 0000000..bfe7840
--- /dev/null
+++ b/java/res/drawable-hdpi/btn_keyboard_toggle_off.png
Binary files differ
diff --git a/java/res/drawable-hdpi/btn_keyboard_toggle_on.png b/java/res/drawable-hdpi/btn_keyboard_toggle_on.png
new file mode 100644
index 0000000..0a1221e
--- /dev/null
+++ b/java/res/drawable-hdpi/btn_keyboard_toggle_on.png
Binary files differ
diff --git a/java/res/drawable-hdpi/btn_led_off.9.png b/java/res/drawable-hdpi/btn_led_off.9.png
new file mode 100644
index 0000000..a60f965
--- /dev/null
+++ b/java/res/drawable-hdpi/btn_led_off.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/btn_led_on.9.png b/java/res/drawable-hdpi/btn_led_on.9.png
new file mode 100644
index 0000000..c902609
--- /dev/null
+++ b/java/res/drawable-hdpi/btn_led_on.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/dialog_top_dark_bottom_medium.9.png b/java/res/drawable-hdpi/dialog_top_dark_bottom_medium.9.png
new file mode 100644
index 0000000..ab6c036
--- /dev/null
+++ b/java/res/drawable-hdpi/dialog_top_dark_bottom_medium.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/dialog_top_dark_bottom_medium.png b/java/res/drawable-hdpi/dialog_top_dark_bottom_medium.png
deleted file mode 100755
index 7c79a4f..0000000
--- a/java/res/drawable-hdpi/dialog_top_dark_bottom_medium.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-hdpi/ic_subtype_keyboard.png b/java/res/drawable-hdpi/ic_subtype_keyboard.png
new file mode 100755
index 0000000..7015e26
--- /dev/null
+++ 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
new file mode 100644
index 0000000..cb86a55
--- /dev/null
+++ b/java/res/drawable-hdpi/ic_subtype_mic.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
new file mode 100644
index 0000000..6ba42db
--- /dev/null
+++ b/java/res/drawable-hdpi/keyboard_key_feedback_background.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
new file mode 100644
index 0000000..4d0b601
--- /dev/null
+++ 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
new file mode 100644
index 0000000..8e2461b
--- /dev/null
+++ b/java/res/drawable-hdpi/keyboard_popup_panel_background.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/keyboard_popup_panel_trans_background.9.png b/java/res/drawable-hdpi/keyboard_popup_panel_trans_background.9.png
new file mode 100644
index 0000000..fd7366e
--- /dev/null
+++ b/java/res/drawable-hdpi/keyboard_popup_panel_trans_background.9.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
new file mode 100644
index 0000000..3e4eff6
--- /dev/null
+++ 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
new file mode 100644
index 0000000..1d24cc8
--- /dev/null
+++ 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
new file mode 100644
index 0000000..b77803d
--- /dev/null
+++ b/java/res/drawable-hdpi/sym_bkeyboard_done.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_bkeyboard_globe.png b/java/res/drawable-hdpi/sym_bkeyboard_globe.png
new file mode 100644
index 0000000..f5dbe0c
--- /dev/null
+++ b/java/res/drawable-hdpi/sym_bkeyboard_globe.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_bkeyboard_mic.png b/java/res/drawable-hdpi/sym_bkeyboard_mic.png
new file mode 100644
index 0000000..512f460
--- /dev/null
+++ 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
new file mode 100644
index 0000000..678a790
--- /dev/null
+++ 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
new file mode 100644
index 0000000..4e68e35
--- /dev/null
+++ 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
new file mode 100644
index 0000000..546663f
--- /dev/null
+++ 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
new file mode 100644
index 0000000..57f9a8d
--- /dev/null
+++ 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
new file mode 100644
index 0000000..de50438
--- /dev/null
+++ 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
new file mode 100644
index 0000000..1d2e1ef
--- /dev/null
+++ 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
new file mode 100644
index 0000000..39788b7
--- /dev/null
+++ 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
new file mode 100644
index 0000000..fff6f27
--- /dev/null
+++ 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
new file mode 100644
index 0000000..8cc1a95
--- /dev/null
+++ 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
new file mode 100644
index 0000000..0217425
--- /dev/null
+++ 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
new file mode 100644
index 0000000..200714f
--- /dev/null
+++ 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
new file mode 100644
index 0000000..0a46122
--- /dev/null
+++ 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
new file mode 100644
index 0000000..ca22bd5
--- /dev/null
+++ 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
new file mode 100644
index 0000000..426e159
--- /dev/null
+++ 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
new file mode 100644
index 0000000..1b6f884
--- /dev/null
+++ b/java/res/drawable-hdpi/sym_bkeyboard_search.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_bkeyboard_shift.png b/java/res/drawable-hdpi/sym_bkeyboard_shift.png
new file mode 100644
index 0000000..5a22dd3
--- /dev/null
+++ 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
new file mode 100644
index 0000000..5664491
--- /dev/null
+++ 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
new file mode 100644
index 0000000..cd0ebe2
--- /dev/null
+++ 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
new file mode 100644
index 0000000..3466e12
--- /dev/null
+++ b/java/res/drawable-hdpi/sym_bkeyboard_tab.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_bkeyboard_tabprev.png b/java/res/drawable-hdpi/sym_bkeyboard_tabprev.png
new file mode 100644
index 0000000..eb4f0eb
--- /dev/null
+++ b/java/res/drawable-hdpi/sym_bkeyboard_tabprev.png
Binary files differ
diff --git a/java/res/drawable-hdpi/voice_swipe_hint.png b/java/res/drawable-hdpi/voice_swipe_hint.png
new file mode 100644
index 0000000..130f83a
--- /dev/null
+++ b/java/res/drawable-hdpi/voice_swipe_hint.png
Binary files differ
diff --git a/java/res/drawable-land-hdpi/btn_keyboard_key_normal_off_stone.9.png b/java/res/drawable-land-hdpi/btn_keyboard_key_normal_off_stone.9.png
new file mode 100644
index 0000000..67a204f
--- /dev/null
+++ b/java/res/drawable-land-hdpi/btn_keyboard_key_normal_off_stone.9.png
Binary files differ
diff --git a/java/res/drawable-land-hdpi/btn_keyboard_key_normal_on_stone.9.png b/java/res/drawable-land-hdpi/btn_keyboard_key_normal_on_stone.9.png
new file mode 100644
index 0000000..63cbe60
--- /dev/null
+++ b/java/res/drawable-land-hdpi/btn_keyboard_key_normal_on_stone.9.png
Binary files differ
diff --git a/java/res/drawable-land-hdpi/btn_keyboard_key_normal_stone.9.png b/java/res/drawable-land-hdpi/btn_keyboard_key_normal_stone.9.png
new file mode 100644
index 0000000..0dd33b4
--- /dev/null
+++ b/java/res/drawable-land-hdpi/btn_keyboard_key_normal_stone.9.png
Binary files differ
diff --git a/java/res/drawable-land-mdpi/btn_keyboard_key_normal_off.9.png b/java/res/drawable-land-mdpi/btn_keyboard_key_normal_off.9.png
deleted file mode 100644
index bda9b83..0000000
--- a/java/res/drawable-land-mdpi/btn_keyboard_key_normal_off.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-land-mdpi/btn_keyboard_key_normal_off_stone.9.png b/java/res/drawable-land-mdpi/btn_keyboard_key_normal_off_stone.9.png
new file mode 100644
index 0000000..67a204f
--- /dev/null
+++ b/java/res/drawable-land-mdpi/btn_keyboard_key_normal_off_stone.9.png
Binary files differ
diff --git a/java/res/drawable-land-mdpi/btn_keyboard_key_normal_on.9.png b/java/res/drawable-land-mdpi/btn_keyboard_key_normal_on.9.png
deleted file mode 100644
index 0c16ed5..0000000
--- a/java/res/drawable-land-mdpi/btn_keyboard_key_normal_on.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-land-mdpi/btn_keyboard_key_normal_on_stone.9.png b/java/res/drawable-land-mdpi/btn_keyboard_key_normal_on_stone.9.png
new file mode 100644
index 0000000..63cbe60
--- /dev/null
+++ b/java/res/drawable-land-mdpi/btn_keyboard_key_normal_on_stone.9.png
Binary files differ
diff --git a/java/res/drawable-land-mdpi/btn_keyboard_key_normal_stone.9.png b/java/res/drawable-land-mdpi/btn_keyboard_key_normal_stone.9.png
new file mode 100644
index 0000000..0dd33b4
--- /dev/null
+++ b/java/res/drawable-land-mdpi/btn_keyboard_key_normal_stone.9.png
Binary files differ
diff --git a/java/res/drawable-land-mdpi/btn_keyboard_key_pressed_off.9.png b/java/res/drawable-land-mdpi/btn_keyboard_key_pressed_off.9.png
deleted file mode 100644
index bdcf06e..0000000
--- a/java/res/drawable-land-mdpi/btn_keyboard_key_pressed_off.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-land-mdpi/btn_keyboard_key_pressed_on.9.png b/java/res/drawable-land-mdpi/btn_keyboard_key_pressed_on.9.png
deleted file mode 100644
index 79621a9..0000000
--- a/java/res/drawable-land-mdpi/btn_keyboard_key_pressed_on.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-mdpi/btn_close_normal.png b/java/res/drawable-mdpi/btn_close_normal.png
new file mode 100644
index 0000000..4c6e79d
--- /dev/null
+++ b/java/res/drawable-mdpi/btn_close_normal.png
Binary files differ
diff --git a/java/res/drawable-mdpi/btn_close_pressed.png b/java/res/drawable-mdpi/btn_close_pressed.png
new file mode 100644
index 0000000..fc983af
--- /dev/null
+++ b/java/res/drawable-mdpi/btn_close_pressed.png
Binary files differ
diff --git a/java/res/drawable-mdpi/btn_close_selected.png b/java/res/drawable-mdpi/btn_close_selected.png
new file mode 100644
index 0000000..f2bf91a
--- /dev/null
+++ b/java/res/drawable-mdpi/btn_close_selected.png
Binary files differ
diff --git a/java/res/drawable-mdpi/btn_keyboard_key_normal_off_stone.9.png b/java/res/drawable-mdpi/btn_keyboard_key_normal_off_stone.9.png
new file mode 100644
index 0000000..b67732c
--- /dev/null
+++ b/java/res/drawable-mdpi/btn_keyboard_key_normal_off_stone.9.png
Binary files differ
diff --git a/java/res/drawable-mdpi/btn_keyboard_key_normal_on_stone.9.png b/java/res/drawable-mdpi/btn_keyboard_key_normal_on_stone.9.png
new file mode 100644
index 0000000..534f1cd
--- /dev/null
+++ b/java/res/drawable-mdpi/btn_keyboard_key_normal_on_stone.9.png
Binary files differ
diff --git a/java/res/drawable-mdpi/btn_keyboard_key_normal_stone.9.png b/java/res/drawable-mdpi/btn_keyboard_key_normal_stone.9.png
new file mode 100644
index 0000000..fba10b8
--- /dev/null
+++ b/java/res/drawable-mdpi/btn_keyboard_key_normal_stone.9.png
Binary files differ
diff --git a/java/res/drawable-mdpi/btn_keyboard_normal_metal.9.png b/java/res/drawable-mdpi/btn_keyboard_normal_metal.9.png
new file mode 100644
index 0000000..f4fe0a8
--- /dev/null
+++ b/java/res/drawable-mdpi/btn_keyboard_normal_metal.9.png
Binary files differ
diff --git a/java/res/drawable-mdpi/btn_keyboard_toggle_off.png b/java/res/drawable-mdpi/btn_keyboard_toggle_off.png
new file mode 100644
index 0000000..21399a4
--- /dev/null
+++ b/java/res/drawable-mdpi/btn_keyboard_toggle_off.png
Binary files differ
diff --git a/java/res/drawable-mdpi/btn_keyboard_toggle_on.png b/java/res/drawable-mdpi/btn_keyboard_toggle_on.png
new file mode 100644
index 0000000..22d5683
--- /dev/null
+++ b/java/res/drawable-mdpi/btn_keyboard_toggle_on.png
Binary files differ
diff --git a/java/res/drawable-mdpi/btn_led_off.9.png b/java/res/drawable-mdpi/btn_led_off.9.png
new file mode 100644
index 0000000..68ce7a6
--- /dev/null
+++ b/java/res/drawable-mdpi/btn_led_off.9.png
Binary files differ
diff --git a/java/res/drawable-mdpi/btn_led_on.9.png b/java/res/drawable-mdpi/btn_led_on.9.png
new file mode 100644
index 0000000..fe77abb
--- /dev/null
+++ b/java/res/drawable-mdpi/btn_led_on.9.png
Binary files differ
diff --git a/java/res/drawable/cancel.png b/java/res/drawable-mdpi/cancel.png
similarity index 100%
rename from java/res/drawable/cancel.png
rename to java/res/drawable-mdpi/cancel.png
Binary files differ
diff --git a/java/res/drawable/caution.png b/java/res/drawable-mdpi/caution.png
similarity index 100%
rename from java/res/drawable/caution.png
rename to java/res/drawable-mdpi/caution.png
Binary files differ
diff --git a/java/res/drawable/dialog_top_dark_bottom_medium.9.png b/java/res/drawable-mdpi/dialog_top_dark_bottom_medium.9.png
similarity index 100%
rename from java/res/drawable/dialog_top_dark_bottom_medium.9.png
rename to java/res/drawable-mdpi/dialog_top_dark_bottom_medium.9.png
Binary files differ
diff --git a/java/res/drawable/ic_dialog_alert_large.png b/java/res/drawable-mdpi/ic_dialog_alert_large.png
similarity index 100%
rename from java/res/drawable/ic_dialog_alert_large.png
rename to java/res/drawable-mdpi/ic_dialog_alert_large.png
Binary files differ
diff --git a/java/res/drawable/ic_dialog_voice_input.png b/java/res/drawable-mdpi/ic_dialog_voice_input.png
similarity index 100%
rename from java/res/drawable/ic_dialog_voice_input.png
rename to java/res/drawable-mdpi/ic_dialog_voice_input.png
Binary files differ
diff --git a/java/res/drawable/ic_dialog_wave_0_0.png b/java/res/drawable-mdpi/ic_dialog_wave_0_0.png
similarity index 100%
rename from java/res/drawable/ic_dialog_wave_0_0.png
rename to java/res/drawable-mdpi/ic_dialog_wave_0_0.png
Binary files differ
diff --git a/java/res/drawable/ic_dialog_wave_1_3.png b/java/res/drawable-mdpi/ic_dialog_wave_1_3.png
similarity index 100%
rename from java/res/drawable/ic_dialog_wave_1_3.png
rename to java/res/drawable-mdpi/ic_dialog_wave_1_3.png
Binary files differ
diff --git a/java/res/drawable/ic_dialog_wave_2_3.png b/java/res/drawable-mdpi/ic_dialog_wave_2_3.png
similarity index 100%
rename from java/res/drawable/ic_dialog_wave_2_3.png
rename to java/res/drawable-mdpi/ic_dialog_wave_2_3.png
Binary files differ
diff --git a/java/res/drawable/ic_dialog_wave_3_3.png b/java/res/drawable-mdpi/ic_dialog_wave_3_3.png
similarity index 100%
rename from java/res/drawable/ic_dialog_wave_3_3.png
rename to java/res/drawable-mdpi/ic_dialog_wave_3_3.png
Binary files differ
diff --git a/java/res/drawable/ic_dialog_wave_4_3.png b/java/res/drawable-mdpi/ic_dialog_wave_4_3.png
similarity index 100%
rename from java/res/drawable/ic_dialog_wave_4_3.png
rename to java/res/drawable-mdpi/ic_dialog_wave_4_3.png
Binary files differ
diff --git a/java/res/drawable-mdpi/ic_subtype_keyboard.png b/java/res/drawable-mdpi/ic_subtype_keyboard.png
new file mode 100755
index 0000000..0d7ebd4
--- /dev/null
+++ b/java/res/drawable-mdpi/ic_subtype_keyboard.png
Binary files differ
diff --git a/java/res/drawable-mdpi/ic_subtype_mic.png b/java/res/drawable-mdpi/ic_subtype_mic.png
new file mode 100644
index 0000000..247d5b3
--- /dev/null
+++ b/java/res/drawable-mdpi/ic_subtype_mic.png
Binary files differ
diff --git a/java/res/drawable-mdpi/keyboard_key_feedback_background.9.png b/java/res/drawable-mdpi/keyboard_key_feedback_background.9.png
new file mode 100644
index 0000000..2a80f09
--- /dev/null
+++ b/java/res/drawable-mdpi/keyboard_key_feedback_background.9.png
Binary files differ
diff --git a/java/res/drawable-mdpi/keyboard_key_feedback_more_background.9.png b/java/res/drawable-mdpi/keyboard_key_feedback_more_background.9.png
new file mode 100755
index 0000000..29aa285
--- /dev/null
+++ b/java/res/drawable-mdpi/keyboard_key_feedback_more_background.9.png
Binary files differ
diff --git a/java/res/drawable-mdpi/keyboard_popup_panel_background.9.png b/java/res/drawable-mdpi/keyboard_popup_panel_background.9.png
new file mode 100644
index 0000000..36d75df
--- /dev/null
+++ b/java/res/drawable-mdpi/keyboard_popup_panel_background.9.png
Binary files differ
diff --git a/java/res/drawable-mdpi/keyboard_popup_panel_trans_background.9.png b/java/res/drawable-mdpi/keyboard_popup_panel_trans_background.9.png
new file mode 100644
index 0000000..4ba2a49
--- /dev/null
+++ b/java/res/drawable-mdpi/keyboard_popup_panel_trans_background.9.png
Binary files differ
diff --git a/java/res/drawable/mic_slash.png b/java/res/drawable-mdpi/mic_slash.png
similarity index 100%
rename from java/res/drawable/mic_slash.png
rename to java/res/drawable-mdpi/mic_slash.png
Binary files differ
diff --git a/java/res/drawable/ok_cancel.png b/java/res/drawable-mdpi/ok_cancel.png
similarity index 100%
rename from java/res/drawable/ok_cancel.png
rename to java/res/drawable-mdpi/ok_cancel.png
Binary files differ
diff --git a/java/res/drawable/speak_now_level0.png b/java/res/drawable-mdpi/speak_now_level0.png
similarity index 100%
rename from java/res/drawable/speak_now_level0.png
rename to java/res/drawable-mdpi/speak_now_level0.png
Binary files differ
diff --git a/java/res/drawable/speak_now_level1.png b/java/res/drawable-mdpi/speak_now_level1.png
similarity index 100%
rename from java/res/drawable/speak_now_level1.png
rename to java/res/drawable-mdpi/speak_now_level1.png
Binary files differ
diff --git a/java/res/drawable/speak_now_level2.png b/java/res/drawable-mdpi/speak_now_level2.png
similarity index 100%
rename from java/res/drawable/speak_now_level2.png
rename to java/res/drawable-mdpi/speak_now_level2.png
Binary files differ
diff --git a/java/res/drawable/speak_now_level3.png b/java/res/drawable-mdpi/speak_now_level3.png
similarity index 100%
rename from java/res/drawable/speak_now_level3.png
rename to java/res/drawable-mdpi/speak_now_level3.png
Binary files differ
diff --git a/java/res/drawable/speak_now_level4.png b/java/res/drawable-mdpi/speak_now_level4.png
similarity index 100%
rename from java/res/drawable/speak_now_level4.png
rename to java/res/drawable-mdpi/speak_now_level4.png
Binary files differ
diff --git a/java/res/drawable/speak_now_level5.png b/java/res/drawable-mdpi/speak_now_level5.png
similarity index 100%
rename from java/res/drawable/speak_now_level5.png
rename to java/res/drawable-mdpi/speak_now_level5.png
Binary files differ
diff --git a/java/res/drawable/speak_now_level6.png b/java/res/drawable-mdpi/speak_now_level6.png
similarity index 100%
rename from java/res/drawable/speak_now_level6.png
rename to java/res/drawable-mdpi/speak_now_level6.png
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_bkeyboard_123_mic.png b/java/res/drawable-mdpi/sym_bkeyboard_123_mic.png
new file mode 100644
index 0000000..0749b5f
--- /dev/null
+++ b/java/res/drawable-mdpi/sym_bkeyboard_123_mic.png
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_bkeyboard_delete.png b/java/res/drawable-mdpi/sym_bkeyboard_delete.png
new file mode 100644
index 0000000..1a5ff43
--- /dev/null
+++ b/java/res/drawable-mdpi/sym_bkeyboard_delete.png
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_bkeyboard_done.png b/java/res/drawable-mdpi/sym_bkeyboard_done.png
new file mode 100644
index 0000000..05ce7c6
--- /dev/null
+++ b/java/res/drawable-mdpi/sym_bkeyboard_done.png
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_bkeyboard_globe.png b/java/res/drawable-mdpi/sym_bkeyboard_globe.png
new file mode 100644
index 0000000..c6595cf
--- /dev/null
+++ b/java/res/drawable-mdpi/sym_bkeyboard_globe.png
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_bkeyboard_mic.png b/java/res/drawable-mdpi/sym_bkeyboard_mic.png
new file mode 100644
index 0000000..a6cb1cc
--- /dev/null
+++ b/java/res/drawable-mdpi/sym_bkeyboard_mic.png
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_bkeyboard_num0.png b/java/res/drawable-mdpi/sym_bkeyboard_num0.png
new file mode 100644
index 0000000..7188f9c
--- /dev/null
+++ b/java/res/drawable-mdpi/sym_bkeyboard_num0.png
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_bkeyboard_num1.png b/java/res/drawable-mdpi/sym_bkeyboard_num1.png
new file mode 100644
index 0000000..2a31bd4
--- /dev/null
+++ b/java/res/drawable-mdpi/sym_bkeyboard_num1.png
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_bkeyboard_num2.png b/java/res/drawable-mdpi/sym_bkeyboard_num2.png
new file mode 100644
index 0000000..c1e9cc9
--- /dev/null
+++ b/java/res/drawable-mdpi/sym_bkeyboard_num2.png
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_bkeyboard_num3.png b/java/res/drawable-mdpi/sym_bkeyboard_num3.png
new file mode 100644
index 0000000..e998766
--- /dev/null
+++ b/java/res/drawable-mdpi/sym_bkeyboard_num3.png
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_bkeyboard_num4.png b/java/res/drawable-mdpi/sym_bkeyboard_num4.png
new file mode 100644
index 0000000..7f0f3cc
--- /dev/null
+++ b/java/res/drawable-mdpi/sym_bkeyboard_num4.png
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_bkeyboard_num5.png b/java/res/drawable-mdpi/sym_bkeyboard_num5.png
new file mode 100644
index 0000000..5f748b4
--- /dev/null
+++ b/java/res/drawable-mdpi/sym_bkeyboard_num5.png
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_bkeyboard_num6.png b/java/res/drawable-mdpi/sym_bkeyboard_num6.png
new file mode 100644
index 0000000..78aae74
--- /dev/null
+++ b/java/res/drawable-mdpi/sym_bkeyboard_num6.png
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_bkeyboard_num7.png b/java/res/drawable-mdpi/sym_bkeyboard_num7.png
new file mode 100644
index 0000000..5bb874c
--- /dev/null
+++ b/java/res/drawable-mdpi/sym_bkeyboard_num7.png
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_bkeyboard_num8.png b/java/res/drawable-mdpi/sym_bkeyboard_num8.png
new file mode 100644
index 0000000..6b58fdc
--- /dev/null
+++ b/java/res/drawable-mdpi/sym_bkeyboard_num8.png
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_bkeyboard_num9.png b/java/res/drawable-mdpi/sym_bkeyboard_num9.png
new file mode 100644
index 0000000..f348c92
--- /dev/null
+++ b/java/res/drawable-mdpi/sym_bkeyboard_num9.png
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_bkeyboard_numalt.png b/java/res/drawable-mdpi/sym_bkeyboard_numalt.png
new file mode 100644
index 0000000..4fa410b
--- /dev/null
+++ b/java/res/drawable-mdpi/sym_bkeyboard_numalt.png
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_bkeyboard_numpound.png b/java/res/drawable-mdpi/sym_bkeyboard_numpound.png
new file mode 100644
index 0000000..9126eed
--- /dev/null
+++ b/java/res/drawable-mdpi/sym_bkeyboard_numpound.png
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_bkeyboard_numstar.png b/java/res/drawable-mdpi/sym_bkeyboard_numstar.png
new file mode 100644
index 0000000..9b9f1b9
--- /dev/null
+++ b/java/res/drawable-mdpi/sym_bkeyboard_numstar.png
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_bkeyboard_return.png b/java/res/drawable-mdpi/sym_bkeyboard_return.png
new file mode 100644
index 0000000..e76225d
--- /dev/null
+++ b/java/res/drawable-mdpi/sym_bkeyboard_return.png
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_bkeyboard_search.png b/java/res/drawable-mdpi/sym_bkeyboard_search.png
new file mode 100644
index 0000000..1f18015
--- /dev/null
+++ b/java/res/drawable-mdpi/sym_bkeyboard_search.png
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_bkeyboard_shift.png b/java/res/drawable-mdpi/sym_bkeyboard_shift.png
new file mode 100644
index 0000000..c981188
--- /dev/null
+++ b/java/res/drawable-mdpi/sym_bkeyboard_shift.png
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_bkeyboard_shift_locked.png b/java/res/drawable-mdpi/sym_bkeyboard_shift_locked.png
new file mode 100644
index 0000000..b8cebd0
--- /dev/null
+++ b/java/res/drawable-mdpi/sym_bkeyboard_shift_locked.png
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_bkeyboard_space.png b/java/res/drawable-mdpi/sym_bkeyboard_space.png
new file mode 100644
index 0000000..4da7ee8
--- /dev/null
+++ b/java/res/drawable-mdpi/sym_bkeyboard_space.png
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_bkeyboard_tab.png b/java/res/drawable-mdpi/sym_bkeyboard_tab.png
new file mode 100644
index 0000000..2cb991c
--- /dev/null
+++ b/java/res/drawable-mdpi/sym_bkeyboard_tab.png
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_bkeyboard_tabprev.png b/java/res/drawable-mdpi/sym_bkeyboard_tabprev.png
new file mode 100644
index 0000000..5298291
--- /dev/null
+++ b/java/res/drawable-mdpi/sym_bkeyboard_tabprev.png
Binary files differ
diff --git a/java/res/drawable/voice_ime_background.9.png b/java/res/drawable-mdpi/voice_ime_background.9.png
similarity index 100%
rename from java/res/drawable/voice_ime_background.9.png
rename to java/res/drawable-mdpi/voice_ime_background.9.png
Binary files differ
diff --git a/java/res/drawable/voice_swipe_hint.png b/java/res/drawable-mdpi/voice_swipe_hint.png
similarity index 100%
rename from java/res/drawable/voice_swipe_hint.png
rename to java/res/drawable-mdpi/voice_swipe_hint.png
Binary files differ
diff --git a/java/res/drawable/working.png b/java/res/drawable-mdpi/working.png
similarity index 100%
rename from java/res/drawable/working.png
rename to java/res/drawable-mdpi/working.png
Binary files differ
diff --git a/java/res/drawable/btn_close.xml b/java/res/drawable/btn_close.xml
new file mode 100644
index 0000000..ee58138
--- /dev/null
+++ b/java/res/drawable/btn_close.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <item android:state_pressed="false" android:state_focused="false"
+        android:drawable="@drawable/btn_close_normal" />
+
+    <item android:state_pressed="true"
+        android:drawable="@drawable/btn_close_pressed" />
+
+    <item android:state_focused="true"
+        android:drawable="@drawable/btn_close_selected" />
+</selector>
diff --git a/java/res/drawable/btn_keyboard_key2.xml b/java/res/drawable/btn_keyboard_key2.xml
new file mode 100644
index 0000000..bd745b7
--- /dev/null
+++ b/java/res/drawable/btn_keyboard_key2.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <!-- Toggle keys. Use checkable/checked state. -->
+
+    <item android:state_checkable="true" android:state_checked="true"
+          android:state_pressed="true"
+          android:drawable="@drawable/btn_keyboard_key_pressed_on" />
+    <item android:state_checkable="true" android:state_pressed="true"
+          android:drawable="@drawable/btn_keyboard_key_fulltrans_pressed" />
+    <item android:state_checkable="true" android:state_checked="true"
+          android:drawable="@drawable/btn_keyboard_key_normal_on" />
+    <item android:state_checkable="true"
+          android:drawable="@drawable/btn_keyboard_key_fulltrans_normal" />
+
+    <!-- Normal keys -->
+
+    <item android:state_pressed="true"
+          android:drawable="@drawable/btn_keyboard_key_fulltrans_pressed" />
+    <item android:drawable="@drawable/btn_keyboard_key_fulltrans_normal" />
+</selector>
diff --git a/java/res/drawable/btn_keyboard_key3.xml b/java/res/drawable/btn_keyboard_key3.xml
new file mode 100644
index 0000000..dbe82d5
--- /dev/null
+++ b/java/res/drawable/btn_keyboard_key3.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <!-- Toggle keys. Use checkable/checked state. -->
+
+    <item android:state_checkable="true" android:state_checked="true"
+          android:state_pressed="true"
+          android:drawable="@drawable/btn_keyboard_key_pressed_on" />
+    <item android:state_checkable="true" android:state_pressed="true"
+          android:drawable="@drawable/btn_keyboard_key_fulltrans_normal" />
+    <item android:state_checkable="true" android:state_checked="true"
+          android:drawable="@drawable/btn_keyboard_key_normal_on" />
+    <item android:state_checkable="true"
+          android:drawable="@drawable/btn_keyboard_key_fulltrans_pressed" />
+
+    <!-- Normal keys -->
+
+    <item android:state_pressed="true"
+          android:drawable="@drawable/btn_keyboard_key_fulltrans_normal" />
+    <item android:drawable="@drawable/btn_keyboard_key_fulltrans_pressed" />
+</selector>
diff --git a/java/res/drawable/btn_keyboard_key_stone.xml b/java/res/drawable/btn_keyboard_key_stone.xml
new file mode 100644
index 0000000..a6040a0
--- /dev/null
+++ b/java/res/drawable/btn_keyboard_key_stone.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <!-- Toggle keys. Use checkable/checked state. -->
+
+    <item android:state_checkable="true" android:state_checked="true"
+          android:state_pressed="true"
+          android:drawable="@drawable/btn_keyboard_key_normal_on_stone" />
+    <item android:state_checkable="true" android:state_pressed="true"
+          android:drawable="@drawable/btn_keyboard_key_normal_off_stone" />
+    <item android:state_checkable="true" android:state_checked="true"
+          android:drawable="@drawable/btn_keyboard_key_normal_on_stone" />
+    <item android:state_checkable="true"
+          android:drawable="@drawable/btn_keyboard_key_normal_off_stone" />
+
+    <!-- Normal keys -->
+
+    <item android:state_pressed="true"
+          android:drawable="@drawable/btn_keyboard_key_fulltrans_pressed" />
+    <item android:drawable="@drawable/btn_keyboard_key_normal_stone" />
+</selector>
diff --git a/java/res/drawable/keyboard_key_feedback.xml b/java/res/drawable/keyboard_key_feedback.xml
new file mode 100644
index 0000000..159ba86
--- /dev/null
+++ b/java/res/drawable/keyboard_key_feedback.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_long_pressable="true"
+            android:drawable="@drawable/keyboard_key_feedback_more_background" />
+
+    <item android:drawable="@drawable/keyboard_key_feedback_background" />
+</selector>
diff --git a/java/res/layout/input.xml b/java/res/layout/input_basic.xml
similarity index 83%
rename from java/res/layout/input.xml
rename to java/res/layout/input_basic.xml
index 1d7c6f7..168eba6 100755
--- a/java/res/layout/input.xml
+++ b/java/res/layout/input_basic.xml
@@ -20,10 +20,12 @@
 
 <com.android.inputmethod.latin.LatinKeyboardView
         xmlns:android="http://schemas.android.com/apk/res/android"
-        android:id="@android:id/keyboardView"
+        xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+        android:id="@+id/LatinkeyboardBaseView"
         android:layout_alignParentBottom="true"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:background="@drawable/keyboard_background"
-        android:keyBackground="@drawable/btn_keyboard_key"
+
+        latin:keyBackground="@drawable/btn_keyboard_key"
         />
diff --git a/java/res/layout/input.xml b/java/res/layout/input_basic_highcontrast.xml
similarity index 63%
copy from java/res/layout/input.xml
copy to java/res/layout/input_basic_highcontrast.xml
index 1d7c6f7..19ff1db 100755
--- a/java/res/layout/input.xml
+++ b/java/res/layout/input_basic_highcontrast.xml
@@ -1,29 +1,32 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-/* 
+/*
 **
-** Copyright 2008, The Android Open Source Project
+** 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.
 */
 -->
 
 <com.android.inputmethod.latin.LatinKeyboardView
         xmlns:android="http://schemas.android.com/apk/res/android"
-        android:id="@android:id/keyboardView"
+        xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+
+        android:id="@+id/LatinkeyboardBaseView"
         android:layout_alignParentBottom="true"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:background="@drawable/keyboard_background"
-        android:keyBackground="@drawable/btn_keyboard_key"
+        android:background="@android:color/black"
+
+        latin:keyBackground="@drawable/btn_keyboard_key3"
         />
diff --git a/java/res/layout/input_stone_bold.xml b/java/res/layout/input_stone_bold.xml
new file mode 100755
index 0000000..e3588bb
--- /dev/null
+++ b/java/res/layout/input_stone_bold.xml
@@ -0,0 +1,37 @@
+<?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.
+*/
+-->
+
+<com.android.inputmethod.latin.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:layout_alignParentBottom="true"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:background="@drawable/keyboard_background"
+        android:textStyle="bold"
+
+        latin:keyBackground="@drawable/btn_keyboard_key_stone"
+        latin:keyTextColor="@color/latinkeyboard_key_color_black"
+        latin:shadowColor="@color/latinkeyboard_key_color_white"
+        latin:keyTextStyle="bold"
+        latin:symbolColorScheme="black"
+        latin:popupLayout="@layout/input_stone_popup"
+        />
diff --git a/java/res/layout/input_stone_normal.xml b/java/res/layout/input_stone_normal.xml
new file mode 100755
index 0000000..fd7bf85
--- /dev/null
+++ b/java/res/layout/input_stone_normal.xml
@@ -0,0 +1,35 @@
+<?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.
+*/
+-->
+
+<com.android.inputmethod.latin.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:layout_alignParentBottom="true"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:background="@drawable/keyboard_background"
+
+        latin:keyBackground="@drawable/btn_keyboard_key_stone"
+        latin:keyTextColor="@color/latinkeyboard_key_color_black"
+        latin:shadowColor="@color/latinkeyboard_key_color_white"
+        latin:symbolColorScheme="black"
+        latin:popupLayout="@layout/input_stone_popup"
+        />
diff --git a/java/res/layout/input_stone_popup.xml b/java/res/layout/input_stone_popup.xml
new file mode 100755
index 0000000..1efa56c
--- /dev/null
+++ b/java/res/layout/input_stone_popup.xml
@@ -0,0 +1,50 @@
+<?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"
+        >
+    <com.android.inputmethod.latin.LatinKeyboardBaseView
+            xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+            android:id="@+id/LatinKeyboardBaseView"
+            android:layout_alignParentBottom="true"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:background="@drawable/keyboard_background"
+
+            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"
+        />
+    <ImageButton android:id="@+id/closeButton"
+        android:background="@android:color/transparent"
+        android:src="@drawable/btn_close"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:layout_marginLeft="8dp"
+        android:clickable="true"
+        />
+</LinearLayout>
diff --git a/java/res/layout/input_trans.xml b/java/res/layout/input_trans.xml
index 94806f7..4c0979c 100755
--- a/java/res/layout/input_trans.xml
+++ b/java/res/layout/input_trans.xml
@@ -20,11 +20,13 @@
 
 <com.android.inputmethod.latin.LatinKeyboardView
         xmlns:android="http://schemas.android.com/apk/res/android"
+        xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
         android:id="@android:id/keyboardView"
         android:layout_alignParentBottom="true"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:background="#A0000000"
+        android:background="@color/latinkeyboard_extension_background"
         android:verticalCorrection="0dip"
-        android:keyBackground="@drawable/btn_keyboard_key_fulltrans"
+
+        latin:keyBackground="@drawable/btn_keyboard_key_fulltrans"
         />
diff --git a/java/res/layout/keyboard_key_preview.xml b/java/res/layout/keyboard_key_preview.xml
new file mode 100644
index 0000000..64eaa65
--- /dev/null
+++ b/java/res/layout/keyboard_key_preview.xml
@@ -0,0 +1,29 @@
+<?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.
+*/
+-->
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="80sp"
+    android:textSize="40sp"
+    android:textColor="?android:attr/textColorPrimaryInverse"
+    android:minWidth="32dip"
+    android:gravity="center"
+    android:background="@drawable/keyboard_key_feedback"
+    />
diff --git a/java/res/layout/keyboard_popup_keyboard.xml b/java/res/layout/keyboard_popup_keyboard.xml
new file mode 100644
index 0000000..a1b571a
--- /dev/null
+++ b/java/res/layout/keyboard_popup_keyboard.xml
@@ -0,0 +1,47 @@
+<?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"
+        >
+    <com.android.inputmethod.latin.LatinKeyboardBaseView
+            xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+            android:id="@+id/LatinKeyboardBaseView"
+            android:layout_alignParentBottom="true"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:background="@android:color/transparent"
+
+            latin:keyPreviewLayout="@layout/keyboard_key_preview"
+            latin:popupLayout="@layout/keyboard_popup_keyboard"
+            />
+    <ImageButton android:id="@+id/closeButton"
+        android:background="@android:color/transparent"
+        android:src="@drawable/btn_close"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:layout_marginLeft="8dp"
+        android:clickable="true"
+        />
+</LinearLayout>
diff --git a/java/res/values-cs/strings.xml b/java/res/values-cs/strings.xml
index 4687406..798d080 100644
--- a/java/res/values-cs/strings.xml
+++ b/java/res/values-cs/strings.xml
@@ -24,6 +24,8 @@
     <string name="english_ime_settings" msgid="6661589557206947774">"Nastavení klávesnice Android"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Při stisku klávesy vibrovat"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"Zvuk při stisku klávesy"</string>
+    <!-- no translation found for popup_on_keypress (123894815723512944) -->
+    <skip />
     <string name="hit_correction" msgid="4855351009261318389">"Opravovat překlepy"</string>
     <string name="hit_correction_summary" msgid="8761701873008070796">"Povolit opravu chyb vstupu"</string>
     <string name="hit_correction_land" msgid="2567691684825205448">"Chyby vstupu v zobrazení na šířku"</string>
@@ -46,6 +48,8 @@
     <string name="show_suggestions_summary" msgid="1989672863935759654">"Zobrazovat navržená slova během psaní"</string>
     <string name="auto_complete" msgid="1103196318775486023">"Automatické dokončování"</string>
     <string name="auto_complete_summary" msgid="6113149638718274624">"Stisknutím mezerníku nebo interpunkčního znaménka automaticky vložíte zvýrazněné slovo."</string>
+    <string name="bigram_suggestion" msgid="1323347224043514969">"Návrh Bigram"</string>
+    <string name="bigram_suggestion_summary" msgid="4383845146070101531">"Použít předchozí slovo ke zlepšení návrhu"</string>
   <string-array name="prediction_modes">
     <item msgid="4870266572388153286">"Žádný"</item>
     <item msgid="1669461741568287396">"Základní"</item>
@@ -128,4 +132,12 @@
     <string name="language_selection_summary" msgid="187110938289512256">"Jazyk můžete změnit posunutím prstu po mezerníku."</string>
     <string name="hint_add_to_dictionary" msgid="8058519710062071085">"← Uložte slovo opětovným klepnutím"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"K dispozici je slovník"</string>
+    <!-- no translation found for prefs_enable_log (6620424505072963557) -->
+    <skip />
+    <!-- no translation found for prefs_description_log (5827825607258246003) -->
+    <skip />
+    <!-- no translation found for keyboard_layout (437433231038683666) -->
+    <skip />
+    <!-- no translation found for prefs_debug_mode (3889340783846594980) -->
+    <skip />
 </resources>
diff --git a/java/res/values-da/strings.xml b/java/res/values-da/strings.xml
index 7a2c59f..9c525a3 100644
--- a/java/res/values-da/strings.xml
+++ b/java/res/values-da/strings.xml
@@ -24,6 +24,8 @@
     <string name="english_ime_settings" msgid="6661589557206947774">"Indstillinger for Android-tastatur"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibration ved tastetryk"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"Lyd ved tastetryk"</string>
+    <!-- no translation found for popup_on_keypress (123894815723512944) -->
+    <skip />
     <string name="hit_correction" msgid="4855351009261318389">"Ret stavefejl"</string>
     <string name="hit_correction_summary" msgid="8761701873008070796">"Aktiver fejlretning af input"</string>
     <string name="hit_correction_land" msgid="2567691684825205448">"Inputfejl i landskab"</string>
@@ -46,6 +48,8 @@
     <string name="show_suggestions_summary" msgid="1989672863935759654">"Vis ordforslag under indtastning"</string>
     <string name="auto_complete" msgid="1103196318775486023">"Udfyld automatisk"</string>
     <string name="auto_complete_summary" msgid="6113149638718274624">"Mellemrumstast og tegnsætning indsætter automatisk fremhævet ord"</string>
+    <string name="bigram_suggestion" msgid="1323347224043514969">"Bigram-forslag"</string>
+    <string name="bigram_suggestion_summary" msgid="4383845146070101531">"Brug forrige ord for at forbedre forslag"</string>
   <string-array name="prediction_modes">
     <item msgid="4870266572388153286">"Ingen"</item>
     <item msgid="1669461741568287396">"Grundlæggende"</item>
@@ -128,4 +132,12 @@
     <string name="language_selection_summary" msgid="187110938289512256">"Træk fingeren på mellemrumstasten for at skifte sprog"</string>
     <string name="hint_add_to_dictionary" msgid="8058519710062071085">"← Tast igen for at gemme"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Ordbog er tilgængelig"</string>
+    <!-- no translation found for prefs_enable_log (6620424505072963557) -->
+    <skip />
+    <!-- no translation found for prefs_description_log (5827825607258246003) -->
+    <skip />
+    <!-- no translation found for keyboard_layout (437433231038683666) -->
+    <skip />
+    <!-- no translation found for prefs_debug_mode (3889340783846594980) -->
+    <skip />
 </resources>
diff --git a/java/res/values-de/strings.xml b/java/res/values-de/strings.xml
index 9295f23..047b114 100644
--- a/java/res/values-de/strings.xml
+++ b/java/res/values-de/strings.xml
@@ -24,6 +24,8 @@
     <string name="english_ime_settings" msgid="6661589557206947774">"Android-Tastatureinstellungen"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibrieren b. Tastendruck"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"Ton bei Tastendruck"</string>
+    <!-- no translation found for popup_on_keypress (123894815723512944) -->
+    <skip />
     <string name="hit_correction" msgid="4855351009261318389">"Eingabefehler korrigieren"</string>
     <string name="hit_correction_summary" msgid="8761701873008070796">"Korrektur von Eingabefehlern aktivieren"</string>
     <string name="hit_correction_land" msgid="2567691684825205448">"Eingabefehler im Querformat"</string>
@@ -46,6 +48,8 @@
     <string name="show_suggestions_summary" msgid="1989672863935759654">"Vorgeschlagene Wörter während des Tippens anzeigen"</string>
     <string name="auto_complete" msgid="1103196318775486023">"Autom. vervollständigen"</string>
     <string name="auto_complete_summary" msgid="6113149638718274624">"Leertaste und Interpunktion fügen autom. ein markiertes Wort ein"</string>
+    <string name="bigram_suggestion" msgid="1323347224043514969">"Bigramm-Vorschläge"</string>
+    <string name="bigram_suggestion_summary" msgid="4383845146070101531">"Zur Verbesserung des Vorschlags vorheriges Wort verwenden"</string>
   <string-array name="prediction_modes">
     <item msgid="4870266572388153286">"Kein"</item>
     <item msgid="1669461741568287396">"Standard"</item>
@@ -128,4 +132,12 @@
     <string name="language_selection_summary" msgid="187110938289512256">"Finger über die Leertaste bewegen, um die Eingabesprache zu wechseln"</string>
     <string name="hint_add_to_dictionary" msgid="8058519710062071085">"← Zum Speichern erneut tippen"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Wörterbuch verfügbar"</string>
+    <!-- no translation found for prefs_enable_log (6620424505072963557) -->
+    <skip />
+    <!-- no translation found for prefs_description_log (5827825607258246003) -->
+    <skip />
+    <!-- no translation found for keyboard_layout (437433231038683666) -->
+    <skip />
+    <!-- no translation found for prefs_debug_mode (3889340783846594980) -->
+    <skip />
 </resources>
diff --git a/java/res/values-el/strings.xml b/java/res/values-el/strings.xml
index 8c78e5c..ef79ea0 100644
--- a/java/res/values-el/strings.xml
+++ b/java/res/values-el/strings.xml
@@ -24,6 +24,8 @@
     <string name="english_ime_settings" msgid="6661589557206947774">"Ρυθμίσεις πληκτρολογίου Android"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Δόνηση κατά το πάτημα πλήκτρων"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"Ήχος κατά το πάτημα πλήκτρων"</string>
+    <!-- no translation found for popup_on_keypress (123894815723512944) -->
+    <skip />
     <string name="hit_correction" msgid="4855351009261318389">"Διόρθωση σφαλμάτων πληκτρολόγησης"</string>
     <string name="hit_correction_summary" msgid="8761701873008070796">"Ενεργοποίηση διόρθωσης σφαλμάτων εισόδου"</string>
     <string name="hit_correction_land" msgid="2567691684825205448">"Σφάλματα οριζόντιας εισαγωγής"</string>
@@ -46,6 +48,8 @@
     <string name="show_suggestions_summary" msgid="1989672863935759654">"Προβολή προτεινόμενων λέξεων κατά την πληκτρολόγηση"</string>
     <string name="auto_complete" msgid="1103196318775486023">"Αυτόματη συμπλήρωση"</string>
     <string name="auto_complete_summary" msgid="6113149638718274624">"Τα πλήκ.διαστήμ.και τονισμού εισάγ.αυτόμ.την επιλ.λέξη"</string>
+    <string name="bigram_suggestion" msgid="1323347224043514969">"Προτάσεις bigram"</string>
+    <string name="bigram_suggestion_summary" msgid="4383845146070101531">"Χρήση προηγούμενης λέξης για τη βελτίωση πρότασης"</string>
   <string-array name="prediction_modes">
     <item msgid="4870266572388153286">"Καμία"</item>
     <item msgid="1669461741568287396">"Βασική"</item>
@@ -128,4 +132,12 @@
     <string name="language_selection_summary" msgid="187110938289512256">"Σύρετε το δάχτυλο στο πλήκτρο διαστήματος για να αλλάξετε γλώσσα"</string>
     <string name="hint_add_to_dictionary" msgid="8058519710062071085">"← Πατήστε ξανά για αποθήκευση"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Λεξικό διαθέσιμο"</string>
+    <!-- no translation found for prefs_enable_log (6620424505072963557) -->
+    <skip />
+    <!-- no translation found for prefs_description_log (5827825607258246003) -->
+    <skip />
+    <!-- no translation found for keyboard_layout (437433231038683666) -->
+    <skip />
+    <!-- no translation found for prefs_debug_mode (3889340783846594980) -->
+    <skip />
 </resources>
diff --git a/java/res/values-es-rUS/strings.xml b/java/res/values-es-rUS/strings.xml
index 4fd2e10..8cf11df 100644
--- a/java/res/values-es-rUS/strings.xml
+++ b/java/res/values-es-rUS/strings.xml
@@ -24,6 +24,8 @@
     <string name="english_ime_settings" msgid="6661589557206947774">"Configuración de teclado de Android"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibrar al pulsar teclas"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"Sonar al pulsar las teclas"</string>
+    <!-- no translation found for popup_on_keypress (123894815723512944) -->
+    <skip />
     <string name="hit_correction" msgid="4855351009261318389">"Corregir errores de escritura"</string>
     <string name="hit_correction_summary" msgid="8761701873008070796">"Habilitar corrección de error de entrada"</string>
     <string name="hit_correction_land" msgid="2567691684825205448">"Errores de entrada apaisada"</string>
@@ -46,6 +48,8 @@
     <string name="show_suggestions_summary" msgid="1989672863935759654">"Mostrar palabras sugeridas mientras escribe"</string>
     <string name="auto_complete" msgid="1103196318775486023">"Completar automát."</string>
     <string name="auto_complete_summary" msgid="6113149638718274624">"La barra espaciadora o la puntuación insertan automáticamente la palabra resaltada."</string>
+    <string name="bigram_suggestion" msgid="1323347224043514969">"Sugerencias de Vigoran"</string>
+    <string name="bigram_suggestion_summary" msgid="4383845146070101531">"Utiliza la palabra anterior para mejorar la sugerencia"</string>
   <string-array name="prediction_modes">
     <item msgid="4870266572388153286">"Ninguno"</item>
     <item msgid="1669461741568287396">"Básico"</item>
@@ -128,4 +132,12 @@
     <string name="language_selection_summary" msgid="187110938289512256">"Deslizarse manualmente por la barra espaciadora para cambiar el idioma"</string>
     <string name="hint_add_to_dictionary" msgid="8058519710062071085">"← Presionar nuevamente para guardar"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Diccionario disponible"</string>
+    <!-- no translation found for prefs_enable_log (6620424505072963557) -->
+    <skip />
+    <!-- no translation found for prefs_description_log (5827825607258246003) -->
+    <skip />
+    <!-- no translation found for keyboard_layout (437433231038683666) -->
+    <skip />
+    <!-- no translation found for prefs_debug_mode (3889340783846594980) -->
+    <skip />
 </resources>
diff --git a/java/res/values-es/strings.xml b/java/res/values-es/strings.xml
index 7789a39..6d38239 100644
--- a/java/res/values-es/strings.xml
+++ b/java/res/values-es/strings.xml
@@ -24,6 +24,8 @@
     <string name="english_ime_settings" msgid="6661589557206947774">"Ajustes del teclado de Android"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibrar al pulsar tecla"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"Sonido al pulsar tecla"</string>
+    <!-- no translation found for popup_on_keypress (123894815723512944) -->
+    <skip />
     <string name="hit_correction" msgid="4855351009261318389">"Corregir errores de escritura"</string>
     <string name="hit_correction_summary" msgid="8761701873008070796">"Habilitar la introducción de corrección de errores"</string>
     <string name="hit_correction_land" msgid="2567691684825205448">"Errores de introducción de datos en vista horizontal"</string>
@@ -46,6 +48,8 @@
     <string name="show_suggestions_summary" msgid="1989672863935759654">"Muestra las palabras sugeridas mientras se escribe."</string>
     <string name="auto_complete" msgid="1103196318775486023">"Autocompletar"</string>
     <string name="auto_complete_summary" msgid="6113149638718274624">"La barra espaciadora y los signos de puntuación insertan automáticamente la palabra resaltada."</string>
+    <string name="bigram_suggestion" msgid="1323347224043514969">"Sugerencias de bigramas"</string>
+    <string name="bigram_suggestion_summary" msgid="4383845146070101531">"Usar palabra anterior para mejorar sugerencias"</string>
   <string-array name="prediction_modes">
     <item msgid="4870266572388153286">"Ninguno"</item>
     <item msgid="1669461741568287396">"Básico"</item>
@@ -128,4 +132,12 @@
     <string name="language_selection_summary" msgid="187110938289512256">"Deslizar el dedo por la barra espaciadora para cambiar el idioma"</string>
     <string name="hint_add_to_dictionary" msgid="8058519710062071085">"← Volver a tocar para guardar"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Hay un diccionario disponible."</string>
+    <!-- no translation found for prefs_enable_log (6620424505072963557) -->
+    <skip />
+    <!-- no translation found for prefs_description_log (5827825607258246003) -->
+    <skip />
+    <!-- no translation found for keyboard_layout (437433231038683666) -->
+    <skip />
+    <!-- no translation found for prefs_debug_mode (3889340783846594980) -->
+    <skip />
 </resources>
diff --git a/java/res/values-fr/strings.xml b/java/res/values-fr/strings.xml
index 5589c67..544789b 100644
--- a/java/res/values-fr/strings.xml
+++ b/java/res/values-fr/strings.xml
@@ -24,6 +24,8 @@
     <string name="english_ime_settings" msgid="6661589557206947774">"Paramètres du clavier Android"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibrer à chaque touche"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"Son à chaque touche"</string>
+    <!-- no translation found for popup_on_keypress (123894815723512944) -->
+    <skip />
     <string name="hit_correction" msgid="4855351009261318389">"Corriger les fautes de frappe"</string>
     <string name="hit_correction_summary" msgid="8761701873008070796">"Activer la correction des erreurs de saisie"</string>
     <string name="hit_correction_land" msgid="2567691684825205448">"Erreurs de saisie en mode paysage"</string>
@@ -46,6 +48,8 @@
     <string name="show_suggestions_summary" msgid="1989672863935759654">"Afficher les suggestions de terme lors de la saisie"</string>
     <string name="auto_complete" msgid="1103196318775486023">"Saisie semi-automatique"</string>
     <string name="auto_complete_summary" msgid="6113149638718274624">"Insérer auto. le terme surligné avec barre espace/ponctuation"</string>
+    <string name="bigram_suggestion" msgid="1323347224043514969">"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-array name="prediction_modes">
     <item msgid="4870266572388153286">"Aucun"</item>
     <item msgid="1669461741568287396">"Simple"</item>
@@ -128,4 +132,12 @@
     <string name="language_selection_summary" msgid="187110938289512256">"Faites glisser votre doigt sur la barre d\'espacement pour changer la langue."</string>
     <string name="hint_add_to_dictionary" msgid="8058519710062071085">"← Appuyer de nouveau pour enregistrer"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Dictionnaire disponible"</string>
+    <!-- no translation found for prefs_enable_log (6620424505072963557) -->
+    <skip />
+    <!-- no translation found for prefs_description_log (5827825607258246003) -->
+    <skip />
+    <!-- no translation found for keyboard_layout (437433231038683666) -->
+    <skip />
+    <!-- no translation found for prefs_debug_mode (3889340783846594980) -->
+    <skip />
 </resources>
diff --git a/java/res/values-it/strings.xml b/java/res/values-it/strings.xml
index 7c4ffbe..486a60e 100644
--- a/java/res/values-it/strings.xml
+++ b/java/res/values-it/strings.xml
@@ -24,6 +24,8 @@
     <string name="english_ime_settings" msgid="6661589557206947774">"Impostazioni tastiera Android"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibrazione tasti"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"Suono tasti"</string>
+    <!-- no translation found for popup_on_keypress (123894815723512944) -->
+    <skip />
     <string name="hit_correction" msgid="4855351009261318389">"Correggi errori di digitazione"</string>
     <string name="hit_correction_summary" msgid="8761701873008070796">"Attiva la correzione degli errori di inserimento"</string>
     <string name="hit_correction_land" msgid="2567691684825205448">"Errori di inserimento in visualizzazione orizzontale"</string>
@@ -46,6 +48,8 @@
     <string name="show_suggestions_summary" msgid="1989672863935759654">"Visualizza le parole suggerite durante la digitazione"</string>
     <string name="auto_complete" msgid="1103196318775486023">"Completamento autom."</string>
     <string name="auto_complete_summary" msgid="6113149638718274624">"Barra spaziatrice e punteggiatura inseriscono la parola evidenziata"</string>
+    <string name="bigram_suggestion" msgid="1323347224043514969">"Suggerimenti sui bigrammi"</string>
+    <string name="bigram_suggestion_summary" msgid="4383845146070101531">"Utilizza parola precedente per migliorare il suggerimento"</string>
   <string-array name="prediction_modes">
     <item msgid="4870266572388153286">"Nessuna"</item>
     <item msgid="1669461741568287396">"Base"</item>
@@ -128,4 +132,12 @@
     <string name="language_selection_summary" msgid="187110938289512256">"Scorri il dito sulla barra spaziatrice per cambiare la lingua"</string>
     <string name="hint_add_to_dictionary" msgid="8058519710062071085">"← Tocca di nuovo per salvare"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Dizionario disponibile"</string>
+    <!-- no translation found for prefs_enable_log (6620424505072963557) -->
+    <skip />
+    <!-- no translation found for prefs_description_log (5827825607258246003) -->
+    <skip />
+    <!-- no translation found for keyboard_layout (437433231038683666) -->
+    <skip />
+    <!-- no translation found for prefs_debug_mode (3889340783846594980) -->
+    <skip />
 </resources>
diff --git a/java/res/values-ja/strings.xml b/java/res/values-ja/strings.xml
index 1412c85..cfa5a97 100644
--- a/java/res/values-ja/strings.xml
+++ b/java/res/values-ja/strings.xml
@@ -24,6 +24,8 @@
     <string name="english_ime_settings" msgid="6661589557206947774">"Androidキーボードの設定"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"キー操作バイブ"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"キー操作音"</string>
+    <!-- no translation found for popup_on_keypress (123894815723512944) -->
+    <skip />
     <string name="hit_correction" msgid="4855351009261318389">"入力ミス補正"</string>
     <string name="hit_correction_summary" msgid="8761701873008070796">"入力間違いを自動修正する"</string>
     <string name="hit_correction_land" msgid="2567691684825205448">"横表示での入力修正"</string>
@@ -46,6 +48,8 @@
     <string name="show_suggestions_summary" msgid="1989672863935759654">"入力時に入力候補を表示する"</string>
     <string name="auto_complete" msgid="1103196318775486023">"オートコンプリート"</string>
     <string name="auto_complete_summary" msgid="6113149638718274624">"反転表示されている変換候補をスペースまたは句読点キーで挿入する"</string>
+    <string name="bigram_suggestion" msgid="1323347224043514969">"バイグラム入力候補表示"</string>
+    <string name="bigram_suggestion_summary" msgid="4383845146070101531">"直前の単語から入力候補を予測します"</string>
   <string-array name="prediction_modes">
     <item msgid="4870266572388153286">"なし"</item>
     <item msgid="1669461741568287396">"基本"</item>
@@ -128,4 +132,12 @@
     <string name="language_selection_summary" msgid="187110938289512256">"スペースバーで指をスライドさせて言語を変更する"</string>
     <string name="hint_add_to_dictionary" msgid="8058519710062071085">"←保存するにはもう一度タップ"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"辞書を利用できます"</string>
+    <!-- no translation found for prefs_enable_log (6620424505072963557) -->
+    <skip />
+    <!-- no translation found for prefs_description_log (5827825607258246003) -->
+    <skip />
+    <!-- no translation found for keyboard_layout (437433231038683666) -->
+    <skip />
+    <!-- no translation found for prefs_debug_mode (3889340783846594980) -->
+    <skip />
 </resources>
diff --git a/java/res/values-ko/strings.xml b/java/res/values-ko/strings.xml
index a574a6b..8fd4e63 100644
--- a/java/res/values-ko/strings.xml
+++ b/java/res/values-ko/strings.xml
@@ -24,6 +24,8 @@
     <string name="english_ime_settings" msgid="6661589557206947774">"Android 키보드 설정"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"키를 누를 때 진동 발생"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"키를 누를 때 소리 발생"</string>
+    <!-- no translation found for popup_on_keypress (123894815723512944) -->
+    <skip />
     <string name="hit_correction" msgid="4855351009261318389">"입력 오류 수정"</string>
     <string name="hit_correction_summary" msgid="8761701873008070796">"입력 오류 수정 사용"</string>
     <string name="hit_correction_land" msgid="2567691684825205448">"가로 입력 오류"</string>
@@ -46,6 +48,8 @@
     <string name="show_suggestions_summary" msgid="1989672863935759654">"글자를 입력하는 동안 추천 단어를 표시"</string>
     <string name="auto_complete" msgid="1103196318775486023">"자동 완성"</string>
     <string name="auto_complete_summary" msgid="6113149638718274624">"스페이스바와 문장부호 키로 강조 표시된 단어를 자동 삽입"</string>
+    <string name="bigram_suggestion" msgid="1323347224043514969">"Bigram 추천"</string>
+    <string name="bigram_suggestion_summary" msgid="4383845146070101531">"이전 단어를 사용하여 추천 기능 개선"</string>
   <string-array name="prediction_modes">
     <item msgid="4870266572388153286">"없음"</item>
     <item msgid="1669461741568287396">"기본"</item>
@@ -128,4 +132,12 @@
     <string name="language_selection_summary" msgid="187110938289512256">"손가락을 스페이스바에서 미끄러지듯 움직여 언어 변경"</string>
     <string name="hint_add_to_dictionary" msgid="8058519710062071085">"← 저장하려면 다시 누르세요."</string>
     <string name="has_dictionary" msgid="6071847973466625007">"사전 사용 가능"</string>
+    <!-- no translation found for prefs_enable_log (6620424505072963557) -->
+    <skip />
+    <!-- no translation found for prefs_description_log (5827825607258246003) -->
+    <skip />
+    <!-- no translation found for keyboard_layout (437433231038683666) -->
+    <skip />
+    <!-- no translation found for prefs_debug_mode (3889340783846594980) -->
+    <skip />
 </resources>
diff --git a/java/res/values-nb/strings.xml b/java/res/values-nb/strings.xml
index 6008994..7fbac9b 100644
--- a/java/res/values-nb/strings.xml
+++ b/java/res/values-nb/strings.xml
@@ -24,6 +24,8 @@
     <string name="english_ime_settings" msgid="6661589557206947774">"Innstillinger for skjermtastatur"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibrer ved tastetrykk"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"Lyd ved tastetrykk"</string>
+    <!-- no translation found for popup_on_keypress (123894815723512944) -->
+    <skip />
     <string name="hit_correction" msgid="4855351009261318389">"Rett opp skrivefeil"</string>
     <string name="hit_correction_summary" msgid="8761701873008070796">"Slå på retting av skrivefeil"</string>
     <string name="hit_correction_land" msgid="2567691684825205448">"Rett opp skrivefeil i breddeformat"</string>
@@ -46,6 +48,8 @@
     <string name="show_suggestions_summary" msgid="1989672863935759654">"Vis foreslåtte ord under skriving"</string>
     <string name="auto_complete" msgid="1103196318775486023">"Autofullføring"</string>
     <string name="auto_complete_summary" msgid="6113149638718274624">"Mellomrom og punktum setter automatisk inn valgt ord"</string>
+    <string name="bigram_suggestion" msgid="1323347224043514969">"Bigram-forslag"</string>
+    <string name="bigram_suggestion_summary" msgid="4383845146070101531">"Bruk forrige ord til å forbedre forslaget"</string>
   <string-array name="prediction_modes">
     <item msgid="4870266572388153286">"Ingen"</item>
     <item msgid="1669461741568287396">"Grunnleggende"</item>
@@ -128,4 +132,12 @@
     <string name="language_selection_summary" msgid="187110938289512256">"Dra fingeren på mellomromstasten for å endre språk"</string>
     <string name="hint_add_to_dictionary" msgid="8058519710062071085">"← Trykk på nytt for å lagre"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Ordbok tilgjengelig"</string>
+    <!-- no translation found for prefs_enable_log (6620424505072963557) -->
+    <skip />
+    <!-- no translation found for prefs_description_log (5827825607258246003) -->
+    <skip />
+    <!-- no translation found for keyboard_layout (437433231038683666) -->
+    <skip />
+    <!-- no translation found for prefs_debug_mode (3889340783846594980) -->
+    <skip />
 </resources>
diff --git a/java/res/values-nl/strings.xml b/java/res/values-nl/strings.xml
index 27147b8..b4b0ab2 100644
--- a/java/res/values-nl/strings.xml
+++ b/java/res/values-nl/strings.xml
@@ -24,6 +24,8 @@
     <string name="english_ime_settings" msgid="6661589557206947774">"Instellingen voor Android-toetsenbord"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Trillen bij druk op toets"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"Geluid bij druk op een toets"</string>
+    <!-- no translation found for popup_on_keypress (123894815723512944) -->
+    <skip />
     <string name="hit_correction" msgid="4855351009261318389">"Typefouten corrigeren"</string>
     <string name="hit_correction_summary" msgid="8761701873008070796">"Foutcorrectie tijdens invoer inschakelen"</string>
     <string name="hit_correction_land" msgid="2567691684825205448">"Invoerfouten in liggende weergave"</string>
@@ -46,6 +48,8 @@
     <string name="show_suggestions_summary" msgid="1989672863935759654">"Voorgestelde woorden weergeven tijdens typen"</string>
     <string name="auto_complete" msgid="1103196318775486023">"Auto-aanvullen"</string>
     <string name="auto_complete_summary" msgid="6113149638718274624">"Gemarkeerd woord automatisch invoegen met spatiebalk en interpunctie"</string>
+    <string name="bigram_suggestion" msgid="1323347224043514969">"Digram-suggesties"</string>
+    <string name="bigram_suggestion_summary" msgid="4383845146070101531">"Vorig woord gebruiken om suggestie te verbeteren"</string>
   <string-array name="prediction_modes">
     <item msgid="4870266572388153286">"Geen"</item>
     <item msgid="1669461741568287396">"Basis"</item>
@@ -128,4 +132,12 @@
     <string name="language_selection_summary" msgid="187110938289512256">"Schuif uw vinger over de spatiebalk om de taal te wijzigen"</string>
     <string name="hint_add_to_dictionary" msgid="8058519710062071085">"← Tik nogmaals om op te slaan"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Woordenboek beschikbaar"</string>
+    <!-- no translation found for prefs_enable_log (6620424505072963557) -->
+    <skip />
+    <!-- no translation found for prefs_description_log (5827825607258246003) -->
+    <skip />
+    <!-- no translation found for keyboard_layout (437433231038683666) -->
+    <skip />
+    <!-- no translation found for prefs_debug_mode (3889340783846594980) -->
+    <skip />
 </resources>
diff --git a/java/res/values-pl/strings.xml b/java/res/values-pl/strings.xml
index 1d42efd..8ca1650 100644
--- a/java/res/values-pl/strings.xml
+++ b/java/res/values-pl/strings.xml
@@ -24,6 +24,8 @@
     <string name="english_ime_settings" msgid="6661589557206947774">"Ustawienia klawiatury Android"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Wibracja przy naciśnięciu"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"Dźwięk przy naciśnięciu"</string>
+    <!-- no translation found for popup_on_keypress (123894815723512944) -->
+    <skip />
     <string name="hit_correction" msgid="4855351009261318389">"Popraw błędy pisowni"</string>
     <string name="hit_correction_summary" msgid="8761701873008070796">"Włącz poprawianie błędów wprowadzania"</string>
     <string name="hit_correction_land" msgid="2567691684825205448">"Błędy wprowadzania w orientacji poziomej"</string>
@@ -46,6 +48,8 @@
     <string name="show_suggestions_summary" msgid="1989672863935759654">"Wyświetl sugerowane słowa podczas wpisywania"</string>
     <string name="auto_complete" msgid="1103196318775486023">"Autouzupełnianie"</string>
     <string name="auto_complete_summary" msgid="6113149638718274624">"Spacja i znaki przestankowe wstawiają wyróżnione słowo"</string>
+    <string name="bigram_suggestion" msgid="1323347224043514969">"Sugestie dla bigramów"</string>
+    <string name="bigram_suggestion_summary" msgid="4383845146070101531">"Używaj poprzedniego wyrazu, aby polepszyć sugestię"</string>
   <string-array name="prediction_modes">
     <item msgid="4870266572388153286">"Brak"</item>
     <item msgid="1669461741568287396">"Podstawowy"</item>
@@ -128,4 +132,12 @@
     <string name="language_selection_summary" msgid="187110938289512256">"Przesuń palcem po spacji, aby zmienić język"</string>
     <string name="hint_add_to_dictionary" msgid="8058519710062071085">"← Dotknij ponownie, aby zapisać"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Słownik dostępny"</string>
+    <!-- no translation found for prefs_enable_log (6620424505072963557) -->
+    <skip />
+    <!-- no translation found for prefs_description_log (5827825607258246003) -->
+    <skip />
+    <!-- no translation found for keyboard_layout (437433231038683666) -->
+    <skip />
+    <!-- no translation found for prefs_debug_mode (3889340783846594980) -->
+    <skip />
 </resources>
diff --git a/java/res/values-pt-rPT/strings.xml b/java/res/values-pt-rPT/strings.xml
index fac0e2d..5fbff0f 100644
--- a/java/res/values-pt-rPT/strings.xml
+++ b/java/res/values-pt-rPT/strings.xml
@@ -24,6 +24,8 @@
     <string name="english_ime_settings" msgid="6661589557206947774">"Definições de teclado do Android"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibrar ao primir as teclas"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"Som ao premir as teclas"</string>
+    <!-- no translation found for popup_on_keypress (123894815723512944) -->
+    <skip />
     <string name="hit_correction" msgid="4855351009261318389">"Corrigir erros de escrita"</string>
     <string name="hit_correction_summary" msgid="8761701873008070796">"Activar a correcção de erros de entrada"</string>
     <string name="hit_correction_land" msgid="2567691684825205448">"Erros de entrada na horizontal"</string>
@@ -46,6 +48,8 @@
     <string name="show_suggestions_summary" msgid="1989672863935759654">"Apresentar sugestões de palavras ao escrever"</string>
     <string name="auto_complete" msgid="1103196318775486023">"Conclusão automática"</string>
     <string name="auto_complete_summary" msgid="6113149638718274624">"A barra de espaços e a pontuação inserem automaticamente uma palavra realçada"</string>
+    <string name="bigram_suggestion" msgid="1323347224043514969">"Sugestões Bigram"</string>
+    <string name="bigram_suggestion_summary" msgid="4383845146070101531">"Utilizar a palavra anterior para melhorar a sugestão"</string>
   <string-array name="prediction_modes">
     <item msgid="4870266572388153286">"Nenhum"</item>
     <item msgid="1669461741568287396">"Básico"</item>
@@ -128,4 +132,12 @@
     <string name="language_selection_summary" msgid="187110938289512256">"Deslize o dedo pela barra de espaço para alterar o idioma"</string>
     <string name="hint_add_to_dictionary" msgid="8058519710062071085">"← Toque novamente para guardar"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Dicionário disponível"</string>
+    <!-- no translation found for prefs_enable_log (6620424505072963557) -->
+    <skip />
+    <!-- no translation found for prefs_description_log (5827825607258246003) -->
+    <skip />
+    <!-- no translation found for keyboard_layout (437433231038683666) -->
+    <skip />
+    <!-- no translation found for prefs_debug_mode (3889340783846594980) -->
+    <skip />
 </resources>
diff --git a/java/res/values-pt/strings.xml b/java/res/values-pt/strings.xml
index ceab82a..70288ef 100644
--- a/java/res/values-pt/strings.xml
+++ b/java/res/values-pt/strings.xml
@@ -24,6 +24,8 @@
     <string name="english_ime_settings" msgid="6661589557206947774">"Configurações de teclado Android"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibrar ao tocar a tecla"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"Som ao tocar a tecla"</string>
+    <!-- no translation found for popup_on_keypress (123894815723512944) -->
+    <skip />
     <string name="hit_correction" msgid="4855351009261318389">"Corrigir erros de digitação"</string>
     <string name="hit_correction_summary" msgid="8761701873008070796">"Ativar a correção de erro de entrada"</string>
     <string name="hit_correction_land" msgid="2567691684825205448">"Erros de entrada de paisagem"</string>
@@ -46,6 +48,8 @@
     <string name="show_suggestions_summary" msgid="1989672863935759654">"Exibir sugestões de palavras durante a digitação"</string>
     <string name="auto_complete" msgid="1103196318775486023">"Conclusão automática"</string>
     <string name="auto_complete_summary" msgid="6113149638718274624">"Barra de espaço e pontuação inserem a palavra destacada"</string>
+    <string name="bigram_suggestion" msgid="1323347224043514969">"Sugestões de bigrama"</string>
+    <string name="bigram_suggestion_summary" msgid="4383845146070101531">"Usar palavra anterior para melhorar a sugestão"</string>
   <string-array name="prediction_modes">
     <item msgid="4870266572388153286">"Nenhum"</item>
     <item msgid="1669461741568287396">"Básico"</item>
@@ -128,4 +132,12 @@
     <string name="language_selection_summary" msgid="187110938289512256">"Deslize o dedo na barra de espaços para alterar o idioma"</string>
     <string name="hint_add_to_dictionary" msgid="8058519710062071085">"← Toque novamente para salvar"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Dicionário disponível"</string>
+    <!-- no translation found for prefs_enable_log (6620424505072963557) -->
+    <skip />
+    <!-- no translation found for prefs_description_log (5827825607258246003) -->
+    <skip />
+    <!-- no translation found for keyboard_layout (437433231038683666) -->
+    <skip />
+    <!-- no translation found for prefs_debug_mode (3889340783846594980) -->
+    <skip />
 </resources>
diff --git a/java/res/values-rm/strings.xml b/java/res/values-rm/strings.xml
new file mode 100644
index 0000000..c32a2ef
--- /dev/null
+++ b/java/res/values-rm/strings.xml
@@ -0,0 +1,145 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+**
+** Copyright 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="english_ime_name" msgid="7252517407088836577">"Tastatura Android"</string>
+    <string name="english_ime_settings" msgid="6661589557206947774">"Parameters da la tastatura Android"</string>
+    <string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibrar cun smatgar in buttun"</string>
+    <string name="sound_on_keypress" msgid="6093592297198243644">"Tun cun smatgar in buttun"</string>
+    <!-- no translation found for popup_on_keypress (123894815723512944) -->
+    <skip />
+    <string name="hit_correction" msgid="4855351009261318389">"Curreger sbagls d\'endataziun"</string>
+    <string name="hit_correction_summary" msgid="8761701873008070796">"Activar la correctura da sbagls d\'endataziun"</string>
+    <string name="hit_correction_land" msgid="2567691684825205448">"Sbagls d\'endataziun en il format orizontal"</string>
+    <string name="hit_correction_land_summary" msgid="4076803842198368328">"Activar la correctura da sbagls d\'endataziun"</string>
+    <string name="auto_correction" msgid="7911639788808958255">"Propostas da pleds"</string>
+    <string name="auto_correction_summary" msgid="6881047311475758267">"Curreger automaticamain il pled precedent"</string>
+    <string name="prediction" msgid="466220283138359837">"Propostas da pleds"</string>
+    <string name="prediction_category" msgid="7027100625580696660">"Parameters da las propostas per pleds"</string>
+    <string name="prediction_summary" msgid="459788228830873110">"Activar la cumplettaziun automatica durant l\'endataziun"</string>
+    <string name="auto_complete_dialog_title" msgid="2172048590607201920">"Cumplettaziun automatica"</string>
+    <string name="prediction_landscape" msgid="4874601565593216183">"Engrondir il champ da text"</string>
+    <string name="prediction_landscape_summary" msgid="6736551095997839472">"Zuppentar propostas da pleds en il format orizontal"</string>
+    <string name="auto_cap" msgid="1719746674854628252">"Maiusclas automaticas"</string>
+    <string name="auto_cap_summary" msgid="3260681697600786825">"Scriver grond l\'entschatta da mintga frasa"</string>
+    <string name="auto_punctuate" msgid="7276672334264521751">"Interpuncziun automatica"</string>
+    <string name="auto_punctuate_summary" msgid="6589441565817502132"></string>
+    <string name="quick_fixes" msgid="5353213327680897927">"Correcturas sveltas"</string>
+    <string name="quick_fixes_summary" msgid="3405028402510332373">"Curregia sbagls da tippar currents"</string>
+    <string name="show_suggestions" msgid="507074425254289133">"Mussar las propostas"</string>
+    <string name="show_suggestions_summary" msgid="1989672863935759654">"Mussar pleds proponids durant l\'endataziun"</string>
+    <string name="auto_complete" msgid="1103196318775486023">"Cumplettaziun automatica"</string>
+    <string name="auto_complete_summary" msgid="6113149638718274624">"Inserir auto. il pled marcà cun la tasta da vid/interpuncziun"</string>
+    <!-- no translation found for bigram_suggestion (1323347224043514969) -->
+    <skip />
+    <!-- no translation found for bigram_suggestion_summary (4383845146070101531) -->
+    <skip />
+  <string-array name="prediction_modes">
+    <item msgid="4870266572388153286">"Nagin"</item>
+    <item msgid="1669461741568287396">"Simpel"</item>
+    <item msgid="4894328801530136615">"Avanzà"</item>
+  </string-array>
+    <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : Memorisà"</string>
+    <string name="alternates_for_a" msgid="2566516493365324765">"àáâãäåæ"</string>
+    <string name="alternates_for_e" msgid="3900510936875547555">"èéêë"</string>
+    <string name="alternates_for_i" msgid="7097915268629342242">"ìíîï"</string>
+    <string name="alternates_for_o" msgid="6151402748321267776">"òóöôõœø"</string>
+    <string name="alternates_for_u" msgid="5899096818189442934">"ùúûü"</string>
+    <string name="alternates_for_s" msgid="348762530927662188">"§ß"</string>
+    <string name="alternates_for_n" msgid="6257322556221886400">"ñ"</string>
+    <string name="alternates_for_c" msgid="151699780720639892">"ç"</string>
+    <string name="alternates_for_y" msgid="1722776806607271199">"ýÿ"</string>
+    <string name="tip_long_press" msgid="6101270866284343344">"Tegnair smatgà per mussar ils accents (à, é, etc.)"</string>
+    <string name="tip_dismiss" msgid="7585579046862204381">"Smatgar ↶ per serrar la tastatura"</string>
+    <string name="tip_access_symbols" msgid="6344098517525531652">"Acceder a cifras e simbols"</string>
+    <string name="tip_add_to_dictionary" msgid="1487293888469227817">"Smatgar ditg sin il pled dal tut a sanestra per l\'agiuntar al dicziunari"</string>
+    <string name="touch_to_continue" msgid="7869803257948414531">"Tutgar quest commentari per cuntinuar »"</string>
+    <string name="touch_to_finish" msgid="7990196086480585789">"Tutgar qua, per serrar quest commentari e cumenzar a tippar!"</string>
+    <string name="tip_to_open_keyboard" msgid="6821200275486950452"><b>"La tastatura vegn adina averta sche Vus tutgais in champ da text."</b></string>
+    <string name="tip_to_view_accents" msgid="5433158573693308501"><b>"Tegnai smatgà ina tasta per mussar ils segns spezials"\n"(ø, ö, ô, ó etc.)."</b></string>
+    <string name="tip_to_open_symbols" msgid="7345139325622444880"><b>"Midai a numers e simbols cun tutgar quest buttun."</b></string>
+    <string name="tip_to_close_symbols" msgid="5227724217206927185"><b>"Turnai a letras cun smatgar danovamain quest buttun."</b></string>
+    <string name="tip_to_launch_settings" msgid="8402961128983196128"><b>"Tegnai smatgà quest buttun per midar ils parameters da tastatura, sco p. ex. la cumplettaziun automatica."</b></string>
+    <string name="tip_to_start_typing" msgid="7213843601369174313"><b>"Empruvai!"</b></string>
+    <string name="label_go_key" msgid="1635148082137219148">"Dai"</string>
+    <string name="label_next_key" msgid="362972844525672568">"Vinavant"</string>
+    <string name="label_done_key" msgid="2441578748772529288">"Finì"</string>
+    <string name="label_send_key" msgid="2815056534433717444">"Trametter"</string>
+    <string name="label_symbol_key" msgid="6175820506864489453">"?123"</string>
+    <string name="label_phone_key" msgid="4275497665515080551">"123"</string>
+    <string name="label_alpha_key" msgid="8864943487292437456">"ABC"</string>
+    <string name="label_alt_key" msgid="2846315350346694811">"ALT"</string>
+    <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>
+    <string name="voice_warning_may_not_understand" msgid="4611518823070986445">"Ils cumonds vocals èn ina funcziunalitad experimentala che utilisescha la renconuschientscha vocala da rait da Google."</string>
+    <string name="voice_warning_how_to_turn_off" msgid="5652369578498701761">"Per deactivar ils cumonds vocals, avri ils parameters da tastatura."</string>
+    <string name="voice_hint_dialog_message" msgid="6892342981545727994">"Per utilisar ils cumonds vocals, smatgai il buttun dal microfon u stritgai cun il det sur la tastatura dal visur."</string>
+    <string name="voice_listening" msgid="467518160751321844">"Ussa discurrer"</string>
+    <string name="voice_working" msgid="6666937792815731889">"Operaziun en progress"</string>
+    <string name="voice_initializing" msgid="661962047129906646"></string>
+    <string name="voice_error" msgid="5140896300312186162">"Errur. Empruvai anc ina giada."</string>
+    <string name="voice_network_error" msgid="6649556447401862563">"Impussibel da connectar."</string>
+    <string name="voice_too_much_speech" msgid="5746973620134227376">"Errur - discurrì memia ditg."</string>
+    <string name="voice_audio_error" msgid="5072707727016414454">"Problem audio"</string>
+    <string name="voice_server_error" msgid="7807129913977261644">"Errur dal server"</string>
+    <string name="voice_speech_timeout" msgid="8461817525075498795">"Betg udì ina frasa vocala"</string>
+    <string name="voice_no_match" msgid="4285117547030179174">"Betg chattà correspundenzas"</string>
+    <string name="voice_not_installed" msgid="5552450909753842415">"Betg installà la tschertga vocala"</string>
+    <string name="voice_swipe_hint" msgid="6943546180310682021"><b>"Commentari:"</b>" Stritgai cun il det sur la tastatura per discurrer."</string>
+    <string name="voice_punctuation_hint" msgid="1611389463237317754"><b>"Commentari:"</b>" Empruvai la proxima giada d\'agiuntar segns d\'interpuncziun sco \"punct\", \"comma\" u \"segn da dumonda\" cun cumonds vocals."</string>
+    <string name="cancel" msgid="6830980399865683324">"Interrumper"</string>
+    <string name="ok" msgid="7898366843681727667">"OK"</string>
+    <string name="voice_input" msgid="2466640768843347841">"Cumonds vocals"</string>
+  <string-array name="voice_input_modes">
+    <item msgid="1349082139076086774">"Sin la tastatura principala"</item>
+    <item msgid="8529385602829095903">"Sin la tastatura da simbols"</item>
+    <item msgid="7283103513488381103">"Deactivà"</item>
+  </string-array>
+  <string-array name="voice_input_modes_summary">
+    <item msgid="554248625705084903">"Microfon sin la tastatura principala"</item>
+    <item msgid="6907837061058876770">"Microfon sin la tastatura da simbols"</item>
+    <item msgid="3664304608587798036">"Ils cumonds vocals èn deactivads"</item>
+  </string-array>
+    <string name="auto_submit" msgid="9151008027068358518">"Trametter automaticamain suenter il cumond vocal"</string>
+    <string name="auto_submit_summary" msgid="4961875269610384226">"Smatgai sin la tasta enter sche Vus exequis ina tschertga u siglis al proxim champ."</string>
+    <string name="open_the_keyboard" msgid="2215920976029260466"><font size="17"><b>"Avrir la tastatura"\n</b></font><font size="3">\n</font>"Tutgai inqual champ da text."</string>
+    <string name="close_the_keyboard" msgid="6251022259044940103"><font size="17"><b>"Serrar la tastatura"\n</b></font><font size="3">\n</font>"Smatgai il buttun \"Enavos\"."</string>
+    <string name="touch_and_hold" msgid="6154166367273010534"><font size="17"><b>"Tutgar e tegnair smatgà in buttun per acceder a las opziuns"\n</b></font><font size="3">\n</font>"Accedi a segns d\'interpuncziun ed accents."</string>
+    <string name="keyboard_settings" msgid="4585753477617374032"><font size="17"><b>"Parameters da tastatura"\n</b></font><font size="3">\n</font>"Tutgai e tegnai smatgà il buttun "<b>"?123"</b>"."</string>
+    <string name="popular_domain_0" msgid="3745279225122472969">".com"</string>
+    <string name="popular_domain_1" msgid="1370572248164278467">".net"</string>
+    <string name="popular_domain_2" msgid="3036812463748402878">".org"</string>
+    <string name="popular_domain_3" msgid="8718639560809452028">".gov"</string>
+    <string name="popular_domain_4" msgid="35359437471311470">".edu"</string>
+    <string name="inputMethod" msgid="7854532062009028116">"Metoda d\'endataziun"</string>
+    <string name="language_selection_title" msgid="1651299598555326750">"Linguas da cumonds vocals"</string>
+    <string name="language_selection_summary" msgid="187110938289512256">"Stritgar cun il det sur la tasta da vid per midar la lingua"</string>
+    <string name="hint_add_to_dictionary" msgid="8058519710062071085">"← Tippar danovamain per memorisar"</string>
+    <string name="has_dictionary" msgid="6071847973466625007">"Dicziunari disponibel"</string>
+    <!-- no translation found for prefs_enable_log (6620424505072963557) -->
+    <skip />
+    <!-- no translation found for prefs_description_log (5827825607258246003) -->
+    <skip />
+    <!-- no translation found for keyboard_layout (437433231038683666) -->
+    <skip />
+    <!-- no translation found for prefs_debug_mode (3889340783846594980) -->
+    <skip />
+</resources>
diff --git a/java/res/values-ru/strings.xml b/java/res/values-ru/strings.xml
index 9e5c59b..1898d27 100644
--- a/java/res/values-ru/strings.xml
+++ b/java/res/values-ru/strings.xml
@@ -24,6 +24,8 @@
     <string name="english_ime_settings" msgid="6661589557206947774">"Настройки клавиатуры Android"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Виброотклик клавиш"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"Звук клавиш"</string>
+    <!-- no translation found for popup_on_keypress (123894815723512944) -->
+    <skip />
     <string name="hit_correction" msgid="4855351009261318389">"Исправлять опечатки"</string>
     <string name="hit_correction_summary" msgid="8761701873008070796">"Включить исправление ошибок при вводе"</string>
     <string name="hit_correction_land" msgid="2567691684825205448">"Ошибки при вводе в горизонтальной ориентации"</string>
@@ -46,6 +48,8 @@
     <string name="show_suggestions_summary" msgid="1989672863935759654">"Предлагать варианты слов во время ввода"</string>
     <string name="auto_complete" msgid="1103196318775486023">"Автозавершение"</string>
     <string name="auto_complete_summary" msgid="6113149638718274624">"При нажатии пробела вставлять предложенное слово"</string>
+    <string name="bigram_suggestion" msgid="1323347224043514969">"Биграммные подсказки "</string>
+    <string name="bigram_suggestion_summary" msgid="4383845146070101531">"Используйте предыдущее слово, чтобы исправить подсказку"</string>
   <string-array name="prediction_modes">
     <item msgid="4870266572388153286">"Нет"</item>
     <item msgid="1669461741568287396">"Основной"</item>
@@ -128,4 +132,12 @@
     <string name="language_selection_summary" msgid="187110938289512256">"Для изменения языка проведите пальцем по пробелу"</string>
     <string name="hint_add_to_dictionary" msgid="8058519710062071085">"← Нажмите повторно, чтобы сохранить"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Доступен словарь"</string>
+    <!-- no translation found for prefs_enable_log (6620424505072963557) -->
+    <skip />
+    <!-- no translation found for prefs_description_log (5827825607258246003) -->
+    <skip />
+    <!-- no translation found for keyboard_layout (437433231038683666) -->
+    <skip />
+    <!-- no translation found for prefs_debug_mode (3889340783846594980) -->
+    <skip />
 </resources>
diff --git a/java/res/values-sr/strings.xml b/java/res/values-sr/strings.xml
new file mode 100644
index 0000000..f706ebc
--- /dev/null
+++ b/java/res/values-sr/strings.xml
@@ -0,0 +1,299 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 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.
+*/
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Title for Latin keyboard  -->
+    <string name="english_ime_name">Андроидова тастатура</string>
+    <!-- Title for Latin keyboard settings activity / dialog -->
+    <string name="english_ime_settings">Подешавања андроидове тастатуре</string>
+
+    <!-- Option to provide vibrate/haptic feedback on keypress -->
+    <string name="vibrate_on_keypress">Вибрације при притиску</string>
+    <!-- Option to play back sound on keypress in soft keyboard -->
+    <string name="sound_on_keypress">Звук при притиску</string>
+
+    <!-- Option to enable using nearby keys when correcting/predicting -->
+    <string name="hit_correction">Исправљање грешака</string>
+
+    <!-- Description for hit_correction  -->
+    <string name="hit_correction_summary">Исправљање грешака при уносу</string>
+
+    <!-- Option to enable using nearby keys when correcting/predicting in landscape-->
+    <string name="hit_correction_land">Грешке при водоравној оријентацији</string>
+
+    <!-- Description for hit_correction in landscape -->
+    <string name="hit_correction_land_summary">Исправљање грешака при уносу при
+        водоравном положају</string>
+
+    <!-- Option to automatically correct word on hitting space -->
+    <string name="auto_correction">Предлози речи</string>
+
+    <!-- Description for auto_correction -->
+    <string name="auto_correction_summary">Аутоматска исправка претходно унесене речи</string>
+
+    <!-- Option to enable text prediction -->
+    <string name="prediction">Предлози речи</string>
+    <!-- Category title for text prediction -->
+    <string name="prediction_category">Подешавања за предлоге речи</string>
+    <!-- Description for text prediction -->
+    <string name="prediction_summary">Укључи аутоматске наставке при уносу</string>
+
+    <!-- Dialog title for auto complete choices -->
+    <string name="auto_complete_dialog_title">Аутоматски наставци</string>
+
+    <!-- Option to enable text prediction in landscape -->
+    <string name="prediction_landscape">Увећан поље за унос текста</string>
+    <!-- Description for text prediction -->
+    <string name="prediction_landscape_summary">Сакриј предлоге речи при водоравном положају</string>
+
+    <!-- Option to enable auto capitalization of sentences -->
+    <string name="auto_cap">Аутоматска величина слова</string>
+    <!-- Description for auto cap -->
+    <string name="auto_cap_summary">Велико слово на почетку реченице</string>
+    <!-- Option to enable auto punctuate -->
+    <string name="auto_punctuate">Аутоматска интерпункција</string>
+    <!-- Description for auto punctuate -->
+    <string name="auto_punctuate_summary">Аутоматско постављање интерпункцијских знака при уносу.</string>
+
+    <!-- Option to enable quick fixes -->
+    <string name="quick_fixes">Брзе исправке</string>
+    <!-- Description for quick fixes -->
+    <string name="quick_fixes_summary">Аутоматска исправка честих грешака</string>
+
+    <!-- Option to enable showing suggestions -->
+    <string name="show_suggestions">Приказ предлога</string>
+    <!-- Description for show suggestions -->
+    <string name="show_suggestions_summary">Приказује предлоге речи током уноса</string>
+
+    <!-- Option to enable auto completion -->
+    <string name="auto_complete">Аутоматска допуна</string>
+    <!-- Description for auto completion -->
+    <string name="auto_complete_summary">Размакница и интерпункција аутоматски убацују означену реч.</string>
+
+    <!-- Array of prediction modes -->
+    <string-array name="prediction_modes">
+        <item>Искључено</item>
+        <item>Основно</item>
+        <item>Напредно</item>
+    </string-array>
+
+    <string-array name="prediction_modes_values" translatable="false">
+        <item>@string/prediction_none</item>
+        <item>@string/prediction_basic</item>
+        <item>@string/prediction_full</item>
+    </string-array>
+
+    <!-- Indicates that a word has been added to the dictionary -->
+    <string name="added_word"><xliff:g id="word">%s</xliff:g> : Saved</string>
+    <!-- Tip to long press on keys -->
+    <string name="tip_long_press">Дуги притисак на тастере открива проширене знаке (ø, ö, итд.)</string>
+    <!-- Tip to dismiss keyboard -->
+    <string name="tip_dismiss">Притисните тастер за назад \u21B6 како бисте затворили тастатуру</string>
+    <!-- Tip to press ?123 to access numbers and symbols -->
+    <string name="tip_access_symbols">Приступ бројевима и симболима</string>
+    <!-- Tip to long press on typed word to add to dictionary -->
+    <string name="tip_add_to_dictionary">Притисните и држите притиснуту реч са крајње леве стране
+        како бисте је додали у речник</string>
+
+    <!-- Instruction to touch the bubble to continue -->
+    <string name="touch_to_continue">Притисните овај подсетник да наставите »</string>
+
+    <!-- Instruction to touch the bubble to start typing -->
+    <string name="touch_to_finish">Притисните овде да бисте затворили подсетник и наставили унос!</string>
+
+    <!-- Tutorial tip 1 - The keyboard opens any time you touch a text field -->
+    <string name="tip_to_open_keyboard"><b>Тастатура се отвара кад год је потребно да унесете текст</b></string>
+
+    <!-- Tutorial tip 2 - Touch and hold a key to view accents (examples) -->
+    <string name="tip_to_view_accents"><b>Притисните и држите тастер како бисте видели проширене
+        знаке\n(„, ‟, итд.)</b>
+    </string>
+
+    <!-- Tutorial tip 3 - How to switch to number/symbol keyboard -->
+    <string name="tip_to_open_symbols"><b>Пребаците се на бројеве и симболе притиском на овај тастер
+        </b></string>
+
+    <!-- Tutorial tip 4 - How to switch back to alphabet keyboard -->
+    <string name="tip_to_close_symbols"><b>Вратите се назад на слова притиском на овај тастер</b></string>
+
+    <!-- Tutorial tip 5 - How to launch keyboard settings -->
+    <string name="tip_to_launch_settings"><b>Притисните и држите притиснут овај тастер да бисте променили
+        подешавања тастатуре, попут аутоматског настављања</b></string>
+
+    <!-- Tutorial tip 6 - Done with the tutorial -->
+    <string name="tip_to_start_typing"><b>Пробајте сами!</b></string>
+
+
+    <!-- Label for soft enter key when it performs GO action.  Must be short to fit on key! -->
+    <string name="label_go_key">Иди</string>
+    <!-- Label for soft enter key when it performs NEXT action.  Must be short to fit on key! -->
+    <string name="label_next_key">Даље</string>
+    <!-- Label for soft enter key when it performs DONE action.  Must be short to fit on key! -->
+    <string name="label_done_key">Крај</string>
+    <!-- Label for soft enter key when it performs SEND action.  Must be short to fit on key! -->
+    <string name="label_send_key">Шаљи</string>
+    <!-- Label for "switch to symbols" key.  Must be short to fit on key! -->
+    <string name="label_symbol_key">\?123</string>
+    <!-- Label for "switch to numeric" key.  Must be short to fit on key! -->
+    <string name="label_phone_key">123</string>
+    <!-- Label for "switch to alphabetic" key.  Must be short to fit on key! -->
+    <string name="label_alpha_key">АБВ</string>
+    <!-- Label for ALT modifier key.  Must be short to fit on key! -->
+    <string name="label_alt_key">ALT</string>
+
+    <!-- Voice related labels -->
+
+    <!-- Title of the warning dialog that shows when a user initiates voice input for
+         the first time. -->
+    <string name="voice_warning_title">Говорни унос</string>
+
+    <!-- Message that gets put at the top of the warning dialog if the user is attempting to use
+         voice input in a currently unsupported locale. Voice input will work for such a user,
+         but it will only recognize them in English. -->
+    <string name="voice_warning_locale_not_supported">Говорни унос није тренутно подржан на Вашем језику,
+       али ради на енглеском.</string>
+
+    <!-- Message of the warning dialog that shows when a user initiates voice input for
+         the first time, or turns it on in settings. -->
+    <string name="voice_warning_may_not_understand">Говорни унос је експериментална могућност која користи
+        Google-ово мрежно препознавање говора.</string>
+
+    <!-- An additional part of the warning dialog for voice input that only shows when the user
+         actually initiates voice input, rather than just turning it on in settings. -->
+    <string name="voice_warning_how_to_turn_off">Како бисте искључили говорни унос, изаберите подешавања
+        тастатуре.</string>
+
+    <!-- Message to show when user clicks the swiping hint (which says
+        "Swipe across keyboard to speak"). Also shown when enabling settings. -->
+    <string name="voice_hint_dialog_message">Како бисте укључили говорни унос, притисните дугме са сличицом
+        микрофона или превуците прстом преко целе дужине тастатуре.</string>
+
+    <!-- Short message to tell the user the system is ready for them to speak. -->
+    <string name="voice_listening">Говорите сада</string>
+
+    <!-- Short message shown after the user finishes speaking. -->
+    <string name="voice_working">Обрада је у току</string>
+
+    <!-- Short message shown before the user should speak. -->
+    <string name="voice_initializing"></string>
+
+    <!-- Short message shown when a generic error occurs. -->
+    <string name="voice_error">Грешка.  Молимо пробајте поново.</string>
+
+    <!-- Short message shown for a network error. -->
+    <string name="voice_network_error">Повезивање није успело</string>
+
+    <!-- Short message shown for a network error where the utterance was really long,
+         in which case we should suggest that the user speak less. -->
+    <string name="voice_too_much_speech">Грешка, говор је предугачак.</string>
+
+    <!-- Short message shown for an audio error. -->
+    <string name="voice_audio_error">Проблем са звуком</string>
+
+    <!-- Short message shown for an error with the voice server. -->
+    <string name="voice_server_error">Грешка на серверу</string>
+
+    <!-- Short message shown when no speech is heard. -->
+    <string name="voice_speech_timeout">Говор није снимљен</string>
+
+    <!-- Short message shown when the server couldn't parse any speech. -->
+    <string name="voice_no_match">Нема погодака</string>
+
+    <!-- Short message shown when the user initiates voice and voice
+        search is not installed. -->
+    <string name="voice_not_installed">Говорна претрага није инсталирана</string>
+
+    <!-- Short hint shown in candidate view to explain voice input. -->
+    <string name="voice_swipe_hint"><b>Савет:</b> Превуците прстом преко тастатуре а онда говорите.</string>
+
+    <!-- Short hint shown in candidate view to explain that user can speak punctuation. -->
+    <string name="voice_punctuation_hint"><b>Савет:</b> Следећи пут, изговорите назив интерпункције,
+        попут „тачка“, „запета“ или „знак питања“.</string>
+
+    <!-- Label on button to stop recognition. Must be short to fit on button. -->
+    <string name="cancel">Откажи</string>
+
+    <!-- Label on button when an error occurs -->
+    <string name="ok">У реду</string>
+
+    <!-- Preferences item for enabling speech input -->
+    <string name="voice_input">Говорни унос</string>
+
+    <!-- Array of Voice Input modes -->
+    <string-array name="voice_input_modes">
+        <item>На главној тастатури</item>
+        <item>На симболичкој тастатури</item>
+        <item>Искључен</item>
+    </string-array>
+
+    <string-array name="voice_input_modes_values" translatable="false">
+        <item>@string/voice_mode_main</item>
+        <item>@string/voice_mode_symbols</item>
+        <item>@string/voice_mode_off</item>
+    </string-array>
+
+    <!-- Array of Voice Input modes summary -->
+    <string-array name="voice_input_modes_summary">
+        <item>Микрофон на главној тастатури</item>
+        <item>Микрофон на симболичкој тастатури</item>
+        <item>Говорни унос је искључен</item>
+    </string-array>
+
+    <!-- Press the "enter" key after the user speaks. Option on settings.-->
+    <string name="auto_submit">Аутоматско слање по говорном уносу</string>
+
+    <!-- Press the "enter" key after the user speaks. Summary of option in settings.-->
+    <string name="auto_submit_summary">Дугме за претрагу се аутоматски притиска при претрази или преласку
+        на следеће поље за унос.</string>
+
+    <!-- IME Tutorial screen (ROMAN) --><skip />
+    <!-- appears above image showing the user to click on a TextView to show the IME -->
+    <string name="open_the_keyboard"><font size="17"><b>Отварање тастатуре\n</b></font><font size="3">\n</font>Touch any text field.</string>
+
+    <!-- appears above the image showing the back button used to close the keyboard -->
+    <string name="close_the_keyboard"><font size="17"><b>Затварање тастатуре\n</b></font><font size="3">\n</font>Press the Back key.</string>
+
+    <!-- appears above image showing how to use touch and hold -->
+    <string name="touch_and_hold"><font size="17"><b>Притисните \u0026 и држите пристиснут тастер за опције\n</b></font><font size="3">\n</font>Приступ акцентима и интерпункцији.</string>
+
+    <!-- appears above image showing how to access keyboard settings -->
+    <string name="keyboard_settings"><font size="17"><b>Подешавање тастатуре\n</b></font><font size="3">\n</font>Притисните \u0026 и држите тастер <b>\?123\</b>.</string>
+
+    <!-- popular web domains for the locale - most popular, displayed on the keyboard -->
+    <string name="popular_domain_0">".rs"</string>
+    <!-- popular web domains for the locale - item 1, displayed in the popup -->
+    <string name="popular_domain_1">".com"</string>
+    <!-- popular web domains for the locale - item 2, displayed in the popup -->
+    <string name="popular_domain_2">".net"</string>
+    <!-- popular web domains for the locale - item 3, displayed in the popup -->
+    <string name="popular_domain_3">".org"</string>
+    <!-- popular web domains for the locale - item 4, displayed in the popup -->
+    <string name="popular_domain_4">".edu"</string>
+
+    <!-- Menu item for launching Input method switcher -->
+    <string name="inputMethod">Метод за унос</string>
+
+    <!-- Title for input language selection screen -->
+    <string name="language_selection_title">Језици за унос</string>
+    <!-- Title summary for input language selection screen -->
+    <string name="language_selection_summary">Превуците прстом по размакници за промену језика</string>
+
+    <!-- Add to dictionary hint -->
+    <string name="hint_add_to_dictionary">\u2190 Притисните опет да бисте сачували</string>
+</resources>
diff --git a/java/res/values-sv/strings.xml b/java/res/values-sv/strings.xml
index bb1d47c..49359e8 100644
--- a/java/res/values-sv/strings.xml
+++ b/java/res/values-sv/strings.xml
@@ -24,6 +24,8 @@
     <string name="english_ime_settings" msgid="6661589557206947774">"Inställningar för Androids tangentbord"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibrera vid tangenttryck"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"Knappljud"</string>
+    <!-- no translation found for popup_on_keypress (123894815723512944) -->
+    <skip />
     <string name="hit_correction" msgid="4855351009261318389">"Rätta skrivfel"</string>
     <string name="hit_correction_summary" msgid="8761701873008070796">"Aktivera rättning av felaktiga inmatningar"</string>
     <string name="hit_correction_land" msgid="2567691684825205448">"Inmatningsfel i liggande vy"</string>
@@ -46,6 +48,8 @@
     <string name="show_suggestions_summary" msgid="1989672863935759654">"Visar ordförslag när du skriver"</string>
     <string name="auto_complete" msgid="1103196318775486023">"Komplettera automatiskt"</string>
     <string name="auto_complete_summary" msgid="6113149638718274624">"Blanksteg och punkt infogar automatiskt markerat ord"</string>
+    <string name="bigram_suggestion" msgid="1323347224043514969">"Bigramförslag"</string>
+    <string name="bigram_suggestion_summary" msgid="4383845146070101531">"Förbättra förslaget med föregående ord"</string>
   <string-array name="prediction_modes">
     <item msgid="4870266572388153286">"Inget"</item>
     <item msgid="1669461741568287396">"Grundinställningar"</item>
@@ -128,4 +132,12 @@
     <string name="language_selection_summary" msgid="187110938289512256">"Dra med fingret på blanksteg om du vill ändra språk"</string>
     <string name="hint_add_to_dictionary" msgid="8058519710062071085">"← Peka igen för att spara"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"En ordlista är tillgänglig"</string>
+    <!-- no translation found for prefs_enable_log (6620424505072963557) -->
+    <skip />
+    <!-- no translation found for prefs_description_log (5827825607258246003) -->
+    <skip />
+    <!-- no translation found for keyboard_layout (437433231038683666) -->
+    <skip />
+    <!-- no translation found for prefs_debug_mode (3889340783846594980) -->
+    <skip />
 </resources>
diff --git a/java/res/values-tr/strings.xml b/java/res/values-tr/strings.xml
index 9baa1bf..73ad111 100644
--- a/java/res/values-tr/strings.xml
+++ b/java/res/values-tr/strings.xml
@@ -24,6 +24,8 @@
     <string name="english_ime_settings" msgid="6661589557206947774">"Android klavye ayarları"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Tuşa basıldığında titret"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"Tuşa basıldığında ses çıkar"</string>
+    <!-- no translation found for popup_on_keypress (123894815723512944) -->
+    <skip />
     <string name="hit_correction" msgid="4855351009261318389">"Yazım hatalarını düzelt"</string>
     <string name="hit_correction_summary" msgid="8761701873008070796">"Giriş hatası düzeltmeyi etkinleştir"</string>
     <string name="hit_correction_land" msgid="2567691684825205448">"Yatay giriş hataları"</string>
@@ -46,6 +48,8 @@
     <string name="show_suggestions_summary" msgid="1989672863935759654">"Yazarken önerilen kelimeleri görüntüle"</string>
     <string name="auto_complete" msgid="1103196318775486023">"Otomatik tamamla"</string>
     <string name="auto_complete_summary" msgid="6113149638718274624">"Boşluk tuşu ve noktalama vurgulanan kelimeyi otomatik ekler"</string>
+    <string name="bigram_suggestion" msgid="1323347224043514969">"Bigram Önerileri"</string>
+    <string name="bigram_suggestion_summary" msgid="4383845146070101531">"Öneriyi geliştirmek için önceki kelimeyi kullanın"</string>
   <string-array name="prediction_modes">
     <item msgid="4870266572388153286">"Yok"</item>
     <item msgid="1669461741568287396">"Temel"</item>
@@ -128,4 +132,12 @@
     <string name="language_selection_summary" msgid="187110938289512256">"Dili değiştirmek için parmağınızı boşluk çubuğu üzerinde kaydırın"</string>
     <string name="hint_add_to_dictionary" msgid="8058519710062071085">"← Kaydetmek için tekrar dokunun"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Sözlük kullanılabilir"</string>
+    <!-- no translation found for prefs_enable_log (6620424505072963557) -->
+    <skip />
+    <!-- no translation found for prefs_description_log (5827825607258246003) -->
+    <skip />
+    <!-- no translation found for keyboard_layout (437433231038683666) -->
+    <skip />
+    <!-- no translation found for prefs_debug_mode (3889340783846594980) -->
+    <skip />
 </resources>
diff --git a/java/res/values-zh-rCN/strings.xml b/java/res/values-zh-rCN/strings.xml
index dd64bed..046d1d6 100644
--- a/java/res/values-zh-rCN/strings.xml
+++ b/java/res/values-zh-rCN/strings.xml
@@ -24,6 +24,8 @@
     <string name="english_ime_settings" msgid="6661589557206947774">"Android 键盘设置"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"按键时振动"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"按键时播放音效"</string>
+    <!-- no translation found for popup_on_keypress (123894815723512944) -->
+    <skip />
     <string name="hit_correction" msgid="4855351009261318389">"纠正输入错误"</string>
     <string name="hit_correction_summary" msgid="8761701873008070796">"启用输入错误纠正功能"</string>
     <string name="hit_correction_land" msgid="2567691684825205448">"横向输入错误"</string>
@@ -46,6 +48,8 @@
     <string name="show_suggestions_summary" msgid="1989672863935759654">"输入时启用联想提示"</string>
     <string name="auto_complete" msgid="1103196318775486023">"自动填写"</string>
     <string name="auto_complete_summary" msgid="6113149638718274624">"按空格键和标点符号时自动插入突出显示的字词"</string>
+    <string name="bigram_suggestion" msgid="1323347224043514969">"双连词建议"</string>
+    <string name="bigram_suggestion_summary" msgid="4383845146070101531">"使用以前的字词改进建议"</string>
   <string-array name="prediction_modes">
     <item msgid="4870266572388153286">"无"</item>
     <item msgid="1669461741568287396">"基本模式"</item>
@@ -128,4 +132,12 @@
     <string name="language_selection_summary" msgid="187110938289512256">"在空格键上滑动手指可更改语言"</string>
     <string name="hint_add_to_dictionary" msgid="8058519710062071085">"← 再次点按即可保存"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"提供字典"</string>
+    <!-- no translation found for prefs_enable_log (6620424505072963557) -->
+    <skip />
+    <!-- no translation found for prefs_description_log (5827825607258246003) -->
+    <skip />
+    <!-- no translation found for keyboard_layout (437433231038683666) -->
+    <skip />
+    <!-- no translation found for prefs_debug_mode (3889340783846594980) -->
+    <skip />
 </resources>
diff --git a/java/res/values-zh-rTW/strings.xml b/java/res/values-zh-rTW/strings.xml
index 5c54cc8..182f711 100644
--- a/java/res/values-zh-rTW/strings.xml
+++ b/java/res/values-zh-rTW/strings.xml
@@ -24,6 +24,8 @@
     <string name="english_ime_settings" msgid="6661589557206947774">"Android 鍵盤設定"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"按鍵時震動"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"按鍵時播放音效"</string>
+    <!-- no translation found for popup_on_keypress (123894815723512944) -->
+    <skip />
     <string name="hit_correction" msgid="4855351009261318389">"修正輸入錯誤"</string>
     <string name="hit_correction_summary" msgid="8761701873008070796">"啟用輸入錯誤修正功能"</string>
     <string name="hit_correction_land" msgid="2567691684825205448">"橫向輸入錯誤"</string>
@@ -46,6 +48,8 @@
     <string name="show_suggestions_summary" msgid="1989672863935759654">"打字時顯示建議字詞"</string>
     <string name="auto_complete" msgid="1103196318775486023">"自動完成"</string>
     <string name="auto_complete_summary" msgid="6113149638718274624">"在反白顯示的字詞處自動插入空白鍵和標點符號鍵盤"</string>
+    <string name="bigram_suggestion" msgid="1323347224043514969">"雙連詞建議"</string>
+    <string name="bigram_suggestion_summary" msgid="4383845146070101531">"根據前一個字詞自動找出更適合的建議"</string>
   <string-array name="prediction_modes">
     <item msgid="4870266572388153286">"無"</item>
     <item msgid="1669461741568287396">"基本模式"</item>
@@ -128,4 +132,12 @@
     <string name="language_selection_summary" msgid="187110938289512256">"以手指在空白鍵上滑動可變更語言"</string>
     <string name="hint_add_to_dictionary" msgid="8058519710062071085">"← 再次輕按可儲存"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"可使用字典"</string>
+    <!-- no translation found for prefs_enable_log (6620424505072963557) -->
+    <skip />
+    <!-- no translation found for prefs_description_log (5827825607258246003) -->
+    <skip />
+    <!-- no translation found for keyboard_layout (437433231038683666) -->
+    <skip />
+    <!-- no translation found for prefs_debug_mode (3889340783846594980) -->
+    <skip />
 </resources>
diff --git a/java/res/values/attrs.xml b/java/res/values/attrs.xml
new file mode 100644
index 0000000..e3171eb
--- /dev/null
+++ b/java/res/values/attrs.xml
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 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.
+-->
+
+<resources>
+
+    <declare-styleable name="LatinKeyboardBaseView">
+        <!-- Default KeyboardView style. -->
+        <attr name="keyboardViewStyle" format="reference" />
+
+        <!-- Image for the key. This image needs to be a StateListDrawable, with the following
+             possible states: normal, pressed, checkable, checkable+pressed, checkable+checked,
+             checkable+checked+pressed. -->
+        <attr name="keyBackground" format="reference" />
+
+        <!-- Size of the text for character keys. -->
+        <attr name="keyTextSize" format="dimension" />
+
+        <!-- Size of the text for custom keys with some text and no icon. -->
+        <attr name="labelTextSize" format="dimension" />
+
+        <!-- Color to use for the label in a key. -->
+        <attr name="keyTextColor" format="color" />
+
+        <!-- Layout resource for key press feedback.-->
+        <attr name="keyPreviewLayout" format="reference" />
+
+        <!-- Vertical offset of the key press feedback from the key. -->
+        <attr name="keyPreviewOffset" format="dimension" />
+
+        <!-- Height of the key press feedback popup. -->
+        <attr name="keyPreviewHeight" format="dimension" />
+
+        <!-- Amount to offset the touch Y coordinate by, for bias correction. -->
+        <attr name="verticalCorrection" format="dimension" />
+
+        <!-- Layout resource for popup keyboards. -->
+        <attr name="popupLayout" format="reference" />
+
+        <attr name="shadowColor" format="color" />
+        <attr name="shadowRadius" format="float" />
+        <attr name="backgroundDimAmount" format="float" />
+
+        <attr name="keyTextStyle">
+            <flag name="normal" value="0" />
+            <flag name="bold" value="1" />
+            <flag name="italic" value="2" />
+        </attr>
+
+        <attr name="symbolColorScheme">
+            <flag name="white" value="0" />
+            <flag name="black" value="1" />
+        </attr>
+
+    </declare-styleable>
+
+</resources>
diff --git a/java/res/values/bools.xml b/java/res/values/bools.xml
index ebe2f04..f5f2c3d 100644
--- a/java/res/values/bools.xml
+++ b/java/res/values/bools.xml
@@ -25,4 +25,7 @@
     <bool name="im_is_default">false</bool>
     <!-- Whether or not voice input is enabled by default. -->
     <bool name="voice_input_default">true</bool>
+    <bool name="config_swipeDisambiguation">true</bool>
+    <!-- Whether or not Popup on key press is enabled by default -->
+    <bool name="default_popup_preview">true</bool>
 </resources>
diff --git a/java/res/values/colors.xml b/java/res/values/colors.xml
index c90d9f6..343a940 100644
--- a/java/res/values/colors.xml
+++ b/java/res/values/colors.xml
@@ -21,4 +21,12 @@
     <color name="candidate_normal">#FF000000</color>
     <color name="candidate_recommended">#FFE35900</color>
     <color name="candidate_other">#ff808080</color>
-</resources>
\ No newline at end of file
+    <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">#FF808080</color>
+    <color name="latinkeyboard_extension_background">#A0000000</color>
+    <color name="latinkeyboard_text_color">#FF000000</color>
+    <color name="latinkeyboard_key_color_white">#FFFFFFFF</color>
+    <color name="latinkeyboard_key_color_black">#FF000000</color>
+</resources>
diff --git a/java/res/values/dimens.xml b/java/res/values/dimens.xml
index 5b2095c..39dce9d 100644
--- a/java/res/values/dimens.xml
+++ b/java/res/values/dimens.xml
@@ -23,4 +23,9 @@
     <dimen name="bubble_pointer_offset">22dip</dimen>
     <dimen name="candidate_strip_height">42dip</dimen>
     <dimen name="spacebar_vertical_correction">4dip</dimen>
-</resources>
\ No newline at end of file
+    <!-- If the screen height in landscape is larger than the below value, then the keyboard
+         will not go into extract (fullscreen) mode. -->
+    <dimen name="max_height_for_fullscreen">2.5in</dimen>
+    <dimen name="key_text_size">22sp</dimen>
+    <dimen name="key_debounce_hysteresis_distance">0.05in</dimen>
+</resources>
diff --git a/java/res/values/donottranslate.xml b/java/res/values/donottranslate.xml
index d501735..b7bfd9c 100644
--- a/java/res/values/donottranslate.xml
+++ b/java/res/values/donottranslate.xml
@@ -21,9 +21,9 @@
     <!-- 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>
+    <string name="sentence_separators">.,!?)</string>
     <!-- Symbols that are suggested between words -->
-    <string name="suggested_punctuations">!?,@_</string>
+    <string name="suggested_punctuations">!?,\u0022\u0027:()-/@_</string>
     <!-- Accented characters related to "d" -->
     <string name="alternates_for_d"></string>
     <!-- Accented characters related to "r" -->
diff --git a/java/res/values/strings.xml b/java/res/values/strings.xml
index 35dd3e0..c72cba7 100644
--- a/java/res/values/strings.xml
+++ b/java/res/values/strings.xml
@@ -25,9 +25,13 @@
 
     <!-- Option to provide vibrate/haptic feedback on keypress -->
     <string name="vibrate_on_keypress">Vibrate on keypress</string>
+
     <!-- Option to play back sound on keypress in soft keyboard -->
     <string name="sound_on_keypress">Sound on keypress</string>
-    
+
+    <!-- Option to pop up the character with a larger font above soft keyboard -->
+    <string name="popup_on_keypress">Popup on keypress</string>
+
     <!-- Option to enable using nearby keys when correcting/predicting -->
     <string name="hit_correction">Correct typing errors</string>
     
@@ -85,6 +89,11 @@
     <!-- Description for auto completion -->
     <string name="auto_complete_summary">Spacebar and punctuation automatically insert highlighted word</string>
     
+    <!-- Option to enable bigram completion -->
+    <string name="bigram_suggestion">Bigram Suggestions</string>
+    <!-- Description for auto completion -->
+    <string name="bigram_suggestion_summary">Use previous word to improve suggestion</string>
+
     <!-- Array of prediction modes -->
     <string-array name="prediction_modes">
         <item>None</item>
@@ -322,4 +331,34 @@
     
     <!-- Inform the user that a particular language has an available dictionary -->
     <string name="has_dictionary">Dictionary available</string>
+
+    <!-- Option to send logs -->
+    <string name="prefs_enable_log">Enable user feedback</string>
+    <!-- Description for sending logs -->
+    <string name="prefs_description_log">Help improve this input method editor by automatically sending usage statistics and crash reports to Google.</string>
+
+    <string name="keyboard_layout">Keyboard Theme</string>
+    <string name="layout_basic" translatable="false">Basic</string>
+    <string name="layout_high_contrast" translatable="false">Basic (High Contrast)</string>
+    <string name="layout_stone_bold"  translatable="false">Default (bold)</string>
+    <string name="layout_stone_normal"  translatable="false">Default (normal)</string>
+
+    <string-array name="keyboard_layout_modes" translatable="false">
+        <item>@string/layout_basic</item>
+        <item>@string/layout_high_contrast</item>
+        <item>@string/layout_stone_normal</item>
+        <item>@string/layout_stone_bold</item>
+    </string-array>
+
+    <string-array name="keyboard_layout_modes_values" translatable="false">
+        <item>0</item>
+        <item>1</item>
+        <item>2</item>
+        <item>3</item>
+    </string-array>
+
+    <string name="prefs_debug_mode">Debug (Temporary)</string>
+
+    <string name="subtype_mode_keyboard">keyboard</string>
+    <string name="subtype_mode_voice">voice</string>
 </resources>
diff --git a/java/res/values/styles.xml b/java/res/values/styles.xml
new file mode 100644
index 0000000..24fee02
--- /dev/null
+++ b/java/res/values/styles.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 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.
+-->
+
+<resources>
+    <style name="LatinKeyboardBaseView">
+        <item name="android:background">@drawable/keyboard_background</item>
+
+        <item name="keyBackground">@drawable/btn_keyboard_key</item>
+        <item name="keyTextSize">@dimen/key_text_size</item>
+        <item name="keyTextColor">#FFFFFFFF</item>
+        <item name="keyPreviewLayout">@layout/keyboard_key_preview</item>
+        <item name="keyPreviewOffset">-12dip</item>
+        <item name="keyPreviewHeight">80dip</item>
+        <item name="labelTextSize">14sp</item>
+        <item name="popupLayout">@layout/keyboard_popup_keyboard</item>
+        <item name="verticalCorrection">-10dip</item>
+        <item name="shadowColor">#BB000000</item>
+        <item name="shadowRadius">2.75</item>
+        <item name="backgroundDimAmount">0.5</item>
+        <item name="symbolColorScheme">white</item>
+    </style>
+</resources>
diff --git a/java/res/xml-da/kbd_qwerty.xml b/java/res/xml-da/kbd_qwerty.xml
new file mode 100644
index 0000000..472f8be
--- /dev/null
+++ b/java/res/xml-da/kbd_qwerty.xml
@@ -0,0 +1,213 @@
+<?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.
+*/
+-->
+
+<!--
+    Danish Keyboard Layout
+
+    Just a copy of the Norwegian layout, with æ/ø switched.
+-->
+
+<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
+    android:keyWidth="9.09%p"
+    android:horizontalGap="0px"
+    android:verticalGap="0px"
+    android:keyHeight="@dimen/key_height"
+    >
+
+    <Row>
+        <Key android:codes="113" android:keyLabel="q"
+                android:keyWidth="8.75%p" android:keyEdgeFlags="left"/>
+        <Key android:codes="119" android:keyLabel="w"/>
+        <Key android:codes="101" android:keyLabel="e"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="éèêëę€"/>
+        <Key android:codes="114" android:keyLabel="r"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="ř"/>
+        <Key android:codes="116" android:keyLabel="t"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="ťþ"/>
+        <Key android:codes="121" android:keyLabel="y"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="ýÿü"/>
+        <Key android:codes="117" android:keyLabel="u"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="úùûū"/>
+        <Key android:codes="105" android:keyLabel="i"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="íìîï"/>
+        <Key android:codes="111" android:keyLabel="o"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="óòôõ"/>
+        <Key android:codes="112" android:keyLabel="p"/>
+        <Key android:keyLabel="å"
+                android:keyWidth="8.75%p" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row>
+        <Key android:codes="97" android:keyLabel="a"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="áàâąã"
+                android:keyWidth="8.75%p" android:keyEdgeFlags="left"/>
+        <Key android:codes="115" android:keyLabel="s"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="śšşß"/>
+        <Key android:codes="100" android:keyLabel="d"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="ðď"/>
+        <Key android:codes="102" android:keyLabel="f"/>
+        <Key android:codes="103" android:keyLabel="g"/>
+        <Key android:codes="104" android:keyLabel="h"/>
+        <Key android:codes="106" android:keyLabel="j"/>
+        <Key android:codes="107" android:keyLabel="k"/>
+        <Key android:codes="108" android:keyLabel="l"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="ł"/>
+        <Key android:keyLabel="æ"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="ä"/>
+        <Key android:keyLabel="ø"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="öœ"
+                android:keyWidth="8.75%p" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row android:keyWidth="10%p">
+        <Key android:codes="-1" android:keyIcon="@drawable/sym_keyboard_shift"
+                android:keyWidth="15%p" android:isModifier="true"
+                android:iconPreview="@drawable/sym_keyboard_feedback_shift"
+                android:isSticky="true" android:keyEdgeFlags="left"/>
+        <Key android:codes="122" android:keyLabel="z"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="źžż"/>
+        <Key android:codes="120" android:keyLabel="x"/>
+        <Key android:codes="99" android:keyLabel="c"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="çćč"/>
+        <Key android:codes="118" android:keyLabel="v"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="w"/>
+        <Key android:codes="98" android:keyLabel="b"/>
+        <Key android:codes="110" android:keyLabel="n"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="ńñň"/>
+        <Key android:codes="109" android:keyLabel="m"/>
+        <Key android:codes="-5" android:keyIcon="@drawable/sym_keyboard_delete"
+                android:keyWidth="15%p" android:keyEdgeFlags="right"
+                android:iconPreview="@drawable/sym_keyboard_feedback_delete"
+                android:isRepeatable="true"/>
+    </Row>
+
+    <Row android:keyboardMode="@+id/mode_normal" android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyLabel="@string/label_symbol_key"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="_"
+                android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+        <Key android:codes="@integer/key_f1" android:keyIcon="@drawable/sym_keyboard_mic"
+                android:iconPreview="@drawable/sym_keyboard_feedback_mic"
+                android:keyWidth="10%p"/>
+        <Key android:codes="32" android:keyIcon="@drawable/sym_keyboard_space"
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:keyWidth="40%p" android:isRepeatable="true"/>
+        <Key android:codes="46" android:keyLabel="." android:popupKeyboard="@xml/popup_punctuation"
+                android:keyWidth="10%p"/>
+        <Key android:codes="10" android:keyIcon="@drawable/sym_keyboard_return"
+                android:iconPreview="@drawable/sym_keyboard_feedback_return"
+                android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row android:keyboardMode="@+id/mode_url" android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyLabel="@string/label_symbol_key"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="_"
+                android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+        <Key android:keyLabel="/" android:keyWidth="10%p"/>
+        <Key android:codes="32" android:keyIcon="@drawable/sym_keyboard_space"
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:keyWidth="40%p" android:isRepeatable="true"/>
+        <!--Key android:keyLabel="@string/popular_domain_0"
+                android:keyOutputText="@string/popular_domain_0"
+                android:popupKeyboard="@xml/popup_domains"
+                android:keyWidth="20%p"/-->
+        <Key android:keyLabel="." android:popupKeyboard="@xml/popup_punctuation"
+                android:keyWidth="10%p"/>
+        <Key android:codes="10" android:keyIcon="@drawable/sym_keyboard_return"
+                android:iconPreview="@drawable/sym_keyboard_feedback_return"
+                android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row android:keyboardMode="@+id/mode_email" android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyLabel="@string/label_symbol_key"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="_"
+                android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+        <Key android:keyLabel="\@"/>
+        <Key android:codes="32" android:keyIcon="@drawable/sym_keyboard_space"
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:keyWidth="40%p" android:isRepeatable="true"/>
+        <!--Key android:keyLabel="@string/popular_domain_0"
+                android:keyOutputText="@string/popular_domain_0"
+                android:popupKeyboard="@xml/popup_domains"
+                android:keyWidth="20%p"/-->
+        <Key android:keyLabel="." android:popupKeyboard="@xml/popup_punctuation"/>
+        <Key android:codes="10" android:keyIcon="@drawable/sym_keyboard_return"
+                android:iconPreview="@drawable/sym_keyboard_feedback_return"
+                android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row android:keyboardMode="@+id/mode_im" android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyLabel="@string/label_symbol_key"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="_"
+                android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+        <Key android:codes="@integer/key_f1" android:keyIcon="@drawable/sym_keyboard_mic"
+                android:iconPreview="@drawable/sym_keyboard_feedback_mic"
+                android:keyWidth="10%p"/>
+        <Key android:codes="32" android:keyIcon="@drawable/sym_keyboard_space"
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:keyWidth="40%p" android:isRepeatable="true"/>
+        <Key android:codes="46" android:keyLabel="." android:popupKeyboard="@xml/popup_punctuation"
+                android:keyWidth="10%p"/>
+        <Key android:keyLabel=":-)" android:keyOutputText=":-) "
+                android:popupKeyboard="@xml/popup_smileys"
+                android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row android:keyboardMode="@+id/mode_webentry" android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyLabel="@string/label_symbol_key"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="_"
+                android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+        <Key android:codes="@integer/key_f1" android:keyIcon="@drawable/sym_keyboard_mic"
+                android:iconPreview="@drawable/sym_keyboard_feedback_mic"
+                android:keyWidth="10%p"/>
+        <Key android:codes="32" android:keyIcon="@drawable/sym_keyboard_space"
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:keyWidth="20%p" android:isRepeatable="true"/>
+        <Key android:codes="9" android:keyIcon="@drawable/sym_keyboard_tab"
+                android:iconPreview="@drawable/sym_keyboard_feedback_tab"
+                android:keyWidth="20%p"/>
+        <Key android:keyLabel="." android:popupKeyboard="@xml/popup_punctuation"/>
+        <Key android:codes="10" android:keyIcon="@drawable/sym_keyboard_return"
+                android:iconPreview="@drawable/sym_keyboard_feedback_return"
+                android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+
+</Keyboard>
diff --git a/java/res/xml-da/kbd_qwerty_black.xml b/java/res/xml-da/kbd_qwerty_black.xml
new file mode 100644
index 0000000..2b41cf1
--- /dev/null
+++ b/java/res/xml-da/kbd_qwerty_black.xml
@@ -0,0 +1,213 @@
+<?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.
+*/
+-->
+
+<!--
+    Danish Keyboard Layout
+
+    Just a copy of the Norwegian layout, with æ/ø switched.
+-->
+
+<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
+    android:keyWidth="9.09%p"
+    android:horizontalGap="0px"
+    android:verticalGap="0px"
+    android:keyHeight="@dimen/key_height"
+    >
+
+    <Row>
+        <Key android:codes="113" android:keyLabel="q"
+                android:keyWidth="8.75%p" android:keyEdgeFlags="left"/>
+        <Key android:codes="119" android:keyLabel="w"/>
+        <Key android:codes="101" android:keyLabel="e"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="éèêëę€"/>
+        <Key android:codes="114" android:keyLabel="r"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="ř"/>
+        <Key android:codes="116" android:keyLabel="t"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="ťþ"/>
+        <Key android:codes="121" android:keyLabel="y"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="ýÿü"/>
+        <Key android:codes="117" android:keyLabel="u"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="úùûū"/>
+        <Key android:codes="105" android:keyLabel="i"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="íìîï"/>
+        <Key android:codes="111" android:keyLabel="o"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="óòôõ"/>
+        <Key android:codes="112" android:keyLabel="p"/>
+        <Key android:keyLabel="å"
+                android:keyWidth="8.75%p" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row>
+        <Key android:codes="97" android:keyLabel="a"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="áàâąã"
+                android:keyWidth="8.75%p" android:keyEdgeFlags="left"/>
+        <Key android:codes="115" android:keyLabel="s"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="śšşß"/>
+        <Key android:codes="100" android:keyLabel="d"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="ðď"/>
+        <Key android:codes="102" android:keyLabel="f"/>
+        <Key android:codes="103" android:keyLabel="g"/>
+        <Key android:codes="104" android:keyLabel="h"/>
+        <Key android:codes="106" android:keyLabel="j"/>
+        <Key android:codes="107" android:keyLabel="k"/>
+        <Key android:codes="108" android:keyLabel="l"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="ł"/>
+        <Key android:keyLabel="æ"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="ä"/>
+        <Key android:keyLabel="ø"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="öœ"
+                android:keyWidth="8.75%p" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row android:keyWidth="10%p">
+        <Key android:codes="-1" android:keyIcon="@drawable/sym_bkeyboard_shift"
+                android:keyWidth="15%p" android:isModifier="true"
+                android:iconPreview="@drawable/sym_keyboard_feedback_shift"
+                android:isSticky="true" android:keyEdgeFlags="left"/>
+        <Key android:codes="122" android:keyLabel="z"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="źžż"/>
+        <Key android:codes="120" android:keyLabel="x"/>
+        <Key android:codes="99" android:keyLabel="c"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="çćč"/>
+        <Key android:codes="118" android:keyLabel="v"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="w"/>
+        <Key android:codes="98" android:keyLabel="b"/>
+        <Key android:codes="110" android:keyLabel="n"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="ńñň"/>
+        <Key android:codes="109" android:keyLabel="m"/>
+        <Key android:codes="-5" android:keyIcon="@drawable/sym_bkeyboard_delete"
+                android:keyWidth="15%p" android:keyEdgeFlags="right"
+                android:iconPreview="@drawable/sym_keyboard_feedback_delete"
+                android:isRepeatable="true"/>
+    </Row>
+
+    <Row android:keyboardMode="@+id/mode_normal" android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyLabel="@string/label_symbol_key"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="_"
+                android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+        <Key android:codes="@integer/key_f1" android:keyIcon="@drawable/sym_bkeyboard_mic"
+                android:iconPreview="@drawable/sym_keyboard_feedback_mic"
+                android:keyWidth="10%p"/>
+        <Key android:codes="32" android:keyIcon="@drawable/sym_bkeyboard_space"
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:keyWidth="40%p" android:isRepeatable="true"/>
+        <Key android:codes="46" android:keyLabel="." android:popupKeyboard="@xml/popup_punctuation"
+                android:keyWidth="10%p"/>
+        <Key android:codes="10" android:keyIcon="@drawable/sym_bkeyboard_return"
+                android:iconPreview="@drawable/sym_keyboard_feedback_return"
+                android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row android:keyboardMode="@+id/mode_url" android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyLabel="@string/label_symbol_key"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="_"
+                android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+        <Key android:keyLabel="/" android:keyWidth="10%p"/>
+        <Key android:codes="32" android:keyIcon="@drawable/sym_bkeyboard_space"
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:keyWidth="40%p" android:isRepeatable="true"/>
+        <!--Key android:keyLabel="@string/popular_domain_0"
+                android:keyOutputText="@string/popular_domain_0"
+                android:popupKeyboard="@xml/popup_domains"
+                android:keyWidth="20%p"/-->
+        <Key android:keyLabel="." android:popupKeyboard="@xml/popup_punctuation"
+                android:keyWidth="10%p"/>
+        <Key android:codes="10" android:keyIcon="@drawable/sym_bkeyboard_return"
+                android:iconPreview="@drawable/sym_keyboard_feedback_return"
+                android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row android:keyboardMode="@+id/mode_email" android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyLabel="@string/label_symbol_key"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="_"
+                android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+        <Key android:keyLabel="\@"/>
+        <Key android:codes="32" android:keyIcon="@drawable/sym_bkeyboard_space"
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:keyWidth="40%p" android:isRepeatable="true"/>
+        <!--Key android:keyLabel="@string/popular_domain_0"
+                android:keyOutputText="@string/popular_domain_0"
+                android:popupKeyboard="@xml/popup_domains"
+                android:keyWidth="20%p"/-->
+        <Key android:keyLabel="." android:popupKeyboard="@xml/popup_punctuation"/>
+        <Key android:codes="10" android:keyIcon="@drawable/sym_bkeyboard_return"
+                android:iconPreview="@drawable/sym_keyboard_feedback_return"
+                android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row android:keyboardMode="@+id/mode_im" android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyLabel="@string/label_symbol_key"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="_"
+                android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+        <Key android:codes="@integer/key_f1" android:keyIcon="@drawable/sym_bkeyboard_mic"
+                android:iconPreview="@drawable/sym_keyboard_feedback_mic"
+                android:keyWidth="10%p"/>
+        <Key android:codes="32" android:keyIcon="@drawable/sym_bkeyboard_space"
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:keyWidth="40%p" android:isRepeatable="true"/>
+        <Key android:codes="46" android:keyLabel="." android:popupKeyboard="@xml/popup_punctuation"
+                android:keyWidth="10%p"/>
+        <Key android:keyLabel=":-)" android:keyOutputText=":-) "
+                android:popupKeyboard="@xml/popup_smileys"
+                android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row android:keyboardMode="@+id/mode_webentry" android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyLabel="@string/label_symbol_key"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="_"
+                android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+        <Key android:codes="@integer/key_f1" android:keyIcon="@drawable/sym_bkeyboard_mic"
+                android:iconPreview="@drawable/sym_keyboard_feedback_mic"
+                android:keyWidth="10%p"/>
+        <Key android:codes="32" android:keyIcon="@drawable/sym_bkeyboard_space"
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:keyWidth="20%p" android:isRepeatable="true"/>
+        <Key android:codes="9" android:keyIcon="@drawable/sym_bkeyboard_tab"
+                android:iconPreview="@drawable/sym_keyboard_feedback_tab"
+                android:keyWidth="20%p"/>
+        <Key android:keyLabel="." android:popupKeyboard="@xml/popup_punctuation"/>
+        <Key android:codes="10" android:keyIcon="@drawable/sym_bkeyboard_return"
+                android:iconPreview="@drawable/sym_keyboard_feedback_return"
+                android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+
+</Keyboard>
diff --git a/java/res/xml-de/kbd_qwerty_black.xml b/java/res/xml-de/kbd_qwerty_black.xml
new file mode 100755
index 0000000..366f871
--- /dev/null
+++ b/java/res/xml-de/kbd_qwerty_black.xml
@@ -0,0 +1,189 @@
+<?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.
+*/
+-->
+
+<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
+    android:keyWidth="10%p"
+    android:horizontalGap="0px"
+    android:verticalGap="0px"
+    android:keyHeight="@dimen/key_height"
+    >
+
+    <Row>
+        <Key android:codes="113" android:keyLabel="q" android:keyEdgeFlags="left"/>
+        <Key android:codes="119" android:keyLabel="w"/>
+        <Key android:codes="101" android:keyLabel="e"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_e"
+        />
+        <Key android:codes="114" android:keyLabel="r"/>
+        <Key android:codes="116" android:keyLabel="t"/>
+        <Key android:codes="122" android:keyLabel="z" />
+        <Key android:codes="117" android:keyLabel="u"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_u"
+        />
+        <Key android:codes="105" android:keyLabel="i"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_i"
+        />
+        <Key android:codes="111" android:keyLabel="o"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_o"
+        />
+        <Key android:codes="112" android:keyLabel="p" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row>
+        <Key android:codes="97" android:keyLabel="a" android:horizontalGap="5%p"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_a"
+                android:keyEdgeFlags="left"/>
+        <Key android:codes="115" android:keyLabel="s"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_s"
+        />
+        <Key android:codes="100" android:keyLabel="d"/>
+        <Key android:codes="102" android:keyLabel="f"/>
+        <Key android:codes="103" android:keyLabel="g"/>
+        <Key android:codes="104" android:keyLabel="h"/>
+        <Key android:codes="106" android:keyLabel="j"/>
+        <Key android:codes="107" android:keyLabel="k"/>
+        <Key android:codes="108" android:keyLabel="l" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row>
+        <Key android:codes="-1" android:keyIcon="@drawable/sym_bkeyboard_shift"
+                android:keyWidth="15%p" android:isModifier="true"
+                android:iconPreview="@drawable/sym_keyboard_feedback_shift"
+                android:isSticky="true" android:keyEdgeFlags="left"/>
+        <Key android:codes="121" android:keyLabel="y"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_y"
+        />
+        <Key android:codes="120" android:keyLabel="x"/>
+        <Key android:codes="99" android:keyLabel="c"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_c"
+        />
+        <Key android:codes="118" android:keyLabel="v"/>
+        <Key android:codes="98" android:keyLabel="b"/>
+        <Key android:codes="110" android:keyLabel="n"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_n"
+        />
+        <Key android:codes="109" android:keyLabel="m"/>
+        <Key android:codes="-5" android:keyIcon="@drawable/sym_bkeyboard_delete"
+                android:keyWidth="15%p" android:keyEdgeFlags="right"
+                android:iconPreview="@drawable/sym_keyboard_feedback_delete"
+                android:isRepeatable="true"/>
+    </Row>
+
+    <Row android:keyboardMode="@+id/mode_normal" android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyLabel="@string/label_symbol_key"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="_"
+                android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+        <Key android:codes="@integer/key_f1"
+                android:keyWidth="10%p"/>
+        <Key android:codes="32" android:keyIcon="@drawable/sym_bkeyboard_space"
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:keyWidth="40%p" android:isRepeatable="true"/>
+        <Key android:codes="46" android:keyLabel="." android:popupKeyboard="@xml/popup_punctuation"
+                android:keyWidth="10%p"/>
+        <Key android:codes="10" android:keyIcon="@drawable/sym_bkeyboard_return"
+                android:iconPreview="@drawable/sym_keyboard_feedback_return"
+                android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row android:keyboardMode="@+id/mode_url" android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyLabel="@string/label_symbol_key"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="_"
+                android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+        <Key android:keyLabel="/" android:keyWidth="10%p"/>
+        <Key android:codes="32" android:keyIcon="@drawable/sym_bkeyboard_space"
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:keyWidth="40%p" android:isRepeatable="true"/>
+        <!--Key android:keyLabel="@string/popular_domain_0"
+                android:keyOutputText="@string/popular_domain_0"
+                android:popupKeyboard="@xml/popup_domains"
+                android:keyWidth="20%p"/-->
+        <Key android:keyLabel="." android:popupKeyboard="@xml/popup_punctuation"
+                android:keyWidth="10%p"/>
+        <Key android:codes="10" android:keyIcon="@drawable/sym_bkeyboard_return"
+                android:iconPreview="@drawable/sym_keyboard_feedback_return"
+                android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row android:keyboardMode="@+id/mode_email" android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyLabel="@string/label_symbol_key"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="_"
+                android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+        <Key android:keyLabel="\@"/>
+        <Key android:codes="32" android:keyIcon="@drawable/sym_bkeyboard_space"
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:keyWidth="40%p" android:isRepeatable="true"/>
+        <!--Key android:keyLabel="@string/popular_domain_0"
+                android:keyOutputText="@string/popular_domain_0"
+                android:popupKeyboard="@xml/popup_domains"
+                android:keyWidth="20%p"/-->
+        <Key android:keyLabel="." android:popupKeyboard="@xml/popup_punctuation"/>
+        <Key android:codes="10" android:keyIcon="@drawable/sym_bkeyboard_return"
+                android:iconPreview="@drawable/sym_keyboard_feedback_return"
+                android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row android:keyboardMode="@+id/mode_im" android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyLabel="@string/label_symbol_key"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="_"
+                android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+        <Key android:codes="@integer/key_f1" android:keyIcon="@drawable/sym_bkeyboard_globe"
+                android:keyWidth="10%p"/>
+        <Key android:codes="32" android:keyIcon="@drawable/sym_bkeyboard_space"
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:keyWidth="40%p" android:isRepeatable="true"/>
+        <Key android:codes="46" android:keyLabel="." android:popupKeyboard="@xml/popup_punctuation"
+                android:keyWidth="10%p"/>
+        <Key android:keyLabel=":-)" android:keyOutputText=":-) "
+                android:popupKeyboard="@xml/popup_smileys"
+                android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row android:keyboardMode="@+id/mode_webentry" android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyLabel="@string/label_symbol_key"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="_"
+                android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+        <Key android:codes="@integer/key_f1" android:keyIcon="@drawable/sym_bkeyboard_globe"
+                android:keyWidth="10%p"/>
+        <Key android:codes="32" android:keyIcon="@drawable/sym_bkeyboard_space"
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:keyWidth="20%p" android:isRepeatable="true"/>
+        <Key android:codes="9" android:keyIcon="@drawable/sym_bkeyboard_tab"
+                android:iconPreview="@drawable/sym_keyboard_feedback_tab"
+                android:keyWidth="20%p"/>
+        <Key android:keyLabel="." android:popupKeyboard="@xml/popup_punctuation"/>
+        <Key android:codes="10" android:keyIcon="@drawable/sym_bkeyboard_return"
+                android:iconPreview="@drawable/sym_keyboard_feedback_return"
+                android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+</Keyboard>
diff --git a/java/res/xml-fr/kbd_qwerty_black.xml b/java/res/xml-fr/kbd_qwerty_black.xml
new file mode 100644
index 0000000..1b799a5
--- /dev/null
+++ b/java/res/xml-fr/kbd_qwerty_black.xml
@@ -0,0 +1,192 @@
+<?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.
+*/
+-->
+
+<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
+    android:keyWidth="10%p"
+    android:horizontalGap="0px"
+    android:verticalGap="0px"
+    android:keyHeight="@dimen/key_height"
+    >
+
+    <Row>
+        <Key android:codes="97" android:keyLabel="a"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_a"
+                android:keyEdgeFlags="left"/>
+        <Key android:codes="122" android:keyLabel="z"/>
+        <Key android:codes="101" android:keyLabel="e"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_e"
+        />
+        <Key android:codes="114" android:keyLabel="r"/>
+        <Key android:codes="116" android:keyLabel="t"/>
+        <Key android:codes="121" android:keyLabel="y"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_y"
+        />
+        <Key android:codes="117" android:keyLabel="u"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_u"
+        />
+        <Key android:codes="105" android:keyLabel="i"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_i"
+        />
+        <Key android:codes="111" android:keyLabel="o"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_o"
+        />
+        <Key android:codes="112" android:keyLabel="p" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row>
+        <Key android:codes="113" android:keyLabel="q" android:keyEdgeFlags="left"/>
+        <Key android:codes="115" android:keyLabel="s"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_s"
+        />
+        <Key android:codes="100" android:keyLabel="d"/>
+        <Key android:codes="102" android:keyLabel="f"/>
+        <Key android:codes="103" android:keyLabel="g"/>
+        <Key android:codes="104" android:keyLabel="h"/>
+        <Key android:codes="106" android:keyLabel="j"/>
+        <Key android:codes="107" android:keyLabel="k"/>
+        <Key android:codes="108" android:keyLabel="l"/>
+        <Key android:codes="109" android:keyLabel="m" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row>
+        <Key android:codes="-1" android:keyIcon="@drawable/sym_bkeyboard_shift"
+                android:keyWidth="15%p" android:isModifier="true"
+                android:iconPreview="@drawable/sym_keyboard_feedback_shift"
+                android:isSticky="true" android:keyEdgeFlags="left"/>
+        <Key android:codes="119" android:keyLabel="w"/>
+        <Key android:codes="120" android:keyLabel="x"/>
+        <Key android:codes="99" android:keyLabel="c"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_c"
+        />
+        <Key android:codes="118" android:keyLabel="v"/>
+        <Key android:codes="98" android:keyLabel="b"/>
+        <Key android:codes="110" android:keyLabel="n"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_n"
+        />
+        <!--Key android:codes="233,224,232,234" android:keyLabel="é"/-->
+        <Key android:keyLabel="\'"/>
+        <Key android:codes="-5" android:keyIcon="@drawable/sym_bkeyboard_delete"
+                android:keyWidth="15%p" android:keyEdgeFlags="right"
+                android:iconPreview="@drawable/sym_keyboard_feedback_delete"
+                android:isRepeatable="true"/>
+    </Row>
+
+    <Row android:keyboardMode="@+id/mode_normal" android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyLabel="@string/label_symbol_key"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="_"
+                android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+        <Key android:codes="@integer/key_f1" android:keyIcon="@drawable/sym_bkeyboard_globe"
+                android:keyWidth="10%p"/>
+        <Key android:codes="32" android:keyIcon="@drawable/sym_bkeyboard_space"
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:keyWidth="40%p" android:isRepeatable="true"/>
+        <Key android:codes="46" android:keyLabel="." android:popupKeyboard="@xml/popup_punctuation"
+                android:keyWidth="10%p"/>
+        <Key android:codes="10" android:keyIcon="@drawable/sym_bkeyboard_return"
+                android:iconPreview="@drawable/sym_keyboard_feedback_return"
+                android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row android:keyboardMode="@+id/mode_url" android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyLabel="@string/label_symbol_key"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="_"
+                android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+        <Key android:keyLabel="/" android:keyWidth="10%p"/>
+        <Key android:codes="32" android:keyIcon="@drawable/sym_bkeyboard_space"
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:keyWidth="40%p" android:isRepeatable="true"/>
+        <!--Key android:keyLabel="@string/popular_domain_0"
+                android:keyOutputText="@string/popular_domain_0"
+                android:popupKeyboard="@xml/popup_domains"
+                android:keyWidth="20%p"/-->
+        <Key android:keyLabel="." android:popupKeyboard="@xml/popup_punctuation"
+                android:keyWidth="10%p"/>
+        <Key android:codes="10" android:keyIcon="@drawable/sym_bkeyboard_return"
+                android:iconPreview="@drawable/sym_keyboard_feedback_return"
+                android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row android:keyboardMode="@+id/mode_email" android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyLabel="@string/label_symbol_key"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="_"
+                android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+        <Key android:keyLabel="\@"/>
+        <Key android:codes="32" android:keyIcon="@drawable/sym_bkeyboard_space"
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:keyWidth="40%p" android:isRepeatable="true"/>
+        <!--Key android:keyLabel="@string/popular_domain_0"
+                android:keyOutputText="@string/popular_domain_0"
+                android:popupKeyboard="@xml/popup_domains"
+                android:keyWidth="20%p"/-->
+        <Key android:keyLabel="." android:popupKeyboard="@xml/popup_punctuation"/>
+        <Key android:codes="10" android:keyIcon="@drawable/sym_bkeyboard_return"
+                android:iconPreview="@drawable/sym_keyboard_feedback_return"
+                android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row android:keyboardMode="@+id/mode_im" android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyLabel="@string/label_symbol_key"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="_"
+                android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+        <Key android:codes="@integer/key_f1" android:keyIcon="@drawable/sym_bkeyboard_globe"
+                android:keyWidth="10%p"/>
+        <Key android:codes="32" android:keyIcon="@drawable/sym_bkeyboard_space"
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:keyWidth="40%p" android:isRepeatable="true"/>
+        <Key android:codes="46" android:keyLabel="." android:popupKeyboard="@xml/popup_punctuation"
+                android:keyWidth="10%p"/>
+        <Key android:keyLabel=":-)" android:keyOutputText=":-) "
+                android:popupKeyboard="@xml/popup_smileys"
+                android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row android:keyboardMode="@+id/mode_webentry" android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyLabel="@string/label_symbol_key"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="_"
+                android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+        <Key android:codes="@integer/key_f1" android:keyIcon="@drawable/sym_bkeyboard_globe"
+                android:keyWidth="10%p"/>
+        <Key android:codes="32" android:keyIcon="@drawable/sym_bkeyboard_space"
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:keyWidth="20%p" android:isRepeatable="true"/>
+        <Key android:codes="9" android:keyIcon="@drawable/sym_bkeyboard_tab"
+                android:iconPreview="@drawable/sym_keyboard_feedback_tab"
+                android:keyWidth="20%p"/>
+        <Key android:keyLabel="." android:popupKeyboard="@xml/popup_punctuation"/>
+        <Key android:codes="10" android:keyIcon="@drawable/sym_bkeyboard_return"
+                android:iconPreview="@drawable/sym_keyboard_feedback_return"
+                android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+
+</Keyboard>
diff --git a/java/res/xml-iw/kbd_qwerty.xml b/java/res/xml-iw/kbd_qwerty.xml
new file mode 100755
index 0000000..b893f1a
--- /dev/null
+++ b/java/res/xml-iw/kbd_qwerty.xml
@@ -0,0 +1,164 @@
+<?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.
+*/
+-->
+
+<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
+    android:keyWidth="10%p"
+    android:horizontalGap="0px"
+    android:verticalGap="0px"
+    android:keyHeight="@dimen/key_height"
+    >
+
+    <Row>
+        <Key android:keyLabel="ק" 
+	     android:horizontalGap="5%p"
+	     android:keyEdgeFlags="left"/>
+        <Key android:keyLabel="ר"/>
+        <Key android:keyLabel="א"/>
+        <Key android:keyLabel="ט"/>
+        <Key android:keyLabel="ו"/>
+        <Key android:keyLabel="ן"/>
+        <Key android:keyLabel="ם"/>
+        <Key android:keyLabel="פ"/>
+        <Key android:codes="-5" 
+	     android:horizontalGap="1.25%p"
+	     android:keyIcon="@drawable/sym_keyboard_delete"
+            android:keyWidth="13.75%p" android:keyEdgeFlags="right"
+            android:iconPreview="@drawable/sym_keyboard_feedback_delete"
+            android:isRepeatable="true"/>
+    </Row>
+
+    <Row>
+        <Key android:keyLabel="ש" android:keyEdgeFlags="left"/>
+        <Key android:keyLabel="ד"/>
+        <Key android:keyLabel="ג"/>
+        <Key android:keyLabel="כ"/>
+        <Key android:keyLabel="ע"/>
+        <Key android:keyLabel="י"/>
+        <Key android:keyLabel="ח"/>
+        <Key android:keyLabel="ל"/>
+        <Key android:keyLabel="ך"/>
+        <Key android:keyLabel="ף" android:keyEdgeFlags="right"/>
+    </Row>
+    
+    <Row>
+        <Key android:keyLabel="ז" android:horizontalGap="5%p" android:keyEdgeFlags="left"/>
+        <Key android:keyLabel="ס"/>
+        <Key android:keyLabel="ב"/>
+        <Key android:keyLabel="ה"/>
+        <Key android:keyLabel="נ"/>
+        <Key android:keyLabel="מ"/>
+        <Key android:keyLabel="צ"/>
+        <Key android:keyLabel="ת"/>
+        <Key android:keyLabel="ץ" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row android:keyboardMode="@+id/mode_normal" android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyLabel="@string/label_symbol_key" 
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="_"
+                android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+        <Key android:codes="@integer/key_f1" android:keyIcon="@drawable/sym_keyboard_globe"
+                android:keyWidth="10%p"/>
+        <Key android:codes="32" android:keyIcon="@drawable/sym_keyboard_space"
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:keyWidth="40%p" android:isRepeatable="true"/>
+        <Key android:codes="46" android:keyLabel="." android:popupKeyboard="@xml/popup_punctuation" 
+                android:keyWidth="10%p"/>
+        <Key android:codes="10" android:keyIcon="@drawable/sym_keyboard_return"
+                android:iconPreview="@drawable/sym_keyboard_feedback_return"
+                android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row android:keyboardMode="@+id/mode_url" android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyLabel="@string/label_symbol_key"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="_"
+                android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+        <Key android:keyLabel="/" android:keyWidth="10%p"/>
+        <Key android:codes="32" android:keyIcon="@drawable/sym_keyboard_space"
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:keyWidth="40%p" android:isRepeatable="true"/>
+        <!--Key android:keyLabel="@string/popular_domain_0"
+                android:keyOutputText="@string/popular_domain_0"
+                android:popupKeyboard="@xml/popup_domains"
+                android:keyWidth="20%p"/-->
+        <Key android:keyLabel="." android:popupKeyboard="@xml/popup_punctuation"
+                android:keyWidth="10%p"/>
+        <Key android:codes="10" android:keyIcon="@drawable/sym_keyboard_return"
+                android:iconPreview="@drawable/sym_keyboard_feedback_return"
+                android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row android:keyboardMode="@+id/mode_email" android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyLabel="@string/label_symbol_key"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="_"
+                android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+        <Key android:keyLabel="\@" android:keyWidth="10%p"/>
+        <Key android:codes="32" android:keyIcon="@drawable/sym_keyboard_space"
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:keyWidth="40%p" android:isRepeatable="true"/>
+        <!--Key android:keyLabel="@string/popular_domain_0"
+                android:keyOutputText="@string/popular_domain_0"
+                android:popupKeyboard="@xml/popup_domains"
+                android:keyWidth="20%p"/-->
+        <Key android:keyLabel="." android:popupKeyboard="@xml/popup_punctuation"/>
+        <Key android:codes="10" android:keyIcon="@drawable/sym_keyboard_return"
+                android:iconPreview="@drawable/sym_keyboard_feedback_return"
+                android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row android:keyboardMode="@+id/mode_im" android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyLabel="@string/label_symbol_key"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="_"
+                android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+        <Key android:codes="@integer/key_f1" android:keyIcon="@drawable/sym_keyboard_globe"
+                android:keyWidth="10%p"/>
+        <Key android:codes="32" android:keyIcon="@drawable/sym_keyboard_space"
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:keyWidth="40%p" android:isRepeatable="true"/>
+        <Key android:codes="46" android:keyLabel="." android:popupKeyboard="@xml/popup_punctuation" 
+                android:keyWidth="10%p"/>
+        <Key android:keyLabel=":-)" android:keyOutputText=":-) "
+                android:popupKeyboard="@xml/popup_smileys"
+                android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row android:keyboardMode="@+id/mode_webentry" android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyLabel="@string/label_symbol_key"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="_"
+                android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+        <Key android:codes="@integer/key_f1" android:keyIcon="@drawable/sym_keyboard_globe"
+                android:keyWidth="10%p"/>
+        <Key android:codes="32" android:keyIcon="@drawable/sym_keyboard_space"
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:keyWidth="20%p" android:isRepeatable="true"/>
+        <Key android:codes="9" android:keyIcon="@drawable/sym_keyboard_tab"
+                android:iconPreview="@drawable/sym_keyboard_feedback_tab"
+                android:keyWidth="20%p"/>
+        <Key android:keyLabel="." android:popupKeyboard="@xml/popup_punctuation"/>
+        <Key android:codes="10" android:keyIcon="@drawable/sym_keyboard_return"
+                android:iconPreview="@drawable/sym_keyboard_feedback_return"
+                android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+</Keyboard>
+    
diff --git a/java/res/xml-iw/kbd_qwerty_black.xml b/java/res/xml-iw/kbd_qwerty_black.xml
new file mode 100755
index 0000000..0dcf513
--- /dev/null
+++ b/java/res/xml-iw/kbd_qwerty_black.xml
@@ -0,0 +1,164 @@
+<?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.
+*/
+-->
+
+<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
+    android:keyWidth="10%p"
+    android:horizontalGap="0px"
+    android:verticalGap="0px"
+    android:keyHeight="@dimen/key_height"
+    >
+
+    <Row>
+        <Key android:keyLabel="ק"
+	     android:horizontalGap="5%p"
+	     android:keyEdgeFlags="left"/>
+        <Key android:keyLabel="ר"/>
+        <Key android:keyLabel="א"/>
+        <Key android:keyLabel="ט"/>
+        <Key android:keyLabel="ו"/>
+        <Key android:keyLabel="ן"/>
+        <Key android:keyLabel="ם"/>
+        <Key android:keyLabel="פ"/>
+        <Key android:codes="-5"
+	     android:horizontalGap="1.25%p"
+	     android:keyIcon="@drawable/sym_bkeyboard_delete"
+            android:keyWidth="13.75%p" android:keyEdgeFlags="right"
+            android:iconPreview="@drawable/sym_keyboard_feedback_delete"
+            android:isRepeatable="true"/>
+    </Row>
+
+    <Row>
+        <Key android:keyLabel="ש" android:keyEdgeFlags="left"/>
+        <Key android:keyLabel="ד"/>
+        <Key android:keyLabel="ג"/>
+        <Key android:keyLabel="כ"/>
+        <Key android:keyLabel="ע"/>
+        <Key android:keyLabel="י"/>
+        <Key android:keyLabel="ח"/>
+        <Key android:keyLabel="ל"/>
+        <Key android:keyLabel="ך"/>
+        <Key android:keyLabel="ף" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row>
+        <Key android:keyLabel="ז" android:horizontalGap="5%p" android:keyEdgeFlags="left"/>
+        <Key android:keyLabel="ס"/>
+        <Key android:keyLabel="ב"/>
+        <Key android:keyLabel="ה"/>
+        <Key android:keyLabel="נ"/>
+        <Key android:keyLabel="מ"/>
+        <Key android:keyLabel="צ"/>
+        <Key android:keyLabel="ת"/>
+        <Key android:keyLabel="ץ" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row android:keyboardMode="@+id/mode_normal" android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyLabel="@string/label_symbol_key"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="_"
+                android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+        <Key android:codes="@integer/key_f1" android:keyIcon="@drawable/sym_bkeyboard_globe"
+                android:keyWidth="10%p"/>
+        <Key android:codes="32" android:keyIcon="@drawable/sym_bkeyboard_space"
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:keyWidth="40%p" android:isRepeatable="true"/>
+        <Key android:codes="46" android:keyLabel="." android:popupKeyboard="@xml/popup_punctuation"
+                android:keyWidth="10%p"/>
+        <Key android:codes="10" android:keyIcon="@drawable/sym_bkeyboard_return"
+                android:iconPreview="@drawable/sym_keyboard_feedback_return"
+                android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row android:keyboardMode="@+id/mode_url" android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyLabel="@string/label_symbol_key"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="_"
+                android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+        <Key android:keyLabel="/" android:keyWidth="10%p"/>
+        <Key android:codes="32" android:keyIcon="@drawable/sym_bkeyboard_space"
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:keyWidth="40%p" android:isRepeatable="true"/>
+        <!--Key android:keyLabel="@string/popular_domain_0"
+                android:keyOutputText="@string/popular_domain_0"
+                android:popupKeyboard="@xml/popup_domains"
+                android:keyWidth="20%p"/-->
+        <Key android:keyLabel="." android:popupKeyboard="@xml/popup_punctuation"
+                android:keyWidth="10%p"/>
+        <Key android:codes="10" android:keyIcon="@drawable/sym_bkeyboard_return"
+                android:iconPreview="@drawable/sym_keyboard_feedback_return"
+                android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row android:keyboardMode="@+id/mode_email" android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyLabel="@string/label_symbol_key"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="_"
+                android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+        <Key android:keyLabel="\@" android:keyWidth="10%p"/>
+        <Key android:codes="32" android:keyIcon="@drawable/sym_bkeyboard_space"
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:keyWidth="40%p" android:isRepeatable="true"/>
+        <!--Key android:keyLabel="@string/popular_domain_0"
+                android:keyOutputText="@string/popular_domain_0"
+                android:popupKeyboard="@xml/popup_domains"
+                android:keyWidth="20%p"/-->
+        <Key android:keyLabel="." android:popupKeyboard="@xml/popup_punctuation"/>
+        <Key android:codes="10" android:keyIcon="@drawable/sym_bkeyboard_return"
+                android:iconPreview="@drawable/sym_keyboard_feedback_return"
+                android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row android:keyboardMode="@+id/mode_im" android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyLabel="@string/label_symbol_key"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="_"
+                android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+        <Key android:codes="@integer/key_f1" android:keyIcon="@drawable/sym_bkeyboard_globe"
+                android:keyWidth="10%p"/>
+        <Key android:codes="32" android:keyIcon="@drawable/sym_bkeyboard_space"
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:keyWidth="40%p" android:isRepeatable="true"/>
+        <Key android:codes="46" android:keyLabel="." android:popupKeyboard="@xml/popup_punctuation"
+                android:keyWidth="10%p"/>
+        <Key android:keyLabel=":-)" android:keyOutputText=":-) "
+                android:popupKeyboard="@xml/popup_smileys"
+                android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row android:keyboardMode="@+id/mode_webentry" android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyLabel="@string/label_symbol_key"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="_"
+                android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+        <Key android:codes="@integer/key_f1" android:keyIcon="@drawable/sym_bkeyboard_globe"
+                android:keyWidth="10%p"/>
+        <Key android:codes="32" android:keyIcon="@drawable/sym_bkeyboard_space"
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:keyWidth="20%p" android:isRepeatable="true"/>
+        <Key android:codes="9" android:keyIcon="@drawable/sym_bkeyboard_tab"
+                android:iconPreview="@drawable/sym_keyboard_feedback_tab"
+                android:keyWidth="20%p"/>
+        <Key android:keyLabel="." android:popupKeyboard="@xml/popup_punctuation"/>
+        <Key android:codes="10" android:keyIcon="@drawable/sym_bkeyboard_return"
+                android:iconPreview="@drawable/sym_keyboard_feedback_return"
+                android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+</Keyboard>
+
diff --git a/java/res/xml-nb/kbd_qwerty.xml b/java/res/xml-nb/kbd_qwerty.xml
new file mode 100644
index 0000000..d2f0258
--- /dev/null
+++ b/java/res/xml-nb/kbd_qwerty.xml
@@ -0,0 +1,211 @@
+<?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.
+*/
+-->
+
+<!--
+    Norwegian Keyboard Layout
+
+    Just a copy of the Swedish layout, with ä/æ and ö/ø switched.
+-->
+
+<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
+    android:keyWidth="9.09%p"
+    android:horizontalGap="0px"
+    android:verticalGap="0px"
+    android:keyHeight="@dimen/key_height"
+    >
+
+    <Row>
+        <Key android:codes="113" android:keyLabel="q"
+                android:keyWidth="8.75%p" android:keyEdgeFlags="left"/>
+        <Key android:codes="119" android:keyLabel="w"/>
+        <Key android:codes="101" android:keyLabel="e"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="éèêëę€"/>
+        <Key android:codes="114" android:keyLabel="r"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="ř"/>
+        <Key android:codes="116" android:keyLabel="t"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="ťþ"/>
+        <Key android:codes="121" android:keyLabel="y"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="ýÿ"/>
+        <Key android:codes="117" android:keyLabel="u"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="üúùûū"/>
+        <Key android:codes="105" android:keyLabel="i"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="íìîï"/>
+        <Key android:codes="111" android:keyLabel="o"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="öóòôõ"/>
+        <Key android:codes="112" android:keyLabel="p"/>
+        <Key android:keyLabel="å"
+                android:keyWidth="8.75%p" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row>
+        <Key android:codes="97" android:keyLabel="a"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="äáàâąã"
+                android:keyWidth="8.75%p" android:keyEdgeFlags="left"/>
+        <Key android:codes="115" android:keyLabel="s"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="śšşß"/>
+        <Key android:codes="100" android:keyLabel="d"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="ðď"/>
+        <Key android:codes="102" android:keyLabel="f"/>
+        <Key android:codes="103" android:keyLabel="g"/>
+        <Key android:codes="104" android:keyLabel="h"/>
+        <Key android:codes="106" android:keyLabel="j"/>
+        <Key android:codes="107" android:keyLabel="k"/>
+        <Key android:codes="108" android:keyLabel="l"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="ł"/>
+        <Key android:keyLabel="ø"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="œ"/>
+        <Key android:keyLabel="æ"
+                android:keyWidth="8.75%p" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row android:keyWidth="10%p">
+        <Key android:codes="-1" android:keyIcon="@drawable/sym_keyboard_shift"
+                android:keyWidth="15%p" android:isModifier="true"
+                android:iconPreview="@drawable/sym_keyboard_feedback_shift"
+                android:isSticky="true" android:keyEdgeFlags="left"/>
+        <Key android:codes="122" android:keyLabel="z"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="źžż"/>
+        <Key android:codes="120" android:keyLabel="x"/>
+        <Key android:codes="99" android:keyLabel="c"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="çćč"/>
+        <Key android:codes="118" android:keyLabel="v"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="w"/>
+        <Key android:codes="98" android:keyLabel="b"/>
+        <Key android:codes="110" android:keyLabel="n"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="ńñň"/>
+        <Key android:codes="109" android:keyLabel="m"/>
+        <Key android:codes="-5" android:keyIcon="@drawable/sym_keyboard_delete"
+                android:keyWidth="15%p" android:keyEdgeFlags="right"
+                android:iconPreview="@drawable/sym_keyboard_feedback_delete"
+                android:isRepeatable="true"/>
+    </Row>
+
+    <Row android:keyboardMode="@+id/mode_normal" android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyLabel="@string/label_symbol_key"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="_"
+                android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+        <Key android:codes="@integer/key_f1" android:keyIcon="@drawable/sym_keyboard_mic"
+                android:iconPreview="@drawable/sym_keyboard_feedback_mic"
+                android:keyWidth="10%p"/>
+        <Key android:codes="32" android:keyIcon="@drawable/sym_keyboard_space"
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:keyWidth="40%p" android:isRepeatable="true"/>
+        <Key android:codes="46" android:keyLabel="." android:popupKeyboard="@xml/popup_punctuation"
+                android:keyWidth="10%p"/>
+        <Key android:codes="10" android:keyIcon="@drawable/sym_keyboard_return"
+                android:iconPreview="@drawable/sym_keyboard_feedback_return"
+                android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row android:keyboardMode="@+id/mode_url" android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyLabel="@string/label_symbol_key"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="_"
+                android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+        <Key android:keyLabel="/" android:keyWidth="10%p"/>
+        <Key android:codes="32" android:keyIcon="@drawable/sym_keyboard_space"
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:keyWidth="40%p" android:isRepeatable="true"/>
+        <!--Key android:keyLabel="@string/popular_domain_0"
+                android:keyOutputText="@string/popular_domain_0"
+                android:popupKeyboard="@xml/popup_domains"
+                android:keyWidth="20%p"/-->
+        <Key android:keyLabel="." android:popupKeyboard="@xml/popup_punctuation"
+                android:keyWidth="10%p"/>
+        <Key android:codes="10" android:keyIcon="@drawable/sym_keyboard_return"
+                android:iconPreview="@drawable/sym_keyboard_feedback_return"
+                android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row android:keyboardMode="@+id/mode_email" android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyLabel="@string/label_symbol_key"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="_"
+                android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+        <Key android:keyLabel="\@"/>
+        <Key android:codes="32" android:keyIcon="@drawable/sym_keyboard_space"
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:keyWidth="40%p" android:isRepeatable="true"/>
+        <!--Key android:keyLabel="@string/popular_domain_0"
+                android:keyOutputText="@string/popular_domain_0"
+                android:popupKeyboard="@xml/popup_domains"
+                android:keyWidth="20%p"/-->
+        <Key android:keyLabel="." android:popupKeyboard="@xml/popup_punctuation"/>
+        <Key android:codes="10" android:keyIcon="@drawable/sym_keyboard_return"
+                android:iconPreview="@drawable/sym_keyboard_feedback_return"
+                android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row android:keyboardMode="@+id/mode_im" android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyLabel="@string/label_symbol_key"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="_"
+                android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+        <Key android:codes="@integer/key_f1" android:keyIcon="@drawable/sym_keyboard_mic"
+                android:iconPreview="@drawable/sym_keyboard_feedback_mic"
+                android:keyWidth="10%p"/>
+        <Key android:codes="32" android:keyIcon="@drawable/sym_keyboard_space"
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:keyWidth="40%p" android:isRepeatable="true"/>
+        <Key android:codes="46" android:keyLabel="." android:popupKeyboard="@xml/popup_punctuation"
+                android:keyWidth="10%p"/>
+        <Key android:keyLabel=":-)" android:keyOutputText=":-) "
+                android:popupKeyboard="@xml/popup_smileys"
+                android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row android:keyboardMode="@+id/mode_webentry" android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyLabel="@string/label_symbol_key"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="_"
+                android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+        <Key android:codes="@integer/key_f1" android:keyIcon="@drawable/sym_keyboard_mic"
+                android:iconPreview="@drawable/sym_keyboard_feedback_mic"
+                android:keyWidth="10%p"/>
+        <Key android:codes="32" android:keyIcon="@drawable/sym_keyboard_space"
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:keyWidth="20%p" android:isRepeatable="true"/>
+        <Key android:codes="9" android:keyIcon="@drawable/sym_keyboard_tab"
+                android:iconPreview="@drawable/sym_keyboard_feedback_tab"
+                android:keyWidth="20%p"/>
+        <Key android:keyLabel="." android:popupKeyboard="@xml/popup_punctuation"/>
+        <Key android:codes="10" android:keyIcon="@drawable/sym_keyboard_return"
+                android:iconPreview="@drawable/sym_keyboard_feedback_return"
+                android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+
+</Keyboard>
diff --git a/java/res/xml-nb/kbd_qwerty_black.xml b/java/res/xml-nb/kbd_qwerty_black.xml
new file mode 100644
index 0000000..150ff7f
--- /dev/null
+++ b/java/res/xml-nb/kbd_qwerty_black.xml
@@ -0,0 +1,211 @@
+<?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.
+*/
+-->
+
+<!--
+    Norwegian Keyboard Layout
+
+    Just a copy of the Swedish layout, with ä/æ and ö/ø switched.
+-->
+
+<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
+    android:keyWidth="9.09%p"
+    android:horizontalGap="0px"
+    android:verticalGap="0px"
+    android:keyHeight="@dimen/key_height"
+    >
+
+    <Row>
+        <Key android:codes="113" android:keyLabel="q"
+                android:keyWidth="8.75%p" android:keyEdgeFlags="left"/>
+        <Key android:codes="119" android:keyLabel="w"/>
+        <Key android:codes="101" android:keyLabel="e"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="éèêëę€"/>
+        <Key android:codes="114" android:keyLabel="r"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="ř"/>
+        <Key android:codes="116" android:keyLabel="t"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="ťþ"/>
+        <Key android:codes="121" android:keyLabel="y"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="ýÿ"/>
+        <Key android:codes="117" android:keyLabel="u"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="üúùûū"/>
+        <Key android:codes="105" android:keyLabel="i"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="íìîï"/>
+        <Key android:codes="111" android:keyLabel="o"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="öóòôõ"/>
+        <Key android:codes="112" android:keyLabel="p"/>
+        <Key android:keyLabel="å"
+                android:keyWidth="8.75%p" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row>
+        <Key android:codes="97" android:keyLabel="a"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="äáàâąã"
+                android:keyWidth="8.75%p" android:keyEdgeFlags="left"/>
+        <Key android:codes="115" android:keyLabel="s"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="śšşß"/>
+        <Key android:codes="100" android:keyLabel="d"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="ðď"/>
+        <Key android:codes="102" android:keyLabel="f"/>
+        <Key android:codes="103" android:keyLabel="g"/>
+        <Key android:codes="104" android:keyLabel="h"/>
+        <Key android:codes="106" android:keyLabel="j"/>
+        <Key android:codes="107" android:keyLabel="k"/>
+        <Key android:codes="108" android:keyLabel="l"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="ł"/>
+        <Key android:keyLabel="ø"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="œ"/>
+        <Key android:keyLabel="æ"
+                android:keyWidth="8.75%p" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row android:keyWidth="10%p">
+        <Key android:codes="-1" android:keyIcon="@drawable/sym_bkeyboard_shift"
+                android:keyWidth="15%p" android:isModifier="true"
+                android:iconPreview="@drawable/sym_keyboard_feedback_shift"
+                android:isSticky="true" android:keyEdgeFlags="left"/>
+        <Key android:codes="122" android:keyLabel="z"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="źžż"/>
+        <Key android:codes="120" android:keyLabel="x"/>
+        <Key android:codes="99" android:keyLabel="c"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="çćč"/>
+        <Key android:codes="118" android:keyLabel="v"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="w"/>
+        <Key android:codes="98" android:keyLabel="b"/>
+        <Key android:codes="110" android:keyLabel="n"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="ńñň"/>
+        <Key android:codes="109" android:keyLabel="m"/>
+        <Key android:codes="-5" android:keyIcon="@drawable/sym_bkeyboard_delete"
+                android:keyWidth="15%p" android:keyEdgeFlags="right"
+                android:iconPreview="@drawable/sym_keyboard_feedback_delete"
+                android:isRepeatable="true"/>
+    </Row>
+
+    <Row android:keyboardMode="@+id/mode_normal" android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyLabel="@string/label_symbol_key"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="_"
+                android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+        <Key android:codes="@integer/key_f1" android:keyIcon="@drawable/sym_bkeyboard_mic"
+                android:iconPreview="@drawable/sym_keyboard_feedback_mic"
+                android:keyWidth="10%p"/>
+        <Key android:codes="32" android:keyIcon="@drawable/sym_bkeyboard_space"
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:keyWidth="40%p" android:isRepeatable="true"/>
+        <Key android:codes="46" android:keyLabel="." android:popupKeyboard="@xml/popup_punctuation"
+                android:keyWidth="10%p"/>
+        <Key android:codes="10" android:keyIcon="@drawable/sym_bkeyboard_return"
+                android:iconPreview="@drawable/sym_keyboard_feedback_return"
+                android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row android:keyboardMode="@+id/mode_url" android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyLabel="@string/label_symbol_key"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="_"
+                android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+        <Key android:keyLabel="/" android:keyWidth="10%p"/>
+        <Key android:codes="32" android:keyIcon="@drawable/sym_bkeyboard_space"
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:keyWidth="40%p" android:isRepeatable="true"/>
+        <!--Key android:keyLabel="@string/popular_domain_0"
+                android:keyOutputText="@string/popular_domain_0"
+                android:popupKeyboard="@xml/popup_domains"
+                android:keyWidth="20%p"/-->
+        <Key android:keyLabel="." android:popupKeyboard="@xml/popup_punctuation"
+                android:keyWidth="10%p"/>
+        <Key android:codes="10" android:keyIcon="@drawable/sym_bkeyboard_return"
+                android:iconPreview="@drawable/sym_keyboard_feedback_return"
+                android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row android:keyboardMode="@+id/mode_email" android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyLabel="@string/label_symbol_key"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="_"
+                android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+        <Key android:keyLabel="\@"/>
+        <Key android:codes="32" android:keyIcon="@drawable/sym_bkeyboard_space"
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:keyWidth="40%p" android:isRepeatable="true"/>
+        <!--Key android:keyLabel="@string/popular_domain_0"
+                android:keyOutputText="@string/popular_domain_0"
+                android:popupKeyboard="@xml/popup_domains"
+                android:keyWidth="20%p"/-->
+        <Key android:keyLabel="." android:popupKeyboard="@xml/popup_punctuation"/>
+        <Key android:codes="10" android:keyIcon="@drawable/sym_bkeyboard_return"
+                android:iconPreview="@drawable/sym_keyboard_feedback_return"
+                android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row android:keyboardMode="@+id/mode_im" android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyLabel="@string/label_symbol_key"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="_"
+                android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+        <Key android:codes="@integer/key_f1" android:keyIcon="@drawable/sym_bkeyboard_mic"
+                android:iconPreview="@drawable/sym_keyboard_feedback_mic"
+                android:keyWidth="10%p"/>
+        <Key android:codes="32" android:keyIcon="@drawable/sym_bkeyboard_space"
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:keyWidth="40%p" android:isRepeatable="true"/>
+        <Key android:codes="46" android:keyLabel="." android:popupKeyboard="@xml/popup_punctuation"
+                android:keyWidth="10%p"/>
+        <Key android:keyLabel=":-)" android:keyOutputText=":-) "
+                android:popupKeyboard="@xml/popup_smileys"
+                android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row android:keyboardMode="@+id/mode_webentry" android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyLabel="@string/label_symbol_key"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="_"
+                android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+        <Key android:codes="@integer/key_f1" android:keyIcon="@drawable/sym_bkeyboard_mic"
+                android:iconPreview="@drawable/sym_keyboard_feedback_mic"
+                android:keyWidth="10%p"/>
+        <Key android:codes="32" android:keyIcon="@drawable/sym_bkeyboard_space"
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:keyWidth="20%p" android:isRepeatable="true"/>
+        <Key android:codes="9" android:keyIcon="@drawable/sym_bkeyboard_tab"
+                android:iconPreview="@drawable/sym_keyboard_feedback_tab"
+                android:keyWidth="20%p"/>
+        <Key android:keyLabel="." android:popupKeyboard="@xml/popup_punctuation"/>
+        <Key android:codes="10" android:keyIcon="@drawable/sym_bkeyboard_return"
+                android:iconPreview="@drawable/sym_keyboard_feedback_return"
+                android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+
+</Keyboard>
diff --git a/java/res/xml-ru/kbd_qwerty_black.xml b/java/res/xml-ru/kbd_qwerty_black.xml
new file mode 100755
index 0000000..4923be0
--- /dev/null
+++ b/java/res/xml-ru/kbd_qwerty_black.xml
@@ -0,0 +1,175 @@
+<?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.
+*/
+-->
+
+<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
+    android:keyWidth="9.09%p"
+    android:horizontalGap="0px"
+    android:verticalGap="0px"
+    android:keyHeight="@dimen/key_height"
+    >
+
+    <Row>
+        <Key android:keyLabel="й" android:keyWidth="8.75%p"
+                android:keyEdgeFlags="left"/>
+        <Key android:keyLabel="ц"/>
+        <Key android:keyLabel="у"/>
+        <Key android:keyLabel="к"/>
+        <Key android:keyLabel="е"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="ё" />
+        <Key android:keyLabel="н"/>
+        <Key android:keyLabel="г"/>
+        <Key android:keyLabel="ш"/>
+        <Key android:keyLabel="щ"/>
+        <Key android:keyLabel="з"/>
+        <Key android:keyLabel="х" android:keyWidth="8.75%p"
+                android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row>
+        <Key android:keyLabel="ф" android:keyWidth="8.75%p"
+                android:keyEdgeFlags="left"/>
+        <Key android:keyLabel="ы"/>
+        <Key android:keyLabel="в"/>
+        <Key android:keyLabel="а"/>
+        <Key android:keyLabel="п"/>
+        <Key android:keyLabel="р"/>
+        <Key android:keyLabel="о"/>
+        <Key android:keyLabel="л"/>
+        <Key android:keyLabel="д"/>
+        <Key android:keyLabel="ж"/>
+        <Key android:keyLabel="э" android:keyWidth="8.75%p"
+                android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row android:keyWidth="8.5%p">
+        <Key android:codes="-1" android:keyIcon="@drawable/sym_bkeyboard_shift"
+                android:keyWidth="11.75%p" android:isModifier="true"
+                android:iconPreview="@drawable/sym_keyboard_feedback_shift"
+                android:isSticky="true" android:keyEdgeFlags="left"/>
+        <Key android:keyLabel="я"/>
+        <Key android:keyLabel="ч"/>
+        <Key android:keyLabel="с"/>
+        <Key android:keyLabel="м"/>
+        <Key android:keyLabel="и"/>
+        <Key android:keyLabel="т"/>
+        <Key android:keyLabel="ь"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="ъ" />
+        <Key android:keyLabel="б"/>
+        <Key android:keyLabel="ю"/>
+        <Key android:codes="-5" android:keyIcon="@drawable/sym_bkeyboard_delete"
+                android:keyWidth="11.75%p" android:keyEdgeFlags="right"
+                android:iconPreview="@drawable/sym_keyboard_feedback_delete"
+                android:isRepeatable="true"/>
+    </Row>
+
+    <Row android:keyboardMode="@+id/mode_normal" android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyLabel="@string/label_symbol_key"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="_"
+                android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+        <Key android:codes="@integer/key_f1" android:keyIcon="@drawable/sym_bkeyboard_globe"
+                android:keyWidth="10%p"/>
+        <Key android:codes="32" android:keyIcon="@drawable/sym_bkeyboard_space"
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:keyWidth="40%p" android:isRepeatable="true"/>
+        <Key android:codes="46" android:keyLabel="." android:popupKeyboard="@xml/popup_punctuation"
+                android:keyWidth="10%p"/>
+        <Key android:codes="10" android:keyIcon="@drawable/sym_bkeyboard_return"
+                android:iconPreview="@drawable/sym_keyboard_feedback_return"
+                android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row android:keyboardMode="@+id/mode_url" android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyLabel="@string/label_symbol_key"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="_"
+                android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+        <Key android:keyLabel="/" android:keyWidth="10%p"/>
+        <Key android:codes="32" android:keyIcon="@drawable/sym_bkeyboard_space"
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:keyWidth="40%p" android:isRepeatable="true"/>
+        <!--Key android:keyLabel="@string/popular_domain_0"
+                android:keyOutputText="@string/popular_domain_0"
+                android:popupKeyboard="@xml/popup_domains"
+                android:keyWidth="20%p"/-->
+        <Key android:keyLabel="." android:popupKeyboard="@xml/popup_punctuation"
+                android:keyWidth="10%p"/>
+        <Key android:codes="10" android:keyIcon="@drawable/sym_bkeyboard_return"
+                android:iconPreview="@drawable/sym_keyboard_feedback_return"
+                android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row android:keyboardMode="@+id/mode_email" android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyLabel="@string/label_symbol_key"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="_"
+                android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+        <Key android:keyLabel="\@" android:keyWidth="10%p"/>
+        <Key android:codes="32" android:keyIcon="@drawable/sym_bkeyboard_space"
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:keyWidth="40%p" android:isRepeatable="true"/>
+        <!--Key android:keyLabel="@string/popular_domain_0"
+                android:keyOutputText="@string/popular_domain_0"
+                android:popupKeyboard="@xml/popup_domains"
+                android:keyWidth="20%p"/-->
+        <Key android:keyLabel="." android:popupKeyboard="@xml/popup_punctuation"/>
+        <Key android:codes="10" android:keyIcon="@drawable/sym_bkeyboard_return"
+                android:iconPreview="@drawable/sym_keyboard_feedback_return"
+                android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row android:keyboardMode="@+id/mode_im" android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyLabel="@string/label_symbol_key"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="_"
+                android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+        <Key android:codes="@integer/key_f1" android:keyIcon="@drawable/sym_bkeyboard_globe"
+                android:keyWidth="10%p"/>
+        <Key android:codes="32" android:keyIcon="@drawable/sym_bkeyboard_space"
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:keyWidth="40%p" android:isRepeatable="true"/>
+        <Key android:codes="46" android:keyLabel="." android:popupKeyboard="@xml/popup_punctuation"
+                android:keyWidth="10%p"/>
+        <Key android:keyLabel=":-)" android:keyOutputText=":-) "
+                android:popupKeyboard="@xml/popup_smileys"
+                android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row android:keyboardMode="@+id/mode_webentry" android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyLabel="@string/label_symbol_key"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="_"
+                android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+        <Key android:codes="@integer/key_f1" android:keyIcon="@drawable/sym_bkeyboard_globe"
+                android:keyWidth="10%p"/>
+        <Key android:codes="32" android:keyIcon="@drawable/sym_bkeyboard_space"
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:keyWidth="20%p" android:isRepeatable="true"/>
+        <Key android:codes="9" android:keyIcon="@drawable/sym_bkeyboard_tab"
+                android:iconPreview="@drawable/sym_keyboard_feedback_tab"
+                android:keyWidth="20%p"/>
+        <Key android:keyLabel="." android:popupKeyboard="@xml/popup_punctuation"/>
+        <Key android:codes="10" android:keyIcon="@drawable/sym_bkeyboard_return"
+                android:iconPreview="@drawable/sym_keyboard_feedback_return"
+                android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+</Keyboard>
diff --git a/java/res/xml-sr/kbd_qwerty.xml b/java/res/xml-sr/kbd_qwerty.xml
new file mode 100644
index 0000000..e4884a8
--- /dev/null
+++ b/java/res/xml-sr/kbd_qwerty.xml
@@ -0,0 +1,171 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 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.
+*/
+-->
+
+<!-- Serbian keyboard layout, based on the X11 layout for Serbian -->
+<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
+    android:keyWidth="9.09%p"
+    android:horizontalGap="0px"
+    android:verticalGap="0px"
+    android:keyHeight="@dimen/key_height"
+    >
+
+    <Row>
+        <Key android:keyLabel="љ"
+                android:keyEdgeFlags="left" />
+        <Key android:keyLabel="њ" />
+        <Key android:keyLabel="е" />
+        <Key android:keyLabel="р" />
+        <Key android:keyLabel="т" />
+        <Key android:keyLabel="з" />
+        <Key android:keyLabel="у" />
+        <Key android:keyLabel="и" />
+        <Key android:keyLabel="о" />
+        <Key android:keyLabel="п" />
+        <Key android:keyLabel="ш"
+                android:keyEdgeFlags="right" />
+    </Row>
+
+    <Row>
+        <Key android:keyLabel="а"
+                android:keyEdgeFlags="left" />
+        <Key android:keyLabel="с" />
+        <Key android:keyLabel="д" />
+        <Key android:keyLabel="ф" />
+        <Key android:keyLabel="г" />
+        <Key android:keyLabel="х" />
+        <Key android:keyLabel="ј" />
+        <Key android:keyLabel="к" />
+        <Key android:keyLabel="л" />
+        <Key android:keyLabel="ч" />
+        <Key android:keyLabel="ћ" />
+        <Key android:keyLabel="ђ"
+                android:keyEdgeFlags="right" />
+    </Row>
+
+    <Row android:keyWidth="8.5%p">
+        <Key android:codes="-1" android:keyIcon="@drawable/sym_keyboard_shift"
+                android:keyWidth="11.75%p" android:isModifier="true"
+                android:iconPreview="@drawable/sym_keyboard_feedback_shift"
+                android:isSticky="true" android:keyEdgeFlags="left"/>
+        <Key android:keyLabel="ж" />
+        <Key android:keyLabel="џ" />
+        <Key android:keyLabel="ц" />
+        <Key android:keyLabel="в" />
+        <Key android:keyLabel="б" />
+        <Key android:keyLabel="н" />
+        <Key android:keyLabel="м" />
+        <Key android:codes="-5" android:keyIcon="@drawable/sym_keyboard_delete"
+                android:keyWidth="11.75%p" android:keyEdgeFlags="right"
+                android:iconPreview="@drawable/sym_keyboard_feedback_delete"
+                android:isRepeatable="true"/>
+    </Row>
+
+    <Row android:keyboardMode="@+id/mode_normal" android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyLabel="@string/label_symbol_key"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="_"
+                android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+        <Key android:codes="@integer/key_f1" android:keyIcon="@drawable/sym_keyboard_globe"
+                android:keyWidth="10%p"/>
+        <Key android:codes="32" android:keyIcon="@drawable/sym_keyboard_space"
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:keyWidth="40%p" android:isRepeatable="true"/>
+        <Key android:codes="46" android:keyLabel="." android:popupKeyboard="@xml/popup_punctuation"
+                android:keyWidth="10%p"/>
+        <Key android:codes="10" android:keyIcon="@drawable/sym_keyboard_return"
+                android:iconPreview="@drawable/sym_keyboard_feedback_return"
+                android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row android:keyboardMode="@+id/mode_url" android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyLabel="@string/label_symbol_key"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="_"
+                android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+        <Key android:keyLabel="/" android:keyWidth="10%p"/>
+        <Key android:codes="32" android:keyIcon="@drawable/sym_keyboard_space"
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:keyWidth="20%p" android:isRepeatable="true"/>
+        <Key android:keyLabel="@string/popular_domain_0"
+                android:keyOutputText="@string/popular_domain_0"
+                android:popupKeyboard="@xml/popup_domains"
+                android:keyWidth="20%p"/>
+        <Key android:keyLabel="." android:popupKeyboard="@xml/popup_punctuation"
+                android:keyWidth="10%p"/>
+        <Key android:codes="10" android:keyIcon="@drawable/sym_keyboard_return"
+                android:iconPreview="@drawable/sym_keyboard_feedback_return"
+                android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row android:keyboardMode="@+id/mode_email" android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyLabel="@string/label_symbol_key"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="_"
+                android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+        <Key android:keyLabel="\@" android:keyWidth="10%p"/>
+        <Key android:codes="32" android:keyIcon="@drawable/sym_keyboard_space"
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:keyWidth="20%p" android:isRepeatable="true"/>
+        <Key android:keyLabel="@string/popular_domain_0"
+                android:keyOutputText="@string/popular_domain_0"
+                android:popupKeyboard="@xml/popup_domains"
+                android:keyWidth="20%p"/>
+        <Key android:keyLabel="." android:popupKeyboard="@xml/popup_punctuation"/>
+        <Key android:codes="10" android:keyIcon="@drawable/sym_keyboard_return"
+                android:iconPreview="@drawable/sym_keyboard_feedback_return"
+                android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row android:keyboardMode="@+id/mode_im" android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyLabel="@string/label_symbol_key"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="_"
+                android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+        <Key android:codes="@integer/key_f1" android:keyIcon="@drawable/sym_keyboard_globe"
+                android:keyWidth="10%p"/>
+        <Key android:codes="32" android:keyIcon="@drawable/sym_keyboard_space"
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:keyWidth="40%p" android:isRepeatable="true"/>
+        <Key android:codes="46" android:keyLabel="." android:popupKeyboard="@xml/popup_punctuation"
+                android:keyWidth="10%p"/>
+        <Key android:keyLabel=":-)" android:keyOutputText=":-) "
+                android:popupKeyboard="@xml/popup_smileys"
+                android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row android:keyboardMode="@+id/mode_webentry" android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyLabel="@string/label_symbol_key"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="_"
+                android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+        <Key android:codes="@integer/key_f1" android:keyIcon="@drawable/sym_keyboard_globe"
+                android:keyWidth="10%p"/>
+        <Key android:codes="32" android:keyIcon="@drawable/sym_keyboard_space"
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:keyWidth="20%p" android:isRepeatable="true"/>
+        <Key android:codes="9" android:keyIcon="@drawable/sym_keyboard_tab"
+                android:iconPreview="@drawable/sym_keyboard_feedback_tab"
+                android:keyWidth="20%p"/>
+        <Key android:keyLabel="." android:popupKeyboard="@xml/popup_punctuation"/>
+        <Key android:codes="10" android:keyIcon="@drawable/sym_keyboard_return"
+                android:iconPreview="@drawable/sym_keyboard_feedback_return"
+                android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+</Keyboard>
diff --git a/java/res/xml-sr/kbd_qwerty_black.xml b/java/res/xml-sr/kbd_qwerty_black.xml
new file mode 100644
index 0000000..30d094a
--- /dev/null
+++ b/java/res/xml-sr/kbd_qwerty_black.xml
@@ -0,0 +1,171 @@
+<?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.
+*/
+-->
+
+<!-- Serbian keyboard layout, based on the X11 layout for Serbian -->
+<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
+    android:keyWidth="9.09%p"
+    android:horizontalGap="0px"
+    android:verticalGap="0px"
+    android:keyHeight="@dimen/key_height"
+    >
+
+    <Row>
+        <Key android:keyLabel="љ"
+                android:keyEdgeFlags="left" />
+        <Key android:keyLabel="њ" />
+        <Key android:keyLabel="е" />
+        <Key android:keyLabel="р" />
+        <Key android:keyLabel="т" />
+        <Key android:keyLabel="з" />
+        <Key android:keyLabel="у" />
+        <Key android:keyLabel="и" />
+        <Key android:keyLabel="о" />
+        <Key android:keyLabel="п" />
+        <Key android:keyLabel="ш"
+                android:keyEdgeFlags="right" />
+    </Row>
+
+    <Row>
+        <Key android:keyLabel="а"
+                android:keyEdgeFlags="left" />
+        <Key android:keyLabel="с" />
+        <Key android:keyLabel="д" />
+        <Key android:keyLabel="ф" />
+        <Key android:keyLabel="г" />
+        <Key android:keyLabel="х" />
+        <Key android:keyLabel="ј" />
+        <Key android:keyLabel="к" />
+        <Key android:keyLabel="л" />
+        <Key android:keyLabel="ч" />
+        <Key android:keyLabel="ћ" />
+        <Key android:keyLabel="ђ"
+                android:keyEdgeFlags="right" />
+    </Row>
+
+    <Row android:keyWidth="8.5%p">
+        <Key android:codes="-1" android:keyIcon="@drawable/sym_bkeyboard_shift"
+                android:keyWidth="11.75%p" android:isModifier="true"
+                android:iconPreview="@drawable/sym_keyboard_feedback_shift"
+                android:isSticky="true" android:keyEdgeFlags="left"/>
+        <Key android:keyLabel="ж" />
+        <Key android:keyLabel="џ" />
+        <Key android:keyLabel="ц" />
+        <Key android:keyLabel="в" />
+        <Key android:keyLabel="б" />
+        <Key android:keyLabel="н" />
+        <Key android:keyLabel="м" />
+        <Key android:codes="-5" android:keyIcon="@drawable/sym_bkeyboard_delete"
+                android:keyWidth="11.75%p" android:keyEdgeFlags="right"
+                android:iconPreview="@drawable/sym_keyboard_feedback_delete"
+                android:isRepeatable="true"/>
+    </Row>
+
+    <Row android:keyboardMode="@+id/mode_normal" android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyLabel="@string/label_symbol_key"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="_"
+                android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+        <Key android:codes="@integer/key_f1" android:keyIcon="@drawable/sym_bkeyboard_globe"
+                android:keyWidth="10%p"/>
+        <Key android:codes="32" android:keyIcon="@drawable/sym_bkeyboard_space"
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:keyWidth="40%p" android:isRepeatable="true"/>
+        <Key android:codes="46" android:keyLabel="." android:popupKeyboard="@xml/popup_punctuation"
+                android:keyWidth="10%p"/>
+        <Key android:codes="10" android:keyIcon="@drawable/sym_bkeyboard_return"
+                android:iconPreview="@drawable/sym_keyboard_feedback_return"
+                android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row android:keyboardMode="@+id/mode_url" android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyLabel="@string/label_symbol_key"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="_"
+                android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+        <Key android:keyLabel="/" android:keyWidth="10%p"/>
+        <Key android:codes="32" android:keyIcon="@drawable/sym_bkeyboard_space"
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:keyWidth="20%p" android:isRepeatable="true"/>
+        <Key android:keyLabel="@string/popular_domain_0"
+                android:keyOutputText="@string/popular_domain_0"
+                android:popupKeyboard="@xml/popup_domains"
+                android:keyWidth="20%p"/>
+        <Key android:keyLabel="." android:popupKeyboard="@xml/popup_punctuation"
+                android:keyWidth="10%p"/>
+        <Key android:codes="10" android:keyIcon="@drawable/sym_bkeyboard_return"
+                android:iconPreview="@drawable/sym_keyboard_feedback_return"
+                android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row android:keyboardMode="@+id/mode_email" android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyLabel="@string/label_symbol_key"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="_"
+                android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+        <Key android:keyLabel="\@" android:keyWidth="10%p"/>
+        <Key android:codes="32" android:keyIcon="@drawable/sym_bkeyboard_space"
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:keyWidth="20%p" android:isRepeatable="true"/>
+        <Key android:keyLabel="@string/popular_domain_0"
+                android:keyOutputText="@string/popular_domain_0"
+                android:popupKeyboard="@xml/popup_domains"
+                android:keyWidth="20%p"/>
+        <Key android:keyLabel="." android:popupKeyboard="@xml/popup_punctuation"/>
+        <Key android:codes="10" android:keyIcon="@drawable/sym_bkeyboard_return"
+                android:iconPreview="@drawable/sym_keyboard_feedback_return"
+                android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row android:keyboardMode="@+id/mode_im" android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyLabel="@string/label_symbol_key"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="_"
+                android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+        <Key android:codes="@integer/key_f1" android:keyIcon="@drawable/sym_bkeyboard_globe"
+                android:keyWidth="10%p"/>
+        <Key android:codes="32" android:keyIcon="@drawable/sym_bkeyboard_space"
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:keyWidth="40%p" android:isRepeatable="true"/>
+        <Key android:codes="46" android:keyLabel="." android:popupKeyboard="@xml/popup_punctuation"
+                android:keyWidth="10%p"/>
+        <Key android:keyLabel=":-)" android:keyOutputText=":-) "
+                android:popupKeyboard="@xml/popup_smileys"
+                android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row android:keyboardMode="@+id/mode_webentry" android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyLabel="@string/label_symbol_key"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="_"
+                android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+        <Key android:codes="@integer/key_f1" android:keyIcon="@drawable/sym_bkeyboard_globe"
+                android:keyWidth="10%p"/>
+        <Key android:codes="32" android:keyIcon="@drawable/sym_bkeyboard_space"
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:keyWidth="20%p" android:isRepeatable="true"/>
+        <Key android:codes="9" android:keyIcon="@drawable/sym_bkeyboard_tab"
+                android:iconPreview="@drawable/sym_keyboard_feedback_tab"
+                android:keyWidth="20%p"/>
+        <Key android:keyLabel="." android:popupKeyboard="@xml/popup_punctuation"/>
+        <Key android:codes="10" android:keyIcon="@drawable/sym_bkeyboard_return"
+                android:iconPreview="@drawable/sym_keyboard_feedback_return"
+                android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+</Keyboard>
diff --git a/java/res/xml-sv/kbd_qwerty_black.xml b/java/res/xml-sv/kbd_qwerty_black.xml
new file mode 100644
index 0000000..6604bc8
--- /dev/null
+++ b/java/res/xml-sv/kbd_qwerty_black.xml
@@ -0,0 +1,215 @@
+<?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.
+*/
+-->
+
+<!--
+    Swedish Keyboard Layout
+
+    Key positioning: Svensk standard SS 66 22 41
+    Foreign letters: Svenska skrivregler (2:a uppl.) §302
+    Local additions: ۧ
+-->
+
+<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
+    android:keyWidth="9.09%p"
+    android:horizontalGap="0px"
+    android:verticalGap="0px"
+    android:keyHeight="@dimen/key_height"
+    >
+
+    <Row>
+        <Key android:codes="113" android:keyLabel="q"
+                android:keyWidth="8.75%p" android:keyEdgeFlags="left"/>
+        <Key android:codes="119" android:keyLabel="w"/>
+        <Key android:codes="101" android:keyLabel="e"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="éèêëę€"/>
+        <Key android:codes="114" android:keyLabel="r"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="ř"/>
+        <Key android:codes="116" android:keyLabel="t"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="ťþ"/>
+        <Key android:codes="121" android:keyLabel="y"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="ýÿü"/>
+        <Key android:codes="117" android:keyLabel="u"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="úùûū"/>
+        <Key android:codes="105" android:keyLabel="i"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="íìîï"/>
+        <Key android:codes="111" android:keyLabel="o"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="óòôõ"/>
+        <Key android:codes="112" android:keyLabel="p"/>
+        <Key android:keyLabel="å"
+                android:keyWidth="8.75%p" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row>
+        <Key android:codes="97" android:keyLabel="a"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="áàâąã"
+                android:keyWidth="8.75%p" android:keyEdgeFlags="left"/>
+        <Key android:codes="115" android:keyLabel="s"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="śšşß"/>
+        <Key android:codes="100" android:keyLabel="d"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="ðď"/>
+        <Key android:codes="102" android:keyLabel="f"/>
+        <Key android:codes="103" android:keyLabel="g"/>
+        <Key android:codes="104" android:keyLabel="h"/>
+        <Key android:codes="106" android:keyLabel="j"/>
+        <Key android:codes="107" android:keyLabel="k"/>
+        <Key android:codes="108" android:keyLabel="l"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="ł"/>
+        <Key android:keyLabel="ö"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="øœ"/>
+        <Key android:keyLabel="ä"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="æ"
+                android:keyWidth="8.75%p" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row android:keyWidth="10%p">
+        <Key android:codes="-1" android:keyIcon="@drawable/sym_bkeyboard_shift"
+                android:keyWidth="15%p" android:isModifier="true"
+                android:iconPreview="@drawable/sym_keyboard_feedback_shift"
+                android:isSticky="true" android:keyEdgeFlags="left"/>
+        <Key android:codes="122" android:keyLabel="z"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="źžż"/>
+        <Key android:codes="120" android:keyLabel="x"/>
+        <Key android:codes="99" android:keyLabel="c"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="çćč"/>
+        <Key android:codes="118" android:keyLabel="v"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="w"/>
+        <Key android:codes="98" android:keyLabel="b"/>
+        <Key android:codes="110" android:keyLabel="n"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="ńñň"/>
+        <Key android:codes="109" android:keyLabel="m"/>
+        <Key android:codes="-5" android:keyIcon="@drawable/sym_bkeyboard_delete"
+                android:keyWidth="15%p" android:keyEdgeFlags="right"
+                android:iconPreview="@drawable/sym_keyboard_feedback_delete"
+                android:isRepeatable="true"/>
+    </Row>
+
+    <Row android:keyboardMode="@+id/mode_normal" android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyLabel="@string/label_symbol_key"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="_"
+                android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+        <Key android:codes="@integer/key_f1" android:keyIcon="@drawable/sym_bkeyboard_mic"
+                android:iconPreview="@drawable/sym_keyboard_feedback_mic"
+                android:keyWidth="10%p"/>
+        <Key android:codes="32" android:keyIcon="@drawable/sym_bkeyboard_space"
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:keyWidth="40%p" android:isRepeatable="true"/>
+        <Key android:codes="46" android:keyLabel="." android:popupKeyboard="@xml/popup_punctuation"
+                android:keyWidth="10%p"/>
+        <Key android:codes="10" android:keyIcon="@drawable/sym_bkeyboard_return"
+                android:iconPreview="@drawable/sym_keyboard_feedback_return"
+                android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row android:keyboardMode="@+id/mode_url" android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyLabel="@string/label_symbol_key"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="_"
+                android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+        <Key android:keyLabel="/" android:keyWidth="10%p"/>
+        <Key android:codes="32" android:keyIcon="@drawable/sym_bkeyboard_space"
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:keyWidth="40%p" android:isRepeatable="true"/>
+        <!--Key android:keyLabel="@string/popular_domain_0"
+                android:keyOutputText="@string/popular_domain_0"
+                android:popupKeyboard="@xml/popup_domains"
+                android:keyWidth="20%p"/-->
+        <Key android:keyLabel="." android:popupKeyboard="@xml/popup_punctuation"
+                android:keyWidth="10%p"/>
+        <Key android:codes="10" android:keyIcon="@drawable/sym_bkeyboard_return"
+                android:iconPreview="@drawable/sym_keyboard_feedback_return"
+                android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row android:keyboardMode="@+id/mode_email" android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyLabel="@string/label_symbol_key"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="_"
+                android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+        <Key android:keyLabel="\@"/>
+        <Key android:codes="32" android:keyIcon="@drawable/sym_bkeyboard_space"
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:keyWidth="40%p" android:isRepeatable="true"/>
+        <!--Key android:keyLabel="@string/popular_domain_0"
+                android:keyOutputText="@string/popular_domain_0"
+                android:popupKeyboard="@xml/popup_domains"
+                android:keyWidth="20%p"/-->
+        <Key android:keyLabel="." android:popupKeyboard="@xml/popup_punctuation"/>
+        <Key android:codes="10" android:keyIcon="@drawable/sym_bkeyboard_return"
+                android:iconPreview="@drawable/sym_keyboard_feedback_return"
+                android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row android:keyboardMode="@+id/mode_im" android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyLabel="@string/label_symbol_key"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="_"
+                android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+        <Key android:codes="@integer/key_f1" android:keyIcon="@drawable/sym_bkeyboard_mic"
+                android:iconPreview="@drawable/sym_keyboard_feedback_mic"
+                android:keyWidth="10%p"/>
+        <Key android:codes="32" android:keyIcon="@drawable/sym_bkeyboard_space"
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:keyWidth="40%p" android:isRepeatable="true"/>
+        <Key android:codes="46" android:keyLabel="." android:popupKeyboard="@xml/popup_punctuation"
+                android:keyWidth="10%p"/>
+        <Key android:keyLabel=":-)" android:keyOutputText=":-) "
+                android:popupKeyboard="@xml/popup_smileys"
+                android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row android:keyboardMode="@+id/mode_webentry" android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyLabel="@string/label_symbol_key"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="_"
+                android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+        <Key android:codes="@integer/key_f1" android:keyIcon="@drawable/sym_bkeyboard_mic"
+                android:iconPreview="@drawable/sym_keyboard_feedback_mic"
+                android:keyWidth="10%p"/>
+        <Key android:codes="32" android:keyIcon="@drawable/sym_bkeyboard_space"
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:keyWidth="20%p" android:isRepeatable="true"/>
+        <Key android:codes="9" android:keyIcon="@drawable/sym_bkeyboard_tab"
+                android:iconPreview="@drawable/sym_keyboard_feedback_tab"
+                android:keyWidth="20%p"/>
+        <Key android:keyLabel="." android:popupKeyboard="@xml/popup_punctuation"/>
+        <Key android:codes="10" android:keyIcon="@drawable/sym_bkeyboard_return"
+                android:iconPreview="@drawable/sym_keyboard_feedback_return"
+                android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+
+</Keyboard>
diff --git a/java/res/xml/dictionary.xml b/java/res/xml/dictionary.xml
new file mode 100644
index 0000000..7b770a8
--- /dev/null
+++ b/java/res/xml/dictionary.xml
@@ -0,0 +1,23 @@
+<?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.
+*/
+-->
+
+<dictionary>
+    <part name = "main" />
+</dictionary>
\ No newline at end of file
diff --git a/java/res/xml/kbd_alpha_black.xml b/java/res/xml/kbd_alpha_black.xml
new file mode 100644
index 0000000..108e466
--- /dev/null
+++ b/java/res/xml/kbd_alpha_black.xml
@@ -0,0 +1,106 @@
+<?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.
+*/
+-->
+
+<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
+    android:keyWidth="10%p"
+    android:horizontalGap="0px"
+    android:verticalGap="0px"
+    android:keyHeight="@dimen/key_height"
+    >
+
+    <Row>
+        <Key android:keyLabel="a"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_a"
+                android:keyEdgeFlags="left" />
+        <Key android:keyLabel="b" />
+        <Key android:keyLabel="c"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_c" />
+        <Key android:keyLabel="d" />
+        <Key android:keyLabel="e"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_e" />
+        <Key android:keyLabel="f" />
+        <Key android:keyLabel="g" />
+        <Key android:keyLabel="h" />
+        <Key android:keyLabel="i"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_i" />
+        <Key android:keyLabel="j" android:keyEdgeFlags="right" />
+    </Row>
+    <Row>
+        <Key android:keyLabel="k" android:keyEdgeFlags="left" />
+        <Key android:keyLabel="l" />
+        <Key android:keyLabel="m" />
+        <Key android:keyLabel="n"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_n" />
+        <Key android:keyLabel="o"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_o" />
+        <Key android:keyLabel="p" />
+        <Key android:keyLabel="q" />
+        <Key android:keyLabel="r" />
+        <Key android:keyLabel="s"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_s" />
+        <Key android:keyLabel="t" android:keyEdgeFlags="right" />
+    </Row>
+
+    <Row>
+        <Key android:codes="-1" android:keyIcon="@drawable/sym_bkeyboard_shift"
+                android:keyWidth="15%p" android:isModifier="true"
+                android:iconPreview="@drawable/sym_keyboard_feedback_shift"
+                android:isSticky="true" android:keyEdgeFlags="left"/>
+        <Key android:keyLabel="u"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_u" />
+        <Key android:keyLabel="v"/>
+        <Key android:keyLabel="w"/>
+        <Key android:keyLabel="x"/>
+        <Key android:keyLabel="y"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_y"
+        />
+        <Key android:keyLabel="z"/>
+        <Key android:keyLabel=","/>
+        <Key android:codes="-5" android:keyIcon="@drawable/sym_bkeyboard_delete"
+                android:keyWidth="15%p" android:keyEdgeFlags="right"
+                android:iconPreview="@drawable/sym_keyboard_feedback_delete"
+                android:isRepeatable="true"/>
+    </Row>
+
+    <Row android:rowEdgeFlags="bottom">
+        <Key android:codes="-3" android:keyIcon="@drawable/sym_bkeyboard_done"
+                android:iconPreview="@drawable/sym_keyboard_feedback_done"
+                android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+        <Key android:codes="-2" android:keyLabel="123" android:keyWidth="15%p"/>
+        <Key android:codes="32" android:keyIcon="@drawable/sym_bkeyboard_space"
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:keyWidth="30%p" android:isRepeatable="true"/>
+        <Key android:codes="46" android:keyLabel="."
+                android:popupKeyboard="@xml/popup_punctuation"
+                android:keyWidth="15%p"/>
+        <Key android:codes="10" android:keyIcon="@drawable/sym_bkeyboard_return"
+                android:iconPreview="@drawable/sym_keyboard_feedback_return"
+                android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+</Keyboard>
diff --git a/java/res/xml/kbd_phone_black.xml b/java/res/xml/kbd_phone_black.xml
new file mode 100755
index 0000000..b7f9096
--- /dev/null
+++ b/java/res/xml/kbd_phone_black.xml
@@ -0,0 +1,67 @@
+<?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.
+*/
+-->
+<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
+    android:keyWidth="26.67%p"
+    android:horizontalGap="0px"
+    android:verticalGap="0px"
+    android:keyHeight="@dimen/key_height"
+    >
+
+    <Row>
+        <Key android:codes="49" android:keyIcon="@drawable/sym_bkeyboard_num1" android:keyEdgeFlags="left"/>
+        <Key android:codes="50" android:keyIcon="@drawable/sym_bkeyboard_num2"/>
+        <Key android:codes="51" android:keyIcon="@drawable/sym_bkeyboard_num3"/>
+        <Key android:keyLabel="-" android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row>
+        <Key android:codes="52" android:keyIcon="@drawable/sym_bkeyboard_num4" android:keyEdgeFlags="left"/>
+        <Key android:codes="53" android:keyIcon="@drawable/sym_bkeyboard_num5"/>
+        <Key android:codes="54" android:keyIcon="@drawable/sym_bkeyboard_num6"/>
+        <Key android:keyLabel="." android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row>
+        <Key android:codes="55" android:keyIcon="@drawable/sym_bkeyboard_num7" android:keyEdgeFlags="left"/>
+        <Key android:codes="56" android:keyIcon="@drawable/sym_bkeyboard_num8"/>
+        <Key android:codes="57" android:keyIcon="@drawable/sym_bkeyboard_num9"/>
+        <Key android:codes="-5" android:keyIcon="@drawable/sym_bkeyboard_delete"
+                android:iconPreview="@drawable/sym_keyboard_feedback_delete"
+                android:keyWidth="20%p"
+                android:isRepeatable="true" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyIcon="@drawable/sym_bkeyboard_numalt"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="_"
+                android:iconPreview="@drawable/sym_keyboard_feedback_numalt"/>
+
+        <Key android:codes="48" android:keyIcon="@drawable/sym_bkeyboard_num0"/>
+
+        <Key android:codes="32" android:keyIcon="@drawable/sym_bkeyboard_space"
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:isRepeatable="true"/>
+        <Key android:codes="10" android:keyIcon="@drawable/sym_bkeyboard_return"
+                android:keyWidth="20%p"
+                android:keyEdgeFlags="right"/>
+    </Row>
+
+</Keyboard>
diff --git a/java/res/xml/kbd_phone_symbols_black.xml b/java/res/xml/kbd_phone_symbols_black.xml
new file mode 100755
index 0000000..c73e5fa
--- /dev/null
+++ b/java/res/xml/kbd_phone_symbols_black.xml
@@ -0,0 +1,70 @@
+<?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.
+*/
+-->
+
+<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
+    android:keyWidth="26.67%p"
+    android:horizontalGap="0px"
+    android:verticalGap="0px"
+    android:keyHeight="@dimen/key_height"
+    >
+
+    <Row>
+        <Key android:keyLabel="(" android:keyEdgeFlags="left"/>
+        <Key android:keyLabel="/"/>
+        <Key android:keyLabel=")"/>
+        <Key android:keyLabel="-" android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row>
+        <Key android:keyLabel="N" android:keyEdgeFlags="left"/>
+        <!-- Pause is a comma.
+                Check PhoneNumberUtils.java to see if this has changed. -->
+        <Key android:codes="44" android:keyLabel="Pause"/>
+        <Key android:keyLabel=","/>
+        <Key android:keyLabel="." android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row>
+        <Key android:codes="42" android:keyIcon="@drawable/sym_bkeyboard_numstar"
+                android:keyEdgeFlags="left"/>
+        <!-- Wait is a semicolon. -->
+        <Key android:codes="59" android:keyLabel="Wait"/>
+        <Key android:codes="35" android:keyIcon="@drawable/sym_bkeyboard_numpound"/>
+        <Key android:codes="-5" android:keyIcon="@drawable/sym_bkeyboard_delete"
+                android:iconPreview="@drawable/sym_keyboard_feedback_delete"
+                android:keyWidth="20%p"
+                android:isRepeatable="true" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyLabel="@string/label_phone_key"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="_"
+                android:keyEdgeFlags="left"/>
+        <Key android:keyLabel="+"/>
+        <Key android:codes="32" android:keyIcon="@drawable/sym_bkeyboard_space"
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:isRepeatable="true"/>
+        <Key android:codes="10" android:keyIcon="@drawable/sym_bkeyboard_return"
+                android:keyWidth="20%p"
+                android:keyEdgeFlags="right"/>
+    </Row>
+
+</Keyboard>
diff --git a/java/res/xml/kbd_qwerty_black.xml b/java/res/xml/kbd_qwerty_black.xml
new file mode 100755
index 0000000..d013ae0
--- /dev/null
+++ b/java/res/xml/kbd_qwerty_black.xml
@@ -0,0 +1,207 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 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.
+*/
+-->
+
+<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
+    android:keyWidth="10%p"
+    android:horizontalGap="0px"
+    android:verticalGap="0px"
+    android:keyHeight="@dimen/key_height"
+    >
+
+    <Row>
+        <Key android:codes="113" android:keyLabel="q" android:keyEdgeFlags="left"/>
+        <Key android:codes="119" android:keyLabel="w"/>
+        <Key android:codes="101" android:keyLabel="e"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_e"
+        />
+        <Key android:codes="114" android:keyLabel="r"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_r"/>
+        <Key android:codes="116" android:keyLabel="t"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_t"/>
+        <Key android:codes="121" android:keyLabel="y"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_y"
+        />
+        <Key android:codes="117" android:keyLabel="u"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_u"
+        />
+        <Key android:codes="105" android:keyLabel="i"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_i"
+        />
+        <Key android:codes="111" android:keyLabel="o"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_o"
+        />
+        <Key android:codes="112" android:keyLabel="p" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row>
+        <Key android:codes="97" android:keyLabel="a" android:horizontalGap="5%p"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_a"
+                android:keyEdgeFlags="left"/>
+        <Key android:codes="115" android:keyLabel="s"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_s"
+        />
+        <Key android:codes="100" android:keyLabel="d"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_d"/>
+        <Key android:codes="102" android:keyLabel="f"/>
+        <Key android:codes="103" android:keyLabel="g"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_g"
+        />
+        <Key android:codes="104" android:keyLabel="h"/>
+        <Key android:codes="106" android:keyLabel="j"/>
+        <Key android:codes="107" android:keyLabel="k"/>
+        <Key android:codes="108" android:keyLabel="l"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_l"
+                android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row>
+        <Key android:codes="-1" android:keyIcon="@drawable/sym_bkeyboard_shift"
+                android:keyWidth="15%p" android:isModifier="true"
+                android:iconPreview="@drawable/sym_keyboard_feedback_shift"
+                android:isSticky="true" android:keyEdgeFlags="left"/>
+        <Key android:codes="122" android:keyLabel="z"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_z"/>
+        <Key android:codes="120" android:keyLabel="x"/>
+        <Key android:codes="99" android:keyLabel="c"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_c"
+        />
+        <Key android:codes="118" android:keyLabel="v"/>
+        <Key android:codes="98" android:keyLabel="b"/>
+        <Key android:codes="110" android:keyLabel="n"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_n"
+        />
+        <Key android:codes="109" android:keyLabel="m"/>
+        <Key android:codes="-5" android:keyIcon="@drawable/sym_bkeyboard_delete"
+                android:keyWidth="15%p" android:keyEdgeFlags="right"
+                android:iconPreview="@drawable/sym_keyboard_feedback_delete"
+                android:isRepeatable="true"/>
+    </Row>
+
+    <Row android:keyboardMode="@+id/mode_normal" android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyLabel="@string/label_symbol_key"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="_"
+                android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+        <Key android:codes="@integer/key_f1" android:keyIcon="@drawable/sym_bkeyboard_mic"
+                android:iconPreview="@drawable/sym_keyboard_feedback_mic"
+                android:keyWidth="10%p"/>
+        <Key android:codes="32" android:keyIcon="@drawable/sym_bkeyboard_space"
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:keyWidth="40%p" android:isRepeatable="true"/>
+        <Key android:codes="46" android:keyLabel="." android:popupKeyboard="@xml/popup_punctuation"
+                android:keyWidth="10%p"/>
+        <Key android:codes="10" android:keyIcon="@drawable/sym_bkeyboard_return"
+                android:iconPreview="@drawable/sym_keyboard_feedback_return"
+                android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row android:keyboardMode="@+id/mode_url" android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyLabel="@string/label_symbol_key"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="_"
+                android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+        <Key android:keyLabel="/" android:keyWidth="10%p"/>
+        <Key android:codes="32" android:keyIcon="@drawable/sym_bkeyboard_space"
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:keyWidth="40%p" android:isRepeatable="true"/>
+        <!--Key android:keyLabel="@string/popular_domain_0"
+                android:keyOutputText="@string/popular_domain_0"
+                android:popupKeyboard="@xml/popup_domains"
+                android:keyWidth="20%p"/-->
+        <Key android:keyLabel="." android:popupKeyboard="@xml/popup_punctuation"
+                android:keyWidth="10%p"/>
+        <Key android:codes="10" android:keyIcon="@drawable/sym_bkeyboard_return"
+                android:iconPreview="@drawable/sym_keyboard_feedback_return"
+                android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row android:keyboardMode="@+id/mode_email" android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyLabel="@string/label_symbol_key"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="_"
+                android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+        <Key android:keyLabel="\@"/>
+        <Key android:codes="32" android:keyIcon="@drawable/sym_bkeyboard_space"
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:keyWidth="40%p" android:isRepeatable="true"/>
+        <!--Key android:keyLabel="@string/popular_domain_0"
+                android:keyOutputText="@string/popular_domain_0"
+                android:popupKeyboard="@xml/popup_domains"
+                android:keyWidth="20%p"/-->
+        <Key android:keyLabel="." android:popupKeyboard="@xml/popup_punctuation"/>
+        <Key android:codes="10" android:keyIcon="@drawable/sym_bkeyboard_return"
+                android:iconPreview="@drawable/sym_keyboard_feedback_return"
+                android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row android:keyboardMode="@+id/mode_im" android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyLabel="@string/label_symbol_key"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="_"
+                android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+        <Key android:codes="@integer/key_f1" android:keyIcon="@drawable/sym_bkeyboard_mic"
+                android:iconPreview="@drawable/sym_keyboard_feedback_mic"
+                android:keyWidth="10%p"/>
+        <Key android:codes="32" android:keyIcon="@drawable/sym_bkeyboard_space"
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:keyWidth="40%p" android:isRepeatable="true"/>
+        <Key android:codes="46" android:keyLabel="." android:popupKeyboard="@xml/popup_punctuation"
+                android:keyWidth="10%p"/>
+        <Key android:keyLabel=":-)" android:keyOutputText=":-) "
+                android:popupKeyboard="@xml/popup_smileys"
+                android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row android:keyboardMode="@+id/mode_webentry" android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyLabel="@string/label_symbol_key"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="_"
+                android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+        <Key android:codes="@integer/key_f1" android:keyIcon="@drawable/sym_bkeyboard_mic"
+                android:iconPreview="@drawable/sym_keyboard_feedback_mic"
+                android:keyWidth="10%p"/>
+        <Key android:codes="32" android:keyIcon="@drawable/sym_bkeyboard_space"
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:keyWidth="20%p" android:isRepeatable="true"/>
+        <Key android:codes="9" android:keyIcon="@drawable/sym_bkeyboard_tab"
+                android:iconPreview="@drawable/sym_keyboard_feedback_tab"
+                android:keyWidth="20%p"/>
+        <Key android:keyLabel="." android:popupKeyboard="@xml/popup_punctuation"/>
+        <Key android:codes="10" android:keyIcon="@drawable/sym_bkeyboard_return"
+                android:iconPreview="@drawable/sym_keyboard_feedback_return"
+                android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+
+</Keyboard>
diff --git a/java/res/xml/kbd_symbols_black.xml b/java/res/xml/kbd_symbols_black.xml
new file mode 100755
index 0000000..5652f7f
--- /dev/null
+++ b/java/res/xml/kbd_symbols_black.xml
@@ -0,0 +1,141 @@
+<?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.
+*/
+-->
+
+<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
+    android:keyWidth="10%p"
+    android:horizontalGap="0px"
+    android:verticalGap="0px"
+    android:keyHeight="@dimen/key_height"
+    >
+
+    <Row>
+        <Key android:codes="49" android:keyLabel="1" android:keyEdgeFlags="left"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="¹½⅓¼⅛"
+        />
+        <Key android:codes="50" android:keyLabel="2"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="²⅔"
+        />
+        <Key android:codes="51" android:keyLabel="3"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="³¾⅜"
+        />
+        <Key android:codes="52" android:keyLabel="4"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="⁴"
+        />
+        <Key android:codes="53" android:keyLabel="5"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="⅝"
+        />
+        <Key android:codes="54" android:keyLabel="6"/>
+        <Key android:codes="55" android:keyLabel="7"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="⅞"
+        />
+        <Key android:codes="56" android:keyLabel="8"/>
+        <Key android:codes="57" android:keyLabel="9"/>
+        <Key android:codes="48" android:keyLabel="0"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="ⁿ∅"
+                android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row>
+        <Key android:codes="64" android:keyLabel="\@" android:keyEdgeFlags="left"/>
+        <Key android:codes="35" android:keyLabel="\#"/>
+        <Key android:codes="36" android:keyLabel="$"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="¢£€¥₣₤₱"
+        />
+        <Key android:codes="37" android:keyLabel="%"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="‰"
+        />
+        <Key android:codes="38" android:keyLabel="&amp;"/>
+        <Key android:codes="42" android:keyLabel="*"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="†‡★"
+        />
+        <Key android:codes="45" android:keyLabel="-"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="_–—"
+        />
+        <Key android:keyLabel="+"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="±"
+        />
+        <Key android:codes="40" android:keyLabel="("
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="[{&lt;"
+        />
+        <Key android:codes="41" android:keyLabel=")" android:keyEdgeFlags="right"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="]}&gt;"
+        />
+    </Row>
+
+    <Row>
+        <Key android:codes="-1" android:keyLabel="@string/label_alt_key"
+                android:keyWidth="15%p" android:isModifier="true"
+                android:isSticky="true" android:keyEdgeFlags="left"/>
+        <Key android:codes="33" android:keyLabel="!"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="¡"
+        />
+        <Key android:codes="34" android:keyLabel="&quot;"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="“”«»˝"
+        />
+        <Key android:codes="39" android:keyLabel="\'"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="‘’"
+        />
+        <Key android:codes="58" android:keyLabel=":"/>
+        <Key android:codes="59" android:keyLabel=";"/>
+        <Key android:codes="47" android:keyLabel="/" />
+        <Key android:codes="63" android:keyLabel="\?"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="¿"
+        />
+        <Key android:codes="-5" android:keyIcon="@drawable/sym_bkeyboard_delete" android:keyWidth="15%p" android:keyEdgeFlags="right"
+                android:iconPreview="@drawable/sym_keyboard_feedback_delete"
+                android:isRepeatable="true"/>
+    </Row>
+
+    <Row  android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyLabel="@string/label_alpha_key"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="_"
+                android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+        <Key android:codes="@integer/key_f1" android:keyWidth="10%p"/>
+        <Key android:codes="32" android:keyIcon="@drawable/sym_bkeyboard_space"
+                android:keyWidth="40%p"
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:isRepeatable="true"/>
+        <Key android:codes="46" android:keyLabel="."
+                android:popupKeyboard="@xml/popup_punctuation"
+                android:keyWidth="10%p"/>
+        <Key android:codes="10" android:keyIcon="@drawable/sym_bkeyboard_return" android:keyWidth="20%p" android:keyEdgeFlags="right"
+                android:iconPreview="@drawable/sym_keyboard_feedback_return"
+                />
+    </Row>
+</Keyboard>
diff --git a/java/res/xml/kbd_symbols_shift.xml b/java/res/xml/kbd_symbols_shift.xml
index 09b5c3f..ca431fc 100755
--- a/java/res/xml/kbd_symbols_shift.xml
+++ b/java/res/xml/kbd_symbols_shift.xml
@@ -34,7 +34,10 @@
                 android:popupCharacters="♪♥♠♦♣"
         />
         <Key android:keyLabel="√"/>
-        <Key android:keyLabel="π"/>
+        <Key android:keyLabel="π"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="Π"
+        />
         <Key android:keyLabel="÷"/>
         <Key android:keyLabel="×"/>
         <Key android:keyLabel="{"/>
diff --git a/java/res/xml/kbd_symbols_shift_black.xml b/java/res/xml/kbd_symbols_shift_black.xml
new file mode 100755
index 0000000..a8acb9d
--- /dev/null
+++ b/java/res/xml/kbd_symbols_shift_black.xml
@@ -0,0 +1,107 @@
+<?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.
+*/
+-->
+
+<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
+    android:keyWidth="10%p"
+    android:horizontalGap="0px"
+    android:verticalGap="0px"
+    android:keyHeight="@dimen/key_height"
+    >
+
+    <Row>
+        <Key android:keyLabel="~" android:keyEdgeFlags="left"/>
+        <Key android:keyLabel="`"/>
+        <Key android:keyLabel="|"/>
+        <Key android:keyLabel="•"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="♪♥♠♦♣"
+        />
+        <Key android:keyLabel="√"/>
+        <Key android:keyLabel="π"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="Π"
+        />
+        <Key android:keyLabel="÷"/>
+        <Key android:keyLabel="×"/>
+        <Key android:keyLabel="{"/>
+        <Key android:keyLabel="}" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row>
+        <Key android:codes="9" android:keyLabel="\u21E5" android:keyEdgeFlags="left"/>
+        <Key android:keyLabel="£"/>
+        <Key android:keyLabel="¢"/>
+        <Key android:keyLabel="€"/>
+        <Key android:keyLabel="°"/>
+        <Key android:keyLabel="^"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="↑↓←→"
+        />
+        <Key android:keyLabel="_"/>
+        <Key android:keyLabel="="
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="≠≈∞"
+        />
+        <Key android:keyLabel="["/>
+        <Key android:keyLabel="]" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row>
+        <Key android:codes="-1" android:keyLabel="@string/label_alt_key"
+                android:keyWidth="15%p" android:isModifier="true"
+                android:isSticky="true" android:keyEdgeFlags="left"/>
+        <Key android:keyLabel="™"/>
+        <Key android:keyLabel="®"/>
+        <Key android:keyLabel="©"/>
+        <Key android:keyLabel="¶"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="§"
+        />
+        <Key android:keyLabel="\\"/>
+        <Key android:keyLabel="&lt;"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="≤«‹"
+        />
+        <Key android:keyLabel="&gt;"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="≥»›"
+        />
+        <Key android:codes="-5" android:keyIcon="@drawable/sym_bkeyboard_delete" android:keyWidth="15%p" android:keyEdgeFlags="right"
+                android:iconPreview="@drawable/sym_keyboard_feedback_delete"
+                android:isRepeatable="true"/>
+    </Row>
+
+    <Row android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyLabel="@string/label_alpha_key" android:keyWidth="20%p"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="_"
+                android:keyEdgeFlags="left"/>
+        <Key android:keyLabel="„" android:keyWidth="10%p" />
+        <Key android:codes="32" android:keyIcon="@drawable/sym_bkeyboard_space"
+                android:keyWidth="40%p"
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:isRepeatable="true"/>
+        <Key android:keyLabel="…" android:keyWidth="10%p" />
+        <Key android:codes="10" android:keyIcon="@drawable/sym_bkeyboard_return"
+                android:keyWidth="20%p" android:keyEdgeFlags="right"
+                android:iconPreview="@drawable/sym_keyboard_feedback_return"
+                />
+    </Row>
+</Keyboard>
diff --git a/java/res/xml/prefs.xml b/java/res/xml/prefs.xml
index 535b63f..11cc3ac 100644
--- a/java/res/xml/prefs.xml
+++ b/java/res/xml/prefs.xml
@@ -31,12 +31,27 @@
             />
 
     <CheckBoxPreference
+            android:key="popup_on"
+            android:title="@string/popup_on_keypress"
+            android:persistent="true"
+            android:defaultValue="@bool/default_popup_preview"
+            />
+
+    <CheckBoxPreference
             android:key="auto_cap"
             android:title="@string/auto_cap"
             android:persistent="true"
             android:defaultValue="true"
             />
 
+    <CheckBoxPreference
+            android:key="enable_logging"
+            android:title="@string/prefs_enable_log"
+            android:summary="@string/prefs_description_log"
+            android:persistent="true"
+            android:defaultValue="false"
+            />
+
     <ListPreference
             android:key="voice_mode"
             android:title="@string/voice_input"
@@ -46,6 +61,15 @@
             android:defaultValue="@string/voice_mode_main"
             />
 
+    <ListPreference
+            android:key="keyboard_layout"
+            android:title="@string/keyboard_layout"
+            android:persistent="true"
+            android:entryValues="@array/keyboard_layout_modes_values"
+            android:entries="@array/keyboard_layout_modes"
+            android:defaultValue="3"
+            />
+
     <PreferenceScreen
             android:title="@string/language_selection_title"
             android:summary="@string/language_selection_summary">
@@ -81,6 +105,21 @@
             android:defaultValue="@bool/enable_autocorrect"
             android:dependency="show_suggestions"
             />
-            
+
+        <CheckBoxPreference
+            android:key="bigram_suggestion"
+            android:title="@string/bigram_suggestion"
+            android:summary="@string/bigram_suggestion_summary"
+            android:persistent="true"
+            android:defaultValue="true"
+            android:dependency="auto_complete"
+            />
     </PreferenceCategory>            
+
+<CheckBoxPreference
+            android:key="debug_mode"
+            android:title="@string/prefs_debug_mode"
+            android:persistent="true"
+            android:defaultValue="false"
+            />
 </PreferenceScreen>
diff --git a/java/src/com/android/inputmethod/latin/AutoDictionary.java b/java/src/com/android/inputmethod/latin/AutoDictionary.java
index 93f1985..4fbb5b0 100644
--- a/java/src/com/android/inputmethod/latin/AutoDictionary.java
+++ b/java/src/com/android/inputmethod/latin/AutoDictionary.java
@@ -83,14 +83,14 @@
         sDictProjectionMap.put(COLUMN_LOCALE, COLUMN_LOCALE);
     }
 
-    private static DatabaseHelper mOpenHelper = null;
+    private static DatabaseHelper sOpenHelper = null;
 
-    public AutoDictionary(Context context, LatinIME ime, String locale) {
-        super(context);
+    public AutoDictionary(Context context, LatinIME ime, String locale, int dicTypeId) {
+        super(context, dicTypeId);
         mIme = ime;
         mLocale = locale;
-        if (mOpenHelper == null) {
-            mOpenHelper = new DatabaseHelper(getContext());
+        if (sOpenHelper == null) {
+            sOpenHelper = new DatabaseHelper(getContext());
         }
         if (mLocale != null && mLocale.length() > 1) {
             loadDictionary();
@@ -169,7 +169,7 @@
             // Nothing pending? Return
             if (mPendingWrites.isEmpty()) return;
             // Create a background thread to write the pending entries
-            new UpdateDbTask(getContext(), mOpenHelper, mPendingWrites, mLocale).execute();
+            new UpdateDbTask(getContext(), sOpenHelper, mPendingWrites, mLocale).execute();
             // Create a new map for writing new entries into while the old one is written to db
             mPendingWrites = new HashMap<String, Integer>();
         }
@@ -209,7 +209,7 @@
         qb.setProjectionMap(sDictProjectionMap);
 
         // Get the database and run the query
-        SQLiteDatabase db = mOpenHelper.getReadableDatabase();
+        SQLiteDatabase db = sOpenHelper.getReadableDatabase();
         Cursor c = qb.query(db, null, selection, selectionArgs, null, null,
                 DEFAULT_SORT_ORDER);
         return c;
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
index 87de94b..69c2b94 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
@@ -16,10 +16,15 @@
 
 package com.android.inputmethod.latin;
 
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.channels.Channels;
 import java.util.Arrays;
 
 import android.content.Context;
-import android.content.res.AssetManager;
 import android.util.Log;
 
 /**
@@ -27,18 +32,33 @@
  */
 public class BinaryDictionary extends Dictionary {
 
-    public static final int MAX_WORD_LENGTH = 48;
+    /**
+     * There is 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.
+     */
+    protected static final int MAX_WORD_LENGTH = 48;
+
+    private static final String TAG = "BinaryDictionary";
     private static final int MAX_ALTERNATIVES = 16;
-    private static final int MAX_WORDS = 16;
+    private static final int MAX_WORDS = 18;
+    private static final int MAX_BIGRAMS = 60;
 
     private static final int TYPED_LETTER_MULTIPLIER = 2;
     private static final boolean ENABLE_MISSED_CHARACTERS = true;
 
+    private int mDicTypeId;
     private int mNativeDict;
-    private int mDictLength; // This value is set from native code, don't change the name!!!!
+    private int mDictLength;
     private int[] mInputCodes = new int[MAX_WORD_LENGTH * MAX_ALTERNATIVES];
     private char[] mOutputChars = new char[MAX_WORD_LENGTH * MAX_WORDS];
+    private char[] mOutputChars_bigrams = new char[MAX_WORD_LENGTH * MAX_BIGRAMS];
     private int[] mFrequencies = new int[MAX_WORDS];
+    private int[] mFrequencies_bigrams = new int[MAX_BIGRAMS];
+    // Keep a reference to the native dict direct buffer in Java to avoid
+    // unexpected deallocation of the direct buffer.
+    private ByteBuffer mNativeDictDirectBuffer;
 
     static {
         try {
@@ -53,32 +73,122 @@
      * @param context application context for reading resources
      * @param resId the resource containing the raw binary dictionary
      */
-    public BinaryDictionary(Context context, int resId) {
-        if (resId != 0) {
+    public BinaryDictionary(Context context, int[] resId, int dicTypeId) {
+        if (resId != null && resId.length > 0 && resId[0] != 0) {
             loadDictionary(context, resId);
         }
+        mDicTypeId = dicTypeId;
     }
 
-    private native int openNative(AssetManager am, String resourcePath, int typedLetterMultiplier,
+    /**
+     * Create a dictionary from a byte buffer. This is used for testing.
+     * @param context application context for reading resources
+     * @param byteBuffer a ByteBuffer containing the binary dictionary
+     */
+    public BinaryDictionary(Context context, ByteBuffer byteBuffer, int dicTypeId) {
+        if (byteBuffer != null) {
+            if (byteBuffer.isDirect()) {
+                mNativeDictDirectBuffer = byteBuffer;
+            } else {
+                mNativeDictDirectBuffer = ByteBuffer.allocateDirect(byteBuffer.capacity());
+                byteBuffer.rewind();
+                mNativeDictDirectBuffer.put(byteBuffer);
+            }
+            mDictLength = byteBuffer.capacity();
+            mNativeDict = openNative(mNativeDictDirectBuffer,
+                    TYPED_LETTER_MULTIPLIER, FULL_WORD_FREQ_MULTIPLIER);
+        }
+        mDicTypeId = dicTypeId;
+    }
+
+    private native int openNative(ByteBuffer bb, int typedLetterMultiplier,
             int fullWordMultiplier);
     private native void closeNative(int dict);
     private native boolean isValidWordNative(int nativeData, char[] word, int wordLength);
     private native int getSuggestionsNative(int dict, int[] inputCodes, int codesSize, 
-            char[] outputChars, int[] frequencies,
-            int maxWordLength, int maxWords, int maxAlternatives, int skipPos,
-            int[] nextLettersFrequencies, int nextLettersSize);
+            char[] outputChars, int[] frequencies, int maxWordLength, int maxWords,
+            int maxAlternatives, int skipPos, int[] nextLettersFrequencies, int nextLettersSize);
+    private native int getBigramsNative(int dict, char[] prevWord, int prevWordLength,
+            int[] inputCodes, int inputCodesLength, char[] outputChars, int[] frequencies,
+            int maxWordLength, int maxBigrams, int maxAlternatives);
 
-    private final void loadDictionary(Context context, int resId) {
-        AssetManager am = context.getResources().getAssets();
-        String assetName = context.getResources().getString(resId);
-        mNativeDict = openNative(am, assetName, TYPED_LETTER_MULTIPLIER, FULL_WORD_FREQ_MULTIPLIER);
+    private final void loadDictionary(Context context, int[] resId) {
+        InputStream[] is = null;
+        try {
+            // merging separated dictionary into one if dictionary is separated
+            int total = 0;
+            is = new InputStream[resId.length];
+            for (int i = 0; i < resId.length; i++) {
+                is[i] = context.getResources().openRawResource(resId[i]);
+                total += is[i].available();
+            }
+
+            mNativeDictDirectBuffer =
+                ByteBuffer.allocateDirect(total).order(ByteOrder.nativeOrder());
+            int got = 0;
+            for (int i = 0; i < resId.length; i++) {
+                 got += Channels.newChannel(is[i]).read(mNativeDictDirectBuffer);
+            }
+            if (got != total) {
+                Log.e(TAG, "Read " + got + " bytes, expected " + total);
+            } else {
+                mNativeDict = openNative(mNativeDictDirectBuffer,
+                        TYPED_LETTER_MULTIPLIER, FULL_WORD_FREQ_MULTIPLIER);
+                mDictLength = total;
+            }
+        } catch (IOException e) {
+            Log.w(TAG, "No available memory for binary dictionary");
+        } finally {
+            try {
+                if (is != null) {
+                    for (int i = 0; i < is.length; i++) {
+                        is[i].close();
+                    }
+                }
+            } catch (IOException e) {
+                Log.w(TAG, "Failed to close input stream");
+            }
+        }
+    }
+
+
+    @Override
+    public void getBigrams(final WordComposer codes, final CharSequence previousWord,
+            final WordCallback callback, int[] nextLettersFrequencies) {
+
+        char[] chars = previousWord.toString().toCharArray();
+        Arrays.fill(mOutputChars_bigrams, (char) 0);
+        Arrays.fill(mFrequencies_bigrams, 0);
+
+        int codesSize = codes.size();
+        Arrays.fill(mInputCodes, -1);
+        int[] alternatives = codes.getCodesAt(0);
+        System.arraycopy(alternatives, 0, mInputCodes, 0,
+                Math.min(alternatives.length, MAX_ALTERNATIVES));
+
+        int count = getBigramsNative(mNativeDict, chars, chars.length, mInputCodes, codesSize,
+                mOutputChars_bigrams, mFrequencies_bigrams, MAX_WORD_LENGTH, MAX_BIGRAMS,
+                MAX_ALTERNATIVES);
+
+        for (int j = 0; j < count; j++) {
+            if (mFrequencies_bigrams[j] < 1) break;
+            int start = j * MAX_WORD_LENGTH;
+            int len = 0;
+            while (mOutputChars_bigrams[start + len] != 0) {
+                len++;
+            }
+            if (len > 0) {
+                callback.addWord(mOutputChars_bigrams, start, len, mFrequencies_bigrams[j],
+                        mDicTypeId, DataType.BIGRAM);
+            }
+        }
     }
 
     @Override
     public void getWords(final WordComposer codes, final WordCallback callback,
             int[] nextLettersFrequencies) {
         final int codesSize = codes.size();
-        // Wont deal with really long words.
+        // Won't deal with really long words.
         if (codesSize > MAX_WORD_LENGTH - 1) return;
         
         Arrays.fill(mInputCodes, -1);
@@ -119,7 +229,8 @@
                 len++;
             }
             if (len > 0) {
-                callback.addWord(mOutputChars, start, len, mFrequencies[j]);
+                callback.addWord(mOutputChars, start, len, mFrequencies[j], mDicTypeId,
+                        DataType.UNIGRAM);
             }
         }
     }
diff --git a/java/src/com/android/inputmethod/latin/CandidateView.java b/java/src/com/android/inputmethod/latin/CandidateView.java
index 4dc61d4..7fcc3d5 100755
--- a/java/src/com/android/inputmethod/latin/CandidateView.java
+++ b/java/src/com/android/inputmethod/latin/CandidateView.java
@@ -83,7 +83,6 @@
     private int mDescent;
     private boolean mScrolled;
     private boolean mShowingAddToDictionary;
-    private CharSequence mWordToAddToDictionary;
     private CharSequence mAddToDictionaryHint;
 
     private int mTargetScrollX;
@@ -144,9 +143,13 @@
         mPaint.setStrokeWidth(0);
         mPaint.setTextAlign(Align.CENTER);
         mDescent = (int) mPaint.descent();
-        // 80 pixels for a 160dpi device would mean half an inch
+        // 50 pixels for a 160dpi device would mean about 0.3 inch
         mMinTouchableWidth = (int) (getResources().getDisplayMetrics().density * 50);
         
+        // Slightly reluctant to scroll to be able to easily choose the suggestion
+        // 50 pixels for a 160dpi device would mean about 0.3 inch
+        final int touchSlop = (int) (getResources().getDisplayMetrics().density * 50);
+        final int touchSlopSquare = touchSlop * touchSlop;
         mGestureDetector = new GestureDetector(new GestureDetector.SimpleOnGestureListener() {
             @Override
             public void onLongPress(MotionEvent me) {
@@ -160,6 +163,13 @@
             @Override
             public boolean onScroll(MotionEvent e1, MotionEvent e2,
                     float distanceX, float distanceY) {
+                final int deltaX = (int) (e2.getX() - e1.getX());
+                final int deltaY = (int) (e2.getY() - e1.getY());
+                final int distance = (deltaX * deltaX) + (deltaY * deltaY);
+                if (distance < touchSlopSquare) {
+                    return false;
+                }
+
                 final int width = getWidth();
                 mScrolled = true;
                 int scrollX = getScrollX();
@@ -167,7 +177,7 @@
                 if (scrollX < 0) {
                     scrollX = 0;
                 }
-                if (distanceX > 0 && scrollX + width > mTotalWidth) {                    
+                if (distanceX > 0 && scrollX + width > mTotalWidth) {
                     scrollX -= (int) distanceX;
                 }
                 mTargetScrollX = scrollX;
@@ -219,8 +229,7 @@
                     mDivider.getIntrinsicHeight());
         }
         int x = 0;
-        final int count = mSuggestions.size(); 
-        final int width = getWidth();
+        final int count = Math.min(mSuggestions.size(), MAX_SUGGESTIONS);
         final Rect bgPadding = mBgPadding;
         final Paint paint = mPaint;
         final int touchX = mTouchX;
@@ -325,7 +334,6 @@
     }
 
     public void showAddToDictionaryHint(CharSequence word) {
-        mWordToAddToDictionary = word;
         ArrayList<CharSequence> suggestions = new ArrayList<CharSequence>();
         suggestions.add(word);
         suggestions.add(mAddToDictionaryHint);
@@ -341,7 +349,7 @@
 
     public void scrollPrev() {
         int i = 0;
-        final int count = mSuggestions.size();
+        final int count = Math.min(mSuggestions.size(), MAX_SUGGESTIONS);
         int firstItem = 0; // Actually just before the first item, if at the boundary
         while (i < count) {
             if (mWordX[i] < getScrollX() 
@@ -360,7 +368,7 @@
         int i = 0;
         int scrollX = getScrollX();
         int targetX = scrollX;
-        final int count = mSuggestions.size();
+        final int count = Math.min(mSuggestions.size(), MAX_SUGGESTIONS);
         int rightEdge = scrollX + getWidth();
         while (i < count) {
             if (mWordX[i] <= rightEdge &&
@@ -382,8 +390,14 @@
             mScrolled = true;
         }
     }
-    
+
+    /* package */ List<CharSequence> getSuggestions() {
+        return mSuggestions;
+    }
+
     public void clear() {
+        // Don't call mSuggestions.clear() because it's being used for logging
+        // in LatinIME.pickSuggestionManually().
         mSuggestions = EMPTY_LIST;
         mTouchX = OUT_OF_BOUNDS;
         mSelectedString = null;
@@ -418,7 +432,11 @@
             if (y <= 0) {
                 // Fling up!?
                 if (mSelectedString != null) {
+                    // If there are completions from the application, we don't change the state to
+                    // STATE_PICKED_SUGGESTION
                     if (!mShowingCompletions) {
+                        // This "acceptedSuggestion" will not be counted as a word because
+                        // it will be counted in pickSuggestion instead.
                         TextEntryState.acceptedSuggestion(mSuggestions.get(0),
                                 mSelectedString);
                     }
@@ -453,25 +471,6 @@
         }
         return true;
     }
-    
-    /**
-     * For flick through from keyboard, call this method with the x coordinate of the flick 
-     * gesture.
-     * @param x
-     */
-    public void takeSuggestionAt(float x) {
-        mTouchX = (int) x;
-        // To detect candidate
-        onDraw(null);
-        if (mSelectedString != null) {
-            if (!mShowingCompletions) {
-                TextEntryState.acceptedSuggestion(mSuggestions.get(0), mSelectedString);
-            }
-            mService.pickSuggestionManually(mSelectedIndex, mSelectedString);
-        }
-        invalidate();
-        mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_REMOVE_THROUGH_PREVIEW), 200);
-    }
 
     private void hidePreview() {
         mCurrentWordIndex = OUT_OF_BOUNDS;
diff --git a/java/src/com/android/inputmethod/latin/ContactsDictionary.java b/java/src/com/android/inputmethod/latin/ContactsDictionary.java
index 15edb70..ab75868 100644
--- a/java/src/com/android/inputmethod/latin/ContactsDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ContactsDictionary.java
@@ -20,9 +20,10 @@
 import android.content.Context;
 import android.database.ContentObserver;
 import android.database.Cursor;
-import android.os.AsyncTask;
 import android.os.SystemClock;
 import android.provider.ContactsContract.Contacts;
+import android.text.TextUtils;
+import android.util.Log;
 
 public class ContactsDictionary extends ExpandableDictionary {
 
@@ -31,27 +32,35 @@
         Contacts.DISPLAY_NAME,
     };
 
+    /**
+     * Frequency for contacts information into the dictionary
+     */
+    private static final int FREQUENCY_FOR_CONTACTS = 128;
+    private static final int FREQUENCY_FOR_CONTACTS_BIGRAM = 90;
+
     private static final int INDEX_NAME = 1;
 
     private ContentObserver mObserver;
 
     private long mLastLoadedContacts;
 
-    public ContactsDictionary(Context context) {
-        super(context);
+    public ContactsDictionary(Context context, int dicTypeId) {
+        super(context, dicTypeId);
         // Perform a managed query. The Activity will handle closing and requerying the cursor
         // when needed.
         ContentResolver cres = context.getContentResolver();
 
-        cres.registerContentObserver(Contacts.CONTENT_URI, true, mObserver = new ContentObserver(null) {
-            @Override
-            public void onChange(boolean self) {
-                setRequiresReload(true);
-            }
-        });
+        cres.registerContentObserver(
+                Contacts.CONTENT_URI, true,mObserver = new ContentObserver(null) {
+                    @Override
+                    public void onChange(boolean self) {
+                        setRequiresReload(true);
+                    }
+                });
         loadDictionary();
     }
 
+    @Override
     public synchronized void close() {
         if (mObserver != null) {
             getContext().getContentResolver().unregisterContentObserver(mObserver);
@@ -89,6 +98,7 @@
 
                 if (name != null) {
                     int len = name.length();
+                    String prevWord = null;
 
                     // TODO: Better tokenization for non-Latin writing systems
                     for (int i = 0; i < len; i++) {
@@ -112,7 +122,13 @@
                             // capitalization of i.
                             final int wordLen = word.length();
                             if (wordLen < maxWordLength && wordLen > 1) {
-                                super.addWord(word, 128);
+                                super.addWord(word, FREQUENCY_FOR_CONTACTS);
+                                if (!TextUtils.isEmpty(prevWord)) {
+                                    // TODO Do not add email address
+                                    // Not so critical
+                                    super.setBigram(prevWord, word, FREQUENCY_FOR_CONTACTS_BIGRAM);
+                                }
+                                prevWord = word;
                             }
                         }
                     }
diff --git a/java/src/com/android/inputmethod/latin/Dictionary.java b/java/src/com/android/inputmethod/latin/Dictionary.java
index e7b5266..d04bf57 100644
--- a/java/src/com/android/inputmethod/latin/Dictionary.java
+++ b/java/src/com/android/inputmethod/latin/Dictionary.java
@@ -21,7 +21,6 @@
  * strokes.
  */
 abstract public class Dictionary {
-    
     /**
      * Whether or not to replicate the typed word in the suggested list, even if it's valid.
      */
@@ -31,7 +30,11 @@
      * 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;
-    
+
+    public static enum DataType {
+        UNIGRAM, BIGRAM
+    }
+
     /**
      * Interface to be implemented by classes requesting words to be fetched from the dictionary.
      * @see #getWords(WordComposer, WordCallback)
@@ -45,9 +48,12 @@
          * @param wordLength length of valid characters in the character array
          * @param frequency the frequency of occurence. 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);
+        boolean addWord(char[] word, int wordOffset, int wordLength, int frequency, int dicTypeId,
+                DataType dataType);
     }
 
     /**
@@ -65,6 +71,21 @@
             int[] nextLettersFrequencies);
 
     /**
+     * Searches for pairs in the bigram dictionary that matches the previous word and all the
+     * possible words following are added through the callback object.
+     * @param composer the key sequence to match
+     * @param callback the callback object to send possible word following previous word
+     * @param nextLettersFrequencies array of frequencies of next letters that could follow the
+     *        word so far. For instance, "bracke" can be followed by "t", so array['t'] will have
+     *        a non-zero value on returning from this method.
+     *        Pass in null if you don't want the dictionary to look up next letters.
+     */
+    public void getBigrams(final WordComposer composer, final CharSequence previousWord,
+            final WordCallback callback, int[] nextLettersFrequencies) {
+        // empty base implementation
+    }
+
+    /**
      * Checks if the given word occurs in the dictionary
      * @param word the word to search for. The search should be case-insensitive.
      * @return true if the word exists, false otherwise
diff --git a/java/src/com/android/inputmethod/latin/EditingUtil.java b/java/src/com/android/inputmethod/latin/EditingUtil.java
new file mode 100644
index 0000000..be31cb7
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/EditingUtil.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) 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.view.inputmethod.ExtractedText;
+import android.view.inputmethod.ExtractedTextRequest;
+import android.view.inputmethod.InputConnection;
+
+import java.util.regex.Pattern;
+
+/**
+ * Utility methods to deal with editing text through an InputConnection.
+ */
+public class EditingUtil {
+    /**
+     * Number of characters we want to look back in order to identify the previous word
+     */
+    private static final int LOOKBACK_CHARACTER_NUM = 15;
+
+    private EditingUtil() {};
+
+    /**
+     * Append newText to the text field represented by connection.
+     * The new text becomes selected.
+     */
+    public static void appendText(InputConnection connection, String newText) {
+        if (connection == null) {
+            return;
+        }
+
+        // Commit the composing text
+        connection.finishComposingText();
+
+        // Add a space if the field already has text.
+        CharSequence charBeforeCursor = connection.getTextBeforeCursor(1, 0);
+        if (charBeforeCursor != null
+                && !charBeforeCursor.equals(" ")
+                && (charBeforeCursor.length() > 0)) {
+            newText = " " + newText;
+        }
+
+        connection.setComposingText(newText, 1);
+    }
+
+    private static int getCursorPosition(InputConnection connection) {
+        ExtractedText extracted = connection.getExtractedText(
+            new ExtractedTextRequest(), 0);
+        if (extracted == null) {
+          return -1;
+        }
+        return extracted.startOffset + extracted.selectionStart;
+    }
+
+    private static int getSelectionEnd(InputConnection connection) {
+        ExtractedText extracted = connection.getExtractedText(
+            new ExtractedTextRequest(), 0);
+        if (extracted == null) {
+          return -1;
+        }
+        return extracted.startOffset + extracted.selectionEnd;
+    }
+
+    /**
+     * @param connection connection to the current text field.
+     * @param sep 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.
+     */
+    public static String getWordAtCursor(
+            InputConnection connection, String separators) {
+        return getWordAtCursor(connection, separators, null);
+    }
+
+    /**
+     * @param connection connection to the current text field.
+     * @param sep 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.
+     */
+    public static String getWordAtCursor(
+        InputConnection connection, String separators, Range range) {
+        Range r = getWordRangeAtCursor(connection, separators, range);
+        return (r == null) ? null : r.word;
+    }
+
+    /**
+     * Removes the word surrounding the cursor. Parameters are identical to
+     * getWordAtCursor.
+     */
+    public static void deleteWordAtCursor(
+        InputConnection connection, String separators) {
+
+        Range range = getWordRangeAtCursor(connection, separators, null);
+        if (range == null) return;
+
+        connection.finishComposingText();
+        // Move cursor to beginning of word, to avoid crash when cursor is outside
+        // of valid range after deleting text.
+        int newCursor = getCursorPosition(connection) - range.charsBefore;
+        connection.setSelection(newCursor, newCursor);
+        connection.deleteSurroundingText(0, range.charsBefore + range.charsAfter);
+    }
+
+    /**
+     * Represents a range of text, relative to the current cursor position.
+     */
+    public static class Range {
+        /** Characters before selection start */
+        public int charsBefore;
+
+        /**
+         * Characters after selection start, including one trailing word
+         * separator.
+         */
+        public int charsAfter;
+
+        /** The actual characters that make up a word */
+        public String word;
+
+        public Range() {}
+
+        public Range(int charsBefore, int charsAfter, String word) {
+            if (charsBefore < 0 || charsAfter < 0) {
+                throw new IndexOutOfBoundsException();
+            }
+            this.charsBefore = charsBefore;
+            this.charsAfter = charsAfter;
+            this.word = word;
+        }
+    }
+
+    private static Range getWordRangeAtCursor(
+            InputConnection connection, String sep, Range range) {
+        if (connection == null || sep == null) {
+            return null;
+        }
+        CharSequence before = connection.getTextBeforeCursor(1000, 0);
+        CharSequence after = connection.getTextAfterCursor(1000, 0);
+        if (before == null || after == null) {
+            return null;
+        }
+
+        // Find first word separator before the cursor
+        int start = before.length();
+        while (start > 0 && !isWhitespace(before.charAt(start - 1), sep)) start--;
+
+        // Find last word separator after the cursor
+        int end = -1;
+        while (++end < after.length() && !isWhitespace(after.charAt(end), sep));
+
+        int cursor = getCursorPosition(connection);
+        if (start >= 0 && cursor + end <= after.length() + before.length()) {
+            String word = before.toString().substring(start, before.length())
+                    + after.toString().substring(0, end);
+
+            Range returnRange = range != null? range : new Range();
+            returnRange.charsBefore = before.length() - start;
+            returnRange.charsAfter = end;
+            returnRange.word = word;
+            return returnRange;
+        }
+
+        return null;
+    }
+
+    private static boolean isWhitespace(int code, String whitespace) {
+        return whitespace.contains(String.valueOf((char) code));
+    }
+
+    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;
+        }
+        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;
+        }
+    }
+
+    /**
+     * Checks if the cursor is touching/inside a word or the selection is for a whole
+     * word and no more and no less.
+     * @param range the Range object that contains the bounds of the word around the cursor
+     * @param start the start of the selection
+     * @param end the end of the selection, which could be the same as the start, if text is not
+     * in selection mode
+     * @return false if the selection is a partial word or straddling multiple words, true if
+     * the selection is a full word or there is no selection.
+     */
+    public static boolean isFullWordOrInside(Range range, int start, int end) {
+        // Is the cursor inside or touching a word?
+        if (start == end) return true;
+
+        // Is it a selection? Then is the start of the selection the start of the word and
+        // the size of the selection the size of the word? Then return true
+        if (start < end
+                && (range.charsBefore == 0 && range.charsAfter == end - start)) {
+            return true;
+        }
+        return false;
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
index 46bc41c..e954c08 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
@@ -16,24 +16,30 @@
 
 package com.android.inputmethod.latin;
 
-import com.android.inputmethod.latin.Dictionary.WordCallback;
+import java.util.LinkedList;
 
 import android.content.Context;
 import android.os.AsyncTask;
-import android.os.SystemClock;
 
 /**
  * Base class for an in-memory dictionary that can grow dynamically and can
  * be searched for suggestions and valid words.
  */
 public class ExpandableDictionary extends Dictionary {
+    /**
+     * There is difference between what java and native code can handle.
+     * It uses 32 because Java stack overflows when greater value is used.
+     */
+    protected static final int MAX_WORD_LENGTH = 32;
+
     private Context mContext;
     private char[] mWordBuilder = new char[MAX_WORD_LENGTH];
+    private int mDicTypeId;
     private int mMaxDepth;
     private int mInputLength;
     private int[] mNextLettersFrequencies;
+    private StringBuilder sb = new StringBuilder(MAX_WORD_LENGTH);
 
-    public static final int MAX_WORD_LENGTH = 32;
     private static final char QUOTE = '\'';
 
     private boolean mRequiresReload;
@@ -47,7 +53,9 @@
         char code;
         int frequency;
         boolean terminal;
+        Node parent;
         NodeArray children;
+        LinkedList<NextWord> ngrams; // Supports ngram
     }
 
     static class NodeArray {
@@ -71,14 +79,27 @@
         }
     }
 
+    static class NextWord {
+        Node word;
+        NextWord nextWord;
+        int frequency;
+
+        NextWord(Node word, int frequency) {
+            this.word = word;
+            this.frequency = frequency;
+        }
+    }
+
+
     private NodeArray mRoots;
 
     private int[][] mCodes;
 
-    ExpandableDictionary(Context context) {
+    ExpandableDictionary(Context context, int dicTypeId) {
         mContext = context;
         clearDictionary();
         mCodes = new int[MAX_WORD_LENGTH][];
+        mDicTypeId = dicTypeId;
     }
 
     public void loadDictionary() {
@@ -118,12 +139,11 @@
     }
 
     public void addWord(String word, int frequency) {
-        addWordRec(mRoots, word, 0, frequency);
+        addWordRec(mRoots, word, 0, frequency, null);
     }
 
-    private void addWordRec(NodeArray children, final String word,
-            final int depth, final int frequency) {
-        
+    private void addWordRec(NodeArray children, final String word, final int depth,
+            final int frequency, Node parentNode) {
         final int wordLength = word.length();
         final char c = word.charAt(depth);
         // Does children have the current character?
@@ -140,6 +160,7 @@
         if (!found) {
             childNode = new Node();
             childNode.code = c;
+            childNode.parent = parentNode;
             children.add(childNode);
         }
         if (wordLength == depth + 1) {
@@ -152,7 +173,7 @@
         if (childNode.children == null) {
             childNode.children = new NodeArray();
         }
-        addWordRec(childNode.children, word, depth + 1, frequency);
+        addWordRec(childNode.children, word, depth + 1, frequency, childNode);
     }
 
     @Override
@@ -186,7 +207,7 @@
             if (mRequiresReload) startDictionaryLoadingTaskLocked();
             if (mUpdatingDictionary) return false;
         }
-        final int freq = getWordFrequencyRec(mRoots, word, 0, word.length());
+        final int freq = getWordFrequency(word);
         return freq > -1;
     }
 
@@ -194,32 +215,8 @@
      * Returns the word's frequency or -1 if not found
      */
     public int getWordFrequency(CharSequence word) {
-        return getWordFrequencyRec(mRoots, word, 0, word.length());
-    }
-
-    /**
-     * Returns the word's frequency or -1 if not found
-     */
-    private int getWordFrequencyRec(final NodeArray children, final CharSequence word, 
-            final int offset, final int length) {
-        final int count = children.length;
-        char currentChar = word.charAt(offset);
-        for (int j = 0; j < count; j++) {
-            final Node node = children.data[j];
-            if (node.code == currentChar) {
-                if (offset == length - 1) {
-                    if (node.terminal) {
-                        return node.frequency;
-                    }
-                } else {
-                    if (node.children != null) {
-                        int freq = getWordFrequencyRec(node.children, word, offset + 1, length);
-                        if (freq > -1) return freq;
-                    }
-                }
-            }
-        }
-        return -1;
+        Node node = searchNode(mRoots, word, 0, word.length());
+        return (node == null) ? -1 : node.frequency;
     }
 
     /**
@@ -267,7 +264,8 @@
             if (completion) {
                 word[depth] = c;
                 if (terminal) {
-                    if (!callback.addWord(word, 0, depth + 1, freq * snr)) {
+                    if (!callback.addWord(word, 0, depth + 1, freq * snr, mDicTypeId,
+                                DataType.UNIGRAM)) {
                         return;
                     }
                     // Add to frequency of next letters for predictive correction
@@ -305,7 +303,8 @@
                                         || !same(word, depth + 1, codes.getTypedWord())) {
                                     int finalFreq = freq * snr * addedAttenuation;
                                     if (skipPos < 0) finalFreq *= FULL_WORD_FREQ_MULTIPLIER;
-                                    callback.addWord(word, 0, depth + 1, finalFreq);
+                                    callback.addWord(word, 0, depth + 1, finalFreq, mDicTypeId,
+                                            DataType.UNIGRAM);
                                 }
                             }
                             if (children != null) {
@@ -324,6 +323,171 @@
         }
     }
 
+    protected int setBigram(String word1, String word2, int frequency) {
+        return addOrSetBigram(word1, word2, frequency, false);
+    }
+
+    protected int addBigram(String word1, String word2, int frequency) {
+        return addOrSetBigram(word1, word2, frequency, true);
+    }
+
+    /**
+     * Adds bigrams to the in-memory trie structure that is being used to retrieve any word
+     * @param frequency frequency for this bigrams
+     * @param addFrequency if true, it adds to current frequency
+     * @return returns the final frequency
+     */
+    private int addOrSetBigram(String word1, String word2, int frequency, boolean addFrequency) {
+        Node firstWord = searchWord(mRoots, word1, 0, null);
+        Node secondWord = searchWord(mRoots, word2, 0, null);
+        LinkedList<NextWord> bigram = firstWord.ngrams;
+        if (bigram == null || bigram.size() == 0) {
+            firstWord.ngrams = new LinkedList<NextWord>();
+            bigram = firstWord.ngrams;
+        } else {
+            for (NextWord nw : bigram) {
+                if (nw.word == secondWord) {
+                    if (addFrequency) {
+                        nw.frequency += frequency;
+                    } else {
+                        nw.frequency = frequency;
+                    }
+                    return nw.frequency;
+                }
+            }
+        }
+        NextWord nw = new NextWord(secondWord, frequency);
+        firstWord.ngrams.add(nw);
+        return frequency;
+    }
+
+    /**
+     * Searches for the word and add the word if it does not exist.
+     * @return Returns the terminal node of the word we are searching for.
+     */
+    private Node searchWord(NodeArray children, String word, int depth, Node parentNode) {
+        final int wordLength = word.length();
+        final char c = word.charAt(depth);
+        // Does children have the current character?
+        final int childrenLength = children.length;
+        Node childNode = null;
+        boolean found = false;
+        for (int i = 0; i < childrenLength; i++) {
+            childNode = children.data[i];
+            if (childNode.code == c) {
+                found = true;
+                break;
+            }
+        }
+        if (!found) {
+            childNode = new Node();
+            childNode.code = c;
+            childNode.parent = parentNode;
+            children.add(childNode);
+        }
+        if (wordLength == depth + 1) {
+            // Terminate this word
+            childNode.terminal = true;
+            return childNode;
+        }
+        if (childNode.children == null) {
+            childNode.children = new NodeArray();
+        }
+        return searchWord(childNode.children, word, depth + 1, childNode);
+    }
+
+    // @VisibleForTesting
+    boolean reloadDictionaryIfRequired() {
+        synchronized (mUpdatingLock) {
+            // If we need to update, start off a background task
+            if (mRequiresReload) startDictionaryLoadingTaskLocked();
+            // Currently updating contacts, don't return any results.
+            return mUpdatingDictionary;
+        }
+    }
+
+    private void runReverseLookUp(final CharSequence previousWord, final WordCallback callback) {
+        Node prevWord = searchNode(mRoots, previousWord, 0, previousWord.length());
+        if (prevWord != null && prevWord.ngrams != null) {
+            reverseLookUp(prevWord.ngrams, callback);
+        }
+    }
+
+    @Override
+    public void getBigrams(final WordComposer codes, final CharSequence previousWord,
+            final WordCallback callback, int[] nextLettersFrequencies) {
+        if (!reloadDictionaryIfRequired()) {
+            runReverseLookUp(previousWord, callback);
+        }
+    }
+
+    /**
+     * Used only for testing purposes
+     * This function will wait for loading from database to be done
+     */
+    void waitForDictionaryLoading() {
+        while (mUpdatingDictionary) {
+            try {
+                Thread.sleep(100);
+            } catch (InterruptedException e) {
+            }
+        }
+    }
+
+    /**
+     * reverseLookUp retrieves the full word given a list of terminal nodes and adds those words
+     * through callback.
+     * @param terminalNodes list of terminal nodes we want to add
+     */
+    private void reverseLookUp(LinkedList<NextWord> terminalNodes,
+            final WordCallback callback) {
+        Node node;
+        int freq;
+        for (NextWord nextWord : terminalNodes) {
+            node = nextWord.word;
+            freq = nextWord.frequency;
+            // TODO Not the best way to limit suggestion threshold
+            if (freq >= UserBigramDictionary.SUGGEST_THRESHOLD) {
+                sb.setLength(0);
+                do {
+                    sb.insert(0, node.code);
+                    node = node.parent;
+                } while(node != null);
+
+                // TODO better way to feed char array?
+                callback.addWord(sb.toString().toCharArray(), 0, sb.length(), freq, mDicTypeId,
+                        DataType.BIGRAM);
+            }
+        }
+    }
+
+    /**
+     * Search for the terminal node of the word
+     * @return Returns the terminal node of the word if the word exists
+     */
+    private Node searchNode(final NodeArray children, final CharSequence word, final int offset,
+            final int length) {
+        // TODO Consider combining with addWordRec
+        final int count = children.length;
+        char currentChar = word.charAt(offset);
+        for (int j = 0; j < count; j++) {
+            final Node node = children.data[j];
+            if (node.code == currentChar) {
+                if (offset == length - 1) {
+                    if (node.terminal) {
+                        return node;
+                    }
+                } else {
+                    if (node.children != null) {
+                        Node returnNode = searchNode(node.children, word, offset + 1, length);
+                        if (returnNode != null) return returnNode;
+                    }
+                }
+            }
+        }
+        return null;
+    }
+
     protected void clearDictionary() {
         mRoots = new NodeArray();
     }
@@ -332,18 +496,11 @@
         @Override
         protected Void doInBackground(Void... v) {
             loadDictionaryAsync();
-            return null;
-        }
-
-        @Override
-        protected void onPostExecute(Void result) {
-            // TODO Auto-generated method stub
             synchronized (mUpdatingLock) {
                 mUpdatingDictionary = false;
             }
-            super.onPostExecute(result);
+            return null;
         }
-        
     }
 
     static char toLowerCase(char c) {
diff --git a/java/src/com/android/inputmethod/latin/InputLanguageSelection.java b/java/src/com/android/inputmethod/latin/InputLanguageSelection.java
index 5e835e5..4f67227 100644
--- a/java/src/com/android/inputmethod/latin/InputLanguageSelection.java
+++ b/java/src/com/android/inputmethod/latin/InputLanguageSelection.java
@@ -99,7 +99,10 @@
         boolean haveDictionary = false;
         conf.locale = locale;
         res.updateConfiguration(conf, res.getDisplayMetrics());
-        BinaryDictionary bd = new BinaryDictionary(this, R.raw.main);
+
+        int[] dictionaries = LatinIME.getDictionary(res);
+        BinaryDictionary bd = new BinaryDictionary(this, dictionaries, 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) {
diff --git a/java/src/com/android/inputmethod/latin/KeyboardSwitcher.java b/java/src/com/android/inputmethod/latin/KeyboardSwitcher.java
index 1a19644..d049303 100644
--- a/java/src/com/android/inputmethod/latin/KeyboardSwitcher.java
+++ b/java/src/com/android/inputmethod/latin/KeyboardSwitcher.java
@@ -21,12 +21,16 @@
 import java.util.Map;
 
 import android.content.Context;
+import android.content.SharedPreferences;
 import android.content.res.Configuration;
 import android.content.res.Resources;
-import android.inputmethodservice.InputMethodService;
+import android.inputmethodservice.Keyboard;
+import android.preference.PreferenceManager;
+import android.view.InflateException;
 
-public class KeyboardSwitcher {
+public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceChangeListener {
 
+    public static final int MODE_NONE = 0;
     public static final int MODE_TEXT = 1;
     public static final int MODE_SYMBOLS = 2;
     public static final int MODE_PHONE = 3;
@@ -45,6 +49,27 @@
     public static final int KEYBOARDMODE_IM = R.id.mode_im;
     public static final int KEYBOARDMODE_WEB = R.id.mode_webentry;
 
+    public static final String DEFAULT_LAYOUT_ID = "3";
+    public static final String PREF_KEYBOARD_LAYOUT = "keyboard_layout";
+    private static final int[] THEMES = new int [] {
+        R.layout.input_basic, R.layout.input_basic_highcontrast, R.layout.input_stone_normal,
+        R.layout.input_stone_bold};
+
+    // Ids for each characters' color in the keyboard
+    private static final int CHAR_THEME_COLOR_WHITE = 0;
+    private static final int CHAR_THEME_COLOR_BLACK = 1;
+
+    // Tables which contains resource ids for each character theme color
+    private static final int[] KBD_ALPHA = new int[] {R.xml.kbd_alpha, R.xml.kbd_alpha_black};
+    private static final int[] KBD_PHONE = new int[] {R.xml.kbd_phone, R.xml.kbd_phone_black};
+    private static final int[] KBD_PHONE_SYMBOLS = new int[] {
+        R.xml.kbd_phone_symbols, R.xml.kbd_phone_symbols_black};
+    private static final int[] KBD_SYMBOLS = new int[] {
+        R.xml.kbd_symbols, R.xml.kbd_symbols_black};
+    private static final int[] KBD_SYMBOLS_SHIFT = new int[] {
+        R.xml.kbd_symbols_shift, R.xml.kbd_symbols_shift_black};
+    private static final int[] KBD_QWERTY = new int[] {R.xml.kbd_qwerty, R.xml.kbd_qwerty_black};
+
     private static final int SYMBOLS_MODE_STATE_NONE = 0;
     private static final int SYMBOLS_MODE_STATE_BEGIN = 1;
     private static final int SYMBOLS_MODE_STATE_SYMBOL = 2;
@@ -57,9 +82,8 @@
         KEYBOARDMODE_IM,
         KEYBOARDMODE_WEB};
 
-    //LatinIME mContext;
     Context mContext;
-    InputMethodService mInputMethodService;
+    LatinIME mInputMethodService;
     
     private KeyboardId mSymbolsId;
     private KeyboardId mSymbolsShiftedId;
@@ -67,7 +91,7 @@
     private KeyboardId mCurrentId;
     private Map<KeyboardId, LatinKeyboard> mKeyboards;
 
-    private int mMode; /** One of the MODE_XXX values */
+    private int mMode = MODE_NONE; /** One of the MODE_XXX values */
     private int mImeOptions;
     private int mTextMode = MODE_TEXT_QWERTY;
     private boolean mIsSymbols;
@@ -79,13 +103,19 @@
     private int mLastDisplayWidth;
     private LanguageSwitcher mLanguageSwitcher;
     private Locale mInputLocale;
-    private boolean mEnableMultipleLanguages;
 
-    KeyboardSwitcher(Context context, InputMethodService ims) {
+    private int mLayoutId;
+
+    KeyboardSwitcher(Context context, LatinIME ims) {
         mContext = context;
+
+        final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ims);
+        mLayoutId = Integer.valueOf(prefs.getString(PREF_KEYBOARD_LAYOUT, DEFAULT_LAYOUT_ID));
+        prefs.registerOnSharedPreferenceChangeListener(this);
+
         mKeyboards = new HashMap<KeyboardId, LatinKeyboard>();
-        mSymbolsId = new KeyboardId(R.xml.kbd_symbols, false);
-        mSymbolsShiftedId = new KeyboardId(R.xml.kbd_symbols_shift, false);
+        mSymbolsId = makeSymbolsId(false);
+        mSymbolsShiftedId = makeSymbolsShiftedId(false);
         mInputMethodService = ims;
     }
 
@@ -98,14 +128,24 @@
     void setLanguageSwitcher(LanguageSwitcher languageSwitcher) {
         mLanguageSwitcher = languageSwitcher;
         mInputLocale = mLanguageSwitcher.getInputLocale();
-        mEnableMultipleLanguages = mLanguageSwitcher.getLocaleCount() > 1;
     }
 
     void setInputView(LatinKeyboardView inputView) {
         mInputView = inputView;
     }
-    
+
+    private KeyboardId makeSymbolsId(boolean hasVoice) {
+        return new KeyboardId(KBD_SYMBOLS[getCharColorId()], hasVoice);
+    }
+
+    private KeyboardId makeSymbolsShiftedId(boolean hasVoice) {
+        return new KeyboardId(KBD_SYMBOLS_SHIFT[getCharColorId()], hasVoice);
+    }
+
     void makeKeyboards(boolean forceCreate) {
+        mSymbolsId = makeSymbolsId(mHasVoice && !mVoiceOnPrimary);
+        mSymbolsShiftedId = makeSymbolsShiftedId(mHasVoice && !mVoiceOnPrimary);
+
         if (forceCreate) mKeyboards.clear();
         // Configuration change is coming after the keyboard gets recreated. So don't rely on that.
         // If keyboards have already been made, check if we have a screen width change and 
@@ -114,9 +154,6 @@
         if (displayWidth == mLastDisplayWidth) return;
         mLastDisplayWidth = displayWidth;
         if (!forceCreate) mKeyboards.clear();
-        mSymbolsId = new KeyboardId(R.xml.kbd_symbols, mHasVoice && !mVoiceOnPrimary);
-        mSymbolsShiftedId = new KeyboardId(R.xml.kbd_symbols_shift,
-                mHasVoice && !mVoiceOnPrimary);
     }
 
     /**
@@ -140,6 +177,7 @@
             this(xml, 0, false, hasVoice);
         }
 
+        @Override
         public boolean equals(Object other) {
             return other instanceof KeyboardId && equals((KeyboardId) other);
         }
@@ -150,6 +188,7 @@
               && other.mEnableShiftLock == this.mEnableShiftLock;
         }
 
+        @Override
         public int hashCode() {
             return (mXml + 1) * (mKeyboardMode + 1) * (mEnableShiftLock ? 2 : 1)
                     * (mHasVoice ? 4 : 8);
@@ -173,8 +212,14 @@
     void setKeyboardMode(int mode, int imeOptions, boolean enableVoice) {
         mSymbolsModeState = SYMBOLS_MODE_STATE_NONE;
         mPreferSymbols = mode == MODE_SYMBOLS;
-        setKeyboardMode(mode == MODE_SYMBOLS ? MODE_TEXT : mode, imeOptions, enableVoice,
-                mPreferSymbols);
+        if (mode == MODE_SYMBOLS) {
+            mode = MODE_TEXT;
+        }
+        try {
+            setKeyboardMode(mode, imeOptions, enableVoice, mPreferSymbols);
+        } catch (RuntimeException e) {
+            LatinImeLogger.logOnException(mode + "," + imeOptions + "," + mPreferSymbols, e);
+        }
     }
 
     void setKeyboardMode(int mode, int imeOptions, boolean enableVoice, boolean isSymbols) {
@@ -186,10 +231,10 @@
         }
         mIsSymbols = isSymbols;
 
-        mInputView.setPreviewEnabled(true);
+        mInputView.setPreviewEnabled(mInputMethodService.getPopupOn());
         KeyboardId id = getKeyboardId(mode, imeOptions, isSymbols);
-
-        LatinKeyboard keyboard = getKeyboard(id);
+        LatinKeyboard keyboard = null;
+        keyboard = getKeyboard(id);
 
         if (mode == MODE_PHONE) {
             mInputView.setPhoneKeyboard(keyboard);
@@ -201,6 +246,7 @@
         keyboard.setShifted(false);
         keyboard.setShiftLocked(keyboard.isShiftLocked());
         keyboard.setImeOptions(mContext.getResources(), mMode, imeOptions);
+        keyboard.setBlackFlag(isBlackSym());
     }
 
     private LatinKeyboard getKeyboard(KeyboardId id) {
@@ -212,8 +258,10 @@
             orig.updateConfiguration(conf, null);
             LatinKeyboard keyboard = new LatinKeyboard(
                 mContext, id.mXml, id.mKeyboardMode);
-            keyboard.setVoiceMode(hasVoiceButton(id.mXml == R.xml.kbd_symbols), mHasVoice);
+            keyboard.setVoiceMode(hasVoiceButton(id.mXml == R.xml.kbd_symbols
+                    || id.mXml == R.xml.kbd_symbols_black), mHasVoice);
             keyboard.setLanguageSwitcher(mLanguageSwitcher);
+            keyboard.setBlackFlag(isBlackSym());
             if (id.mKeyboardMode == KEYBOARDMODE_NORMAL
                     || id.mKeyboardMode == KEYBOARDMODE_URL
                     || id.mKeyboardMode == KEYBOARDMODE_IM
@@ -236,31 +284,40 @@
 
     private KeyboardId getKeyboardId(int mode, int imeOptions, boolean isSymbols) {
         boolean hasVoice = hasVoiceButton(isSymbols);
+        int charColorId = getCharColorId();
+        // TODO: generalize for any KeyboardId
+        int keyboardRowsResId = KBD_QWERTY[charColorId];
         if (isSymbols) {
-            return (mode == MODE_PHONE)
-                ? new KeyboardId(R.xml.kbd_phone_symbols, hasVoice)
-                : new KeyboardId(R.xml.kbd_symbols, hasVoice);
+            if (mode == MODE_PHONE) {
+                return new KeyboardId(KBD_PHONE_SYMBOLS[charColorId], hasVoice);
+            } else {
+                return new KeyboardId(KBD_SYMBOLS[charColorId], hasVoice);
+            }
         }
         switch (mode) {
+            case MODE_NONE:
+                LatinImeLogger.logOnWarning(
+                        "getKeyboardId:" + mode + "," + imeOptions + "," + isSymbols);
+                /* fall through */
             case MODE_TEXT:
-                if (mTextMode == MODE_TEXT_QWERTY) {
-                    return new KeyboardId(R.xml.kbd_qwerty, KEYBOARDMODE_NORMAL, true, hasVoice);
-                } else if (mTextMode == MODE_TEXT_ALPHA) {
-                    return new KeyboardId(R.xml.kbd_alpha, KEYBOARDMODE_NORMAL, true, hasVoice);
+                if (mTextMode == MODE_TEXT_ALPHA) {
+                    return new KeyboardId(
+                            KBD_ALPHA[charColorId], KEYBOARDMODE_NORMAL, true, hasVoice);
                 }
-                break;
+                // Normally mTextMode should be MODE_TEXT_QWERTY.
+                return new KeyboardId(keyboardRowsResId, KEYBOARDMODE_NORMAL, true, hasVoice);
             case MODE_SYMBOLS:
-                return new KeyboardId(R.xml.kbd_symbols, hasVoice);
+                return new KeyboardId(KBD_SYMBOLS[charColorId], hasVoice);
             case MODE_PHONE:
-                return new KeyboardId(R.xml.kbd_phone, hasVoice);
+                return new KeyboardId(KBD_PHONE[charColorId], hasVoice);
             case MODE_URL:
-                return new KeyboardId(R.xml.kbd_qwerty, KEYBOARDMODE_URL, true, hasVoice);
+                return new KeyboardId(keyboardRowsResId, KEYBOARDMODE_URL, true, hasVoice);
             case MODE_EMAIL:
-                return new KeyboardId(R.xml.kbd_qwerty, KEYBOARDMODE_EMAIL, true, hasVoice);
+                return new KeyboardId(keyboardRowsResId, KEYBOARDMODE_EMAIL, true, hasVoice);
             case MODE_IM:
-                return new KeyboardId(R.xml.kbd_qwerty, KEYBOARDMODE_IM, true, hasVoice);
+                return new KeyboardId(keyboardRowsResId, KEYBOARDMODE_IM, true, hasVoice);
             case MODE_WEB:
-                return new KeyboardId(R.xml.kbd_qwerty, KEYBOARDMODE_WEB, true, hasVoice);
+                return new KeyboardId(keyboardRowsResId, KEYBOARDMODE_WEB, true, hasVoice);
         }
         return null;
     }
@@ -273,19 +330,6 @@
         return mMode == MODE_TEXT;
     }
     
-    int getTextMode() {
-        return mTextMode;
-    }
-    
-    void setTextMode(int position) {
-        if (position < MODE_TEXT_COUNT && position >= 0) {
-            mTextMode = position;
-        }
-        if (isTextMode()) {
-            setKeyboardMode(MODE_TEXT, mImeOptions, mHasVoice);
-        }
-    }
-
     int getTextModeCount() {
         return MODE_TEXT_COUNT;
     }
@@ -300,6 +344,18 @@
         return false;
     }
 
+    void setShifted(boolean shifted) {
+        if (mInputView != null) {
+            mInputView.setShifted(shifted);
+        }
+    }
+
+    void setShiftLocked(boolean shiftLocked) {
+        if (mInputView != null) {
+            mInputView.setShiftLocked(shiftLocked);
+        }
+    }
+
     void toggleShift() {
         if (mCurrentId.equals(mSymbolsId)) {
             LatinKeyboard symbolsKeyboard = getKeyboard(mSymbolsId);
@@ -314,7 +370,7 @@
             LatinKeyboard symbolsShiftedKeyboard = getKeyboard(mSymbolsShiftedId);
             symbolsShiftedKeyboard.setShifted(false);
             mCurrentId = mSymbolsId;
-            mInputView.setKeyboard(getKeyboard(mSymbolsId));
+            mInputView.setKeyboard(symbolsKeyboard);
             symbolsKeyboard.setShifted(false);
             symbolsKeyboard.setImeOptions(mContext.getResources(), mMode, mImeOptions);
         }
@@ -348,4 +404,72 @@
         }
         return false;
     }
+
+    public LatinKeyboardView getInputView() {
+        return mInputView;
+    }
+
+    public void recreateInputView() {
+        changeLatinKeyboardView(mLayoutId, true);
+    }
+
+    private void changeLatinKeyboardView(int newLayout, boolean forceReset) {
+        if (mLayoutId != newLayout || mInputView == null || forceReset) {
+            if (mInputView != null) {
+                mInputView.closing();
+            }
+            if (THEMES.length <= newLayout) {
+                newLayout = Integer.valueOf(DEFAULT_LAYOUT_ID);
+            }
+
+            LatinIMEUtil.GCUtils.getInstance().reset();
+            boolean tryGC = true;
+            for (int i = 0; i < LatinIMEUtil.GCUtils.GC_TRY_LOOP_MAX && tryGC; ++i) {
+                try {
+                    mInputView = (LatinKeyboardView) mInputMethodService.getLayoutInflater(
+                            ).inflate(THEMES[newLayout], null);
+                    tryGC = false;
+                } catch (OutOfMemoryError e) {
+                    tryGC = LatinIMEUtil.GCUtils.getInstance().tryGCOrWait(
+                            mLayoutId + "," + newLayout, e);
+                } catch (InflateException e) {
+                    tryGC = LatinIMEUtil.GCUtils.getInstance().tryGCOrWait(
+                            mLayoutId + "," + newLayout, e);
+                }
+            }
+            mInputView.setExtentionLayoutResId(THEMES[newLayout]);
+            mInputView.setOnKeyboardActionListener(mInputMethodService);
+            mLayoutId = newLayout;
+        }
+        mInputMethodService.mHandler.post(new Runnable() {
+            public void run() {
+                if (mInputView != null) {
+                    mInputMethodService.setInputView(mInputView);
+                }
+                mInputMethodService.updateInputViewShown();
+            }});
+    }
+
+    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
+        if (PREF_KEYBOARD_LAYOUT.equals(key)) {
+            changeLatinKeyboardView(
+                    Integer.valueOf(sharedPreferences.getString(key, DEFAULT_LAYOUT_ID)), false);
+        }
+    }
+
+    public boolean isBlackSym () {
+        if (mInputView != null && mInputView.getSymbolColorSheme() == 1) {
+            return true;
+        }
+        return false;
+    }
+
+    private int getCharColorId () {
+        if (isBlackSym()) {
+            return CHAR_THEME_COLOR_BLACK;
+        } else {
+            return CHAR_THEME_COLOR_WHITE;
+        }
+    }
+
 }
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 15b537f..9bd16ad 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -16,11 +16,12 @@
 
 package com.android.inputmethod.latin;
 
-import com.android.inputmethod.voice.EditingUtil;
 import com.android.inputmethod.voice.FieldContext;
 import com.android.inputmethod.voice.SettingsUtil;
 import com.android.inputmethod.voice.VoiceInput;
 
+import org.xmlpull.v1.XmlPullParserException;
+
 import android.app.AlertDialog;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -30,9 +31,9 @@
 import android.content.SharedPreferences;
 import android.content.res.Configuration;
 import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
 import android.inputmethodservice.InputMethodService;
 import android.inputmethodservice.Keyboard;
-import android.inputmethodservice.KeyboardView;
 import android.media.AudioManager;
 import android.os.Debug;
 import android.os.Handler;
@@ -40,9 +41,9 @@
 import android.os.SystemClock;
 import android.preference.PreferenceManager;
 import android.speech.SpeechRecognizer;
-import android.text.AutoText;
 import android.text.ClipboardManager;
 import android.text.TextUtils;
+import android.util.DisplayMetrics;
 import android.util.Log;
 import android.util.PrintWriterPrinter;
 import android.util.Printer;
@@ -50,8 +51,8 @@
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.View;
-import android.view.ViewParent;
 import android.view.ViewGroup;
+import android.view.ViewParent;
 import android.view.Window;
 import android.view.WindowManager;
 import android.view.inputmethod.CompletionInfo;
@@ -62,6 +63,7 @@
 import android.view.inputmethod.InputMethodManager;
 
 import java.io.FileDescriptor;
+import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -74,21 +76,25 @@
  * Input method implementation for Qwerty'ish keyboard.
  */
 public class LatinIME extends InputMethodService
-        implements KeyboardView.OnKeyboardActionListener,
+        implements LatinKeyboardBaseView.OnKeyboardActionListener,
         VoiceInput.UiListener,
         SharedPreferences.OnSharedPreferenceChangeListener {
     private static final String TAG = "LatinIME";
+    private static final boolean PERF_DEBUG = false;
     static final boolean DEBUG = false;
     static final boolean TRACE = false;
     static final boolean VOICE_INSTALLED = true;
     static final boolean ENABLE_VOICE_BUTTON = true;
+    private static final boolean MODIFY_TEXT_FOR_CORRECTION = false;
 
     private static final String PREF_VIBRATE_ON = "vibrate_on";
     private static final String PREF_SOUND_ON = "sound_on";
+    private static final String PREF_POPUP_ON = "popup_on";
     private static final String PREF_AUTO_CAP = "auto_cap";
     private static final String PREF_QUICK_FIXES = "quick_fixes";
     private static final String PREF_SHOW_SUGGESTIONS = "show_suggestions";
     private static final String PREF_AUTO_COMPLETE = "auto_complete";
+    private static final String PREF_BIGRAM_SUGGESTIONS = "bigram_suggestion";
     private static final String PREF_VOICE_MODE = "voice_mode";
 
     // Whether or not the user has used voice input before (and thus, whether to show the
@@ -127,6 +133,7 @@
     private static final int MSG_UPDATE_SHIFT_STATE = 2;
     private static final int MSG_VOICE_RESULTS = 3;
     private static final int MSG_START_LISTENING_AFTER_SWIPE = 4;
+    private static final int MSG_UPDATE_OLD_SUGGESTIONS = 5;
 
     // If we detect a swipe gesture within N ms of typing, then swipe is
     // ignored, since it may in fact be two key presses in quick succession.
@@ -145,7 +152,7 @@
     private static final int POS_SETTINGS = 0;
     private static final int POS_METHOD = 1;
 
-    private LatinKeyboardView mInputView;
+    //private LatinKeyboardView mInputView;
     private CandidateViewContainer mCandidateViewContainer;
     private CandidateView mCandidateView;
     private Suggest mSuggest;
@@ -157,6 +164,7 @@
     KeyboardSwitcher mKeyboardSwitcher;
 
     private UserDictionary mUserDictionary;
+    private UserBigramDictionary mUserBigramDictionary;
     private ContactsDictionary mContactsDictionary;
     private AutoDictionary mAutoDictionary;
 
@@ -176,7 +184,6 @@
     private boolean mAfterVoiceInput;
     private boolean mImmediatelyAfterVoiceInput;
     private boolean mShowingVoiceSuggestions;
-    private boolean mImmediatelyAfterVoiceSuggestions;
     private boolean mVoiceInputHighlighted;
     private boolean mEnableVoiceButton;
     private CharSequence mBestWord;
@@ -186,25 +193,32 @@
     private boolean mAutoSpace;
     private boolean mJustAddedAutoSpace;
     private boolean mAutoCorrectEnabled;
+    private boolean mBigramSuggestionEnabled;
     private boolean mAutoCorrectOn;
+    // TODO move this state variable outside LatinIME
     private boolean mCapsLock;
     private boolean mPasswordText;
-    private boolean mEmailText;
     private boolean mVibrateOn;
     private boolean mSoundOn;
+    private boolean mPopupOn;
     private boolean mAutoCap;
     private boolean mQuickFixes;
     private boolean mHasUsedVoiceInput;
     private boolean mHasUsedVoiceInputUnsupportedLocale;
     private boolean mLocaleSupportedForVoiceInput;
     private boolean mShowSuggestions;
-    private boolean mSuggestionShouldReplaceCurrentWord;
     private boolean mIsShowingHint;
     private int     mCorrectionMode;
     private boolean mEnableVoice = true;
     private boolean mVoiceOnPrimary;
     private int     mOrientation;
     private List<CharSequence> mSuggestPuncList;
+    // Keep track of the last selection range to decide if we need to show word alternatives
+    private int     mLastSelectionStart;
+    private int     mLastSelectionEnd;
+
+    // Input type is such that we should not auto-correct
+    private boolean mInputTypeNoAutoCorrect;
 
     // Indicates whether the suggestion strip is to be on in landscape
     private boolean mJustAccepted;
@@ -219,8 +233,9 @@
     private final float FX_VOLUME = -1.0f;
     private boolean mSilentMode;
 
-    private String mWordSeparators;
+    /* package */ String mWordSeparators;
     private String mSentenceSeparators;
+    private String mSuggestPuncs;
     private VoiceInput mVoiceInput;
     private VoiceResults mVoiceResults = new VoiceResults();
     private long mSwipeTriggerTimeMillis;
@@ -228,17 +243,66 @@
 
     // Keeps track of most recently inserted text (multi-character key) for reverting
     private CharSequence mEnteredText;
+    private boolean mRefreshKeyboardRequired;
 
     // For each word, a list of potential replacements, usually from voice.
     private Map<String, List<CharSequence>> mWordToSuggestions =
             new HashMap<String, List<CharSequence>>();
 
+    private ArrayList<WordAlternatives> mWordHistory = new ArrayList<WordAlternatives>();
+
     private class VoiceResults {
         List<String> candidates;
         Map<String, List<CharSequence>> alternatives;
     }
+    
+    public abstract static class WordAlternatives {
+        protected CharSequence mChosenWord;
 
-    private boolean mRefreshKeyboardRequired;
+        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 List<CharSequence> 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 List<CharSequence> getAlternatives() {
+            return getTypedSuggestions(word);
+        }
+    }
 
     Handler mHandler = new Handler() {
         @Override
@@ -247,10 +311,14 @@
                 case MSG_UPDATE_SUGGESTIONS:
                     updateSuggestions();
                     break;
+                case MSG_UPDATE_OLD_SUGGESTIONS:
+                    setOldSuggestions();
+                    break;
                 case MSG_START_TUTORIAL:
                     if (mTutorial == null) {
-                        if (mInputView.isShown()) {
-                            mTutorial = new Tutorial(LatinIME.this, mInputView);
+                        if (mKeyboardSwitcher.getInputView().isShown()) {
+                            mTutorial = new Tutorial(
+                                    LatinIME.this, mKeyboardSwitcher.getInputView());
                             mTutorial.start();
                         } else {
                             // Try again soon if the view is not yet showing
@@ -273,6 +341,7 @@
     };
 
     @Override public void onCreate() {
+        LatinImeLogger.init(this);
         super.onCreate();
         //setStatusIcon(R.drawable.ime_qwerty);
         mResources = getResources();
@@ -288,7 +357,18 @@
         if (inputLanguage == null) {
             inputLanguage = conf.locale.toString();
         }
-        initSuggest(inputLanguage);
+
+        LatinIMEUtil.GCUtils.getInstance().reset();
+        boolean tryGC = true;
+        for (int i = 0; i < LatinIMEUtil.GCUtils.GC_TRY_LOOP_MAX && tryGC; ++i) {
+            try {
+                initSuggest(inputLanguage);
+                tryGC = false;
+            } catch (OutOfMemoryError e) {
+                tryGC = LatinIMEUtil.GCUtils.getInstance().tryGCOrWait(inputLanguage, e);
+            }
+        }
+
         mOrientation = conf.orientation;
         initSuggestPuncList();
 
@@ -311,6 +391,46 @@
         prefs.registerOnSharedPreferenceChangeListener(this);
     }
 
+    /**
+     * Loads a dictionary or multiple separated dictionary
+     * @return returns array of dictionary resource ids
+     */
+    static int[] getDictionary(Resources res) {
+        String packageName = LatinIME.class.getPackage().getName();
+        XmlResourceParser xrp = res.getXml(R.xml.dictionary);
+        int dictionaryCount = 0;
+        ArrayList<Integer> dictionaries = new ArrayList<Integer>();
+
+        try {
+            int current = xrp.getEventType();
+            while (current != XmlResourceParser.END_DOCUMENT) {
+                if (current == XmlResourceParser.START_TAG) {
+                    String tag = xrp.getName();
+                    if (tag != null) {
+                        if (tag.equals("part")) {
+                            String dictFileName = xrp.getAttributeValue(null, "name");
+                            dictionaries.add(res.getIdentifier(dictFileName, "raw", packageName));
+                        }
+                    }
+                }
+                xrp.next();
+                current = xrp.getEventType();
+            }
+        } catch (XmlPullParserException e) {
+            Log.e(TAG, "Dictionary XML parsing failure");
+        } catch (IOException e) {
+            Log.e(TAG, "Dictionary XML IOException");
+        }
+
+        int count = dictionaries.size();
+        int[] dict = new int[count];
+        for (int i = 0; i < count; i++) {
+            dict[i] = dictionaries.get(i);
+        }
+
+        return dict;
+    }
+
     private void initSuggest(String locale) {
         mInputLocale = locale;
 
@@ -324,17 +444,25 @@
         }
         SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
         mQuickFixes = sp.getBoolean(PREF_QUICK_FIXES, true);
-        mSuggest = new Suggest(this, R.raw.main);
+
+        int[] dictionaries = getDictionary(orig);
+        mSuggest = new Suggest(this, dictionaries);
         updateAutoTextEnabled(saveLocale);
         if (mUserDictionary != null) mUserDictionary.close();
         mUserDictionary = new UserDictionary(this, mInputLocale);
         if (mContactsDictionary == null) {
-            mContactsDictionary = new ContactsDictionary(this);
+            mContactsDictionary = new ContactsDictionary(this, Suggest.DIC_CONTACTS);
         }
         if (mAutoDictionary != null) {
             mAutoDictionary.close();
         }
-        mAutoDictionary = new AutoDictionary(this, this, mInputLocale);
+        mAutoDictionary = new AutoDictionary(this, this, mInputLocale, Suggest.DIC_AUTO);
+        if (mUserBigramDictionary != null) {
+            mUserBigramDictionary.close();
+        }
+        mUserBigramDictionary = new UserBigramDictionary(this, this, mInputLocale,
+                Suggest.DIC_USER);
+        mSuggest.setUserBigramDictionary(mUserBigramDictionary);
         mSuggest.setUserDictionary(mUserDictionary);
         mSuggest.setContactsDictionary(mContactsDictionary);
         mSuggest.setAutoDictionary(mAutoDictionary);
@@ -348,12 +476,18 @@
 
     @Override
     public void onDestroy() {
-        mUserDictionary.close();
-        mContactsDictionary.close();
+        if (mUserDictionary != null) {
+            mUserDictionary.close();
+        }
+        if (mContactsDictionary != null) {
+            mContactsDictionary.close();
+        }
         unregisterReceiver(mReceiver);
-        if (VOICE_INSTALLED) {
+        if (VOICE_INSTALLED && mVoiceInput != null) {
             mVoiceInput.destroy();
         }
+        LatinImeLogger.commit();
+        LatinImeLogger.onDestroy();
         super.onDestroy();
     }
 
@@ -393,15 +527,12 @@
 
     @Override
     public View onCreateInputView() {
-        mInputView = (LatinKeyboardView) getLayoutInflater().inflate(
-                R.layout.input, null);
-        mKeyboardSwitcher.setInputView(mInputView);
+        mKeyboardSwitcher.recreateInputView();
         mKeyboardSwitcher.makeKeyboards(true);
-        mInputView.setOnKeyboardActionListener(this);
         mKeyboardSwitcher.setKeyboardMode(
                 KeyboardSwitcher.MODE_TEXT, 0,
                 shouldShowVoiceButton(makeFieldContext(), getCurrentInputEditorInfo()));
-        return mInputView;
+        return mKeyboardSwitcher.getInputView();
     }
 
     @Override
@@ -418,8 +549,9 @@
 
     @Override
     public void onStartInputView(EditorInfo attribute, boolean restarting) {
+        LatinKeyboardView inputView = mKeyboardSwitcher.getInputView();
         // In landscape mode, this method gets called without the input view being created.
-        if (mInputView == null) {
+        if (inputView == null) {
             return;
         }
 
@@ -448,15 +580,12 @@
         mAfterVoiceInput = false;
         mImmediatelyAfterVoiceInput = false;
         mShowingVoiceSuggestions = false;
-        mImmediatelyAfterVoiceSuggestions = false;
         mVoiceInputHighlighted = false;
-        mWordToSuggestions.clear();
         mInputTypeNoAutoCorrect = false;
         mPredictionOn = false;
         mCompletionOn = false;
         mCompletions = null;
         mCapsLock = false;
-        mEmailText = false;
         mEnteredText = null;
 
         switch (attribute.inputType & EditorInfo.TYPE_MASK_CLASS) {
@@ -479,9 +608,6 @@
                         variation == EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD ) {
                     mPredictionOn = false;
                 }
-                if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS) {
-                    mEmailText = true;
-                }
                 if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
                         || variation == EditorInfo.TYPE_TEXT_VARIATION_PERSON_NAME) {
                     mAutoSpace = false;
@@ -532,7 +658,7 @@
                         attribute.imeOptions, enableVoiceButton);
                 updateShiftKeyState(attribute);
         }
-        mInputView.closing();
+        inputView.closing();
         mComposing.setLength(0);
         mPredicting = false;
         mDeleteCount = 0;
@@ -548,7 +674,8 @@
 
         updateCorrectionMode();
 
-        mInputView.setProximityCorrectionEnabled(true);
+        inputView.setPreviewEnabled(mPopupOn);
+        inputView.setProximityCorrectionEnabled(true);
         mPredictionOn = mPredictionOn && (mCorrectionMode > 0 || mShowSuggestions);
         checkTutorial(attribute.privateImeOptions);
         if (TRACE) Debug.startMethodTracing("/data/trace/latinime");
@@ -558,6 +685,8 @@
     public void onFinishInput() {
         super.onFinishInput();
 
+        LatinImeLogger.commit();
+
         if (VOICE_INSTALLED && !mConfigurationChanging) {
             if (mAfterVoiceInput) {
                 mVoiceInput.flushAllTextModificationCounters();
@@ -566,10 +695,11 @@
             mVoiceInput.flushLogs();
             mVoiceInput.cancel();
         }
-        if (mInputView != null) {
-            mInputView.closing();
+        if (mKeyboardSwitcher.getInputView() != null) {
+            mKeyboardSwitcher.getInputView().closing();
         }
         if (mAutoDictionary != null) mAutoDictionary.flushPendingWrites();
+        if (mUserBigramDictionary != null) mUserBigramDictionary.flushPendingWrites();
     }
 
     @Override
@@ -605,15 +735,15 @@
             mVoiceInput.setSelectionSpan(newSelEnd - newSelStart);
         }
 
-        mSuggestionShouldReplaceCurrentWord = false;
         // If the current selection in the text view changes, we should
         // clear whatever candidate text we have.
         if ((((mComposing.length() > 0 && mPredicting) || mVoiceInputHighlighted)
                 && (newSelStart != candidatesEnd
-                    || newSelEnd != candidatesEnd))) {
+                    || newSelEnd != candidatesEnd)
+                && mLastSelectionStart != newSelStart)) {
             mComposing.setLength(0);
             mPredicting = false;
-            updateSuggestions();
+            postUpdateSuggestions();
             TextEntryState.reset();
             InputConnection ic = getCurrentInputConnection();
             if (ic != null) {
@@ -622,10 +752,10 @@
             mVoiceInputHighlighted = false;
         } else if (!mPredicting && !mJustAccepted) {
             switch (TextEntryState.getState()) {
-                case TextEntryState.STATE_ACCEPTED_DEFAULT:
+                case ACCEPTED_DEFAULT:
                     TextEntryState.reset();
                     // fall through
-                case TextEntryState.STATE_SPACE_AFTER_PICKED:
+                case SPACE_AFTER_PICKED:
                     mJustAddedAutoSpace = false;  // The user moved the cursor.
                     break;
             }
@@ -633,32 +763,29 @@
         mJustAccepted = false;
         postUpdateShiftKeyState();
 
-        if (VOICE_INSTALLED) {
-            if (mShowingVoiceSuggestions) {
-                if (mImmediatelyAfterVoiceSuggestions) {
-                    mImmediatelyAfterVoiceSuggestions = false;
-                } else {
-                    updateSuggestions();
-                    mShowingVoiceSuggestions = false;
-                }
-            }
-            if (VoiceInput.ENABLE_WORD_CORRECTIONS) {
-                // If we have alternatives for the current word, then show them.
-                String word = EditingUtil.getWordAtCursor(
-                        getCurrentInputConnection(), getWordSeparators());
-                if (word != null && mWordToSuggestions.containsKey(word.trim())) {
-                    mSuggestionShouldReplaceCurrentWord = true;
-                    final List<CharSequence> suggestions = mWordToSuggestions.get(word.trim());
+        // Make a note of the cursor position
+        mLastSelectionStart = newSelStart;
+        mLastSelectionEnd = newSelEnd;
 
-                    setSuggestions(suggestions, false, true, true);
-                    setCandidatesViewShown(true);
-                }
+
+        // Check if we should go in or out of correction mode.
+        if (isPredictionOn() && mJustRevertedSeparator == null
+                && (candidatesStart == candidatesEnd || newSelStart != oldSelStart
+                        || TextEntryState.isCorrecting())
+                && (newSelStart < newSelEnd - 1 || (!mPredicting))
+                && !mVoiceInputHighlighted) {
+            if (isCursorTouchingWord() || mLastSelectionStart < mLastSelectionEnd) {
+                postUpdateOldSuggestions();
+            } else {
+                abortCorrection(false);
             }
         }
     }
 
     @Override
     public void hideWindow() {
+        LatinImeLogger.commit();
+
         if (TRACE) Debug.stopMethodTracing();
         if (mOptionsDialog != null && mOptionsDialog.isShowing()) {
             mOptionsDialog.dismiss();
@@ -675,13 +802,15 @@
                 mVoiceInput.cancel();
             }
         }
+        mWordToSuggestions.clear();
+        mWordHistory.clear();
         super.hideWindow();
         TextEntryState.endSession();
     }
 
     @Override
     public void onDisplayCompletions(CompletionInfo[] completions) {
-        if (false) {
+        if (DEBUG) {
             Log.i("foo", "Received completions:");
             for (int i=0; i<(completions != null ? completions.length : 0); i++) {
                 Log.i("foo", "  #" + i + ": " + completions[i]);
@@ -699,7 +828,7 @@
                 CompletionInfo ci = completions[i];
                 if (ci != null) stringList.add(ci.getText());
             }
-            //CharSequence typedWord = mWord.getTypedWord();
+            // When in fullscreen mode, show completions generated by the application
             setSuggestions(stringList, true, true, true);
             mBestWord = null;
             setCandidatesViewShown(isCandidateStripVisible() || mCompletionOn);
@@ -711,7 +840,8 @@
         // TODO: Remove this if we support candidates with hard keyboard
         if (onEvaluateInputViewShown()) {
             // Show the candidates view only if input view is showing
-            super.setCandidatesViewShown(shown && mInputView != null && mInputView.isShown());
+            super.setCandidatesViewShown(shown && mKeyboardSwitcher.getInputView() != null
+                    && mKeyboardSwitcher.getInputView().isShown());
         }
     }
 
@@ -724,11 +854,24 @@
     }
 
     @Override
+    public boolean onEvaluateFullscreenMode() {
+        DisplayMetrics dm = getResources().getDisplayMetrics();
+        float displayHeight = dm.heightPixels;
+        // If the display is more than X inches high, don't go to fullscreen mode
+        float dimen = getResources().getDimension(R.dimen.max_height_for_fullscreen);
+        if (displayHeight > dimen) {
+            return false;
+        } else {
+            return super.onEvaluateFullscreenMode();
+        }
+    }
+
+    @Override
     public boolean onKeyDown(int keyCode, KeyEvent event) {
         switch (keyCode) {
             case KeyEvent.KEYCODE_BACK:
-                if (event.getRepeatCount() == 0 && mInputView != null) {
-                    if (mInputView.handleBack()) {
+                if (event.getRepeatCount() == 0 && mKeyboardSwitcher.getInputView() != null) {
+                    if (mKeyboardSwitcher.getInputView().handleBack()) {
                         return true;
                     } else if (mTutorial != null) {
                         mTutorial.close();
@@ -760,8 +903,10 @@
                 if (mTutorial != null) {
                     return true;
                 }
+                LatinKeyboardView inputView = mKeyboardSwitcher.getInputView();
                 // Enable shift key and DPAD to do selections
-                if (mInputView != null && mInputView.isShown() && mInputView.isShifted()) {
+                if (inputView != null && inputView.isShown()
+                        && inputView.isShifted()) {
                     event = new KeyEvent(event.getDownTime(), event.getEventTime(),
                             event.getAction(), event.getKeyCode(), event.getRepeatCount(),
                             event.getDeviceId(), event.getScanCode(),
@@ -794,7 +939,8 @@
             mKeyboardSwitcher = new KeyboardSwitcher(this, this);
         }
         mKeyboardSwitcher.setLanguageSwitcher(mLanguageSwitcher);
-        if (mInputView != null) {
+        if (mKeyboardSwitcher.getInputView() != null
+                && mKeyboardSwitcher.getKeyboardMode() != KeyboardSwitcher.MODE_NONE) {
             mKeyboardSwitcher.setVoiceMode(mEnableVoice && mEnableVoiceButton, mVoiceOnPrimary);
         }
         mKeyboardSwitcher.makeKeyboards(true);
@@ -809,7 +955,7 @@
                 }
                 mCommittedLength = mComposing.length();
                 TextEntryState.acceptedTyped(mComposing);
-                checkAddToDictionary(mComposing, AutoDictionary.FREQUENCY_FOR_TYPED);
+                addToDictionaries(mComposing, AutoDictionary.FREQUENCY_FOR_TYPED);
             }
             updateSuggestions();
         }
@@ -822,9 +968,8 @@
 
     public void updateShiftKeyState(EditorInfo attr) {
         InputConnection ic = getCurrentInputConnection();
-        if (attr != null && mInputView != null && mKeyboardSwitcher.isAlphabetMode()
-                && ic != null) {
-            mInputView.setShifted(mCapsLock || getCursorCapsMode(ic, attr) != 0);
+        if (attr != null && mKeyboardSwitcher.isAlphabetMode() && ic != null) {
+            mKeyboardSwitcher.setShifted(mCapsLock || getCursorCapsMode(ic, attr) != 0);
         }
     }
 
@@ -940,6 +1085,7 @@
             case Keyboard.KEYCODE_DELETE:
                 handleBackspace();
                 mDeleteCount++;
+                LatinImeLogger.logOnDelete();
                 break;
             case Keyboard.KEYCODE_SHIFT:
                 handleShift();
@@ -959,11 +1105,7 @@
                 toggleLanguage(false, false);
                 break;
             case LatinKeyboardView.KEYCODE_SHIFT_LONGPRESS:
-                if (mCapsLock) {
-                    handleShift();
-                } else {
-                    toggleCapsLock();
-                }
+                handleCapsLock();
                 break;
             case Keyboard.KEYCODE_MODE_CHANGE:
                 changeKeyboardMode();
@@ -980,6 +1122,7 @@
                 if (primaryCode != KEYCODE_ENTER) {
                     mJustAddedAutoSpace = false;
                 }
+                LatinImeLogger.logOnInputChar((char)primaryCode);
                 if (isWordSeparator(primaryCode)) {
                     handleSeparator(primaryCode);
                 } else {
@@ -1001,6 +1144,7 @@
         }
         InputConnection ic = getCurrentInputConnection();
         if (ic == null) return;
+        abortCorrection(false);
         ic.beginBatchEdit();
         if (mPredicting) {
             commitTyped(ic);
@@ -1025,6 +1169,8 @@
         InputConnection ic = getCurrentInputConnection();
         if (ic == null) return;
 
+        ic.beginBatchEdit();
+
         if (mAfterVoiceInput) {
             // Don't log delete if the user is pressing delete at
             // the beginning of the text box (hence not deleting anything)
@@ -1055,8 +1201,9 @@
         }
         postUpdateShiftKeyState();
         TextEntryState.backspace();
-        if (TextEntryState.getState() == TextEntryState.STATE_UNDO_COMMIT) {
+        if (TextEntryState.getState() == TextEntryState.State.UNDO_COMMIT) {
             revertLastWord(deleteChar);
+            ic.endBatchEdit();
             return;
         } else if (mEnteredText != null && sameAsTextBeforeCursor(ic, mEnteredText)) {
             ic.deleteSurroundingText(mEnteredText.length(), 0);
@@ -1078,16 +1225,47 @@
             }
         }
         mJustRevertedSeparator = null;
+        ic.endBatchEdit();
     }
 
     private void handleShift() {
         mHandler.removeMessages(MSG_UPDATE_SHIFT_STATE);
-        if (mKeyboardSwitcher.isAlphabetMode()) {
-            // Alphabet keyboard
-            checkToggleCapsLock();
-            mInputView.setShifted(mCapsLock || !mInputView.isShifted());
+        KeyboardSwitcher switcher = mKeyboardSwitcher;
+        LatinKeyboardView inputView = switcher.getInputView();
+        if (switcher.isAlphabetMode()) {
+            if (mCapsLock) {
+                mCapsLock = false;
+                switcher.setShifted(false);
+            } else if (inputView != null) {
+                if (inputView.isShifted()) {
+                    mCapsLock = true;
+                    switcher.setShiftLocked(true);
+                } else {
+                    switcher.setShifted(true);
+                }
+            }
         } else {
-            mKeyboardSwitcher.toggleShift();
+            switcher.toggleShift();
+        }
+    }
+
+    private void handleCapsLock() {
+        mHandler.removeMessages(MSG_UPDATE_SHIFT_STATE);
+        KeyboardSwitcher switcher = mKeyboardSwitcher;
+        if (switcher.isAlphabetMode()) {
+            mCapsLock = !mCapsLock;
+            if (mCapsLock) {
+                switcher.setShiftLocked(true);
+            } else {
+                switcher.setShifted(false);
+            }
+        }
+    }
+
+    private void abortCorrection(boolean force) {
+        if (force || TextEntryState.isCorrecting()) {
+            getCurrentInputConnection().finishComposingText();
+            setSuggestions(null, false, false, false);
         }
     }
 
@@ -1100,24 +1278,31 @@
             // Assume input length is 1. This assumption fails for smiley face insertions.
             mVoiceInput.incrementTextModificationInsertCount(1);
         }
+        abortCorrection(false);
 
         if (isAlphabet(primaryCode) && isPredictionOn() && !isCursorTouchingWord()) {
             if (!mPredicting) {
                 mPredicting = true;
                 mComposing.setLength(0);
+                saveWordInHistory(mBestWord);
                 mWord.reset();
             }
         }
-        if (mInputView.isShifted()) {
-            // TODO: This doesn't work with ß, need to fix it in the next release.
+        if (mKeyboardSwitcher.getInputView().isShifted()) {
+            // TODO: This doesn't work with [beta], need to fix it in the next release.
             if (keyCodes == null || keyCodes[0] < Character.MIN_CODE_POINT
                     || keyCodes[0] > Character.MAX_CODE_POINT) {
                 return;
             }
-            primaryCode = new String(keyCodes, 0, 1).toUpperCase().charAt(0);
+            primaryCode = keyCodes[0];
+            if (mKeyboardSwitcher.isAlphabetMode()) {
+                primaryCode = Character.toUpperCase(primaryCode);
+            }
         }
         if (mPredicting) {
-            if (mInputView.isShifted() && mComposing.length() == 0) {
+            if (mKeyboardSwitcher.getInputView().isShifted()
+                    && mKeyboardSwitcher.isAlphabetMode()
+                    && mComposing.length() == 0) {
                 mWord.setCapitalized(true);
             }
             mComposing.append((char) primaryCode);
@@ -1136,7 +1321,7 @@
             sendKeyChar((char)primaryCode);
         }
         updateShiftKeyState(getCurrentInputEditorInfo());
-        measureCps();
+        if (LatinIME.PERF_DEBUG) measureCps();
         TextEntryState.typedCharacter((char) primaryCode, isWordSeparator(primaryCode));
     }
 
@@ -1160,6 +1345,7 @@
         InputConnection ic = getCurrentInputConnection();
         if (ic != null) {
             ic.beginBatchEdit();
+            abortCorrection(false);
         }
         if (mPredicting) {
             // In certain languages where single quote is a separator, it's better
@@ -1170,8 +1356,7 @@
                     (mJustRevertedSeparator == null
                             || mJustRevertedSeparator.length() == 0
                             || mJustRevertedSeparator.charAt(0) != primaryCode)) {
-                pickDefaultSuggestion();
-                pickedDefault = true;
+                pickedDefault = pickDefaultSuggestion();
                 // Picked the suggestion by the space key.  We consider this
                 // as "added an auto space".
                 if (primaryCode == KEYCODE_SPACE) {
@@ -1189,21 +1374,20 @@
 
         // Handle the case of ". ." -> " .." with auto-space if necessary
         // before changing the TextEntryState.
-        if (TextEntryState.getState() == TextEntryState.STATE_PUNCTUATION_AFTER_ACCEPTED
+        if (TextEntryState.getState() == TextEntryState.State.PUNCTUATION_AFTER_ACCEPTED
                 && primaryCode == KEYCODE_PERIOD) {
             reswapPeriodAndSpace();
         }
 
         TextEntryState.typedCharacter((char) primaryCode, true);
-        if (TextEntryState.getState() == TextEntryState.STATE_PUNCTUATION_AFTER_ACCEPTED
+        if (TextEntryState.getState() == TextEntryState.State.PUNCTUATION_AFTER_ACCEPTED
                 && primaryCode != KEYCODE_ENTER) {
             swapPunctuationAndSpace();
         } else if (isPredictionOn() && primaryCode == KEYCODE_SPACE) {
-        //else if (TextEntryState.STATE_SPACE_AFTER_ACCEPTED) {
             doubleSpace();
         }
-        if (pickedDefault && mBestWord != null) {
-            TextEntryState.acceptedDefault(mWord.getTypedWord(), mBestWord);
+        if (pickedDefault) {
+            TextEntryState.backToAcceptedDefault(mWord.getTypedWord());
         }
         updateShiftKeyState(getCurrentInputEditorInfo());
         if (ic != null) {
@@ -1217,21 +1401,25 @@
             mVoiceInput.cancel();
         }
         requestHideSelf(0);
-        mInputView.closing();
+        mKeyboardSwitcher.getInputView().closing();
         TextEntryState.endSession();
     }
 
-    private void checkToggleCapsLock() {
-        if (mInputView.getKeyboard().isShifted()) {
-            toggleCapsLock();
+    private void saveWordInHistory(CharSequence result) {
+        if (mWord.size() <= 1) {
+            mWord.reset();
+            return;
         }
-    }
+        // Skip if result is null. It happens in some edge case.
+        if (TextUtils.isEmpty(result)) {
+            return;
+        }
 
-    private void toggleCapsLock() {
-        mCapsLock = !mCapsLock;
-        if (mKeyboardSwitcher.isAlphabetMode()) {
-            ((LatinKeyboard) mInputView.getKeyboard()).setShiftLocked(mCapsLock);
-        }
+        // 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 void postUpdateSuggestions() {
@@ -1239,6 +1427,11 @@
         mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_UPDATE_SUGGESTIONS), 100);
     }
 
+    private void postUpdateOldSuggestions() {
+        mHandler.removeMessages(MSG_UPDATE_OLD_SUGGESTIONS);
+        mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_UPDATE_OLD_SUGGESTIONS), 300);
+    }
+
     private boolean isPredictionOn() {
         boolean predictionOn = mPredictionOn;
         return predictionOn;
@@ -1258,8 +1451,8 @@
       mHandler.post(new Runnable() {
           public void run() {
               mRecognizing = false;
-              if (mInputView != null) {
-                setInputView(mInputView);
+              if (mKeyboardSwitcher.getInputView() != null) {
+                setInputView(mKeyboardSwitcher.getInputView());
               }
               updateInputViewShown();
           }});
@@ -1358,7 +1551,7 @@
 
         Window window = mVoiceWarningDialog.getWindow();
         WindowManager.LayoutParams lp = window.getAttributes();
-        lp.token = mInputView.getWindowToken();
+        lp.token = mKeyboardSwitcher.getInputView().getWindowToken();
         lp.type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
         window.setAttributes(lp);
         window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
@@ -1394,7 +1587,8 @@
 
         final List<CharSequence> nBest = new ArrayList<CharSequence>();
         boolean capitalizeFirstWord = preferCapitalization()
-                || (mKeyboardSwitcher.isAlphabetMode() && mInputView.isShifted());
+                || (mKeyboardSwitcher.isAlphabetMode()
+                        && mKeyboardSwitcher.getInputView().isShifted());
         for (String c : mVoiceResults.candidates) {
             if (capitalizeFirstWord) {
                 c = Character.toUpperCase(c.charAt(0)) + c.substring(1, c.length());
@@ -1419,13 +1613,6 @@
 
         if (ic != null) ic.endBatchEdit();
 
-        // Show N-Best alternates, if there is more than one choice.
-        if (nBest.size() > 1) {
-            mImmediatelyAfterVoiceSuggestions = true;
-            mShowingVoiceSuggestions = true;
-            setSuggestions(nBest.subList(1, nBest.size()), false, true, true);
-            setCandidatesViewShown(true);
-        }
         mVoiceInputHighlighted = true;
         mWordToSuggestions.putAll(mVoiceResults.alternatives);
 
@@ -1450,9 +1637,8 @@
     }
 
     private void updateSuggestions() {
-        mSuggestionShouldReplaceCurrentWord = false;
-
-        ((LatinKeyboard) mInputView.getKeyboard()).setPreferredLetters(null);
+        LatinKeyboardView inputView = mKeyboardSwitcher.getInputView();
+        ((LatinKeyboard) inputView.getKeyboard()).setPreferredLetters(null);
 
         // Check if we have a suggestion engine attached.
         if ((mSuggest == null || !isPredictionOn()) && !mVoiceInputHighlighted) {
@@ -1463,24 +1649,56 @@
             setNextSuggestions();
             return;
         }
+        showSuggestions(mWord);
+    }
 
-        List<CharSequence> stringList = mSuggest.getSuggestions(mInputView, mWord, false);
+    private List<CharSequence> getTypedSuggestions(WordComposer word) {
+        List<CharSequence> stringList = mSuggest.getSuggestions(
+                mKeyboardSwitcher.getInputView(), word, false, null);
+        return stringList;
+    }
+
+    private void showCorrections(WordAlternatives alternatives) {
+        List<CharSequence> stringList = alternatives.getAlternatives();
+        ((LatinKeyboard) mKeyboardSwitcher.getInputView().getKeyboard()).setPreferredLetters(null);
+        showSuggestions(stringList, alternatives.getOriginalWord(), false, false);
+    }
+
+    private void showSuggestions(WordComposer word) {
+        //long startTime = System.currentTimeMillis(); // TIME MEASUREMENT!
+        // TODO Maybe need better way of retrieving previous word
+        CharSequence prevWord = EditingUtil.getPreviousWord(getCurrentInputConnection(),
+                mWordSeparators);
+        List<CharSequence> stringList = mSuggest.getSuggestions(
+            mKeyboardSwitcher.getInputView(), word, false, prevWord);
+        //long stopTime = System.currentTimeMillis(); // TIME MEASUREMENT!
+        //Log.d("LatinIME","Suggest Total Time - " + (stopTime - startTime));
+
         int[] nextLettersFrequencies = mSuggest.getNextLettersFrequencies();
 
-        ((LatinKeyboard) mInputView.getKeyboard()).setPreferredLetters(nextLettersFrequencies);
+        ((LatinKeyboard) mKeyboardSwitcher.getInputView().getKeyboard()).setPreferredLetters(
+                nextLettersFrequencies);
 
         boolean correctionAvailable = !mInputTypeNoAutoCorrect && mSuggest.hasMinimalCorrection();
         //|| mCorrectionMode == mSuggest.CORRECTION_FULL;
-        CharSequence typedWord = mWord.getTypedWord();
+        CharSequence typedWord = word.getTypedWord();
         // If we're in basic correct
         boolean typedWordValid = mSuggest.isValidWord(typedWord) ||
-                (preferCapitalization() && mSuggest.isValidWord(typedWord.toString().toLowerCase()));
-        if (mCorrectionMode == Suggest.CORRECTION_FULL) {
+                (preferCapitalization()
+                        && mSuggest.isValidWord(typedWord.toString().toLowerCase()));
+        if (mCorrectionMode == Suggest.CORRECTION_FULL
+                || mCorrectionMode == Suggest.CORRECTION_FULL_BIGRAM) {
             correctionAvailable |= typedWordValid;
         }
         // Don't auto-correct words with multiple capital letter
-        correctionAvailable &= !mWord.isMostlyCaps();
+        correctionAvailable &= !word.isMostlyCaps();
+        correctionAvailable &= !TextEntryState.isCorrecting();
 
+        showSuggestions(stringList, typedWord, typedWordValid, correctionAvailable);
+    }
+
+    private void showSuggestions(List<CharSequence> stringList, CharSequence typedWord,
+            boolean typedWordValid, boolean correctionAvailable) {
         setSuggestions(stringList, false, typedWordValid, correctionAvailable);
         if (stringList.size() > 0) {
             if (correctionAvailable && !typedWordValid && stringList.size() > 1) {
@@ -1494,7 +1712,7 @@
         setCandidatesViewShown(isCandidateStripVisible() || mCompletionOn);
     }
 
-    private void pickDefaultSuggestion() {
+    private boolean pickDefaultSuggestion() {
         // Complete any pending candidate query first
         if (mHandler.hasMessages(MSG_UPDATE_SUGGESTIONS)) {
             mHandler.removeMessages(MSG_UPDATE_SUGGESTIONS);
@@ -1503,14 +1721,18 @@
         if (mBestWord != null && mBestWord.length() > 0) {
             TextEntryState.acceptedDefault(mWord.getTypedWord(), mBestWord);
             mJustAccepted = true;
-            pickSuggestion(mBestWord);
+            pickSuggestion(mBestWord, false);
             // Add the word to the auto dictionary if it's not a known word
-            checkAddToDictionary(mBestWord, AutoDictionary.FREQUENCY_FOR_TYPED);
+            addToDictionaries(mBestWord, AutoDictionary.FREQUENCY_FOR_TYPED);
+            return true;
+
         }
+        return false;
     }
 
     public void pickSuggestionManually(int index, CharSequence suggestion) {
         if (mAfterVoiceInput && mShowingVoiceSuggestions) mVoiceInput.logNBestChoose(index);
+        List<CharSequence> suggestions = mCandidateView.getSuggestions();
 
         if (mAfterVoiceInput && !mShowingVoiceSuggestions) {
             mVoiceInput.flushAllTextModificationCounters();
@@ -1518,6 +1740,7 @@
             mVoiceInput.logTextModifiedByChooseSuggestion(suggestion.length());
         }
 
+        final boolean correcting = TextEntryState.isCorrecting();
         InputConnection ic = getCurrentInputConnection();
         if (ic != null) {
             ic.beginBatchEdit();
@@ -1540,7 +1763,12 @@
         }
 
         // If this is a punctuation, apply it through the normal key press
-        if (suggestion.length() == 1 && isWordSeparator(suggestion.charAt(0))) {
+        if (suggestion.length() == 1 && (isWordSeparator(suggestion.charAt(0))
+                || 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);
             onKey(suggestion.charAt(0), null);
             if (ic != null) {
                 ic.endBatchEdit();
@@ -1548,20 +1776,34 @@
             return;
         }
         mJustAccepted = true;
-        pickSuggestion(suggestion);
+        pickSuggestion(suggestion, correcting);
         // Add the word to the auto dictionary if it's not a known word
         if (index == 0) {
-            checkAddToDictionary(suggestion, AutoDictionary.FREQUENCY_FOR_PICKED);
+            addToDictionaries(suggestion, AutoDictionary.FREQUENCY_FOR_PICKED);
+        } else {
+            addToBigramDictionary(suggestion, 1);
         }
+        LatinImeLogger.logOnManualSuggestion(mComposing.toString(), suggestion.toString(),
+                index, suggestions);
         TextEntryState.acceptedSuggestion(mComposing.toString(), suggestion);
         // Follow it with a space
-        if (mAutoSpace) {
+        if (mAutoSpace && !correcting) {
             sendSpace();
             mJustAddedAutoSpace = true;
         }
-        // Fool the state watcher so that a subsequent backspace will not do a revert
-        TextEntryState.typedCharacter((char) KEYCODE_SPACE, true);
-        if (index == 0 && mCorrectionMode > 0 && !mSuggest.isValidWord(suggestion)) {
+
+        // 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.
+        if (!correcting) {
+            TextEntryState.typedCharacter((char) KEYCODE_SPACE, true);
+            setNextSuggestions();
+        } else {
+            // In case the cursor position doesn't change, make sure we show the suggestions again.
+            postUpdateOldSuggestions();
+        }
+        if (index == 0 && mCorrectionMode > 0 && !mSuggest.isValidWord(suggestion)
+                && !mSuggest.isValidWord(suggestion.toString().toLowerCase())) {
             mCandidateView.showAddToDictionaryHint(suggestion);
         }
         if (ic != null) {
@@ -1569,43 +1811,226 @@
         }
     }
 
-    private void pickSuggestion(CharSequence suggestion) {
+    private void rememberReplacedWord(CharSequence suggestion) {
+        if (mShowingVoiceSuggestions) {
+            // Retain the replaced word in the alternatives array.
+            EditingUtil.Range range = new EditingUtil.Range();
+            String wordToBeReplaced = EditingUtil.getWordAtCursor(getCurrentInputConnection(),
+                    mWordSeparators, range);
+            if (!mWordToSuggestions.containsKey(wordToBeReplaced)) {
+                wordToBeReplaced = wordToBeReplaced.toLowerCase();
+            }
+            if (mWordToSuggestions.containsKey(wordToBeReplaced)) {
+                List<CharSequence> suggestions = mWordToSuggestions.get(wordToBeReplaced);
+                if (suggestions.contains(suggestion)) {
+                    suggestions.remove(suggestion);
+                }
+                suggestions.add(wordToBeReplaced);
+                mWordToSuggestions.remove(wordToBeReplaced);
+                mWordToSuggestions.put(suggestion.toString(), suggestions);
+            }
+        }
+    }
+
+    /**
+     * Commits the chosen word to the text field and saves it for later
+     * retrieval.
+     * @param suggestion the suggestion picked by the user to be committed to
+     *            the text field
+     * @param correcting whether this is due to a correction of an existing
+     *            word.
+     */
+    private void pickSuggestion(CharSequence suggestion, boolean correcting) {
+        LatinKeyboardView inputView = mKeyboardSwitcher.getInputView();
         if (mCapsLock) {
             suggestion = suggestion.toString().toUpperCase();
         } else if (preferCapitalization()
-                || (mKeyboardSwitcher.isAlphabetMode() && mInputView.isShifted())) {
+                || (mKeyboardSwitcher.isAlphabetMode()
+                        && inputView.isShifted())) {
             suggestion = suggestion.toString().toUpperCase().charAt(0)
                     + suggestion.subSequence(1, suggestion.length()).toString();
         }
         InputConnection ic = getCurrentInputConnection();
         if (ic != null) {
-            if (mSuggestionShouldReplaceCurrentWord) {
+            rememberReplacedWord(suggestion);
+            // If text is in correction mode and we're not using composing
+            // text to underline, then the word at the cursor position needs
+            // to be removed before committing the correction
+            if (correcting && !MODIFY_TEXT_FOR_CORRECTION) {
+                if (mLastSelectionStart < mLastSelectionEnd) {
+                    ic.setSelection(mLastSelectionStart, mLastSelectionStart);
+                }
                 EditingUtil.deleteWordAtCursor(ic, getWordSeparators());
             }
-            if (!VoiceInput.DELETE_SYMBOL.equals(suggestion)) {
-                ic.commitText(suggestion, 1);
-            }
+
+            ic.commitText(suggestion, 1);
         }
+        saveWordInHistory(suggestion);
         mPredicting = false;
         mCommittedLength = suggestion.length();
-        ((LatinKeyboard) mInputView.getKeyboard()).setPreferredLetters(null);
-        setNextSuggestions();
+        ((LatinKeyboard) inputView.getKeyboard()).setPreferredLetters(null);
+        // If we just corrected a word, then don't show punctuations
+        if (!correcting) {
+            setNextSuggestions();
+        }
         updateShiftKeyState(getCurrentInputEditorInfo());
     }
 
+    private void setOldSuggestions() {
+        // TODO: Inefficient to check if touching word and then get the touching word. Do it
+        // in one go.
+        mShowingVoiceSuggestions = false;
+        InputConnection ic = getCurrentInputConnection();
+        if (ic == null) return;
+        ic.beginBatchEdit();
+        // If there is a selection, then undo the selection first. Unfortunately this causes
+        // a flicker. TODO: Add getSelectionText() to InputConnection API.
+        if (mLastSelectionStart < mLastSelectionEnd) {
+            ic.setSelection(mLastSelectionStart, mLastSelectionStart);
+        }
+        if (!mPredicting && isCursorTouchingWord()) {
+            EditingUtil.Range range = new EditingUtil.Range();
+            CharSequence touching = EditingUtil.getWordAtCursor(getCurrentInputConnection(),
+                    mWordSeparators, range);
+            // If it's a selection, check if it's an entire word and no more, no less.
+            boolean fullword = EditingUtil.isFullWordOrInside(range, mLastSelectionStart,
+                    mLastSelectionEnd);
+            if (fullword && touching != null && touching.length() > 1) {
+                // Strip out any trailing word separator
+                if (mWordSeparators.indexOf(touching.charAt(touching.length() - 1)) > 0) {
+                    touching = touching.toString().substring(0, touching.length() - 1);
+                }
+
+                // Search for result in spoken word alternatives
+                String selectedWord = touching.toString().trim();
+                if (!mWordToSuggestions.containsKey(selectedWord)){
+                    selectedWord = selectedWord.toLowerCase();
+                }
+                if (mWordToSuggestions.containsKey(selectedWord)){
+                    mShowingVoiceSuggestions = true;
+                    underlineWord(touching, range.charsBefore, range.charsAfter);
+                    List<CharSequence> suggestions = mWordToSuggestions.get(selectedWord);
+                    // If the first letter of touching is capitalized, make all the suggestions
+                    // start with a capital letter.
+                    if (Character.isUpperCase((char) touching.charAt(0))) {
+                        for (int i=0; i< suggestions.size(); i++) {
+                            String origSugg = (String) suggestions.get(i);
+                            String capsSugg = origSugg.toUpperCase().charAt(0)
+                                + origSugg.subSequence(1, origSugg.length()).toString();
+                            suggestions.set(i,capsSugg);
+                        }
+                    }
+                    setSuggestions(suggestions, false, true, true);
+                    setCandidatesViewShown(true);
+                    TextEntryState.selectedForCorrection();
+                    ic.endBatchEdit();
+                    return;
+                }
+
+                // If we didn't find a match, search for result in typed word history
+                WordComposer foundWord = null;
+                WordAlternatives alternatives = null;
+                for (WordAlternatives entry : mWordHistory) {
+                    if (TextUtils.equals(entry.getChosenWord(), touching)) {
+                        if (entry instanceof TypedWordAlternatives) {
+                            foundWord = ((TypedWordAlternatives)entry).word;
+                        }
+                        alternatives = entry;
+                        break;
+                    }
+                }
+                // If we didn't find a match, at least suggest completions
+                if (foundWord == null && mSuggest.isValidWord(touching)) {
+                    foundWord = new WordComposer();
+                    for (int i = 0; i < touching.length(); i++) {
+                        foundWord.add(touching.charAt(i), new int[] { touching.charAt(i) });
+                    }
+                }
+                // Found a match, show suggestions
+                if (foundWord != null || alternatives != null) {
+                    underlineWord(touching, range.charsBefore, range.charsAfter);
+                    TextEntryState.selectedForCorrection();
+                    if (alternatives == null) alternatives = new TypedWordAlternatives(touching,
+                            foundWord);
+                    showCorrections(alternatives);
+                    if (foundWord != null) {
+                        mWord = new WordComposer(foundWord);
+                    } else {
+                        mWord.reset();
+                    }
+                    // Revert the selection
+                    if (mLastSelectionStart < mLastSelectionEnd) {
+                        ic.setSelection(mLastSelectionStart, mLastSelectionEnd);
+                    }
+                    ic.endBatchEdit();
+                    return;
+                }
+                abortCorrection(true);
+            } else {
+                abortCorrection(true);
+                setNextSuggestions();
+            }
+        } else {
+            abortCorrection(true);
+        }
+        // Revert the selection
+        if (mLastSelectionStart < mLastSelectionEnd) {
+            ic.setSelection(mLastSelectionStart, mLastSelectionEnd);
+        }
+        ic.endBatchEdit();
+    }
+
     private void setNextSuggestions() {
         setSuggestions(mSuggestPuncList, false, false, false);
     }
 
-    private void checkAddToDictionary(CharSequence suggestion, int frequencyDelta) {
+    private void underlineWord(CharSequence word, int left, int right) {
+        InputConnection ic = getCurrentInputConnection();
+        if (ic == null) return;
+        if (MODIFY_TEXT_FOR_CORRECTION) {
+            ic.finishComposingText();
+            ic.deleteSurroundingText(left, right);
+            ic.setComposingText(word, 1);
+        }
+        ic.setSelection(mLastSelectionStart, mLastSelectionStart);
+    }
+
+    private void addToDictionaries(CharSequence suggestion, int frequencyDelta) {
+        checkAddToDictionary(suggestion, frequencyDelta, false);
+    }
+
+    private void addToBigramDictionary(CharSequence suggestion, int frequencyDelta) {
+        checkAddToDictionary(suggestion, frequencyDelta, true);
+    }
+
+    /**
+     * Adds to the UserBigramDictionary and/or AutoDictionary
+     * @param addToBigramDictionary true if it should be added to bigram dictionary if possible
+     */
+    private void checkAddToDictionary(CharSequence suggestion, int frequencyDelta,
+            boolean addToBigramDictionary) {
+        if (suggestion == null || suggestion.length() < 1) return;
         // Only auto-add to dictionary if auto-correct is ON. Otherwise we'll be
         // adding words in situations where the user or application really didn't
         // want corrections enabled or learned.
-        if (!(mCorrectionMode == Suggest.CORRECTION_FULL)) return;
-        if (mAutoDictionary.isValidWord(suggestion)
-                || (!mSuggest.isValidWord(suggestion.toString())
+        if (!(mCorrectionMode == Suggest.CORRECTION_FULL
+                || mCorrectionMode == Suggest.CORRECTION_FULL_BIGRAM)) {
+            return;
+        }
+        if (suggestion != null) {
+            if (!addToBigramDictionary && mAutoDictionary.isValidWord(suggestion)
+                    || (!mSuggest.isValidWord(suggestion.toString())
                     && !mSuggest.isValidWord(suggestion.toString().toLowerCase()))) {
-            mAutoDictionary.addWord(suggestion.toString(), frequencyDelta);
+                mAutoDictionary.addWord(suggestion.toString(), frequencyDelta);
+            }
+
+            if (mUserBigramDictionary != null) {
+                CharSequence prevWord = EditingUtil.getPreviousWord(getCurrentInputConnection(),
+                        mSentenceSeparators);
+                if (!TextUtils.isEmpty(prevWord)) {
+                    mUserBigramDictionary.addBigrams(prevWord.toString(), suggestion.toString());
+                }
+            }
         }
     }
 
@@ -1635,7 +2060,6 @@
         if (!mPredicting && length > 0) {
             final InputConnection ic = getCurrentInputConnection();
             mPredicting = true;
-            ic.beginBatchEdit();
             mJustRevertedSeparator = ic.getTextBeforeCursor(1, 0);
             if (deleteChar) ic.deleteSurroundingText(1, 0);
             int toDelete = mCommittedLength;
@@ -1647,7 +2071,6 @@
             ic.deleteSurroundingText(toDelete, 0);
             ic.setComposingText(mComposing, 1);
             TextEntryState.backspace();
-            ic.endBatchEdit();
             postUpdateSuggestions();
         } else {
             sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL);
@@ -1664,7 +2087,7 @@
         return separators.contains(String.valueOf((char)code));
     }
 
-    public boolean isSentenceSeparator(int code) {
+    private boolean isSentenceSeparator(int code) {
         return mSentenceSeparators.contains(String.valueOf((char)code));
     }
 
@@ -1688,7 +2111,7 @@
             ClipboardManager cm = ((ClipboardManager)getSystemService(CLIPBOARD_SERVICE));
             CharSequence text = cm.getText();
             if (!TextUtils.isEmpty(text)) {
-                mInputView.startPlaying(text.toString());
+                mKeyboardSwitcher.getInputView().startPlaying(text.toString());
             }
         }
     }
@@ -1739,7 +2162,7 @@
 
     public void onRelease(int primaryCode) {
         // Reset any drag flags in the keyboard
-        ((LatinKeyboard) mInputView.getKeyboard()).keyReleased();
+        ((LatinKeyboard) mKeyboardSwitcher.getInputView().getKeyboard()).keyReleased();
         //vibrate();
     }
 
@@ -1791,7 +2214,7 @@
         // if mAudioManager is null, we don't have the ringer state yet
         // mAudioManager will be set by updateRingerMode
         if (mAudioManager == null) {
-            if (mInputView != null) {
+            if (mKeyboardSwitcher.getInputView() != null) {
                 updateRingerMode();
             }
         }
@@ -1818,8 +2241,9 @@
         if (!mVibrateOn) {
             return;
         }
-        if (mInputView != null) {
-            mInputView.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP,
+        if (mKeyboardSwitcher.getInputView() != null) {
+            mKeyboardSwitcher.getInputView().performHapticFeedback(
+                    HapticFeedbackConstants.KEYBOARD_TAP,
                     HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING);
         }
     }
@@ -1854,6 +2278,10 @@
         return mWord;
     }
 
+    boolean getPopupOn() {
+        return mPopupOn;
+    }
+
     private void updateCorrectionMode() {
         mHasDictionary = mSuggest != null ? mSuggest.hasMainDictionary() : false;
         mAutoCorrectOn = (mAutoCorrectEnabled || mQuickFixes)
@@ -1861,6 +2289,8 @@
         mCorrectionMode = (mAutoCorrectOn && mAutoCorrectEnabled)
                 ? Suggest.CORRECTION_FULL
                 : (mAutoCorrectOn ? Suggest.CORRECTION_BASIC : Suggest.CORRECTION_NONE);
+        mCorrectionMode = (mBigramSuggestionEnabled && mAutoCorrectOn && mAutoCorrectEnabled)
+                ? Suggest.CORRECTION_FULL_BIGRAM : mCorrectionMode;
         if (mSuggest != null) {
             mSuggest.setCorrectionMode(mCorrectionMode);
         }
@@ -1877,7 +2307,7 @@
         launchSettings(LatinIMESettings.class);
     }
 
-    protected void launchSettings(Class settingsClass) {
+    protected void launchSettings(Class<LatinIMESettings> settingsClass) {
         handleClose();
         Intent intent = new Intent();
         intent.setClass(LatinIME.this, settingsClass);
@@ -1890,6 +2320,8 @@
         SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
         mVibrateOn = sp.getBoolean(PREF_VIBRATE_ON, false);
         mSoundOn = sp.getBoolean(PREF_SOUND_ON, false);
+        mPopupOn = sp.getBoolean(PREF_POPUP_ON,
+                mResources.getBoolean(R.bool.default_popup_preview));
         mAutoCap = sp.getBoolean(PREF_AUTO_CAP, true);
         mQuickFixes = sp.getBoolean(PREF_QUICK_FIXES, true);
         mHasUsedVoiceInput = sp.getBoolean(PREF_HAS_USED_VOICE_INPUT, false);
@@ -1927,6 +2359,7 @@
         }
         mAutoCorrectEnabled = sp.getBoolean(PREF_AUTO_COMPLETE,
                 mResources.getBoolean(R.bool.enable_autocorrect)) & mShowSuggestions;
+        mBigramSuggestionEnabled = sp.getBoolean(PREF_BIGRAM_SUGGESTIONS, true) & mShowSuggestions;
         updateCorrectionMode();
         updateAutoTextEnabled(mResources.getConfiguration().locale);
         mLanguageSwitcher.loadLocales(sp);
@@ -1934,14 +2367,18 @@
 
     private void initSuggestPuncList() {
         mSuggestPuncList = new ArrayList<CharSequence>();
-        String suggestPuncs = mResources.getString(R.string.suggested_punctuations);
-        if (suggestPuncs != null) {
-            for (int i = 0; i < suggestPuncs.length(); i++) {
-                mSuggestPuncList.add(suggestPuncs.subSequence(i, i + 1));
+        mSuggestPuncs = mResources.getString(R.string.suggested_punctuations);
+        if (mSuggestPuncs != null) {
+            for (int i = 0; i < mSuggestPuncs.length(); i++) {
+                mSuggestPuncList.add(mSuggestPuncs.subSequence(i, i + 1));
             }
         }
     }
 
+    private boolean isSuggestedPunctuation(int code) {
+        return mSuggestPuncs.contains(String.valueOf((char)code));
+    }
+
     private void showOptionsMenu() {
         AlertDialog.Builder builder = new AlertDialog.Builder(this);
         builder.setCancelable(true);
@@ -1970,7 +2407,7 @@
         mOptionsDialog = builder.create();
         Window window = mOptionsDialog.getWindow();
         WindowManager.LayoutParams lp = window.getAttributes();
-        lp.token = mInputView.getWindowToken();
+        lp.token = mKeyboardSwitcher.getInputView().getWindowToken();
         lp.type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
         window.setAttributes(lp);
         window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
@@ -1980,7 +2417,7 @@
     private void changeKeyboardMode() {
         mKeyboardSwitcher.toggleSymbols();
         if (mCapsLock && mKeyboardSwitcher.isAlphabetMode()) {
-            ((LatinKeyboard) mInputView.getKeyboard()).setShiftLocked(mCapsLock);
+            mKeyboardSwitcher.setShiftLocked(mCapsLock);
         }
 
         updateShiftKeyState(getCurrentInputEditorInfo());
@@ -2010,19 +2447,17 @@
         p.println("  TextEntryState.state=" + TextEntryState.getState());
         p.println("  mSoundOn=" + mSoundOn);
         p.println("  mVibrateOn=" + mVibrateOn);
+        p.println("  mPopupOn=" + mPopupOn);
     }
 
     // Characters per second measurement
 
-    private static final boolean PERF_DEBUG = false;
     private long mLastCpsTime;
     private static final int CPS_BUFFER_SIZE = 16;
     private long[] mCpsIntervals = new long[CPS_BUFFER_SIZE];
     private int mCpsIndex;
-    private boolean mInputTypeNoAutoCorrect;
 
     private void measureCps() {
-        if (!LatinIME.PERF_DEBUG) return;
         long now = System.currentTimeMillis();
         if (mLastCpsTime == 0) mLastCpsTime = now - 100; // Initial
         mCpsIntervals[mCpsIndex] = now - mLastCpsTime;
diff --git a/java/src/com/android/inputmethod/latin/LatinIMESettings.java b/java/src/com/android/inputmethod/latin/LatinIMESettings.java
index 21b9674..806ef00 100644
--- a/java/src/com/android/inputmethod/latin/LatinIMESettings.java
+++ b/java/src/com/android/inputmethod/latin/LatinIMESettings.java
@@ -24,13 +24,13 @@
 import android.app.backup.BackupManager;
 import android.content.DialogInterface;
 import android.content.SharedPreferences;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
 import android.os.Bundle;
 import android.preference.CheckBoxPreference;
 import android.preference.ListPreference;
-import android.preference.Preference;
 import android.preference.PreferenceActivity;
 import android.preference.PreferenceGroup;
-import android.preference.Preference.OnPreferenceClickListener;
 import android.speech.SpeechRecognizer;
 import android.text.AutoText;
 import android.util.Log;
@@ -43,11 +43,9 @@
         DialogInterface.OnDismissListener {
 
     private static final String QUICK_FIXES_KEY = "quick_fixes";
-    private static final String SHOW_SUGGESTIONS_KEY = "show_suggestions";
     private static final String PREDICTION_SETTINGS_KEY = "prediction_settings";
     private static final String VOICE_SETTINGS_KEY = "voice_mode";
-    private static final String VOICE_ON_PRIMARY_KEY = "voice_on_main";
-    private static final String VOICE_SERVER_KEY = "voice_server_url";
+    private static final String DEBUG_MODE_KEY = "debug_mode";
 
     private static final String TAG = "LatinIMESettings";
 
@@ -55,7 +53,7 @@
     private static final int VOICE_INPUT_CONFIRM_DIALOG = 0;
 
     private CheckBoxPreference mQuickFixes;
-    private CheckBoxPreference mShowSuggestions;
+    private CheckBoxPreference mDebugMode;
     private ListPreference mVoicePreference;
     private boolean mVoiceOn;
 
@@ -69,7 +67,6 @@
         super.onCreate(icicle);
         addPreferencesFromResource(R.xml.prefs);
         mQuickFixes = (CheckBoxPreference) findPreference(QUICK_FIXES_KEY);
-        mShowSuggestions = (CheckBoxPreference) findPreference(SHOW_SUGGESTIONS_KEY);
         mVoicePreference = (ListPreference) findPreference(VOICE_SETTINGS_KEY);
         SharedPreferences prefs = getPreferenceManager().getSharedPreferences();
         prefs.registerOnSharedPreferenceChangeListener(this);
@@ -77,6 +74,9 @@
         mVoiceModeOff = getString(R.string.voice_mode_off);
         mVoiceOn = !(prefs.getString(VOICE_SETTINGS_KEY, mVoiceModeOff).equals(mVoiceModeOff));
         mLogger = VoiceInputLogger.getLogger(this);
+
+        mDebugMode = (CheckBoxPreference) findPreference(DEBUG_MODE_KEY);
+        updateDebugMode(mDebugMode.isChecked());
     }
 
     @Override
@@ -110,11 +110,35 @@
                     .equals(mVoiceModeOff)) {
                 showVoiceConfirmation();
             }
+        } else if (key.equals(DEBUG_MODE_KEY)) {
+            updateDebugMode(prefs.getBoolean(DEBUG_MODE_KEY, false));
         }
         mVoiceOn = !(prefs.getString(VOICE_SETTINGS_KEY, mVoiceModeOff).equals(mVoiceModeOff));
         updateVoiceModeSummary();
     }
 
+    private void updateDebugMode(boolean isDebugMode) {
+        if (mDebugMode == null) {
+            return;
+        }
+        String version = "";
+        try {
+            PackageInfo info = getPackageManager().getPackageInfo(getPackageName(), 0);
+            version = "Version " + info.versionName;
+        } catch (NameNotFoundException e) {
+            Log.e(TAG, "Could not find version info.");
+        }
+        if (!isDebugMode) {
+            mDebugMode.setEnabled(false);
+            mDebugMode.setTitle(version);
+            mDebugMode.setSummary("");
+        } else {
+            mDebugMode.setEnabled(true);
+            mDebugMode.setTitle(getResources().getString(R.string.prefs_debug_mode));
+            mDebugMode.setSummary(version);
+        }
+    }
+
     private void showVoiceConfirmation() {
         mOkClicked = false;
         showDialog(VOICE_INPUT_CONFIRM_DIALOG);
diff --git a/java/src/com/android/inputmethod/latin/LatinIMEUtil.java b/java/src/com/android/inputmethod/latin/LatinIMEUtil.java
new file mode 100644
index 0000000..838b4fe
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/LatinIMEUtil.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 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.
+ */
+
+package com.android.inputmethod.latin;
+
+import android.os.AsyncTask;
+import android.text.format.DateUtils;
+import android.util.Log;
+
+public class LatinIMEUtil {
+
+    /**
+     * Cancel an {@link AsyncTask}.
+     *
+     * @param mayInterruptIfRunning <tt>true</tt> if the thread executing this
+     *        task should be interrupted; otherwise, in-progress tasks are allowed
+     *        to complete.
+     */
+    public static void cancelTask(AsyncTask<?, ?, ?> task, boolean mayInterruptIfRunning) {
+        if (task != null && task.getStatus() != AsyncTask.Status.FINISHED) {
+            task.cancel(mayInterruptIfRunning);
+        }
+    }
+
+    public static class GCUtils {
+        private static final String TAG = "GCUtils";
+        public static final int GC_TRY_COUNT = 2;
+        // GC_TRY_LOOP_MAX is used for the hard limit of GC wait,
+        // GC_TRY_LOOP_MAX should be greater than GC_TRY_COUNT.
+        public static final int GC_TRY_LOOP_MAX = 5;
+        private static final long GC_INTERVAL = DateUtils.SECOND_IN_MILLIS;
+        private static GCUtils sInstance = new GCUtils();
+        private int mGCTryCount = 0;
+
+        public static GCUtils getInstance() {
+            return sInstance;
+        }
+
+        public void reset() {
+            mGCTryCount = 0;
+        }
+
+        public boolean tryGCOrWait(String metaData, Throwable t) {
+            if (LatinImeLogger.sDBG) {
+                Log.d(TAG, "Encountered Exception or Error. Try GC.");
+            }
+            if (mGCTryCount == 0) {
+                System.gc();
+            }
+            if (++mGCTryCount > GC_TRY_COUNT) {
+                LatinImeLogger.logOnException(metaData, t);
+                return false;
+            } else {
+                try {
+                    Thread.sleep(GC_INTERVAL);
+                    return true;
+                } catch (InterruptedException e) {
+                    Log.e(TAG, "Sleep was interrupted.");
+                    LatinImeLogger.logOnException(metaData, t);
+                    return false;
+                }
+            }
+        }
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/LatinImeLogger.java b/java/src/com/android/inputmethod/latin/LatinImeLogger.java
new file mode 100644
index 0000000..19eead0
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/LatinImeLogger.java
@@ -0,0 +1,847 @@
+/*
+ * Copyright (C) 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.
+ */
+
+package com.android.inputmethod.latin;
+
+import com.android.inputmethod.latin.Dictionary.DataType;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.AsyncTask;
+import android.os.DropBoxManager;
+import android.preference.PreferenceManager;
+import android.text.TextUtils;
+import android.text.format.DateUtils;
+import android.util.Log;
+import android.util.Pair;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.net.URLEncoder;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+
+public class LatinImeLogger implements SharedPreferences.OnSharedPreferenceChangeListener {
+    private static final String TAG = "LatinIMELogs";
+    public static boolean sDBG = false;
+    private static boolean sPRINTLOGGING = false;
+    // SUPPRESS_EXCEPTION should be true when released to public.
+    private static final boolean SUPPRESS_EXCEPTION = true;
+    // DEFAULT_LOG_ENABLED should be false when released to public.
+    private static final boolean DEFAULT_LOG_ENABLED = false;
+
+    private static final long MINIMUMSENDINTERVAL = 300 * DateUtils.SECOND_IN_MILLIS; // 300 sec
+    private static final long MINIMUMCOUNTINTERVAL = 20 * DateUtils.SECOND_IN_MILLIS; // 20 sec
+    private static final long MINIMUMSENDSIZE = 40;
+    private static final char SEPARATER = ';';
+    private static final char NULL_CHAR = '\uFFFC';
+    private static final int EXCEPTION_MAX_LENGTH = 400;
+
+    // ID_MANUALSUGGESTION has been replaced by ID_MANUALSUGGESTION_WITH_DATATYPE
+    // private static final int ID_MANUALSUGGESTION = 0;
+    private static final int ID_AUTOSUGGESTIONCANCELLED = 1;
+    private static final int ID_AUTOSUGGESTION = 2;
+    private static final int ID_INPUT_COUNT = 3;
+    private static final int ID_DELETE_COUNT = 4;
+    private static final int ID_WORD_COUNT = 5;
+    private static final int ID_ACTUAL_CHAR_COUNT = 6;
+    private static final int ID_THEME_ID = 7;
+    private static final int ID_SETTING_AUTO_COMPLETE = 8;
+    private static final int ID_VERSION = 9;
+    private static final int ID_EXCEPTION = 10;
+    private static final int ID_MANUALSUGGESTIONCOUNT = 11;
+    private static final int ID_AUTOSUGGESTIONCANCELLEDCOUNT = 12;
+    private static final int ID_AUTOSUGGESTIONCOUNT = 13;
+    private static final int ID_LANGUAGES = 14;
+    private static final int ID_MANUALSUGGESTION_WITH_DATATYPE = 15;
+
+    private static final String PREF_ENABLE_LOG = "enable_logging";
+    private static final String PREF_DEBUG_MODE = "debug_mode";
+    private static final String PREF_AUTO_COMPLETE = "auto_complete";
+
+    public static boolean sLogEnabled = true;
+    /* package */ static LatinImeLogger sLatinImeLogger = new LatinImeLogger();
+    // Store the last auto suggested word.
+    // This is required for a cancellation log of auto suggestion of that word.
+    /* package */ static String sLastAutoSuggestBefore;
+    /* package */ static String sLastAutoSuggestAfter;
+    /* package */ static String sLastAutoSuggestSeparator;
+    // This value holds MAIN, USER, AUTO, etc...
+    private static int sLastAutoSuggestDicTypeId;
+    // This value holds 0 (= unigram), 1 (= bigram) etc...
+    private static int sLastAutoSuggestDataType;
+    private static HashMap<String, Pair<Integer, Integer>> sSuggestDicMap
+            = new HashMap<String, Pair<Integer, Integer>>();
+    private static String[] sPreviousWords;
+    private static DebugKeyEnabler sDebugKeyEnabler = new DebugKeyEnabler();
+
+    private ArrayList<LogEntry> mLogBuffer = null;
+    private ArrayList<LogEntry> mPrivacyLogBuffer = null;
+    /* package */ RingCharBuffer mRingCharBuffer = null;
+
+    private Context mContext = null;
+    private DropBoxManager mDropBox = null;
+    private AddTextToDropBoxTask mAddTextToDropBoxTask;
+    private long mLastTimeActive;
+    private long mLastTimeSend;
+    private long mLastTimeCountEntry;
+
+    private String mThemeId;
+    private String mSelectedLanguages;
+    private String mCurrentLanguage;
+    private int mDeleteCount;
+    private int mInputCount;
+    private int mWordCount;
+    private int[] mAutoSuggestCountPerDic = new int[Suggest.DIC_TYPE_LAST_ID + 1];
+    private int[] mManualSuggestCountPerDic = new int[Suggest.DIC_TYPE_LAST_ID + 1];
+    private int[] mAutoCancelledCountPerDic = new int[Suggest.DIC_TYPE_LAST_ID + 1];
+    private int mActualCharCount;
+
+    private static class LogEntry implements Comparable<LogEntry> {
+        public final int mTag;
+        public final String[] mData;
+        public long mTime;
+
+        public LogEntry (long time, int tag, String[] data) {
+            mTag = tag;
+            mTime = time;
+            mData = data;
+        }
+
+        public int compareTo(LogEntry log2) {
+            if (mData.length == 0 && log2.mData.length == 0) {
+                return 0;
+            } else if (mData.length == 0) {
+                return 1;
+            } else if (log2.mData.length == 0) {
+                return -1;
+            }
+            return log2.mData[0].compareTo(mData[0]);
+        }
+    }
+
+    private class AddTextToDropBoxTask extends AsyncTask<Void, Void, Void> {
+        private final DropBoxManager mDropBox;
+        private final long mTime;
+        private final String mData;
+        public AddTextToDropBoxTask(DropBoxManager db, long time, String data) {
+            mDropBox = db;
+            mTime = time;
+            mData = data;
+        }
+        @Override
+        protected Void doInBackground(Void... params) {
+            if (sPRINTLOGGING) {
+                Log.d(TAG, "Commit log: " + mData);
+            }
+            mDropBox.addText(TAG, mData);
+            return null;
+        }
+        @Override
+        protected void onPostExecute(Void v) {
+            mLastTimeSend = mTime;
+        }
+    }
+
+    private void initInternal(Context context) {
+        mContext = context;
+        mDropBox = (DropBoxManager) mContext.getSystemService(Context.DROPBOX_SERVICE);
+        mLastTimeSend = System.currentTimeMillis();
+        mLastTimeActive = mLastTimeSend;
+        mLastTimeCountEntry = mLastTimeSend;
+        mDeleteCount = 0;
+        mInputCount = 0;
+        mWordCount = 0;
+        mActualCharCount = 0;
+        Arrays.fill(mAutoSuggestCountPerDic, 0);
+        Arrays.fill(mManualSuggestCountPerDic, 0);
+        Arrays.fill(mAutoCancelledCountPerDic, 0);
+        mLogBuffer = new ArrayList<LogEntry>();
+        mPrivacyLogBuffer = new ArrayList<LogEntry>();
+        mRingCharBuffer = new RingCharBuffer(context);
+        final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+        sLogEnabled = prefs.getBoolean(PREF_ENABLE_LOG, DEFAULT_LOG_ENABLED);
+        mThemeId = prefs.getString(KeyboardSwitcher.PREF_KEYBOARD_LAYOUT,
+                KeyboardSwitcher.DEFAULT_LAYOUT_ID);
+        mSelectedLanguages = prefs.getString(LatinIME.PREF_SELECTED_LANGUAGES, "");
+        mCurrentLanguage = prefs.getString(LatinIME.PREF_INPUT_LANGUAGE, "");
+        sPRINTLOGGING = prefs.getBoolean(PREF_DEBUG_MODE, sPRINTLOGGING);
+        sDBG = sPRINTLOGGING;
+        prefs.registerOnSharedPreferenceChangeListener(this);
+    }
+
+    /**
+     * Clear all logged data
+     */
+    private void reset() {
+        mDeleteCount = 0;
+        mInputCount = 0;
+        mWordCount = 0;
+        mActualCharCount = 0;
+        Arrays.fill(mAutoSuggestCountPerDic, 0);
+        Arrays.fill(mManualSuggestCountPerDic, 0);
+        Arrays.fill(mAutoCancelledCountPerDic, 0);
+        mLogBuffer.clear();
+        mPrivacyLogBuffer.clear();
+        mRingCharBuffer.reset();
+    }
+
+    public void destroy() {
+        LatinIMEUtil.cancelTask(mAddTextToDropBoxTask, false);
+    }
+
+    /**
+     * Check if the input string is safe as an entry or not.
+     */
+    private static boolean checkStringDataSafe(String s) {
+        if (sDBG) {
+            Log.d(TAG, "Check String safety: " + s);
+        }
+        for (int i = 0; i < s.length(); ++i) {
+            if (Character.isDigit(s.charAt(i))) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private void addCountEntry(long time) {
+        if (sPRINTLOGGING) {
+            Log.d(TAG, "Log counts. (4)");
+        }
+        mLogBuffer.add(new LogEntry (time, ID_DELETE_COUNT,
+                new String[] {String.valueOf(mDeleteCount)}));
+        mLogBuffer.add(new LogEntry (time, ID_INPUT_COUNT,
+                new String[] {String.valueOf(mInputCount)}));
+        mLogBuffer.add(new LogEntry (time, ID_WORD_COUNT,
+                new String[] {String.valueOf(mWordCount)}));
+        mLogBuffer.add(new LogEntry (time, ID_ACTUAL_CHAR_COUNT,
+                new String[] {String.valueOf(mActualCharCount)}));
+        mDeleteCount = 0;
+        mInputCount = 0;
+        mWordCount = 0;
+        mActualCharCount = 0;
+        mLastTimeCountEntry = time;
+    }
+
+    private void addSuggestionCountEntry(long time) {
+        if (sPRINTLOGGING) {
+            Log.d(TAG, "log suggest counts. (1)");
+        }
+        String[] s = new String[mAutoSuggestCountPerDic.length];
+        for (int i = 0; i < s.length; ++i) {
+            s[i] = String.valueOf(mAutoSuggestCountPerDic[i]);
+        }
+        mLogBuffer.add(new LogEntry(time, ID_AUTOSUGGESTIONCOUNT, s));
+
+        s = new String[mAutoCancelledCountPerDic.length];
+        for (int i = 0; i < s.length; ++i) {
+            s[i] = String.valueOf(mAutoCancelledCountPerDic[i]);
+        }
+        mLogBuffer.add(new LogEntry(time, ID_AUTOSUGGESTIONCANCELLEDCOUNT, s));
+
+        s = new String[mManualSuggestCountPerDic.length];
+        for (int i = 0; i < s.length; ++i) {
+            s[i] = String.valueOf(mManualSuggestCountPerDic[i]);
+        }
+        mLogBuffer.add(new LogEntry(time, ID_MANUALSUGGESTIONCOUNT, s));
+
+        Arrays.fill(mAutoSuggestCountPerDic, 0);
+        Arrays.fill(mManualSuggestCountPerDic, 0);
+        Arrays.fill(mAutoCancelledCountPerDic, 0);
+    }
+
+    private void addThemeIdEntry(long time) {
+        if (sPRINTLOGGING) {
+            Log.d(TAG, "Log theme Id. (1)");
+        }
+        // TODO: Not to convert theme ID here. Currently "2" is treated as "6" in a log server.
+        if (mThemeId.equals("2")) {
+            mThemeId = "6";
+        } else if (mThemeId.equals("3")) {
+            mThemeId = "7";
+        }
+        mLogBuffer.add(new LogEntry (time, ID_THEME_ID,
+                new String[] {mThemeId}));
+    }
+
+    private void addLanguagesEntry(long time) {
+        if (sPRINTLOGGING) {
+            Log.d(TAG, "Log language settings. (1)");
+        }
+        // CurrentLanguage and SelectedLanguages will be blank if user doesn't use multi-language
+        // switching.
+        if (TextUtils.isEmpty(mCurrentLanguage)) {
+            mCurrentLanguage = mContext.getResources().getConfiguration().locale.toString();
+        }
+        mLogBuffer.add(new LogEntry (time, ID_LANGUAGES,
+                new String[] {mCurrentLanguage , mSelectedLanguages}));
+    }
+
+    private void addSettingsEntry(long time) {
+        if (sPRINTLOGGING) {
+            Log.d(TAG, "Log settings. (1)");
+        }
+        final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
+        mLogBuffer.add(new LogEntry (time, ID_SETTING_AUTO_COMPLETE,
+                new String[] {String.valueOf(prefs.getBoolean(PREF_AUTO_COMPLETE,
+                        mContext.getResources().getBoolean(R.bool.enable_autocorrect)))}));
+    }
+
+    private void addVersionNameEntry(long time) {
+        if (sPRINTLOGGING) {
+            Log.d(TAG, "Log Version. (1)");
+        }
+        try {
+            PackageInfo info = mContext.getPackageManager().getPackageInfo(
+                    mContext.getPackageName(), 0);
+            mLogBuffer.add(new LogEntry (time, ID_VERSION,
+                    new String[] {String.valueOf(info.versionCode), info.versionName}));
+        } catch (NameNotFoundException e) {
+            Log.e(TAG, "Could not find version name.");
+        }
+    }
+
+    private void addExceptionEntry(long time, String[] data) {
+        if (sPRINTLOGGING) {
+            Log.d(TAG, "Log Exception. (1)");
+        }
+        mLogBuffer.add(new LogEntry(time, ID_EXCEPTION, data));
+    }
+
+    private void flushPrivacyLogSafely() {
+        if (sPRINTLOGGING) {
+            Log.d(TAG, "Log obfuscated data. (" + mPrivacyLogBuffer.size() + ")");
+        }
+        long now = System.currentTimeMillis();
+        Collections.sort(mPrivacyLogBuffer);
+        for (LogEntry l: mPrivacyLogBuffer) {
+            l.mTime = now;
+            mLogBuffer.add(l);
+        }
+        mPrivacyLogBuffer.clear();
+    }
+
+    /**
+     * Add an entry
+     * @param tag
+     * @param data
+     */
+    private void addData(int tag, Object data) {
+        switch (tag) {
+            case ID_DELETE_COUNT:
+                if (((mLastTimeActive - mLastTimeCountEntry) > MINIMUMCOUNTINTERVAL)
+                        || (mDeleteCount == 0 && mInputCount == 0)) {
+                    addCountEntry(mLastTimeActive);
+                }
+                mDeleteCount += (Integer)data;
+                break;
+            case ID_INPUT_COUNT:
+                if (((mLastTimeActive - mLastTimeCountEntry) > MINIMUMCOUNTINTERVAL)
+                        || (mDeleteCount == 0 && mInputCount == 0)) {
+                    addCountEntry(mLastTimeActive);
+                }
+                mInputCount += (Integer)data;
+                break;
+            case ID_MANUALSUGGESTION_WITH_DATATYPE:
+            case ID_AUTOSUGGESTION:
+                ++mWordCount;
+                String[] dataStrings = (String[]) data;
+                if (dataStrings.length < 2) {
+                    if (sDBG) {
+                        Log.e(TAG, "The length of logged string array is invalid.");
+                    }
+                    break;
+                }
+                mActualCharCount += dataStrings[1].length();
+                if (checkStringDataSafe(dataStrings[0]) && checkStringDataSafe(dataStrings[1])) {
+                    mPrivacyLogBuffer.add(
+                            new LogEntry (System.currentTimeMillis(), tag, dataStrings));
+                } else {
+                    if (sDBG) {
+                        Log.d(TAG, "Skipped to add an entry because data is unsafe.");
+                    }
+                }
+                break;
+            case ID_AUTOSUGGESTIONCANCELLED:
+                --mWordCount;
+                dataStrings = (String[]) data;
+                if (dataStrings.length < 2) {
+                    if (sDBG) {
+                        Log.e(TAG, "The length of logged string array is invalid.");
+                    }
+                    break;
+                }
+                mActualCharCount -= dataStrings[1].length();
+                if (checkStringDataSafe(dataStrings[0]) && checkStringDataSafe(dataStrings[1])) {
+                    mPrivacyLogBuffer.add(
+                            new LogEntry (System.currentTimeMillis(), tag, dataStrings));
+                } else {
+                    if (sDBG) {
+                        Log.d(TAG, "Skipped to add an entry because data is unsafe.");
+                    }
+                }
+                break;
+            case ID_EXCEPTION:
+                dataStrings = (String[]) data;
+                if (dataStrings.length < 2) {
+                    if (sDBG) {
+                        Log.e(TAG, "The length of logged string array is invalid.");
+                    }
+                    break;
+                }
+                addExceptionEntry(System.currentTimeMillis(), dataStrings);
+                break;
+            default:
+                if (sDBG) {
+                    Log.e(TAG, "Log Tag is not entried.");
+                }
+                break;
+        }
+    }
+
+    private void commitInternal() {
+        // if there is no log entry in mLogBuffer, will not send logs to DropBox.
+        if (!mLogBuffer.isEmpty() && (mAddTextToDropBoxTask == null
+                || mAddTextToDropBoxTask.getStatus() == AsyncTask.Status.FINISHED)) {
+            if (sPRINTLOGGING) {
+                Log.d(TAG, "Commit (" + mLogBuffer.size() + ")");
+            }
+            flushPrivacyLogSafely();
+            long now = System.currentTimeMillis();
+            addCountEntry(now);
+            addThemeIdEntry(now);
+            addLanguagesEntry(now);
+            addSettingsEntry(now);
+            addVersionNameEntry(now);
+            addSuggestionCountEntry(now);
+            String s = LogSerializer.createStringFromEntries(mLogBuffer);
+            reset();
+            mAddTextToDropBoxTask = (AddTextToDropBoxTask) new AddTextToDropBoxTask(
+                    mDropBox, now, s).execute();
+        }
+    }
+
+    private void commitInternalAndStopSelf() {
+        if (sDBG) {
+            Log.e(TAG, "Exception was thrown and let's die.");
+        }
+        commitInternal();
+        LatinIME ime = ((LatinIME) mContext);
+        ime.hideWindow();
+        ime.stopSelf();
+    }
+
+    private synchronized void sendLogToDropBox(int tag, Object s) {
+        long now = System.currentTimeMillis();
+        if (sDBG) {
+            String out = "";
+            if (s instanceof String[]) {
+                for (String str: ((String[]) s)) {
+                    out += str + ",";
+                }
+            } else if (s instanceof Integer) {
+                out += (Integer) s;
+            }
+            Log.d(TAG, "SendLog: " + tag + ";" + out + ", will be sent after "
+                    + (- (now - mLastTimeSend - MINIMUMSENDINTERVAL) / 1000) + " sec.");
+        }
+        if (now - mLastTimeActive > MINIMUMSENDINTERVAL) {
+            // Send a log before adding an log entry if the last data is too old.
+            commitInternal();
+            addData(tag, s);
+        } else if (now - mLastTimeSend > MINIMUMSENDINTERVAL) {
+            // Send a log after adding an log entry.
+            addData(tag, s);
+            commitInternal();
+        } else {
+            addData(tag, s);
+        }
+        mLastTimeActive = now;
+    }
+
+    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
+        if (PREF_ENABLE_LOG.equals(key)) {
+            if (sharedPreferences.getBoolean(key, DEFAULT_LOG_ENABLED)) {
+                sLogEnabled = (mContext != null);
+            } else {
+                sLogEnabled = false;
+            }
+            if (sDebugKeyEnabler.check()) {
+                sharedPreferences.edit().putBoolean(PREF_DEBUG_MODE, true).commit();
+            }
+        } else if (KeyboardSwitcher.PREF_KEYBOARD_LAYOUT.equals(key)) {
+            mThemeId = sharedPreferences.getString(KeyboardSwitcher.PREF_KEYBOARD_LAYOUT,
+                    KeyboardSwitcher.DEFAULT_LAYOUT_ID);
+            addThemeIdEntry(mLastTimeActive);
+        } else if (PREF_DEBUG_MODE.equals(key)) {
+            sPRINTLOGGING = sharedPreferences.getBoolean(PREF_DEBUG_MODE, sPRINTLOGGING);
+            sDBG = sPRINTLOGGING;
+        } else if (LatinIME.PREF_INPUT_LANGUAGE.equals(key)) {
+            mCurrentLanguage = sharedPreferences.getString(LatinIME.PREF_INPUT_LANGUAGE, "");
+            addLanguagesEntry(mLastTimeActive);
+        } else if (LatinIME.PREF_INPUT_LANGUAGE.equals(key)) {
+            mSelectedLanguages = sharedPreferences.getString(LatinIME.PREF_SELECTED_LANGUAGES, "");
+        }
+    }
+
+    public static void init(Context context) {
+        sLatinImeLogger.initInternal(context);
+    }
+
+    public static void commit() {
+        if (sLogEnabled) {
+            if (System.currentTimeMillis() - sLatinImeLogger.mLastTimeActive > MINIMUMCOUNTINTERVAL
+                        || (sLatinImeLogger.mLogBuffer.size()
+                                + sLatinImeLogger.mPrivacyLogBuffer.size() > MINIMUMSENDSIZE)) {
+                sLatinImeLogger.commitInternal();
+            }
+        }
+    }
+
+    public static void onDestroy() {
+        sLatinImeLogger.commitInternal();
+        sLatinImeLogger.destroy();
+    }
+
+    // TODO: Handle CharSequence instead of String
+    public static void logOnManualSuggestion(String before, String after, int position
+            , List<CharSequence> suggestions) {
+        if (sLogEnabled) {
+            // log punctuation
+            if (before.length() == 0 && after.length() == 1) {
+                sLatinImeLogger.sendLogToDropBox(ID_MANUALSUGGESTION_WITH_DATATYPE, new String[] {
+                        before, after, String.valueOf(position), ""});
+            } else if (!sSuggestDicMap.containsKey(after)) {
+                if (sDBG) {
+                    Log.e(TAG, "logOnManualSuggestion was cancelled: from unknown dic.");
+                }
+            } else {
+                int dicTypeId = sSuggestDicMap.get(after).first;
+                sLatinImeLogger.mManualSuggestCountPerDic[dicTypeId]++;
+                if (dicTypeId != Suggest.DIC_MAIN) {
+                    if (sDBG) {
+                        Log.d(TAG, "logOnManualSuggestion was cancelled: not from main dic.");
+                    }
+                    before = "";
+                    after = "";
+                    sPreviousWords = null;
+                }
+                // TODO: Don't send a log if this doesn't come from Main Dictionary.
+                {
+                    if (before.equals(after)) {
+                        before = "";
+                        after = "";
+                    }
+
+                    /* Example:
+                     * When user typed "Illegal imm" and picked "immigrants",
+                     * the suggestion list has "immigrants, immediate, immigrant".
+                     * At this time, the log strings will be something like below:
+                     * strings[0 = COLUMN_BEFORE_ID] = imm
+                     * strings[1 = COLUMN_AFTER_ID] = immigrants
+                     * strings[2 = COLUMN_PICKED_POSITION_ID] = 0
+                     * strings[3 = COLUMN_SUGGESTION_LENGTH_ID] = 3
+                     * strings[4 = COLUMN_PREVIOUS_WORDS_COUNT_ID] = 1
+                     * strings[5] = immigrants
+                     * strings[6] = immediate
+                     * strings[7] = immigrant
+                     * strings[8] = 1 (= bigram)
+                     * strings[9] = 0 (= unigram)
+                     * strings[10] = 1 (= bigram)
+                     * strings[11] = Illegal
+                     */
+
+                    // 0 for unigram, 1 for bigram, 2 for trigram...
+                    int previousWordsLength = (sPreviousWords == null) ? 0 : sPreviousWords.length;
+                    int suggestionLength = suggestions.size();
+
+                    final int COLUMN_BEFORE_ID = 0;
+                    final int COLUMN_AFTER_ID = 1;
+                    final int COLUMN_PICKED_POSITION_ID = 2;
+                    final int COLUMN_SUGGESTION_LENGTH_ID = 3;
+                    final int COLUMN_PREVIOUS_WORDS_COUNT_ID = 4;
+                    final int BASE_COLUMN_SIZE = 5;
+
+                    String[] strings =
+                        new String[BASE_COLUMN_SIZE + suggestionLength * 2 + previousWordsLength];
+                    strings[COLUMN_BEFORE_ID] = before;
+                    strings[COLUMN_AFTER_ID] = after;
+                    strings[COLUMN_PICKED_POSITION_ID] = String.valueOf(position);
+                    strings[COLUMN_SUGGESTION_LENGTH_ID] = String.valueOf(suggestionLength);
+                    strings[COLUMN_PREVIOUS_WORDS_COUNT_ID] = String.valueOf(previousWordsLength);
+
+                    for (int i = 0; i < suggestionLength; ++i) {
+                        String s = suggestions.get(i).toString();
+                        if (sSuggestDicMap.containsKey(s)) {
+                            strings[BASE_COLUMN_SIZE + i] = s;
+                            strings[BASE_COLUMN_SIZE + suggestionLength + i]
+                                    = sSuggestDicMap.get(s).second.toString();
+                        } else {
+                            strings[BASE_COLUMN_SIZE + i] = "";
+                            strings[BASE_COLUMN_SIZE + suggestionLength + i] = "";
+                        }
+                    }
+
+                    for (int i = 0; i < previousWordsLength; ++i) {
+                        strings[BASE_COLUMN_SIZE + suggestionLength * 2 + i] = sPreviousWords[i];
+                    }
+
+                    sLatinImeLogger.sendLogToDropBox(ID_MANUALSUGGESTION_WITH_DATATYPE, strings);
+                }
+            }
+            sSuggestDicMap.clear();
+        }
+    }
+
+    public static void logOnAutoSuggestion(String before, String after) {
+        if (sLogEnabled) {
+            if (!sSuggestDicMap.containsKey(after)) {
+                if (sDBG) {
+                    Log.e(TAG, "logOnAutoSuggestion was cancelled: from unknown dic.");
+                }
+            } else {
+                String separator = String.valueOf(sLatinImeLogger.mRingCharBuffer.getLastChar());
+                sLastAutoSuggestDicTypeId = sSuggestDicMap.get(after).first;
+                sLastAutoSuggestDataType = sSuggestDicMap.get(after).second;
+                sLatinImeLogger.mAutoSuggestCountPerDic[sLastAutoSuggestDicTypeId]++;
+                if (sLastAutoSuggestDicTypeId != Suggest.DIC_MAIN) {
+                    if (sDBG) {
+                        Log.d(TAG, "logOnAutoSuggestion was cancelled: not from main dic.");
+                    }
+                    before = "";
+                    after = "";
+                    sPreviousWords = null;
+                }
+                // TODO: Not to send a log if this doesn't come from Main Dictionary.
+                {
+                    if (before.equals(after)) {
+                        before = "";
+                        after = "";
+                    }
+                    int previousWordsLength = (sPreviousWords == null) ? 0 : sPreviousWords.length;
+
+                    final int COLUMN_BEFORE_ID = 0;
+                    final int COLUMN_AFTER_ID = 1;
+                    final int COLUMN_SEPARATOR_ID = 2;
+                    final int COLUMN_DATA_TYPE_ID = 3;
+                    final int BASE_COLUMN_SIZE = 4;
+
+                    String[] strings = new String[4 + previousWordsLength];
+                    strings[COLUMN_BEFORE_ID] = before;
+                    strings[COLUMN_AFTER_ID] = after;
+                    strings[COLUMN_SEPARATOR_ID] = separator;
+                    strings[COLUMN_DATA_TYPE_ID] = String.valueOf(sLastAutoSuggestDataType);
+                    for (int i = 0; i < previousWordsLength; ++i) {
+                        strings[BASE_COLUMN_SIZE + i] = sPreviousWords[i];
+                    }
+                    sLatinImeLogger.sendLogToDropBox(ID_AUTOSUGGESTION, strings);
+                }
+                synchronized (LatinImeLogger.class) {
+                    sLastAutoSuggestBefore = before;
+                    sLastAutoSuggestAfter = after;
+                    sLastAutoSuggestSeparator = separator;
+                }
+            }
+            sSuggestDicMap.clear();
+        }
+    }
+
+    public static void logOnAutoSuggestionCanceled() {
+        if (sLogEnabled) {
+            sLatinImeLogger.mAutoCancelledCountPerDic[sLastAutoSuggestDicTypeId]++;
+            if (sLastAutoSuggestBefore != null && sLastAutoSuggestAfter != null) {
+                String[] strings = new String[] {
+                        sLastAutoSuggestBefore, sLastAutoSuggestAfter, sLastAutoSuggestSeparator};
+                sLatinImeLogger.sendLogToDropBox(ID_AUTOSUGGESTIONCANCELLED, strings);
+            }
+            synchronized (LatinImeLogger.class) {
+                sLastAutoSuggestBefore = "";
+                sLastAutoSuggestAfter = "";
+                sLastAutoSuggestSeparator = "";
+            }
+        }
+    }
+
+    public static void logOnDelete() {
+        if (sLogEnabled) {
+            String mLastWord = sLatinImeLogger.mRingCharBuffer.getLastString();
+            if (!TextUtils.isEmpty(mLastWord)
+                    && mLastWord.equalsIgnoreCase(sLastAutoSuggestBefore)) {
+                logOnAutoSuggestionCanceled();
+            }
+            sLatinImeLogger.mRingCharBuffer.pop();
+            sLatinImeLogger.sendLogToDropBox(ID_DELETE_COUNT, 1);
+        }
+    }
+
+    public static void logOnInputChar(char c) {
+        if (sLogEnabled) {
+            sLatinImeLogger.mRingCharBuffer.push(c);
+            sLatinImeLogger.sendLogToDropBox(ID_INPUT_COUNT, 1);
+        }
+    }
+
+    public static void logOnException(String metaData, Throwable e) {
+        if (sLogEnabled) {
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            PrintStream ps = new PrintStream(baos);
+            e.printStackTrace(ps);
+            String exceptionString = URLEncoder.encode(new String(baos.toByteArray(), 0,
+                    Math.min(EXCEPTION_MAX_LENGTH, baos.size())));
+            sLatinImeLogger.sendLogToDropBox(
+                    ID_EXCEPTION, new String[] {metaData, exceptionString});
+            if (sDBG) {
+                Log.e(TAG, "Exception: " + new String(baos.toByteArray())+ ":" + exceptionString);
+            }
+            if (SUPPRESS_EXCEPTION) {
+                sLatinImeLogger.commitInternalAndStopSelf();
+            } else {
+                sLatinImeLogger.commitInternal();
+                if (e instanceof RuntimeException) {
+                    throw (RuntimeException) e;
+                } else if (e instanceof Error) {
+                    throw (Error) e;
+                }
+            }
+        }
+    }
+
+    public static void logOnWarning(String warning) {
+        if (sLogEnabled) {
+            sLatinImeLogger.sendLogToDropBox(
+                    ID_EXCEPTION, new String[] {warning, ""});
+        }
+    }
+
+    // TODO: This code supports only Bigram.
+    public static void onStartSuggestion(CharSequence previousWords) {
+        if (sLogEnabled) {
+            sSuggestDicMap.clear();
+            sPreviousWords = new String[] {
+                    (previousWords == null) ? "" : previousWords.toString()};
+        }
+    }
+
+    public static void onAddSuggestedWord(String word, int typeId, DataType dataType) {
+        if (sLogEnabled) {
+            sSuggestDicMap.put(word, new Pair<Integer, Integer>(typeId, dataType.ordinal()));
+        }
+    }
+
+    private static class LogSerializer {
+        private static void appendWithLength(StringBuffer sb, String data) {
+            sb.append(data.length());
+            sb.append(SEPARATER);
+            sb.append(data);
+            sb.append(SEPARATER);
+        }
+
+        private static void appendLogEntry(StringBuffer sb, String time, String tag,
+                String[] data) {
+            if (data.length > 0) {
+                appendWithLength(sb, String.valueOf(data.length + 2));
+                appendWithLength(sb, time);
+                appendWithLength(sb, tag);
+                for (String s: data) {
+                    appendWithLength(sb, s);
+                }
+            }
+        }
+
+        public static String createStringFromEntries(ArrayList<LogEntry> logs) {
+            StringBuffer sb = new StringBuffer();
+            for (LogEntry log: logs) {
+                appendLogEntry(sb, String.valueOf(log.mTime), String.valueOf(log.mTag), log.mData);
+            }
+            return sb.toString();
+        }
+    }
+
+    /* package */ static class RingCharBuffer {
+        final int BUFSIZE = 20;
+        private Context mContext;
+        private int mEnd = 0;
+        /* package */ int length = 0;
+        private char[] mCharBuf = new char[BUFSIZE];
+
+        public RingCharBuffer(Context context) {
+            mContext = context;
+        }
+
+        private int normalize(int in) {
+            int ret = in % BUFSIZE;
+            return ret < 0 ? ret + BUFSIZE : ret;
+        }
+        public void push(char c) {
+            mCharBuf[mEnd] = c;
+            mEnd = normalize(mEnd + 1);
+            if (length < BUFSIZE) {
+                ++length;
+            }
+        }
+        public char pop() {
+            if (length < 1) {
+                return NULL_CHAR;
+            } else {
+                mEnd = normalize(mEnd - 1);
+                --length;
+                return mCharBuf[mEnd];
+            }
+        }
+        public char getLastChar() {
+            if (length < 1) {
+                return NULL_CHAR;
+            } else {
+                return mCharBuf[normalize(mEnd - 1)];
+            }
+        }
+        public String getLastString() {
+            StringBuffer sb = new StringBuffer();
+            for (int i = 0; i < length; ++i) {
+                char c = mCharBuf[normalize(mEnd - 1 - i)];
+                if (!((LatinIME)mContext).isWordSeparator(c)) {
+                    sb.append(c);
+                } else {
+                    break;
+                }
+            }
+            return sb.reverse().toString();
+        }
+        public void reset() {
+            length = 0;
+        }
+    }
+
+    private static class DebugKeyEnabler {
+        private int mCounter = 0;
+        private long mLastTime = 0;
+        public boolean check() {
+            if (System.currentTimeMillis() - mLastTime > 10 * 1000) {
+                mCounter = 0;
+                mLastTime = System.currentTimeMillis();
+            } else if (++mCounter >= 10) {
+                return true;
+            }
+            return false;
+        }
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/LatinKeyboard.java b/java/src/com/android/inputmethod/latin/LatinKeyboard.java
index 6aea5d1..db4d167 100644
--- a/java/src/com/android/inputmethod/latin/LatinKeyboard.java
+++ b/java/src/com/android/inputmethod/latin/LatinKeyboard.java
@@ -47,7 +47,6 @@
     private Drawable mShiftLockIcon;
     private Drawable mShiftLockPreviewIcon;
     private Drawable mOldShiftIcon;
-    private Drawable mOldShiftPreviewIcon;
     private Drawable mSpaceIcon;
     private Drawable mSpacePreviewIcon;
     private Drawable mMicIcon;
@@ -68,7 +67,6 @@
     private LanguageSwitcher mLanguageSwitcher;
     private Resources mRes;
     private Context mContext;
-    private int mMode;
     // Whether this keyboard has voice icon on it
     private boolean mHasVoiceButton;
     // Whether voice icon is enabled at all
@@ -77,16 +75,16 @@
     private CharSequence m123Label;
     private boolean mCurrentlyInSpace;
     private SlidingLocaleDrawable mSlidingLocaleIcon;
-    private Rect mBounds = new Rect();
     private int[] mPrefLetterFrequencies;
-    private boolean mPreemptiveCorrection;
     private int mPrefLetter;
     private int mPrefLetterX;
     private int mPrefLetterY;
     private int mPrefDistance;
 
-    private int mExtensionResId; 
-    
+    private int mExtensionResId;
+    // TODO: generalize for any keyboardId
+    private boolean mIsBlackSym;
+
     private static final int SHIFT_OFF = 0;
     private static final int SHIFT_ON = 1;
     private static final int SHIFT_LOCKED = 2;
@@ -107,7 +105,6 @@
         super(context, xmlLayoutResId, mode);
         final Resources res = context.getResources();
         mContext = context;
-        mMode = mode;
         mRes = res;
         mShiftLockIcon = res.getDrawable(R.drawable.sym_keyboard_shift_locked);
         mShiftLockPreviewIcon = res.getDrawable(R.drawable.sym_keyboard_feedback_shift_locked);
@@ -126,7 +123,8 @@
         setDefaultBounds(m123MicPreviewIcon);
         sSpacebarVerticalCorrection = res.getDimensionPixelOffset(
                 R.dimen.spacebar_vertical_correction);
-        mIsAlphaKeyboard = xmlLayoutResId == R.xml.kbd_qwerty;
+        mIsAlphaKeyboard = xmlLayoutResId == R.xml.kbd_qwerty
+                || xmlLayoutResId == R.xml.kbd_qwerty_black;
         mSpaceKeyIndex = indexOf((int) ' ');
     }
 
@@ -182,8 +180,8 @@
                 case EditorInfo.IME_ACTION_SEARCH:
                     mEnterKey.iconPreview = res.getDrawable(
                             R.drawable.sym_keyboard_feedback_search);
-                    mEnterKey.icon = res.getDrawable(
-                            R.drawable.sym_keyboard_search);
+                    mEnterKey.icon = res.getDrawable(mIsBlackSym ?
+                            R.drawable.sym_bkeyboard_search : R.drawable.sym_keyboard_search);
                     mEnterKey.label = null;
                     break;
                 case EditorInfo.IME_ACTION_SEND:
@@ -201,8 +199,8 @@
                     } else {
                         mEnterKey.iconPreview = res.getDrawable(
                                 R.drawable.sym_keyboard_feedback_return);
-                        mEnterKey.icon = res.getDrawable(
-                                R.drawable.sym_keyboard_return);
+                        mEnterKey.icon = res.getDrawable(mIsBlackSym ?
+                                R.drawable.sym_bkeyboard_return : R.drawable.sym_keyboard_return);
                         mEnterKey.label = null;
                     }
                     break;
@@ -224,7 +222,6 @@
                 ((LatinKey)mShiftKey).enableShiftLock();
             }
             mOldShiftIcon = mShiftKey.icon;
-            mOldShiftPreviewIcon = mShiftKey.iconPreview;
         }
     }
 
@@ -277,6 +274,10 @@
         }
     }
 
+    /* package */ boolean isAlphaKeyboard() {
+        return mIsAlphaKeyboard;
+    }
+
     public void setExtension(int resId) {
         mExtensionResId = resId;
     }
@@ -285,6 +286,26 @@
         return mExtensionResId;
     }
 
+    public void setBlackFlag(boolean f) {
+        mIsBlackSym = f;
+        if (f) {
+            mShiftLockIcon = mRes.getDrawable(R.drawable.sym_bkeyboard_shift_locked);
+            mSpaceIcon = mRes.getDrawable(R.drawable.sym_bkeyboard_space);
+            mMicIcon = mRes.getDrawable(R.drawable.sym_bkeyboard_mic);
+            m123MicIcon = mRes.getDrawable(R.drawable.sym_bkeyboard_123_mic);
+        } else {
+            mShiftLockIcon = mRes.getDrawable(R.drawable.sym_keyboard_shift_locked);
+            mSpaceIcon = mRes.getDrawable(R.drawable.sym_keyboard_space);
+            mMicIcon = mRes.getDrawable(R.drawable.sym_keyboard_mic);
+            m123MicIcon = mRes.getDrawable(R.drawable.sym_keyboard_123_mic);
+        }
+        updateF1Key();
+        if (mSpaceKey != null) {
+            mSpaceKey.icon = mSpaceIcon;
+            updateSpaceBarForLocale(f);
+        }
+    }
+
     private void setDefaultBounds(Drawable drawable) {
         drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
     }
@@ -322,39 +343,40 @@
         }
     }
 
-    private void updateSpaceBarForLocale() {
+    private void updateSpaceBarForLocale(boolean isBlack) {
         if (mLocale != null) {
             // Create the graphic for spacebar
             Bitmap buffer = Bitmap.createBitmap(mSpaceKey.width, mSpaceIcon.getIntrinsicHeight(),
                     Bitmap.Config.ARGB_8888);
             Canvas canvas = new Canvas(buffer);
-            drawSpaceBar(canvas, buffer.getWidth(), buffer.getHeight(), 255);
+            drawSpaceBar(canvas, buffer.getWidth(), buffer.getHeight(), 255, isBlack);
             mSpaceKey.icon = new BitmapDrawable(mRes, buffer);
             mSpaceKey.repeatable = mLanguageSwitcher.getLocaleCount() < 2;
         } else {
-            mSpaceKey.icon = mRes.getDrawable(R.drawable.sym_keyboard_space);
+            mSpaceKey.icon = isBlack ? mRes.getDrawable(R.drawable.sym_bkeyboard_space)
+                : mRes.getDrawable(R.drawable.sym_keyboard_space);
             mSpaceKey.repeatable = true;
         }
     }
 
-    private void drawSpaceBar(Canvas canvas, int width, int height, int opacity) {
-        canvas.drawColor(0x00000000, PorterDuff.Mode.CLEAR);
+    private void drawSpaceBar(Canvas canvas, int width, int height, int opacity, boolean isBlack) {
+        canvas.drawColor(mRes.getColor(R.color.latinkeyboard_transparent), PorterDuff.Mode.CLEAR);
         Paint paint = new Paint();
         paint.setAntiAlias(true);
         paint.setAlpha(opacity);
         // Get the text size from the theme
         paint.setTextSize(getTextSizeFromTheme(android.R.style.TextAppearance_Small, 14));
         paint.setTextAlign(Align.CENTER);
-        //// Draw a drop shadow for the text
-        //paint.setShadowLayer(2f, 0, 0, 0xFF000000);
         final String language = getInputLanguage(mSpaceKey.width, paint);
         final int ascent = (int) -paint.ascent();
-        paint.setColor(0x80000000);
-        canvas.drawText(language,
-                width / 2, ascent - 1, paint);
-        paint.setColor(0xFF808080);
-        canvas.drawText(language,
-                width / 2, ascent, paint);
+
+        int shadowColor = isBlack ? mRes.getColor(R.color.latinkeyboard_bar_language_shadow_black)
+                : mRes.getColor(R.color.latinkeyboard_bar_language_shadow_white);
+
+        paint.setColor(shadowColor);
+        canvas.drawText(language, width / 2, ascent - 1, paint);
+        paint.setColor(mRes.getColor(R.color.latinkeyboard_bar_language_text));
+        canvas.drawText(language, width / 2, ascent, paint);
         // Put arrows on either side of the text
         if (mLanguageSwitcher.getLocaleCount() > 1) {
             Rect bounds = new Rect();
@@ -439,7 +461,7 @@
         }
         if (mLocale != null && mLocale.equals(locale)) return;
         mLocale = locale;
-        updateSpaceBarForLocale();
+        updateSpaceBarForLocale(mIsBlackSym);
     }
 
     boolean isCurrentlyInSpace() {
@@ -503,9 +525,10 @@
             // Handle preferred next letter
             final int[] pref = mPrefLetterFrequencies;
             if (mPrefLetter > 0) {
-                if (DEBUG_PREFERRED_LETTER && mPrefLetter == code
-                        && !key.isInsideSuper(x, y)) {
-                    Log.d(TAG, "CORRECTED !!!!!!");
+                if (DEBUG_PREFERRED_LETTER) {
+                    if (mPrefLetter == code && !key.isInsideSuper(x, y)) {
+                        Log.d(TAG, "CORRECTED !!!!!!");
+                    }
                 }
                 return mPrefLetter == code;
             } else {
@@ -684,7 +707,7 @@
             mTextPaint = new TextPaint();
             int textSize = getTextSizeFromTheme(android.R.style.TextAppearance_Medium, 18);
             mTextPaint.setTextSize(textSize);
-            mTextPaint.setColor(0);
+            mTextPaint.setColor(R.color.latinkeyboard_transparent);
             mTextPaint.setTextAlign(Align.CENTER);
             mTextPaint.setAlpha(255);
             mTextPaint.setAntiAlias(true);
@@ -718,7 +741,7 @@
         public void draw(Canvas canvas) {
             canvas.save();
             if (mHitThreshold) {
-                mTextPaint.setColor(0xFF000000);
+                mTextPaint.setColor(mRes.getColor(R.color.latinkeyboard_text_color));
                 canvas.clipRect(0, 0, mWidth, mHeight);
                 if (mCurrentLanguage == null) {
                     mCurrentLanguage = getInputLanguage(mWidth, mTextPaint);
diff --git a/java/src/com/android/inputmethod/latin/LatinKeyboardBaseView.java b/java/src/com/android/inputmethod/latin/LatinKeyboardBaseView.java
new file mode 100644
index 0000000..665c641
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/LatinKeyboardBaseView.java
@@ -0,0 +1,1633 @@
+/*
+ * Copyright (C) 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.
+ */
+
+package com.android.inputmethod.latin;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.Rect;
+import android.graphics.Typeface;
+import android.graphics.Paint.Align;
+import android.graphics.Region.Op;
+import android.graphics.drawable.Drawable;
+import android.inputmethodservice.Keyboard;
+import android.inputmethodservice.Keyboard.Key;
+import android.os.Handler;
+import android.os.Message;
+import android.util.AttributeSet;
+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.ViewConfiguration;
+import android.view.ViewGroup.LayoutParams;
+import android.widget.PopupWindow;
+import android.widget.TextView;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A view that renders a virtual {@link LatinKeyboard}. It handles rendering of keys and
+ * detecting key presses and touch movements.
+ *
+ * @attr ref R.styleable#LatinKeyboardBaseView_keyBackground
+ * @attr ref R.styleable#LatinKeyboardBaseView_keyPreviewLayout
+ * @attr ref R.styleable#LatinKeyboardBaseView_keyPreviewOffset
+ * @attr ref R.styleable#LatinKeyboardBaseView_labelTextSize
+ * @attr ref R.styleable#LatinKeyboardBaseView_keyTextSize
+ * @attr ref R.styleable#LatinKeyboardBaseView_keyTextColor
+ * @attr ref R.styleable#LatinKeyboardBaseView_verticalCorrection
+ * @attr ref R.styleable#LatinKeyboardBaseView_popupLayout
+ */
+public class LatinKeyboardBaseView extends View implements View.OnClickListener {
+
+    public interface OnKeyboardActionListener {
+
+        /**
+         * Called when the user presses a key. This is sent before the
+         * {@link #onKey} is called. For keys that repeat, this is only
+         * called once.
+         *
+         * @param primaryCode
+         *            the unicode of the key being pressed. If the touch is
+         *            not on a valid key, the value will be zero.
+         */
+        void onPress(int primaryCode);
+
+        /**
+         * Called when the user releases a key. This is sent after the
+         * {@link #onKey} is called. For keys that repeat, this is only
+         * called once.
+         *
+         * @param primaryCode
+         *            the code of the key that was released
+         */
+        void onRelease(int primaryCode);
+
+        /**
+         * Send a key press to the listener.
+         *
+         * @param primaryCode
+         *            this is the key that was pressed
+         * @param keyCodes
+         *            the codes for all the possible alternative keys with
+         *            the primary code being the first. If the primary key
+         *            code is a single character such as an alphabet or
+         *            number or symbol, the alternatives will include other
+         *            characters that may be on the same key or adjacent
+         *            keys. These codes are useful to correct for
+         *            accidental presses of a key adjacent to the intended
+         *            key.
+         */
+        void onKey(int primaryCode, int[] keyCodes);
+
+        /**
+         * Sends a sequence of characters to the listener.
+         *
+         * @param text
+         *            the sequence of characters to be displayed.
+         */
+        void onText(CharSequence text);
+
+        /**
+         * Called when the user quickly moves the finger from right to
+         * left.
+         */
+        void swipeLeft();
+
+        /**
+         * Called when the user quickly moves the finger from left to
+         * right.
+         */
+        void swipeRight();
+
+        /**
+         * Called when the user quickly moves the finger from up to down.
+         */
+        void swipeDown();
+
+        /**
+         * Called when the user quickly moves the finger from down to up.
+         */
+        void swipeUp();
+    }
+
+    private static final boolean DEBUG = false;
+    private static final int NOT_A_KEY = -1;
+    private static final int[] KEY_DELETE = { Keyboard.KEYCODE_DELETE };
+    private static final int[] LONG_PRESSABLE_STATE_SET = { android.R.attr.state_long_pressable };
+
+    private Keyboard mKeyboard;
+    private int mCurrentKeyIndex = NOT_A_KEY;
+    private int mLabelTextSize;
+    private int mKeyTextSize;
+    private int mKeyTextColor;
+    private float mShadowRadius;
+    private int mShadowColor;
+    private float mBackgroundDimAmount;
+
+    private TextView mPreviewText;
+    private PopupWindow mPreviewPopup;
+    private int mPreviewTextSizeLarge;
+    private int mPreviewOffset;
+    private int mPreviewHeight;
+    private int[] mOffsetInWindow;
+
+    private PopupWindow mPopupKeyboard;
+    private View mMiniKeyboardContainer;
+    private LatinKeyboardBaseView mMiniKeyboard;
+    private boolean mMiniKeyboardOnScreen;
+    private View mPopupParent;
+    private int mMiniKeyboardOffsetX;
+    private int mMiniKeyboardOffsetY;
+    private Map<Key,View> mMiniKeyboardCache;
+    private int[] mWindowOffset;
+    private Key[] mKeys;
+    private Typeface mKeyTextStyle = Typeface.DEFAULT;
+    private int mSymbolColorScheme = 0;
+
+    /** Listener for {@link OnKeyboardActionListener}. */
+    private OnKeyboardActionListener mKeyboardActionListener;
+
+    private static final int DELAY_BEFORE_PREVIEW = 0;
+    private static final int DELAY_AFTER_PREVIEW = 70;
+    private static final int DEBOUNCE_TIME = 70;
+
+    private int mVerticalCorrection;
+    private int mProximityThreshold;
+
+    private boolean mPreviewCentered = false;
+    private boolean mShowPreview = true;
+    private boolean mShowTouchPoints = true;
+    private int mPopupPreviewX;
+    private int mPopupPreviewY;
+    private int mWindowY;
+
+    private boolean mProximityCorrectOn;
+
+    private Paint mPaint;
+    private Rect mPadding;
+
+    private int mCurrentKey = NOT_A_KEY;
+    private int mDownKey = NOT_A_KEY;
+    private int mStartX;
+    private int mStartY;
+
+    private KeyDebouncer mDebouncer;
+
+    private GestureDetector mGestureDetector;
+    private int mPopupX;
+    private int mPopupY;
+    private int mRepeatKeyIndex = NOT_A_KEY;
+    private int mPopupLayout;
+    private boolean mAbortKey;
+    private Key mInvalidatedKey;
+    private Rect mClipRegion = new Rect(0, 0, 0, 0);
+    private boolean mPossiblePoly;
+    private SwipeTracker mSwipeTracker = new SwipeTracker();
+    private int mSwipeThreshold;
+    private boolean mDisambiguateSwipe;
+
+    // Variables for dealing with multiple pointers
+    private int mOldPointerCount = 1;
+    private float mOldPointerX;
+    private float mOldPointerY;
+
+    private Drawable mKeyBackground;
+
+    private static final int REPEAT_INTERVAL = 50; // ~20 keys per second
+    private static final int REPEAT_START_DELAY = 400;
+    private static final int LONGPRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout();
+
+    private static int MAX_NEARBY_KEYS = 12;
+    private int[] mDistances = new int[MAX_NEARBY_KEYS];
+
+    // For multi-tap
+    private int mLastSentIndex;
+    private int mTapCount;
+    private long mLastTapTime;
+    private boolean mInMultiTap;
+    private static final int MULTITAP_INTERVAL = 800; // milliseconds
+    private StringBuilder mPreviewLabel = new StringBuilder(1);
+
+    /** Whether the keyboard bitmap needs to be redrawn before it's blitted. **/
+    private boolean mDrawPending;
+    /** The dirty region in the keyboard bitmap */
+    private Rect mDirtyRect = new Rect();
+    /** The keyboard bitmap for faster updates */
+    private Bitmap mBuffer;
+    /** Notes if the keyboard just changed, so that we could possibly reallocate the mBuffer. */
+    private boolean mKeyboardChanged;
+    /** The canvas for the above mutable keyboard bitmap */
+    private Canvas mCanvas;
+
+    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_REPEAT_KEY = 3;
+        private static final int MSG_LOGPRESS_KEY = 4;
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_POPUP_PREVIEW:
+                    showKey(msg.arg1);
+                    break;
+                case MSG_DISMISS_PREVIEW:
+                    mPreviewText.setVisibility(INVISIBLE);
+                    break;
+                case MSG_REPEAT_KEY:
+                    if (repeatKey()) {
+                        startKeyRepeatTimer(REPEAT_INTERVAL);
+                    }
+                    break;
+                case MSG_LOGPRESS_KEY:
+                    openPopupIfRequired((MotionEvent) msg.obj);
+                    break;
+            }
+        }
+
+        public void popupPreview(int keyIndex, long delay) {
+            removeMessages(MSG_POPUP_PREVIEW);
+            sendMessageDelayed(obtainMessage(MSG_POPUP_PREVIEW, keyIndex, 0), delay);
+        }
+
+        public void cancelPopupPreview() {
+            removeMessages(MSG_POPUP_PREVIEW);
+        }
+
+        public void dismissPreview(long delay) {
+            sendMessageDelayed(obtainMessage(MSG_DISMISS_PREVIEW), delay);
+        }
+
+        public void cancelDismissPreview() {
+            removeMessages(MSG_DISMISS_PREVIEW);
+        }
+
+        public void startKeyRepeatTimer(long delay) {
+            sendMessageDelayed(obtainMessage(MSG_REPEAT_KEY), delay);
+        }
+
+        public void startLongPressTimer(MotionEvent me, long delay) {
+            sendMessageDelayed(obtainMessage(MSG_LOGPRESS_KEY, me), delay);
+        }
+
+        public void cancelLongPressTimer() {
+            removeMessages(MSG_LOGPRESS_KEY);
+        }
+
+        public void cancelKeyTimers() {
+            removeMessages(MSG_REPEAT_KEY);
+            removeMessages(MSG_LOGPRESS_KEY);
+        }
+
+        public void cancelKeyTimersAndPopupPreview() {
+            removeMessages(MSG_REPEAT_KEY);
+            removeMessages(MSG_LOGPRESS_KEY);
+            removeMessages(MSG_POPUP_PREVIEW);
+        }
+
+        public void cancelAllMessages() {
+            removeMessages(MSG_REPEAT_KEY);
+            removeMessages(MSG_LOGPRESS_KEY);
+            removeMessages(MSG_POPUP_PREVIEW);
+            removeMessages(MSG_DISMISS_PREVIEW);
+        }
+    };
+
+    static class KeyDebouncer {
+        private final Key[] mKeys;
+        private final int mKeyDebounceThresholdSquared;
+
+        // for move de-bouncing
+        private int mLastCodeX;
+        private int mLastCodeY;
+        private int mLastX;
+        private int mLastY;
+
+        // for time de-bouncing
+        private int mLastKey;
+        private long mLastKeyTime;
+        private long mLastMoveTime;
+        private long mCurrentKeyTime;
+
+        KeyDebouncer(Key[] keys, float hysteresisPixel) {
+            if (keys == null || hysteresisPixel < 1.0f)
+                throw new IllegalArgumentException();
+            mKeys = keys;
+            mKeyDebounceThresholdSquared = (int)(hysteresisPixel * hysteresisPixel);
+        }
+
+        public int getLastCodeX() {
+            return mLastCodeX;
+        }
+
+        public int getLastCodeY() {
+            return mLastCodeY;
+        }
+
+        public int getLastX() {
+            return mLastX;
+        }
+
+        public int getLastY() {
+            return mLastY;
+        }
+
+        public int getLastKey() {
+            return mLastKey;
+        }
+
+        public void startMoveDebouncing(int x, int y) {
+            mLastCodeX = x;
+            mLastCodeY = y;
+        }
+
+        public void updateMoveDebouncing(int x, int y) {
+            mLastX = x;
+            mLastY = y;
+        }
+
+        public void resetMoveDebouncing() {
+            mLastCodeX = mLastX;
+            mLastCodeY = mLastY;
+        }
+
+        public boolean isMinorMoveBounce(int x, int y, int newKey, int curKey) {
+            if (newKey == curKey) {
+                return true;
+            } else if (curKey >= 0 && curKey < mKeys.length) {
+                return getSquareDistanceToKeyEdge(x, y, mKeys[curKey])
+                        < mKeyDebounceThresholdSquared;
+            } else {
+                return false;
+            }
+        }
+
+        private static int getSquareDistanceToKeyEdge(int x, int y, Key key) {
+            final int left = key.x;
+            final int right = key.x + key.width;
+            final int top = key.y;
+            final int bottom = key.y + key.height;
+            final int edgeX = x < left ? left : (x > right ? right : x);
+            final int edgeY = y < top ? top : (y > bottom ? bottom : y);
+            final int dx = x - edgeX;
+            final int dy = y - edgeY;
+            return dx * dx + dy * dy;
+        }
+
+        public void startTimeDebouncing(long eventTime) {
+            mLastKey = NOT_A_KEY;
+            mLastKeyTime = 0;
+            mCurrentKeyTime = 0;
+            mLastMoveTime = eventTime;
+        }
+
+        public void updateTimeDebouncing(long eventTime) {
+            mCurrentKeyTime += eventTime - mLastMoveTime;
+            mLastMoveTime = eventTime;
+        }
+
+        public void resetTimeDebouncing(long eventTime, int currentKey) {
+            mLastKey = currentKey;
+            mLastKeyTime = mCurrentKeyTime + eventTime - mLastMoveTime;
+            mCurrentKeyTime = 0;
+            mLastMoveTime = eventTime;
+        }
+
+        public boolean isMinorTimeBounce() {
+            return mCurrentKeyTime < mLastKeyTime && mCurrentKeyTime < DEBOUNCE_TIME
+                && mLastKey != NOT_A_KEY;
+        }
+    }
+
+    public LatinKeyboardBaseView(Context context, AttributeSet attrs) {
+        this(context, attrs, R.attr.keyboardViewStyle);
+    }
+
+    public LatinKeyboardBaseView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+
+        TypedArray a = context.obtainStyledAttributes(
+                attrs, R.styleable.LatinKeyboardBaseView, defStyle, R.style.LatinKeyboardBaseView);
+        LayoutInflater inflate =
+                (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        int previewLayout = 0;
+        int keyTextSize = 0;
+
+        int n = a.getIndexCount();
+
+        for (int i = 0; i < n; i++) {
+            int attr = a.getIndex(i);
+
+            switch (attr) {
+            case R.styleable.LatinKeyboardBaseView_keyBackground:
+                mKeyBackground = a.getDrawable(attr);
+                break;
+            case R.styleable.LatinKeyboardBaseView_verticalCorrection:
+                mVerticalCorrection = a.getDimensionPixelOffset(attr, 0);
+                break;
+            case R.styleable.LatinKeyboardBaseView_keyPreviewLayout:
+                previewLayout = a.getResourceId(attr, 0);
+                break;
+            case R.styleable.LatinKeyboardBaseView_keyPreviewOffset:
+                mPreviewOffset = a.getDimensionPixelOffset(attr, 0);
+                break;
+            case R.styleable.LatinKeyboardBaseView_keyPreviewHeight:
+                mPreviewHeight = a.getDimensionPixelSize(attr, 80);
+                break;
+            case R.styleable.LatinKeyboardBaseView_keyTextSize:
+                mKeyTextSize = a.getDimensionPixelSize(attr, 18);
+                break;
+            case R.styleable.LatinKeyboardBaseView_keyTextColor:
+                mKeyTextColor = a.getColor(attr, 0xFF000000);
+                break;
+            case R.styleable.LatinKeyboardBaseView_labelTextSize:
+                mLabelTextSize = a.getDimensionPixelSize(attr, 14);
+                break;
+            case R.styleable.LatinKeyboardBaseView_popupLayout:
+                mPopupLayout = a.getResourceId(attr, 0);
+                break;
+            case R.styleable.LatinKeyboardBaseView_shadowColor:
+                mShadowColor = a.getColor(attr, 0);
+                break;
+            case R.styleable.LatinKeyboardBaseView_shadowRadius:
+                mShadowRadius = a.getFloat(attr, 0f);
+                break;
+            // TODO: Use Theme (android.R.styleable.Theme_backgroundDimAmount)
+            case R.styleable.LatinKeyboardBaseView_backgroundDimAmount:
+                mBackgroundDimAmount = a.getFloat(attr, 0.5f);
+                break;
+            //case android.R.styleable.
+            case R.styleable.LatinKeyboardBaseView_keyTextStyle:
+                int textStyle = a.getInt(attr, 0);
+                switch (textStyle) {
+                    case 0:
+                        mKeyTextStyle = Typeface.DEFAULT;
+                        break;
+                    case 1:
+                        mKeyTextStyle = Typeface.DEFAULT_BOLD;
+                        break;
+                    default:
+                        mKeyTextStyle = Typeface.defaultFromStyle(textStyle);
+                        break;
+                }
+                break;
+            case R.styleable.LatinKeyboardBaseView_symbolColorScheme:
+                mSymbolColorScheme = a.getInt(attr, 0);
+                break;
+            }
+        }
+
+        mPreviewPopup = new PopupWindow(context);
+        if (previewLayout != 0) {
+            mPreviewText = (TextView) inflate.inflate(previewLayout, null);
+            mPreviewTextSizeLarge = (int) mPreviewText.getTextSize();
+            mPreviewPopup.setContentView(mPreviewText);
+            mPreviewPopup.setBackgroundDrawable(null);
+        } else {
+            mShowPreview = false;
+        }
+
+        mPreviewPopup.setTouchable(false);
+
+        mPopupKeyboard = new PopupWindow(context);
+        mPopupKeyboard.setBackgroundDrawable(null);
+        //mPopupKeyboard.setClippingEnabled(false);
+
+        mPopupParent = this;
+        //mPredicting = true;
+
+        mPaint = new Paint();
+        mPaint.setAntiAlias(true);
+        mPaint.setTextSize(keyTextSize);
+        mPaint.setTextAlign(Align.CENTER);
+        mPaint.setAlpha(255);
+
+        mPadding = new Rect(0, 0, 0, 0);
+        mMiniKeyboardCache = new HashMap<Key,View>();
+        mKeyBackground.getPadding(mPadding);
+
+        mSwipeThreshold = (int) (500 * getResources().getDisplayMetrics().density);
+        // TODO: Refer frameworks/base/core/res/res/values/config.xml
+        mDisambiguateSwipe = getResources().getBoolean(R.bool.config_swipeDisambiguation);
+        resetMultiTap();
+        initGestureDetector();
+    }
+
+    private void initGestureDetector() {
+        mGestureDetector = new GestureDetector(
+                getContext(), new GestureDetector.SimpleOnGestureListener() {
+            @Override
+            public boolean onFling(MotionEvent me1, MotionEvent me2,
+                    float velocityX, float velocityY) {
+                if (mPossiblePoly) return false;
+                final float absX = Math.abs(velocityX);
+                final float absY = Math.abs(velocityY);
+                float deltaX = me2.getX() - me1.getX();
+                float deltaY = me2.getY() - me1.getY();
+                int travelX = getWidth() / 2; // Half the keyboard width
+                int travelY = getHeight() / 2; // Half the keyboard height
+                mSwipeTracker.computeCurrentVelocity(1000);
+                final float endingVelocityX = mSwipeTracker.getXVelocity();
+                final float endingVelocityY = mSwipeTracker.getYVelocity();
+                boolean sendDownKey = false;
+                if (velocityX > mSwipeThreshold && absY < absX && deltaX > travelX) {
+                    if (mDisambiguateSwipe && endingVelocityX < velocityX / 4) {
+                        sendDownKey = true;
+                    } else {
+                        swipeRight();
+                        return true;
+                    }
+                } else if (velocityX < -mSwipeThreshold && absY < absX && deltaX < -travelX) {
+                    if (mDisambiguateSwipe && endingVelocityX > velocityX / 4) {
+                        sendDownKey = true;
+                    } else {
+                        swipeLeft();
+                        return true;
+                    }
+                } else if (velocityY < -mSwipeThreshold && absX < absY && deltaY < -travelY) {
+                    if (mDisambiguateSwipe && endingVelocityY > velocityY / 4) {
+                        sendDownKey = true;
+                    } else {
+                        swipeUp();
+                        return true;
+                    }
+                } else if (velocityY > mSwipeThreshold && absX < absY / 2 && deltaY > travelY) {
+                    if (mDisambiguateSwipe && endingVelocityY < velocityY / 4) {
+                        sendDownKey = true;
+                    } else {
+                        swipeDown();
+                        return true;
+                    }
+                }
+
+                if (sendDownKey) {
+                    detectAndSendKey(mDownKey, mStartX, mStartY, me1.getEventTime());
+                }
+                return false;
+            }
+        });
+
+        mGestureDetector.setIsLongpressEnabled(false);
+    }
+
+    public void setOnKeyboardActionListener(OnKeyboardActionListener listener) {
+        mKeyboardActionListener = listener;
+    }
+
+    /**
+     * Returns the {@link OnKeyboardActionListener} object.
+     * @return the listener attached to this keyboard
+     */
+    protected OnKeyboardActionListener getOnKeyboardActionListener() {
+        return mKeyboardActionListener;
+    }
+
+    /**
+     * Attaches a keyboard to this view. The keyboard can be switched at any time and the
+     * view will re-layout itself to accommodate the keyboard.
+     * @see Keyboard
+     * @see #getKeyboard()
+     * @param keyboard the keyboard to display in this view
+     */
+    public void setKeyboard(Keyboard keyboard) {
+        if (mKeyboard != null) {
+            showPreview(NOT_A_KEY);
+        }
+        // Remove any pending messages, except dismissing preview
+        mHandler.cancelKeyTimersAndPopupPreview();
+        mKeyboard = keyboard;
+        List<Key> keys = mKeyboard.getKeys();
+        mKeys = keys.toArray(new Key[keys.size()]);
+        requestLayout();
+        // Hint to reallocate the buffer if the size changed
+        mKeyboardChanged = true;
+        invalidateAllKeys();
+        computeProximityThreshold(keyboard);
+        mMiniKeyboardCache.clear();
+        // Not really necessary to do every time, but will free up views
+        // Switching to a different keyboard should abort any pending keys so that the key up
+        // doesn't get delivered to the old or new keyboard
+        mAbortKey = true; // Until the next ACTION_DOWN
+    }
+
+    /**
+     * Returns the current keyboard being displayed by this view.
+     * @return the currently attached keyboard
+     * @see #setKeyboard(Keyboard)
+     */
+    public Keyboard getKeyboard() {
+        return mKeyboard;
+    }
+
+    /**
+     * Sets the state of the shift key of the keyboard, if any.
+     * @param shifted whether or not to enable the state of the shift key
+     * @return true if the shift key state changed, false if there was no change
+     */
+    public boolean setShifted(boolean shifted) {
+        if (mKeyboard != null) {
+            if (mKeyboard.setShifted(shifted)) {
+                // The whole keyboard probably needs to be redrawn
+                invalidateAllKeys();
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Returns the state of the shift key of the keyboard, if any.
+     * @return true if the shift is in a pressed state, false otherwise. If there is
+     * no shift key on the keyboard or there is no keyboard attached, it returns false.
+     */
+    public boolean isShifted() {
+        if (mKeyboard != null) {
+            return mKeyboard.isShifted();
+        }
+        return false;
+    }
+
+    /**
+     * 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()
+     */
+    public void setPreviewEnabled(boolean previewEnabled) {
+        mShowPreview = previewEnabled;
+    }
+
+    /**
+     * Returns the enabled state of the key feedback popup.
+     * @return whether or not the key feedback popup is enabled
+     * @see #setPreviewEnabled(boolean)
+     */
+    public boolean isPreviewEnabled() {
+        return mShowPreview;
+    }
+
+    public int getSymbolColorSheme() {
+        return mSymbolColorScheme;
+    }
+
+    public void setVerticalCorrection(int verticalOffset) {
+    }
+
+    public void setPopupParent(View v) {
+        mPopupParent = v;
+    }
+
+    public void setPopupOffset(int x, int y) {
+        mMiniKeyboardOffsetX = x;
+        mMiniKeyboardOffsetY = y;
+        if (mPreviewPopup.isShowing()) {
+            mPreviewPopup.dismiss();
+        }
+    }
+
+    /**
+     * When enabled, calls to {@link OnKeyboardActionListener#onKey} will include key
+     * codes for adjacent keys.  When disabled, only the primary key code will be
+     * reported.
+     * @param enabled whether or not the proximity correction is enabled
+     */
+    public void setProximityCorrectionEnabled(boolean enabled) {
+        mProximityCorrectOn = enabled;
+    }
+
+    /**
+     * Returns true if proximity correction is enabled.
+     */
+    public boolean isProximityCorrectionEnabled() {
+        return mProximityCorrectOn;
+    }
+
+    /**
+     * Popup keyboard close button clicked.
+     * @hide
+     */
+    public void onClick(View v) {
+        dismissPopupKeyboard();
+    }
+
+    protected CharSequence adjustCase(CharSequence label) {
+        if (mKeyboard.isShifted() && label != null && label.length() < 3
+                && Character.isLowerCase(label.charAt(0))) {
+            label = label.toString().toUpperCase();
+        }
+        return label;
+    }
+
+    @Override
+    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        // Round up a little
+        if (mKeyboard == null) {
+            setMeasuredDimension(
+                    getPaddingLeft() + getPaddingRight(), getPaddingTop() + getPaddingBottom());
+        } else {
+            int width = mKeyboard.getMinWidth() + getPaddingLeft() + getPaddingRight();
+            if (MeasureSpec.getSize(widthMeasureSpec) < width + 10) {
+                width = MeasureSpec.getSize(widthMeasureSpec);
+            }
+            setMeasuredDimension(
+                    width, mKeyboard.getHeight() + getPaddingTop() + getPaddingBottom());
+        }
+    }
+
+    /**
+     * Compute the average distance between adjacent keys (horizontally and vertically)
+     * and square it to get the proximity threshold. We use a square here and in computing
+     * the touch distance from a key's center to avoid taking a square root.
+     * @param keyboard
+     */
+    private void computeProximityThreshold(Keyboard keyboard) {
+        if (keyboard == null) return;
+        final Key[] keys = mKeys;
+        if (keys == null) return;
+        int length = keys.length;
+        int dimensionSum = 0;
+        for (int i = 0; i < length; i++) {
+            Key key = keys[i];
+            dimensionSum += Math.min(key.width, key.height) + key.gap;
+        }
+        if (dimensionSum < 0 || length == 0) return;
+        mProximityThreshold = (int) (dimensionSum * 1.4f / length);
+        mProximityThreshold *= mProximityThreshold; // Square it
+
+        final float hysteresisPixel = getContext().getResources()
+                .getDimension(R.dimen.key_debounce_hysteresis_distance);
+        mDebouncer = new KeyDebouncer(keys, hysteresisPixel);
+    }
+
+    @Override
+    public void onSizeChanged(int w, int h, int oldw, int oldh) {
+        super.onSizeChanged(w, h, oldw, oldh);
+        // Release the buffer, if any and it will be reallocated on the next draw
+        mBuffer = null;
+    }
+
+    @Override
+    public void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+        if (mDrawPending || mBuffer == null || mKeyboardChanged) {
+            onBufferDraw();
+        }
+        canvas.drawBitmap(mBuffer, 0, 0, null);
+    }
+
+    private void onBufferDraw() {
+        if (mBuffer == null || mKeyboardChanged) {
+            if (mBuffer == null || mKeyboardChanged &&
+                    (mBuffer.getWidth() != getWidth() || mBuffer.getHeight() != getHeight())) {
+                // Make sure our bitmap is at least 1x1
+                final int width = Math.max(1, getWidth());
+                final int height = Math.max(1, getHeight());
+                mBuffer = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+                mCanvas = new Canvas(mBuffer);
+            }
+            invalidateAllKeys();
+            mKeyboardChanged = false;
+        }
+        final Canvas canvas = mCanvas;
+        canvas.clipRect(mDirtyRect, Op.REPLACE);
+
+        if (mKeyboard == null) return;
+
+        final Paint paint = mPaint;
+        final Drawable keyBackground = mKeyBackground;
+        final Rect clipRegion = mClipRegion;
+        final Rect padding = mPadding;
+        final int kbdPaddingLeft = getPaddingLeft();
+        final int kbdPaddingTop = getPaddingTop();
+        final Key[] keys = mKeys;
+        final Key invalidKey = mInvalidatedKey;
+
+        paint.setColor(mKeyTextColor);
+        boolean drawSingleKey = false;
+        if (invalidKey != null && canvas.getClipBounds(clipRegion)) {
+          // Is clipRegion completely contained within the invalidated key?
+          if (invalidKey.x + kbdPaddingLeft - 1 <= clipRegion.left &&
+                  invalidKey.y + kbdPaddingTop - 1 <= clipRegion.top &&
+                  invalidKey.x + invalidKey.width + kbdPaddingLeft + 1 >= clipRegion.right &&
+                  invalidKey.y + invalidKey.height + kbdPaddingTop + 1 >= clipRegion.bottom) {
+              drawSingleKey = true;
+          }
+        }
+        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 && invalidKey != key) {
+                continue;
+            }
+            int[] drawableState = key.getCurrentDrawableState();
+            keyBackground.setState(drawableState);
+
+            // Switch the character to uppercase if shift is pressed
+            String label = key.label == null? null : adjustCase(key.label).toString();
+
+            final Rect bounds = keyBackground.getBounds();
+            if (key.width != bounds.right ||
+                    key.height != bounds.bottom) {
+                keyBackground.setBounds(0, 0, key.width, key.height);
+            }
+            canvas.translate(key.x + kbdPaddingLeft, key.y + kbdPaddingTop);
+            keyBackground.draw(canvas);
+
+            if (label != null) {
+                // For characters, use large font. For labels like "Done", use small font.
+                if (label.length() > 1 && key.codes.length < 2) {
+                    paint.setTextSize(mLabelTextSize);
+                    paint.setTypeface(Typeface.DEFAULT_BOLD);
+                } else {
+                    paint.setTextSize(mKeyTextSize);
+                    paint.setTypeface(mKeyTextStyle);
+                }
+                // Draw a drop shadow for the text
+                paint.setShadowLayer(mShadowRadius, 0, 0, mShadowColor);
+                // Draw the text
+                canvas.drawText(label,
+                    (key.width - padding.left - padding.right) / 2
+                            + padding.left,
+                    (key.height - padding.top - padding.bottom) / 2
+                            + (paint.getTextSize() - paint.descent()) / 2 + padding.top,
+                    paint);
+                // Turn off drop shadow
+                paint.setShadowLayer(0, 0, 0, 0);
+            } else if (key.icon != null) {
+                final int drawableX = (key.width - padding.left - padding.right
+                                - key.icon.getIntrinsicWidth()) / 2 + padding.left;
+                final int drawableY = (key.height - padding.top - padding.bottom
+                        - key.icon.getIntrinsicHeight()) / 2 + padding.top;
+                canvas.translate(drawableX, drawableY);
+                key.icon.setBounds(0, 0,
+                        key.icon.getIntrinsicWidth(), key.icon.getIntrinsicHeight());
+                key.icon.draw(canvas);
+                canvas.translate(-drawableX, -drawableY);
+            }
+            canvas.translate(-key.x - kbdPaddingLeft, -key.y - kbdPaddingTop);
+        }
+        mInvalidatedKey = null;
+        // Overlay a dark rectangle to dim the keyboard
+        if (mMiniKeyboardOnScreen) {
+            paint.setColor((int) (mBackgroundDimAmount * 0xFF) << 24);
+            canvas.drawRect(0, 0, getWidth(), getHeight(), paint);
+        }
+
+        if (DEBUG) {
+            if (mShowTouchPoints) {
+                int lastX = mDebouncer.getLastX();
+                int lastY = mDebouncer.getLastY();
+                paint.setAlpha(128);
+                paint.setColor(0xFFFF0000);
+                canvas.drawCircle(mStartX, mStartY, 3, paint);
+                canvas.drawLine(mStartX, mStartY, lastX, lastY, paint);
+                paint.setColor(0xFF0000FF);
+                canvas.drawCircle(lastX, lastY, 3, paint);
+                paint.setColor(0xFF00FF00);
+                canvas.drawCircle((mStartX + lastX) / 2, (mStartY + lastY) / 2, 2, paint);
+            }
+        }
+
+        mDrawPending = false;
+        mDirtyRect.setEmpty();
+    }
+
+    private int getKeyIndexAndNearbyCodes(int x, int y, int[] allKeys) {
+        final Key[] keys = mKeys;
+        int primaryIndex = NOT_A_KEY;
+        int closestKey = NOT_A_KEY;
+        int closestKeyDist = mProximityThreshold + 1;
+        Arrays.fill(mDistances, Integer.MAX_VALUE);
+        int [] nearestKeyIndices = mKeyboard.getNearestKeys(x, y);
+        final int keyCount = nearestKeyIndices.length;
+        for (int i = 0; i < keyCount; i++) {
+            final Key key = keys[nearestKeyIndices[i]];
+            int dist = 0;
+            boolean isInside = key.isInside(x,y);
+            if (isInside) {
+                primaryIndex = nearestKeyIndices[i];
+            }
+
+            if (((mProximityCorrectOn
+                    && (dist = key.squaredDistanceFrom(x, y)) < mProximityThreshold)
+                    || isInside)
+                    && key.codes[0] > 32) {
+                // Find insertion point
+                final int nCodes = key.codes.length;
+                if (dist < closestKeyDist) {
+                    closestKeyDist = dist;
+                    closestKey = nearestKeyIndices[i];
+                }
+
+                if (allKeys == null) continue;
+
+                for (int j = 0; j < mDistances.length; j++) {
+                    if (mDistances[j] > dist) {
+                        // Make space for nCodes codes
+                        System.arraycopy(mDistances, j, mDistances, j + nCodes,
+                                mDistances.length - j - nCodes);
+                        System.arraycopy(allKeys, j, allKeys, j + nCodes,
+                                allKeys.length - j - nCodes);
+                        System.arraycopy(key.codes, 0, allKeys, j, nCodes);
+                        Arrays.fill(mDistances, j, j + nCodes, dist);
+                        break;
+                    }
+                }
+            }
+        }
+        if (primaryIndex == NOT_A_KEY) {
+            primaryIndex = closestKey;
+        }
+        return primaryIndex;
+    }
+
+    private void detectAndSendKey(int index, int x, int y, long eventTime) {
+        if (index != NOT_A_KEY && index < mKeys.length) {
+            final Key key = mKeys[index];
+            if (key.text != null) {
+                mKeyboardActionListener.onText(key.text);
+                mKeyboardActionListener.onRelease(NOT_A_KEY);
+            } else {
+                int code = key.codes[0];
+                //TextEntryState.keyPressedAt(key, x, y);
+                int[] codes = new int[MAX_NEARBY_KEYS];
+                Arrays.fill(codes, NOT_A_KEY);
+                getKeyIndexAndNearbyCodes(x, y, codes);
+                // Multi-tap
+                if (mInMultiTap) {
+                    if (mTapCount != -1) {
+                        mKeyboardActionListener.onKey(Keyboard.KEYCODE_DELETE, KEY_DELETE);
+                    } else {
+                        mTapCount = 0;
+                    }
+                    code = key.codes[mTapCount];
+                }
+                /*
+                 * Swap the first and second values in the codes array if the primary code is not
+                 * the first value but the second value in the array. This happens when key
+                 * debouncing is in effect.
+                 */
+                if (codes.length >= 2 && codes[0] != code && codes[1] == code) {
+                    codes[1] = codes[0];
+                    codes[0] = code;
+                }
+                mKeyboardActionListener.onKey(code, codes);
+                mKeyboardActionListener.onRelease(code);
+            }
+            mLastSentIndex = index;
+            mLastTapTime = eventTime;
+        }
+    }
+
+    /**
+     * Handle multi-tap keys by producing the key label for the current multi-tap state.
+     */
+    private CharSequence getPreviewText(Key key) {
+        if (mInMultiTap) {
+            // Multi-tap
+            mPreviewLabel.setLength(0);
+            mPreviewLabel.append((char) key.codes[mTapCount < 0 ? 0 : mTapCount]);
+            return adjustCase(mPreviewLabel);
+        } else {
+            return adjustCase(key.label);
+        }
+    }
+
+    private void showPreview(int keyIndex) {
+        int oldKeyIndex = mCurrentKeyIndex;
+        final PopupWindow previewPopup = mPreviewPopup;
+
+        mCurrentKeyIndex = keyIndex;
+        // Release the old key and press the new key
+        final Key[] keys = mKeys;
+        if (oldKeyIndex != mCurrentKeyIndex) {
+            if (oldKeyIndex != NOT_A_KEY && keys.length > oldKeyIndex) {
+                keys[oldKeyIndex].onReleased(mCurrentKeyIndex == NOT_A_KEY);
+                invalidateKey(oldKeyIndex);
+            }
+            if (mCurrentKeyIndex != NOT_A_KEY && keys.length > mCurrentKeyIndex) {
+                keys[mCurrentKeyIndex].onPressed();
+                invalidateKey(mCurrentKeyIndex);
+            }
+        }
+        // If key changed and preview is on ...
+        if (oldKeyIndex != mCurrentKeyIndex && mShowPreview) {
+            if (keyIndex == NOT_A_KEY) {
+                mHandler.cancelPopupPreview();
+                if (previewPopup.isShowing()) {
+                    mHandler.dismissPreview(DELAY_AFTER_PREVIEW);
+                }
+            } else {
+                if (previewPopup.isShowing() && mPreviewText.getVisibility() == VISIBLE) {
+                    // Show right away, if it's already visible and finger is moving around
+                    showKey(keyIndex);
+                } else {
+                    mHandler.popupPreview(keyIndex, DELAY_BEFORE_PREVIEW);
+                }
+            }
+        }
+    }
+
+    private void showKey(final int keyIndex) {
+        final PopupWindow previewPopup = mPreviewPopup;
+        final Key[] keys = mKeys;
+        if (keyIndex < 0 || keyIndex >= mKeys.length) return;
+        Key key = keys[keyIndex];
+        if (key.icon != null) {
+            mPreviewText.setCompoundDrawables(null, null, null,
+                    key.iconPreview != null ? key.iconPreview : key.icon);
+            mPreviewText.setText(null);
+        } else {
+            mPreviewText.setCompoundDrawables(null, null, null, null);
+            mPreviewText.setText(getPreviewText(key));
+            if (key.label.length() > 1 && key.codes.length < 2) {
+                mPreviewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, mKeyTextSize);
+                mPreviewText.setTypeface(Typeface.DEFAULT_BOLD);
+            } else {
+                mPreviewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, mPreviewTextSizeLarge);
+                mPreviewText.setTypeface(mKeyTextStyle);
+            }
+        }
+        mPreviewText.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
+                MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
+        int popupWidth = Math.max(mPreviewText.getMeasuredWidth(), key.width
+                + mPreviewText.getPaddingLeft() + mPreviewText.getPaddingRight());
+        final int popupHeight = mPreviewHeight;
+        LayoutParams lp = mPreviewText.getLayoutParams();
+        if (lp != null) {
+            lp.width = popupWidth;
+            lp.height = popupHeight;
+        }
+        if (!mPreviewCentered) {
+            mPopupPreviewX = key.x - mPreviewText.getPaddingLeft() + getPaddingLeft();
+            mPopupPreviewY = key.y - popupHeight + mPreviewOffset;
+        } else {
+            // TODO: Fix this if centering is brought back
+            mPopupPreviewX = 160 - mPreviewText.getMeasuredWidth() / 2;
+            mPopupPreviewY = - mPreviewText.getMeasuredHeight();
+        }
+        mHandler.cancelDismissPreview();
+        if (mOffsetInWindow == null) {
+            mOffsetInWindow = new int[2];
+            getLocationInWindow(mOffsetInWindow);
+            mOffsetInWindow[0] += mMiniKeyboardOffsetX; // Offset may be zero
+            mOffsetInWindow[1] += mMiniKeyboardOffsetY; // Offset may be zero
+            int[] mWindowLocation = new int[2];
+            getLocationOnScreen(mWindowLocation);
+            mWindowY = mWindowLocation[1];
+        }
+        // Set the preview background state
+        mPreviewText.getBackground().setState(
+                key.popupResId != 0 ? LONG_PRESSABLE_STATE_SET : EMPTY_STATE_SET);
+        mPopupPreviewX += mOffsetInWindow[0];
+        mPopupPreviewY += mOffsetInWindow[1];
+
+        // If the popup cannot be shown above the key, put it on the side
+        if (mPopupPreviewY + 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.x + key.width <= getWidth() / 2) {
+                mPopupPreviewX += (int) (key.width * 2.5);
+            } else {
+                mPopupPreviewX -= (int) (key.width * 2.5);
+            }
+            mPopupPreviewY += popupHeight;
+        }
+
+        if (previewPopup.isShowing()) {
+            previewPopup.update(mPopupPreviewX, mPopupPreviewY,
+                    popupWidth, popupHeight);
+        } else {
+            previewPopup.setWidth(popupWidth);
+            previewPopup.setHeight(popupHeight);
+            previewPopup.showAtLocation(mPopupParent, Gravity.NO_GRAVITY,
+                    mPopupPreviewX, mPopupPreviewY);
+        }
+        mPreviewText.setVisibility(VISIBLE);
+    }
+
+    /**
+     * Requests a redraw of the entire keyboard. Calling {@link #invalidate} is not sufficient
+     * because the keyboard renders the keys to an off-screen buffer and an invalidate() only
+     * draws the cached buffer.
+     * @see #invalidateKey(int)
+     */
+    public void invalidateAllKeys() {
+        mDirtyRect.union(0, 0, getWidth(), getHeight());
+        mDrawPending = true;
+        invalidate();
+    }
+
+    /**
+     * Invalidates a key so that it will be redrawn on the next repaint. Use this method if only
+     * one key is changing it's content. Any changes that affect the position or size of the key
+     * may not be honored.
+     * @param keyIndex the index of the key in the attached {@link Keyboard}.
+     * @see #invalidateAllKeys
+     */
+    public void invalidateKey(int keyIndex) {
+        if (mKeys == null) return;
+        if (keyIndex < 0 || keyIndex >= mKeys.length) {
+            return;
+        }
+        final Key key = mKeys[keyIndex];
+        mInvalidatedKey = key;
+        mDirtyRect.union(key.x + getPaddingLeft(), key.y + getPaddingTop(),
+                key.x + key.width + getPaddingLeft(), key.y + key.height + getPaddingTop());
+        onBufferDraw();
+        invalidate(key.x + getPaddingLeft(), key.y + getPaddingTop(),
+                key.x + key.width + getPaddingLeft(), key.y + key.height + getPaddingTop());
+    }
+
+    private boolean openPopupIfRequired(MotionEvent me) {
+        // Check if we have a popup layout specified first.
+        if (mPopupLayout == 0) {
+            return false;
+        }
+        if (mCurrentKey < 0 || mCurrentKey >= mKeys.length) {
+            return false;
+        }
+
+        Key popupKey = mKeys[mCurrentKey];
+        boolean result = onLongPress(popupKey);
+        if (result) {
+            mAbortKey = true;
+            showPreview(NOT_A_KEY);
+        }
+        return result;
+    }
+
+    /**
+     * 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
+     * @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) {
+        int popupKeyboardId = popupKey.popupResId;
+
+        if (popupKeyboardId != 0) {
+            mMiniKeyboardContainer = mMiniKeyboardCache.get(popupKey);
+            if (mMiniKeyboardContainer == null) {
+                LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(
+                        Context.LAYOUT_INFLATER_SERVICE);
+                mMiniKeyboardContainer = inflater.inflate(mPopupLayout, null);
+                mMiniKeyboard = (LatinKeyboardBaseView) mMiniKeyboardContainer.findViewById(
+                       R.id.LatinKeyboardBaseView);
+                View closeButton = mMiniKeyboardContainer.findViewById(
+                        R.id.closeButton);
+                if (closeButton != null) closeButton.setOnClickListener(this);
+                mMiniKeyboard.setOnKeyboardActionListener(new OnKeyboardActionListener() {
+                    public void onKey(int primaryCode, int[] keyCodes) {
+                        mKeyboardActionListener.onKey(primaryCode, keyCodes);
+                        dismissPopupKeyboard();
+                    }
+
+                    public void onText(CharSequence text) {
+                        mKeyboardActionListener.onText(text);
+                        dismissPopupKeyboard();
+                    }
+
+                    public void swipeLeft() { }
+                    public void swipeRight() { }
+                    public void swipeUp() { }
+                    public void swipeDown() { }
+                    public void onPress(int primaryCode) {
+                        mKeyboardActionListener.onPress(primaryCode);
+                    }
+                    public void onRelease(int primaryCode) {
+                        mKeyboardActionListener.onRelease(primaryCode);
+                    }
+                });
+                //mInputView.setSuggest(mSuggest);
+                Keyboard keyboard;
+                if (popupKey.popupCharacters != null) {
+                    keyboard = new Keyboard(getContext(), popupKeyboardId,
+                            popupKey.popupCharacters, -1, getPaddingLeft() + getPaddingRight());
+                } else {
+                    keyboard = new Keyboard(getContext(), popupKeyboardId);
+                }
+                mMiniKeyboard.setKeyboard(keyboard);
+                mMiniKeyboard.setPopupParent(this);
+                mMiniKeyboardContainer.measure(
+                        MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.AT_MOST),
+                        MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.AT_MOST));
+
+                mMiniKeyboardCache.put(popupKey, mMiniKeyboardContainer);
+            } else {
+                mMiniKeyboard = (LatinKeyboardBaseView) mMiniKeyboardContainer.findViewById(
+                        R.id.LatinKeyboardBaseView);
+            }
+            if (mWindowOffset == null) {
+                mWindowOffset = new int[2];
+                getLocationInWindow(mWindowOffset);
+            }
+            mPopupX = popupKey.x + getPaddingLeft();
+            mPopupY = popupKey.y + getPaddingTop();
+            mPopupX = mPopupX + popupKey.width - mMiniKeyboardContainer.getMeasuredWidth();
+            mPopupY = mPopupY - mMiniKeyboardContainer.getMeasuredHeight();
+            final int x = mPopupX + mMiniKeyboardContainer.getPaddingRight() + mWindowOffset[0];
+            final int y = mPopupY + mMiniKeyboardContainer.getPaddingBottom() + mWindowOffset[1];
+            mMiniKeyboard.setPopupOffset(x < 0 ? 0 : x, y);
+            mMiniKeyboard.setShifted(isShifted());
+            mPopupKeyboard.setContentView(mMiniKeyboardContainer);
+            mPopupKeyboard.setWidth(mMiniKeyboardContainer.getMeasuredWidth());
+            mPopupKeyboard.setHeight(mMiniKeyboardContainer.getMeasuredHeight());
+            mPopupKeyboard.showAtLocation(this, Gravity.NO_GRAVITY, x, y);
+            mMiniKeyboardOnScreen = true;
+            //mMiniKeyboard.onTouchEvent(getTranslatedEvent(me));
+            invalidateAllKeys();
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent me) {
+        // Convert multi-pointer up/down events to single up/down events to
+        // deal with the typical multi-pointer behavior of two-thumb typing
+        final int pointerCount = me.getPointerCount();
+        final int action = me.getAction();
+        boolean result = false;
+        final long now = me.getEventTime();
+
+        if (pointerCount != mOldPointerCount) {
+            if (pointerCount == 1) {
+                // Send a down event for the latest pointer
+                MotionEvent down = MotionEvent.obtain(now, now, MotionEvent.ACTION_DOWN,
+                        me.getX(), me.getY(), me.getMetaState());
+                result = onModifiedTouchEvent(down, false);
+                down.recycle();
+                // If it's an up action, then deliver the up as well.
+                if (action == MotionEvent.ACTION_UP) {
+                    result = onModifiedTouchEvent(me, true);
+                }
+            } else {
+                // Send an up event for the last pointer
+                MotionEvent up = MotionEvent.obtain(now, now, MotionEvent.ACTION_UP,
+                        mOldPointerX, mOldPointerY, me.getMetaState());
+                result = onModifiedTouchEvent(up, true);
+                up.recycle();
+            }
+        } else {
+            if (pointerCount == 1) {
+                result = onModifiedTouchEvent(me, false);
+                mOldPointerX = me.getX();
+                mOldPointerY = me.getY();
+            } else {
+                // Don't do anything when 2 pointers are down and moving.
+                result = true;
+            }
+        }
+        mOldPointerCount = pointerCount;
+
+        return result;
+    }
+
+    private boolean onModifiedTouchEvent(MotionEvent me, boolean possiblePoly) {
+        int touchX = (int) me.getX() - getPaddingLeft();
+        int touchY = (int) me.getY() + mVerticalCorrection - getPaddingTop();
+        final int action = me.getAction();
+        final long eventTime = me.getEventTime();
+        int keyIndex = getKeyIndexAndNearbyCodes(touchX, touchY, null);
+        mPossiblePoly = possiblePoly;
+
+        // Track the last few movements to look for spurious swipes.
+        if (action == MotionEvent.ACTION_DOWN) mSwipeTracker.clear();
+        mSwipeTracker.addMovement(me);
+
+        // Ignore all motion events until a DOWN.
+        if (mAbortKey
+                && action != MotionEvent.ACTION_DOWN && action != MotionEvent.ACTION_CANCEL) {
+            return true;
+        }
+
+        if (mGestureDetector.onTouchEvent(me)) {
+            showPreview(NOT_A_KEY);
+            mHandler.cancelKeyTimers();
+            return true;
+        }
+
+        // Needs to be called after the gesture detector gets a turn, as it may have
+        // displayed the mini keyboard
+        if (mMiniKeyboardOnScreen && action != MotionEvent.ACTION_CANCEL) {
+            return true;
+        }
+
+        switch (action) {
+            case MotionEvent.ACTION_DOWN:
+                mAbortKey = false;
+                mCurrentKey = keyIndex;
+                mDownKey = keyIndex;
+                mStartX = touchX;
+                mStartY = touchY;
+                mDebouncer.startMoveDebouncing(touchX, touchY);
+                mDebouncer.startTimeDebouncing(eventTime);
+                checkMultiTap(eventTime, keyIndex);
+                mKeyboardActionListener.onPress(keyIndex != NOT_A_KEY ?
+                        mKeys[keyIndex].codes[0] : 0);
+                if (mCurrentKey >= 0 && mKeys[mCurrentKey].repeatable) {
+                    mRepeatKeyIndex = mCurrentKey;
+                    mHandler.startKeyRepeatTimer(REPEAT_START_DELAY);
+                    repeatKey();
+                    // Delivering the key could have caused an abort
+                    if (mAbortKey) {
+                        mRepeatKeyIndex = NOT_A_KEY;
+                        break;
+                    }
+                }
+                if (mCurrentKey != NOT_A_KEY) {
+                    mHandler.startLongPressTimer(me, LONGPRESS_TIMEOUT);
+                }
+                showPreview(keyIndex);
+                break;
+
+            case MotionEvent.ACTION_MOVE:
+                boolean continueLongPress = false;
+                if (keyIndex != NOT_A_KEY) {
+                    if (mCurrentKey == NOT_A_KEY) {
+                        mCurrentKey = keyIndex;
+                        mDebouncer.updateTimeDebouncing(eventTime);
+                    } else if (mDebouncer.isMinorMoveBounce(touchX, touchY, keyIndex,
+                            mCurrentKey)) {
+                        mDebouncer.updateTimeDebouncing(eventTime);
+                        continueLongPress = true;
+                    } else if (mRepeatKeyIndex == NOT_A_KEY) {
+                        resetMultiTap();
+                        mDebouncer.resetTimeDebouncing(eventTime, mCurrentKey);
+                        mDebouncer.resetMoveDebouncing();
+                        mCurrentKey = keyIndex;
+                    }
+                }
+                if (!continueLongPress) {
+                    // Cancel old longpress
+                    mHandler.cancelLongPressTimer();
+                    // Start new longpress if key has changed
+                    if (keyIndex != NOT_A_KEY) {
+                        mHandler.startLongPressTimer(me, LONGPRESS_TIMEOUT);
+                    }
+                }
+                /*
+                 * While time debouncing is in effect, mCurrentKey holds the new key and mDebouncer
+                 * holds the last key.  At ACTION_UP event if time debouncing will be in effect
+                 * eventually, the last key should be sent as the result.  In such case mCurrentKey
+                 * should not be showed as popup preview.
+                 */
+                showPreview(mDebouncer.isMinorTimeBounce() ? mDebouncer.getLastKey() : mCurrentKey);
+                break;
+
+            case MotionEvent.ACTION_UP:
+                mHandler.cancelKeyTimersAndPopupPreview();
+                if (mDebouncer.isMinorMoveBounce(touchX, touchY, keyIndex, mCurrentKey)) {
+                    mDebouncer.updateTimeDebouncing(eventTime);
+                } else {
+                    resetMultiTap();
+                    mDebouncer.resetTimeDebouncing(eventTime, mCurrentKey);
+                    mCurrentKey = keyIndex;
+                }
+                if (mDebouncer.isMinorTimeBounce()) {
+                    mCurrentKey = mDebouncer.getLastKey();
+                    touchX = mDebouncer.getLastCodeX();
+                    touchY = mDebouncer.getLastCodeY();
+                }
+                showPreview(NOT_A_KEY);
+                // If we're not on a repeating key (which sends on a DOWN event)
+                if (mRepeatKeyIndex == NOT_A_KEY && !mMiniKeyboardOnScreen && !mAbortKey) {
+                    detectAndSendKey(mCurrentKey, touchX, touchY, eventTime);
+                }
+                invalidateKey(keyIndex);
+                mRepeatKeyIndex = NOT_A_KEY;
+                break;
+
+            case MotionEvent.ACTION_CANCEL:
+                mHandler.cancelKeyTimersAndPopupPreview();
+                dismissPopupKeyboard();
+                mAbortKey = true;
+                showPreview(NOT_A_KEY);
+                invalidateKey(mCurrentKey);
+                break;
+        }
+        mDebouncer.updateMoveDebouncing(touchX, touchY);
+        return true;
+    }
+
+    private boolean repeatKey() {
+        Key key = mKeys[mRepeatKeyIndex];
+        detectAndSendKey(mCurrentKey, key.x, key.y, mLastTapTime);
+        return true;
+    }
+
+    protected void swipeRight() {
+        mKeyboardActionListener.swipeRight();
+    }
+
+    protected void swipeLeft() {
+        mKeyboardActionListener.swipeLeft();
+    }
+
+    protected void swipeUp() {
+        mKeyboardActionListener.swipeUp();
+    }
+
+    protected void swipeDown() {
+        mKeyboardActionListener.swipeDown();
+    }
+
+    public void closing() {
+        if (mPreviewPopup.isShowing()) {
+            mPreviewPopup.dismiss();
+        }
+        mHandler.cancelAllMessages();
+
+        dismissPopupKeyboard();
+        mBuffer = null;
+        mCanvas = null;
+        mMiniKeyboardCache.clear();
+    }
+
+    @Override
+    public void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        closing();
+    }
+
+    private void dismissPopupKeyboard() {
+        if (mPopupKeyboard.isShowing()) {
+            mPopupKeyboard.dismiss();
+            mMiniKeyboardOnScreen = false;
+            invalidateAllKeys();
+        }
+    }
+
+    public boolean handleBack() {
+        if (mPopupKeyboard.isShowing()) {
+            dismissPopupKeyboard();
+            return true;
+        }
+        return false;
+    }
+
+    private void resetMultiTap() {
+        mLastSentIndex = NOT_A_KEY;
+        mTapCount = 0;
+        mLastTapTime = -1;
+        mInMultiTap = false;
+    }
+
+    private void checkMultiTap(long eventTime, int keyIndex) {
+        if (keyIndex == NOT_A_KEY) return;
+        Key key = mKeys[keyIndex];
+        if (key.codes.length > 1) {
+            mInMultiTap = true;
+            if (eventTime < mLastTapTime + MULTITAP_INTERVAL
+                    && keyIndex == mLastSentIndex) {
+                mTapCount = (mTapCount + 1) % key.codes.length;
+                return;
+            } else {
+                mTapCount = -1;
+                return;
+            }
+        }
+        if (eventTime > mLastTapTime + MULTITAP_INTERVAL || keyIndex != mLastSentIndex) {
+            resetMultiTap();
+        }
+    }
+
+    private static class SwipeTracker {
+
+        static final int NUM_PAST = 4;
+        static final int LONGEST_PAST_TIME = 200;
+
+        final float mPastX[] = new float[NUM_PAST];
+        final float mPastY[] = new float[NUM_PAST];
+        final long mPastTime[] = new long[NUM_PAST];
+
+        float mYVelocity;
+        float mXVelocity;
+
+        public void clear() {
+            mPastTime[0] = 0;
+        }
+
+        public void addMovement(MotionEvent ev) {
+            long time = ev.getEventTime();
+            final int N = ev.getHistorySize();
+            for (int i=0; i<N; i++) {
+                addPoint(ev.getHistoricalX(i), ev.getHistoricalY(i),
+                        ev.getHistoricalEventTime(i));
+            }
+            addPoint(ev.getX(), ev.getY(), time);
+        }
+
+        private void addPoint(float x, float y, long time) {
+            int drop = -1;
+            int i;
+            final long[] pastTime = mPastTime;
+            for (i=0; i<NUM_PAST; i++) {
+                if (pastTime[i] == 0) {
+                    break;
+                } else if (pastTime[i] < time-LONGEST_PAST_TIME) {
+                    drop = i;
+                }
+            }
+            if (i == NUM_PAST && drop < 0) {
+                drop = 0;
+            }
+            if (drop == i) drop--;
+            final float[] pastX = mPastX;
+            final float[] pastY = mPastY;
+            if (drop >= 0) {
+                final int start = drop+1;
+                final int count = NUM_PAST-drop-1;
+                System.arraycopy(pastX, start, pastX, 0, count);
+                System.arraycopy(pastY, start, pastY, 0, count);
+                System.arraycopy(pastTime, start, pastTime, 0, count);
+                i -= (drop+1);
+            }
+            pastX[i] = x;
+            pastY[i] = y;
+            pastTime[i] = time;
+            i++;
+            if (i < NUM_PAST) {
+                pastTime[i] = 0;
+            }
+        }
+
+        public void computeCurrentVelocity(int units) {
+            computeCurrentVelocity(units, Float.MAX_VALUE);
+        }
+
+        public void computeCurrentVelocity(int units, float maxVelocity) {
+            final float[] pastX = mPastX;
+            final float[] pastY = mPastY;
+            final long[] pastTime = mPastTime;
+
+            final float oldestX = pastX[0];
+            final float oldestY = pastY[0];
+            final long oldestTime = pastTime[0];
+            float accumX = 0;
+            float accumY = 0;
+            int N=0;
+            while (N < NUM_PAST) {
+                if (pastTime[N] == 0) {
+                    break;
+                }
+                N++;
+            }
+
+            for (int i=1; i < N; i++) {
+                final int dur = (int)(pastTime[i] - oldestTime);
+                if (dur == 0) continue;
+                float dist = pastX[i] - oldestX;
+                float vel = (dist/dur) * units;   // pixels/frame.
+                if (accumX == 0) accumX = vel;
+                else accumX = (accumX + vel) * .5f;
+
+                dist = pastY[i] - oldestY;
+                vel = (dist/dur) * units;   // pixels/frame.
+                if (accumY == 0) accumY = vel;
+                else accumY = (accumY + vel) * .5f;
+            }
+            mXVelocity = accumX < 0.0f ? Math.max(accumX, -maxVelocity)
+                    : Math.min(accumX, maxVelocity);
+            mYVelocity = accumY < 0.0f ? Math.max(accumY, -maxVelocity)
+                    : Math.min(accumY, maxVelocity);
+        }
+
+        public float getXVelocity() {
+            return mXVelocity;
+        }
+
+        public float getYVelocity() {
+            return mYVelocity;
+        }
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/LatinKeyboardView.java b/java/src/com/android/inputmethod/latin/LatinKeyboardView.java
index 74fc475..38d9cef 100644
--- a/java/src/com/android/inputmethod/latin/LatinKeyboardView.java
+++ b/java/src/com/android/inputmethod/latin/LatinKeyboardView.java
@@ -22,8 +22,6 @@
 import android.graphics.Canvas;
 import android.graphics.Paint;
 import android.inputmethodservice.Keyboard;
-import android.inputmethodservice.KeyboardView;
-import android.inputmethodservice.KeyboardView.OnKeyboardActionListener;
 import android.inputmethodservice.Keyboard.Key;
 import android.os.Handler;
 import android.os.Message;
@@ -33,7 +31,7 @@
 import android.view.MotionEvent;
 import android.widget.PopupWindow;
 
-public class LatinKeyboardView extends KeyboardView {
+public class LatinKeyboardView extends LatinKeyboardBaseView {
 
     static final int KEYCODE_OPTIONS = -100;
     static final int KEYCODE_SHIFT_LONGPRESS = -101;
@@ -66,6 +64,8 @@
     /** The y coordinate of the last row */
     private int mLastRowY;
 
+    private int mExtensionLayoutResId = 0;
+
     public LatinKeyboardView(Context context, AttributeSet attrs) {
         super(context, attrs);
     }
@@ -78,6 +78,10 @@
         mPhoneKeyboard = phoneKeyboard;
     }
 
+    public void setExtentionLayoutResId (int id) {
+        mExtensionLayoutResId = id;
+    }
+
     @Override
     public void setKeyboard(Keyboard k) {
         super.setKeyboard(k);
@@ -107,6 +111,29 @@
         }
     }
 
+    @Override
+    protected CharSequence adjustCase(CharSequence label) {
+        Keyboard keyboard = getKeyboard();
+        if (keyboard.isShifted()
+                && keyboard instanceof LatinKeyboard
+                && ((LatinKeyboard) keyboard).isAlphaKeyboard()
+                && label != null && label.length() < 3
+                && Character.isLowerCase(label.charAt(0))) {
+            label = label.toString().toUpperCase();
+        }
+        return label;
+    }
+
+    public boolean setShiftLocked(boolean shiftLocked) {
+        Keyboard keyboard = getKeyboard();
+        if (keyboard != null && keyboard instanceof LatinKeyboard) {
+            ((LatinKeyboard)keyboard).setShiftLocked(shiftLocked);
+            invalidateAllKeys();
+            return true;
+        }
+        return false;
+    }
+
     /**
      * This function checks to see if we need to handle any sudden jumps in the pointer location
      * that could be due to a multi-touch being treated as a move by the firmware or hardware.
@@ -295,7 +322,8 @@
             mExtensionPopup.setBackgroundDrawable(null);
             LayoutInflater li = (LayoutInflater) getContext().getSystemService(
                     Context.LAYOUT_INFLATER_SERVICE);
-            mExtension = (LatinKeyboardView) li.inflate(R.layout.input_trans, null);
+            mExtension = (LatinKeyboardView) li.inflate(mExtensionLayoutResId == 0 ?
+                    R.layout.input_trans : mExtensionLayoutResId, null);
             mExtension.setExtensionType(true);
             mExtension.setOnKeyboardActionListener(
                     new ExtensionKeyboardListener(getOnKeyboardActionListener()));
@@ -452,27 +480,39 @@
             }
         }
     }
-    
-    void startPlaying(String s) {
-        if (!DEBUG_AUTO_PLAY) return;
-        if (s == null) return;
-        mStringToPlay = s.toLowerCase();
-        mPlaying = true;
-        mDownDelivered = false;
-        mStringIndex = 0;
-        mHandler2.sendEmptyMessageDelayed(MSG_TOUCH_DOWN, 10);
+
+    public void startPlaying(String s) {
+        if (DEBUG_AUTO_PLAY) {
+            if (s == null) return;
+            mStringToPlay = s.toLowerCase();
+            mPlaying = true;
+            mDownDelivered = false;
+            mStringIndex = 0;
+            mHandler2.sendEmptyMessageDelayed(MSG_TOUCH_DOWN, 10);
+        }
     }
 
     @Override
     public void draw(Canvas c) {
-        super.draw(c);
-        if (DEBUG_AUTO_PLAY && mPlaying) {
-            mHandler2.removeMessages(MSG_TOUCH_DOWN);
-            mHandler2.removeMessages(MSG_TOUCH_UP);
-            if (mDownDelivered) {
-                mHandler2.sendEmptyMessageDelayed(MSG_TOUCH_UP, 20);
-            } else {
-                mHandler2.sendEmptyMessageDelayed(MSG_TOUCH_DOWN, 20);
+        LatinIMEUtil.GCUtils.getInstance().reset();
+        boolean tryGC = true;
+        for (int i = 0; i < LatinIMEUtil.GCUtils.GC_TRY_LOOP_MAX && tryGC; ++i) {
+            try {
+                super.draw(c);
+                tryGC = false;
+            } catch (OutOfMemoryError e) {
+                tryGC = LatinIMEUtil.GCUtils.getInstance().tryGCOrWait("LatinKeyboardView", e);
+            }
+        }
+        if (DEBUG_AUTO_PLAY) {
+            if (mPlaying) {
+                mHandler2.removeMessages(MSG_TOUCH_DOWN);
+                mHandler2.removeMessages(MSG_TOUCH_UP);
+                if (mDownDelivered) {
+                    mHandler2.sendEmptyMessageDelayed(MSG_TOUCH_UP, 20);
+                } else {
+                    mHandler2.sendEmptyMessageDelayed(MSG_TOUCH_DOWN, 20);
+                }
             }
         }
         if (DEBUG_LINE) {
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index a70bea0..a96737f 100755
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -16,18 +16,17 @@
 
 package com.android.inputmethod.latin;
 
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
 import android.content.Context;
 import android.text.AutoText;
 import android.text.TextUtils;
 import android.util.Log;
 import android.view.View;
 
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-import com.android.inputmethod.latin.WordComposer;
-
 /**
  * This class loads a dictionary and provides a list of suggestions for a given sequence of 
  * characters. This includes corrections and completions.
@@ -35,9 +34,36 @@
  */
 public class Suggest implements Dictionary.WordCallback {
 
+    private static final String TAG = "Suggest";
+
+    public static final int APPROX_MAX_WORD_LENGTH = 32;
+
     public static final int CORRECTION_NONE = 0;
     public static final int CORRECTION_BASIC = 1;
     public static final int CORRECTION_FULL = 2;
+    public static final int CORRECTION_FULL_BIGRAM = 3;
+
+    /**
+     * 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 data.
+     */
+    public static final double BIGRAM_MULTIPLIER_MIN = 1.2;
+    public static final double BIGRAM_MULTIPLIER_MAX = 1.5;
+
+    /**
+     * 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.
+     */
+    public static final int MAXIMUM_BIGRAM_FREQUENCY = 127;
+
+    public static final int DIC_USER_TYPED = 0;
+    public static final int DIC_MAIN = 1;
+    public static final int DIC_USER = 2;
+    public static final int DIC_AUTO = 3;
+    public static final int DIC_CONTACTS = 4;
+    // If you add a type of dictionary, increment DIC_TYPE_LAST_ID
+    public static final int DIC_TYPE_LAST_ID = 4;
 
     static final int LARGE_DICTIONARY_THRESHOLD = 200 * 1000;
 
@@ -49,11 +75,17 @@
 
     private Dictionary mContactsDictionary;
 
+    private Dictionary mUserBigramDictionary;
+
     private int mPrefMaxSuggestions = 12;
 
+    private static final int PREF_MAX_BIGRAMS = 60;
+
     private boolean mAutoTextEnabled;
 
     private int[] mPriorities = new int[mPrefMaxSuggestions];
+    private int[] mBigramPriorities = new int[PREF_MAX_BIGRAMS];
+
     // Handle predictive correction for only the first 1280 characters for performance reasons
     // If we support scripts that need latin characters beyond that, we should probably use some
     // kind of a sparse array or language specific list with a mapping lookup table.
@@ -61,6 +93,7 @@
     // latin characters.
     private int[] mNextLettersFrequencies = new int[1280];
     private ArrayList<CharSequence> mSuggestions = new ArrayList<CharSequence>();
+    ArrayList<CharSequence> mBigramSuggestions  = new ArrayList<CharSequence>();
     private ArrayList<CharSequence> mStringPool = new ArrayList<CharSequence>();
     private boolean mHaveCorrection;
     private CharSequence mOriginalWord;
@@ -69,11 +102,19 @@
 
     private int mCorrectionMode = CORRECTION_BASIC;
 
+    public Suggest(Context context, int[] dictionaryResId) {
+        mMainDict = new BinaryDictionary(context, dictionaryResId, DIC_MAIN);
+        initPool();
+    }
 
-    public Suggest(Context context, int dictionaryResId) {
-        mMainDict = new BinaryDictionary(context, dictionaryResId);
+    public Suggest(Context context, ByteBuffer byteBuffer) {
+        mMainDict = new BinaryDictionary(context, byteBuffer, DIC_MAIN);
+        initPool();
+    }
+
+    private void initPool() {
         for (int i = 0; i < mPrefMaxSuggestions; i++) {
-            StringBuilder sb = new StringBuilder(32);
+            StringBuilder sb = new StringBuilder(getApproxMaxWordLength());
             mStringPool.add(sb);
         }
     }
@@ -94,6 +135,10 @@
         return mMainDict.getSize() > LARGE_DICTIONARY_THRESHOLD;
     }
 
+    public int getApproxMaxWordLength() {
+        return APPROX_MAX_WORD_LENGTH;
+    }
+
     /**
      * Sets an optional user dictionary resource to be loaded. The user dictionary is consulted
      * before the main dictionary, if set.
@@ -113,6 +158,10 @@
         mAutoDictionary = autoDictionary;
     }
 
+    public void setUserBigramDictionary(Dictionary userBigramDictionary) {
+        mUserBigramDictionary = userBigramDictionary;
+    }
+
     /**
      * Number of suggestions to generate from the input key sequence. This has
      * to be a number between 1 and 100 (inclusive).
@@ -125,9 +174,10 @@
         }
         mPrefMaxSuggestions = maxSuggestions;
         mPriorities = new int[mPrefMaxSuggestions];
-        collectGarbage();
+        mBigramPriorities = new int[PREF_MAX_BIGRAMS];
+        collectGarbage(mSuggestions, mPrefMaxSuggestions);
         while (mStringPool.size() < mPrefMaxSuggestions) {
-            StringBuilder sb = new StringBuilder(32);
+            StringBuilder sb = new StringBuilder(getApproxMaxWordLength());
             mStringPool.add(sb);
         }
     }
@@ -162,30 +212,77 @@
     /**
      * Returns a list of words that match the list of character codes passed in.
      * This list will be overwritten the next time this function is called.
-     * @param a view for retrieving the context for AutoText
-     * @param codes the list of codes. Each list item contains an array of character codes
-     * in order of probability where the character at index 0 in the array has the highest 
-     * probability. 
+     * @param view a view for retrieving the context for AutoText
+     * @param wordComposer contains what is currently being typed
+     * @param prevWordForBigram previous word (used only for bigram)
      * @return list of suggestions.
      */
     public List<CharSequence> getSuggestions(View view, WordComposer wordComposer, 
-            boolean includeTypedWordIfValid) {
+            boolean includeTypedWordIfValid, CharSequence prevWordForBigram) {
+        LatinImeLogger.onStartSuggestion(prevWordForBigram);
         mHaveCorrection = false;
         mCapitalize = wordComposer.isCapitalized();
-        collectGarbage();
+        collectGarbage(mSuggestions, mPrefMaxSuggestions);
         Arrays.fill(mPriorities, 0);
         Arrays.fill(mNextLettersFrequencies, 0);
 
         // Save a lowercase version of the original word
         mOriginalWord = wordComposer.getTypedWord();
         if (mOriginalWord != null) {
-            mOriginalWord = mOriginalWord.toString();
-            mLowerOriginalWord = mOriginalWord.toString().toLowerCase();
+            final String mOriginalWordString = mOriginalWord.toString();
+            mOriginalWord = mOriginalWordString;
+            mLowerOriginalWord = mOriginalWordString.toLowerCase();
+            // Treating USER_TYPED as UNIGRAM suggestion for logging now.
+            LatinImeLogger.onAddSuggestedWord(mOriginalWordString, Suggest.DIC_USER_TYPED,
+                    Dictionary.DataType.UNIGRAM);
         } else {
             mLowerOriginalWord = "";
         }
-        // Search the dictionary only if there are at least 2 characters
-        if (wordComposer.size() > 1) {
+
+        if (wordComposer.size() == 1 && (mCorrectionMode == CORRECTION_FULL_BIGRAM
+                || mCorrectionMode == CORRECTION_BASIC)) {
+            // At first character typed, search only the bigrams
+            Arrays.fill(mBigramPriorities, 0);
+            collectGarbage(mBigramSuggestions, PREF_MAX_BIGRAMS);
+
+            if (!TextUtils.isEmpty(prevWordForBigram)) {
+                CharSequence lowerPrevWord = prevWordForBigram.toString().toLowerCase();
+                if (mMainDict.isValidWord(lowerPrevWord)) {
+                    prevWordForBigram = lowerPrevWord;
+                }
+                if (mUserBigramDictionary != null) {
+                    mUserBigramDictionary.getBigrams(wordComposer, prevWordForBigram, this,
+                            mNextLettersFrequencies);
+                }
+                if (mContactsDictionary != null) {
+                    mContactsDictionary.getBigrams(wordComposer, prevWordForBigram, this,
+                            mNextLettersFrequencies);
+                }
+                if (mMainDict != null) {
+                    mMainDict.getBigrams(wordComposer, prevWordForBigram, this,
+                            mNextLettersFrequencies);
+                }
+                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;
+                    }
+                }
+            }
+
+        } else if (wordComposer.size() > 1) {
+            // At second character typed, search the unigrams (scores being affected by bigrams)
             if (mUserDictionary != null || mContactsDictionary != null) {
                 if (mUserDictionary != null) {
                     mUserDictionary.getWords(wordComposer, this, mNextLettersFrequencies);
@@ -195,26 +292,29 @@
                 }
 
                 if (mSuggestions.size() > 0 && isValidWord(mOriginalWord)
-                        && mCorrectionMode == CORRECTION_FULL) {
+                        && (mCorrectionMode == CORRECTION_FULL
+                        || mCorrectionMode == CORRECTION_FULL_BIGRAM)) {
                     mHaveCorrection = true;
                 }
             }
             mMainDict.getWords(wordComposer, this, mNextLettersFrequencies);
-            if (mCorrectionMode == CORRECTION_FULL && mSuggestions.size() > 0) {
+            if ((mCorrectionMode == CORRECTION_FULL || mCorrectionMode == CORRECTION_FULL_BIGRAM)
+                    && mSuggestions.size() > 0) {
                 mHaveCorrection = true;
             }
         }
         if (mOriginalWord != null) {
             mSuggestions.add(0, mOriginalWord.toString());
         }
-        
+
         // Check if the first suggestion has a minimum number of characters in common
-        if (mCorrectionMode == CORRECTION_FULL && mSuggestions.size() > 1) {
+        if (wordComposer.size() > 1 && mSuggestions.size() > 1
+                && (mCorrectionMode == CORRECTION_FULL
+                || mCorrectionMode == CORRECTION_FULL_BIGRAM)) {
             if (!haveSufficientCommonality(mLowerOriginalWord, mSuggestions.get(1))) {
                 mHaveCorrection = false;
             }
         }
-
         if (mAutoTextEnabled) {
             int i = 0;
             int max = 6;
@@ -240,7 +340,6 @@
                 i++;
             }
         }
-
         removeDupes();
         return mSuggestions;
     }
@@ -294,35 +393,67 @@
         return false;
     }
 
-    public boolean addWord(final char[] word, final int offset, final int length, final int freq) {
+    public boolean addWord(final char[] word, final int offset, final int length, int freq,
+            final int dicTypeId, final Dictionary.DataType dataType) {
+        Dictionary.DataType dataTypeForLog = dataType;
+        ArrayList<CharSequence> suggestions;
+        int[] priorities;
+        int prefMaxSuggestions;
+        if(dataType == Dictionary.DataType.BIGRAM) {
+            suggestions = mBigramSuggestions;
+            priorities = mBigramPriorities;
+            prefMaxSuggestions = PREF_MAX_BIGRAMS;
+        } else {
+            suggestions = mSuggestions;
+            priorities = mPriorities;
+            prefMaxSuggestions = mPrefMaxSuggestions;
+        }
+
         int pos = 0;
-        final int[] priorities = mPriorities;
-        final int prefMaxSuggestions = mPrefMaxSuggestions;
+
         // Check if it's the same word, only caps are different
         if (compareCaseInsensitive(mLowerOriginalWord, word, offset, length)) {
             pos = 0;
         } else {
+            if (dataType == Dictionary.DataType.UNIGRAM) {
+                // Check if the word was already added before (by bigram data)
+                int bigramSuggestion = searchBigramSuggestion(word,offset,length);
+                if(bigramSuggestion >= 0) {
+                    dataTypeForLog = Dictionary.DataType.BIGRAM;
+                    // turn freq from bigram into multiplier specified above
+                    double multiplier = (((double) mBigramPriorities[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]
+                            + "  multiplier: " + multiplier); */
+                    freq = (int)Math.round((freq * multiplier));
+                }
+            }
+
             // Check the last one's priority and bail
             if (priorities[prefMaxSuggestions - 1] >= freq) return true;
             while (pos < prefMaxSuggestions) {
                 if (priorities[pos] < freq
-                        || (priorities[pos] == freq && length < mSuggestions
-                                .get(pos).length())) {
+                        || (priorities[pos] == freq && length < suggestions.get(pos).length())) {
                     break;
                 }
                 pos++;
             }
         }
-        
         if (pos >= prefMaxSuggestions) {
             return true;
         }
+
         System.arraycopy(priorities, pos, priorities, pos + 1,
                 prefMaxSuggestions - pos - 1);
         priorities[pos] = freq;
         int poolSize = mStringPool.size();
         StringBuilder sb = poolSize > 0 ? (StringBuilder) mStringPool.remove(poolSize - 1) 
-                : new StringBuilder(32);
+                : new StringBuilder(getApproxMaxWordLength());
         sb.setLength(0);
         if (mCapitalize) {
             sb.append(Character.toUpperCase(word[offset]));
@@ -332,16 +463,38 @@
         } else {
             sb.append(word, offset, length);
         }
-        mSuggestions.add(pos, sb);
-        if (mSuggestions.size() > prefMaxSuggestions) {
-            CharSequence garbage = mSuggestions.remove(prefMaxSuggestions);
+        suggestions.add(pos, sb);
+        if (suggestions.size() > prefMaxSuggestions) {
+            CharSequence garbage = suggestions.remove(prefMaxSuggestions);
             if (garbage instanceof StringBuilder) {
                 mStringPool.add(garbage);
             }
+        } else {
+            LatinImeLogger.onAddSuggestedWord(sb.toString(), dicTypeId, dataTypeForLog);
         }
         return true;
     }
 
+    private int searchBigramSuggestion(final char[] word, final int offset, final int length) {
+        // TODO This is almost O(n^2). Might need fix.
+        // search whether the word appeared in bigram data
+        int bigramSuggestSize = mBigramSuggestions.size();
+        for(int i = 0; i < bigramSuggestSize; i++) {
+            if(mBigramSuggestions.get(i).length() == length) {
+                boolean chk = true;
+                for(int j = 0; j < length; j++) {
+                    if(mBigramSuggestions.get(i).charAt(j) != word[offset+j]) {
+                        chk = false;
+                        break;
+                    }
+                }
+                if(chk) return i;
+            }
+        }
+
+        return -1;
+    }
+
     public boolean isValidWord(final CharSequence word) {
         if (word == null || word.length() == 0) {
             return false;
@@ -352,21 +505,21 @@
                 || (mContactsDictionary != null && mContactsDictionary.isValidWord(word));
     }
     
-    private void collectGarbage() {
+    private void collectGarbage(ArrayList<CharSequence> suggestions, int prefMaxSuggestions) {
         int poolSize = mStringPool.size();
-        int garbageSize = mSuggestions.size();
-        while (poolSize < mPrefMaxSuggestions && garbageSize > 0) {
-            CharSequence garbage = mSuggestions.get(garbageSize - 1);
+        int garbageSize = suggestions.size();
+        while (poolSize < prefMaxSuggestions && garbageSize > 0) {
+            CharSequence garbage = suggestions.get(garbageSize - 1);
             if (garbage != null && garbage instanceof StringBuilder) {
                 mStringPool.add(garbage);
                 poolSize++;
             }
             garbageSize--;
         }
-        if (poolSize == mPrefMaxSuggestions + 1) {
+        if (poolSize == prefMaxSuggestions + 1) {
             Log.w("Suggest", "String pool got too big: " + poolSize);
         }
-        mSuggestions.clear();
+        suggestions.clear();
     }
 
     public void close() {
diff --git a/java/src/com/android/inputmethod/latin/TextEntryState.java b/java/src/com/android/inputmethod/latin/TextEntryState.java
index d056ceb..9011191 100644
--- a/java/src/com/android/inputmethod/latin/TextEntryState.java
+++ b/java/src/com/android/inputmethod/latin/TextEntryState.java
@@ -17,19 +17,22 @@
 package com.android.inputmethod.latin;
 
 import android.content.Context;
+import android.inputmethodservice.Keyboard.Key;
 import android.text.format.DateFormat;
 import android.util.Log;
 
-import android.inputmethodservice.Keyboard.Key;
-
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.util.Calendar;
 
 public class TextEntryState {
     
+    private static final boolean DBG = false;
+
+    private static final String TAG = "TextEntryState";
+
     private static boolean LOGGING = false;
-    
+
     private static int sBackspaceCount = 0;
     
     private static int sAutoSuggestCount = 0;
@@ -43,35 +46,26 @@
     private static int sSessionCount = 0;
     
     private static int sTypedChars;
-    
+
     private static int sActualChars;
-    
-    private static final String[] STATES = {
-        "Unknown",
-        "Start", 
-        "In word",
-        "Accepted default",
-        "Picked suggestion",
-        "Punc. after word",
-        "Punc. after accepted",
-        "Space after accepted",
-        "Space after picked",
-        "Undo commit"
-    };
-    
-    public static final int STATE_UNKNOWN = 0;
-    public static final int STATE_START = 1;
-    public static final int STATE_IN_WORD = 2;
-    public static final int STATE_ACCEPTED_DEFAULT = 3;
-    public static final int STATE_PICKED_SUGGESTION = 4;
-    public static final int STATE_PUNCTUATION_AFTER_WORD = 5;
-    public static final int STATE_PUNCTUATION_AFTER_ACCEPTED = 6;
-    public static final int STATE_SPACE_AFTER_ACCEPTED = 7;
-    public static final int STATE_SPACE_AFTER_PICKED = 8;
-    public static final int STATE_UNDO_COMMIT = 9;
-    
-    private static int sState = STATE_UNKNOWN;
-    
+
+    public enum State {
+        UNKNOWN,
+        START,
+        IN_WORD,
+        ACCEPTED_DEFAULT,
+        PICKED_SUGGESTION,
+        PUNCTUATION_AFTER_WORD,
+        PUNCTUATION_AFTER_ACCEPTED,
+        SPACE_AFTER_ACCEPTED,
+        SPACE_AFTER_PICKED,
+        UNDO_COMMIT,
+        CORRECTING,
+        PICKED_CORRECTION;
+    }
+
+    private static State sState = State.UNKNOWN;
+
     private static FileOutputStream sKeyLocationFile;
     private static FileOutputStream sUserActionFile;
     
@@ -84,7 +78,7 @@
         sWordNotInDictionaryCount = 0;
         sTypedChars = 0;
         sActualChars = 0;
-        sState = STATE_START;
+        sState = State.START;
         
         if (LOGGING) {
             try {
@@ -129,90 +123,135 @@
         }
         sTypedChars += typedWord.length();
         sActualChars += actualWord.length();
-        sState = STATE_ACCEPTED_DEFAULT;
+        sState = State.ACCEPTED_DEFAULT;
+        LatinImeLogger.logOnAutoSuggestion(typedWord.toString(), actualWord.toString());
+        displayState();
     }
-    
+
+    // State.ACCEPTED_DEFAULT will be changed to other sub-states
+    // (see "case ACCEPTED_DEFAULT" in typedCharacter() below),
+    // and should be restored back to State.ACCEPTED_DEFAULT after processing for each sub-state.
+    public static void backToAcceptedDefault(CharSequence typedWord) {
+        if (typedWord == null) return;
+        switch (sState) {
+            case SPACE_AFTER_ACCEPTED:
+            case PUNCTUATION_AFTER_ACCEPTED:
+            case IN_WORD:
+                sState = State.ACCEPTED_DEFAULT;
+                break;
+        }
+        displayState();
+    }
+
     public static void acceptedTyped(CharSequence typedWord) {
         sWordNotInDictionaryCount++;
-        sState = STATE_PICKED_SUGGESTION;
+        sState = State.PICKED_SUGGESTION;
+        displayState();
     }
 
     public static void acceptedSuggestion(CharSequence typedWord, CharSequence actualWord) {
         sManualSuggestCount++;
+        State oldState = sState;
         if (typedWord.equals(actualWord)) {
             acceptedTyped(typedWord);
         }
-        sState = STATE_PICKED_SUGGESTION;
+        if (oldState == State.CORRECTING || oldState == State.PICKED_CORRECTION) {
+            sState = State.PICKED_CORRECTION;
+        } else {
+            sState = State.PICKED_SUGGESTION;
+        }
+        displayState();
     }
-    
+
+    public static void selectedForCorrection() {
+        sState = State.CORRECTING;
+        displayState();
+    }
+
     public static void typedCharacter(char c, boolean isSeparator) {
         boolean isSpace = c == ' ';
         switch (sState) {
-            case STATE_IN_WORD:
+            case IN_WORD:
                 if (isSpace || isSeparator) {
-                    sState = STATE_START;
+                    sState = State.START;
                 } else {
                     // State hasn't changed.
                 }
                 break;
-            case STATE_ACCEPTED_DEFAULT:
-            case STATE_SPACE_AFTER_PICKED:
+            case ACCEPTED_DEFAULT:
+            case SPACE_AFTER_PICKED:
                 if (isSpace) {
-                    sState = STATE_SPACE_AFTER_ACCEPTED;
+                    sState = State.SPACE_AFTER_ACCEPTED;
                 } else if (isSeparator) {
-                    sState = STATE_PUNCTUATION_AFTER_ACCEPTED;
+                    sState = State.PUNCTUATION_AFTER_ACCEPTED;
                 } else {
-                    sState = STATE_IN_WORD;
+                    sState = State.IN_WORD;
                 }
                 break;
-            case STATE_PICKED_SUGGESTION:
+            case PICKED_SUGGESTION:
+            case PICKED_CORRECTION:
                 if (isSpace) {
-                    sState = STATE_SPACE_AFTER_PICKED;
+                    sState = State.SPACE_AFTER_PICKED;
                 } else if (isSeparator) {
                     // Swap 
-                    sState = STATE_PUNCTUATION_AFTER_ACCEPTED;
+                    sState = State.PUNCTUATION_AFTER_ACCEPTED;
                 } else {
-                    sState = STATE_IN_WORD;
+                    sState = State.IN_WORD;
                 }
                 break;
-            case STATE_START:
-            case STATE_UNKNOWN:
-            case STATE_SPACE_AFTER_ACCEPTED:
-            case STATE_PUNCTUATION_AFTER_ACCEPTED:
-            case STATE_PUNCTUATION_AFTER_WORD:
+            case START:
+            case UNKNOWN:
+            case SPACE_AFTER_ACCEPTED:
+            case PUNCTUATION_AFTER_ACCEPTED:
+            case PUNCTUATION_AFTER_WORD:
                 if (!isSpace && !isSeparator) {
-                    sState = STATE_IN_WORD;
+                    sState = State.IN_WORD;
                 } else {
-                    sState = STATE_START;
+                    sState = State.START;
                 }
                 break;
-            case STATE_UNDO_COMMIT:
+            case UNDO_COMMIT:
                 if (isSpace || isSeparator) {
-                    sState = STATE_ACCEPTED_DEFAULT;
+                    sState = State.ACCEPTED_DEFAULT;
                 } else {
-                    sState = STATE_IN_WORD;
+                    sState = State.IN_WORD;
                 }
+                break;
+            case CORRECTING:
+                sState = State.START;
+                break;
         }
+        displayState();
     }
     
     public static void backspace() {
-        if (sState == STATE_ACCEPTED_DEFAULT) {
-            sState = STATE_UNDO_COMMIT;
+        if (sState == State.ACCEPTED_DEFAULT) {
+            sState = State.UNDO_COMMIT;
             sAutoSuggestUndoneCount++;
-        } else if (sState == STATE_UNDO_COMMIT) {
-            sState = STATE_IN_WORD;
+            LatinImeLogger.logOnAutoSuggestionCanceled();
+        } else if (sState == State.UNDO_COMMIT) {
+            sState = State.IN_WORD;
         }
         sBackspaceCount++;
+        displayState();
     }
-    
+
     public static void reset() {
-        sState = STATE_START;
+        sState = State.START;
+        displayState();
     }
-    
-    public static int getState() {
+
+    public static State getState() {
+        if (DBG) {
+            Log.d(TAG, "Returning state = " + sState);
+        }
         return sState;
     }
-    
+
+    public static boolean isCorrecting() {
+        return sState == State.CORRECTING || sState == State.PICKED_CORRECTION;
+    }
+
     public static void keyPressedAt(Key key, int x, int y) {
         if (LOGGING && sKeyLocationFile != null && key.codes[0] >= 32) {
             String out = 
@@ -229,5 +268,11 @@
             }
         }
     }
+
+    private static void displayState() {
+        if (DBG) {
+            Log.d(TAG, "State = " + sState);
+        }
+    }
 }
 
diff --git a/java/src/com/android/inputmethod/latin/UserBigramDictionary.java b/java/src/com/android/inputmethod/latin/UserBigramDictionary.java
new file mode 100644
index 0000000..c3eab94
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/UserBigramDictionary.java
@@ -0,0 +1,402 @@
+/*
+ * 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.latin;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.database.sqlite.SQLiteQueryBuilder;
+import android.os.AsyncTask;
+import android.provider.BaseColumns;
+import android.util.Log;
+
+/**
+ * Stores all the pairs user types in databases. Prune the database if the size
+ * gets too big. Unlike AutoDictionary, it even stores the pairs that are already
+ * in the dictionary.
+ */
+public class UserBigramDictionary extends ExpandableDictionary {
+    private static final String TAG = "UserBigramDictionary";
+
+    /** Any pair being typed or picked */
+    private static final int FREQUENCY_FOR_TYPED = 2;
+
+    /** 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;
+
+    /**
+     * When it hits maximum bigram pair, it will delete until you are left with
+     * only (sMaxUserBigrams - sDeleteUserBigrams) pairs.
+     * Do not keep this number small to avoid deleting too often.
+     */
+    private static int sDeleteUserBigrams = 1000;
+
+    /**
+     * Database version should increase if the database structure changes
+     */
+    private static final int DATABASE_VERSION = 1;
+
+    private static final String DATABASE_NAME = "userbigram_dict.db";
+
+    /** Name of the words table in the database */
+    private static final String MAIN_TABLE_NAME = "main";
+    // TODO: Consume less space by using a unique id for locale instead of the whole
+    // 2-5 character string. (Same TODO from AutoDictionary)
+    private static final String MAIN_COLUMN_ID = BaseColumns._ID;
+    private static final String MAIN_COLUMN_WORD1 = "word1";
+    private static final String MAIN_COLUMN_WORD2 = "word2";
+    private static final String MAIN_COLUMN_LOCALE = "locale";
+
+    /** Name of the frequency table in the database */
+    private static final String FREQ_TABLE_NAME = "frequency";
+    private static final String FREQ_COLUMN_ID = BaseColumns._ID;
+    private static final String FREQ_COLUMN_PAIR_ID = "pair_id";
+    private static final String FREQ_COLUMN_FREQUENCY = "freq";
+
+    private final LatinIME mIme;
+
+    /** Locale for which this auto dictionary is storing words */
+    private String mLocale;
+
+    private HashSet<Bigram> mPendingWrites = new HashSet<Bigram>();
+    private final Object mPendingWritesLock = new Object();
+    private static volatile boolean sUpdatingDB = false;
+
+    private final static HashMap<String, String> sDictProjectionMap;
+
+    static {
+        sDictProjectionMap = new HashMap<String, String>();
+        sDictProjectionMap.put(MAIN_COLUMN_ID, MAIN_COLUMN_ID);
+        sDictProjectionMap.put(MAIN_COLUMN_WORD1, MAIN_COLUMN_WORD1);
+        sDictProjectionMap.put(MAIN_COLUMN_WORD2, MAIN_COLUMN_WORD2);
+        sDictProjectionMap.put(MAIN_COLUMN_LOCALE, MAIN_COLUMN_LOCALE);
+
+        sDictProjectionMap.put(FREQ_COLUMN_ID, FREQ_COLUMN_ID);
+        sDictProjectionMap.put(FREQ_COLUMN_PAIR_ID, FREQ_COLUMN_PAIR_ID);
+        sDictProjectionMap.put(FREQ_COLUMN_FREQUENCY, FREQ_COLUMN_FREQUENCY);
+    }
+
+    private static DatabaseHelper sOpenHelper = null;
+
+    private static class Bigram {
+        String word1;
+        String word2;
+        int frequency;
+
+        Bigram(String word1, String word2, int frequency) {
+            this.word1 = word1;
+            this.word2 = word2;
+            this.frequency = frequency;
+        }
+
+        @Override
+        public boolean equals(Object bigram) {
+            Bigram bigram2 = (Bigram) bigram;
+            return (word1.equals(bigram2.word1) && word2.equals(bigram2.word2));
+        }
+
+        @Override
+        public int hashCode() {
+            return (word1 + " " + word2).hashCode();
+        }
+    }
+
+    public void setDatabaseMax(int maxUserBigram) {
+        sMaxUserBigrams = maxUserBigram;
+    }
+
+    public void setDatabaseDelete(int deleteUserBigram) {
+        sDeleteUserBigrams = deleteUserBigram;
+    }
+
+    public UserBigramDictionary(Context context, LatinIME ime, String locale, int dicTypeId) {
+        super(context, dicTypeId);
+        mIme = ime;
+        mLocale = locale;
+        if (sOpenHelper == null) {
+            sOpenHelper = new DatabaseHelper(getContext());
+        }
+        if (mLocale != null && mLocale.length() > 1) {
+            loadDictionary();
+        }
+    }
+
+    @Override
+    public void close() {
+        flushPendingWrites();
+        // Don't close the database as locale changes will require it to be reopened anyway
+        // Also, the database is written to somewhat frequently, so it needs to be kept alive
+        // throughout the life of the process.
+        // mOpenHelper.close();
+        super.close();
+    }
+
+    /**
+     * Pair will be added to the userbigram database.
+     */
+    public int addBigrams(String word1, String word2) {
+        // remove caps
+        if (mIme != null && mIme.getCurrentWord().isAutoCapitalized()) {
+            word2 = Character.toLowerCase(word2.charAt(0)) + word2.substring(1);
+        }
+
+        int freq = super.addBigram(word1, word2, FREQUENCY_FOR_TYPED);
+        if (freq > FREQUENCY_MAX) freq = FREQUENCY_MAX;
+        synchronized (mPendingWritesLock) {
+            if (freq == FREQUENCY_FOR_TYPED || mPendingWrites.isEmpty()) {
+                mPendingWrites.add(new Bigram(word1, word2, freq));
+            } else {
+                Bigram bi = new Bigram(word1, word2, freq);
+                mPendingWrites.remove(bi);
+                mPendingWrites.add(bi);
+            }
+        }
+
+        return freq;
+    }
+
+    /**
+     * Schedules a background thread to write any pending words to the database.
+     */
+    public void flushPendingWrites() {
+        synchronized (mPendingWritesLock) {
+            // Nothing pending? Return
+            if (mPendingWrites.isEmpty()) return;
+            // Create a background thread to write the pending entries
+            new UpdateDbTask(getContext(), sOpenHelper, mPendingWrites, mLocale).execute();
+            // Create a new map for writing new entries into while the old one is written to db
+            mPendingWrites = new HashSet<Bigram>();
+        }
+    }
+
+    /** Used for testing purpose **/
+    void waitUntilUpdateDBDone() {
+        synchronized (mPendingWritesLock) {
+            while (sUpdatingDB) {
+                try {
+                    Thread.sleep(100);
+                } catch (InterruptedException e) {
+                }
+            }
+            return;
+        }
+    }
+
+    @Override
+    public void loadDictionaryAsync() {
+        // Load the words that correspond to the current input locale
+        Cursor cursor = query(MAIN_COLUMN_LOCALE + "=?", new String[] { mLocale });
+        try {
+            if (cursor.moveToFirst()) {
+                int word1Index = cursor.getColumnIndex(MAIN_COLUMN_WORD1);
+                int word2Index = cursor.getColumnIndex(MAIN_COLUMN_WORD2);
+                int frequencyIndex = cursor.getColumnIndex(FREQ_COLUMN_FREQUENCY);
+                while (!cursor.isAfterLast()) {
+                    String word1 = cursor.getString(word1Index);
+                    String word2 = cursor.getString(word2Index);
+                    int frequency = cursor.getInt(frequencyIndex);
+                    // Safeguard against adding really long words. Stack may overflow due
+                    // to recursive lookup
+                    if (word1.length() < MAX_WORD_LENGTH && word2.length() < MAX_WORD_LENGTH) {
+                        super.setBigram(word1, word2, frequency);
+                    }
+                    cursor.moveToNext();
+                }
+            }
+        } finally {
+            cursor.close();
+        }
+    }
+
+    /**
+     * Query the database
+     */
+    private Cursor query(String selection, String[] selectionArgs) {
+        SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
+
+        // main INNER JOIN frequency ON (main._id=freq.pair_id)
+        qb.setTables(MAIN_TABLE_NAME + " INNER JOIN " + FREQ_TABLE_NAME + " ON ("
+                + MAIN_TABLE_NAME + "." + MAIN_COLUMN_ID + "=" + FREQ_TABLE_NAME + "."
+                + FREQ_COLUMN_PAIR_ID +")");
+
+        qb.setProjectionMap(sDictProjectionMap);
+
+        // Get the database and run the query
+        SQLiteDatabase db = sOpenHelper.getReadableDatabase();
+        Cursor c = qb.query(db,
+                new String[] { MAIN_COLUMN_WORD1, MAIN_COLUMN_WORD2, FREQ_COLUMN_FREQUENCY },
+                selection, selectionArgs, null, null, null);
+        return c;
+    }
+
+    /**
+     * This class helps open, create, and upgrade the database file.
+     */
+    private static class DatabaseHelper extends SQLiteOpenHelper {
+
+        DatabaseHelper(Context context) {
+            super(context, DATABASE_NAME, null, DATABASE_VERSION);
+        }
+
+        @Override
+        public void onCreate(SQLiteDatabase db) {
+            db.execSQL("PRAGMA foreign_keys = ON;");
+            db.execSQL("CREATE TABLE " + MAIN_TABLE_NAME + " ("
+                    + MAIN_COLUMN_ID + " INTEGER PRIMARY KEY,"
+                    + MAIN_COLUMN_WORD1 + " TEXT,"
+                    + MAIN_COLUMN_WORD2 + " TEXT,"
+                    + MAIN_COLUMN_LOCALE + " TEXT"
+                    + ");");
+            db.execSQL("CREATE TABLE " + FREQ_TABLE_NAME + " ("
+                    + FREQ_COLUMN_ID + " INTEGER PRIMARY KEY,"
+                    + FREQ_COLUMN_PAIR_ID + " INTEGER,"
+                    + FREQ_COLUMN_FREQUENCY + " INTEGER,"
+                    + "FOREIGN KEY(" + FREQ_COLUMN_PAIR_ID + ") REFERENCES " + MAIN_TABLE_NAME
+                    + "(" + MAIN_COLUMN_ID + ")" + " ON DELETE CASCADE"
+                    + ");");
+        }
+
+        @Override
+        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+            Log.w(TAG, "Upgrading database from version " + oldVersion + " to "
+                    + newVersion + ", which will destroy all old data");
+            db.execSQL("DROP TABLE IF EXISTS " + MAIN_TABLE_NAME);
+            db.execSQL("DROP TABLE IF EXISTS " + FREQ_TABLE_NAME);
+            onCreate(db);
+        }
+    }
+
+    /**
+     * Async task to write pending words to the database so that it stays in sync with
+     * the in-memory trie.
+     */
+    private static class UpdateDbTask extends AsyncTask<Void, Void, Void> {
+        private final HashSet<Bigram> mMap;
+        private final DatabaseHelper mDbHelper;
+        private final String mLocale;
+
+        public UpdateDbTask(Context context, DatabaseHelper openHelper,
+                HashSet<Bigram> pendingWrites, String locale) {
+            mMap = pendingWrites;
+            mLocale = locale;
+            mDbHelper = openHelper;
+        }
+
+        /** Prune any old data if the database is getting too big. */
+        private void checkPruneData(SQLiteDatabase db) {
+            db.execSQL("PRAGMA foreign_keys = ON;");
+            Cursor c = db.query(FREQ_TABLE_NAME, new String[] { FREQ_COLUMN_PAIR_ID },
+                    null, null, null, null, null);
+            try {
+                int totalRowCount = c.getCount();
+                // prune out old data if we have too much data
+                if (totalRowCount > sMaxUserBigrams) {
+                    int numDeleteRows = (totalRowCount - sMaxUserBigrams) + sDeleteUserBigrams;
+                    int pairIdColumnId = c.getColumnIndex(FREQ_COLUMN_PAIR_ID);
+                    c.moveToFirst();
+                    int count = 0;
+                    while (count < numDeleteRows && !c.isAfterLast()) {
+                        String pairId = c.getString(pairIdColumnId);
+                        // Deleting from MAIN table will delete the frequencies
+                        // due to FOREIGN KEY .. ON DELETE CASCADE
+                        db.delete(MAIN_TABLE_NAME, MAIN_COLUMN_ID + "=?",
+                            new String[] { pairId });
+                        c.moveToNext();
+                        count++;
+                    }
+                }
+            } finally {
+                c.close();
+            }
+        }
+
+        @Override
+        protected void onPreExecute() {
+            sUpdatingDB = true;
+        }
+
+        @Override
+        protected Void doInBackground(Void... v) {
+            SQLiteDatabase db = mDbHelper.getWritableDatabase();
+            db.execSQL("PRAGMA foreign_keys = ON;");
+            // Write all the entries to the db
+            Iterator<Bigram> iterator = mMap.iterator();
+            while (iterator.hasNext()) {
+                Bigram bi = iterator.next();
+
+                // find pair id
+                Cursor c = db.query(MAIN_TABLE_NAME, new String[] { MAIN_COLUMN_ID },
+                        MAIN_COLUMN_WORD1 + "=? AND " + MAIN_COLUMN_WORD2 + "=? AND "
+                        + MAIN_COLUMN_LOCALE + "=?",
+                        new String[] { bi.word1, bi.word2, mLocale }, null, null, null);
+
+                int pairId;
+                if (c.moveToFirst()) {
+                    // existing pair
+                    pairId = c.getInt(c.getColumnIndex(MAIN_COLUMN_ID));
+                    db.delete(FREQ_TABLE_NAME, FREQ_COLUMN_PAIR_ID + "=?",
+                            new String[] { Integer.toString(pairId) });
+                } else {
+                    // new pair
+                    Long pairIdLong = db.insert(MAIN_TABLE_NAME, null,
+                            getContentValues(bi.word1, bi.word2, mLocale));
+                    pairId = pairIdLong.intValue();
+                }
+                c.close();
+
+                // insert new frequency
+                long s = db.insert(FREQ_TABLE_NAME, null,
+                        getFrequencyContentValues(pairId, bi.frequency));
+            }
+            checkPruneData(db);
+            sUpdatingDB = false;
+
+            return null;
+        }
+
+        private ContentValues getContentValues(String word1, String word2, String locale) {
+            ContentValues values = new ContentValues(3);
+            values.put(MAIN_COLUMN_WORD1, word1);
+            values.put(MAIN_COLUMN_WORD2, word2);
+            values.put(MAIN_COLUMN_LOCALE, locale);
+            return values;
+        }
+
+        private ContentValues getFrequencyContentValues(int pairId, int frequency) {
+           ContentValues values = new ContentValues(2);
+           values.put(FREQ_COLUMN_PAIR_ID, pairId);
+           values.put(FREQ_COLUMN_FREQUENCY, frequency);
+           return values;
+        }
+    }
+
+}
diff --git a/java/src/com/android/inputmethod/latin/UserDictionary.java b/java/src/com/android/inputmethod/latin/UserDictionary.java
index e8ca33a..3315cf6 100644
--- a/java/src/com/android/inputmethod/latin/UserDictionary.java
+++ b/java/src/com/android/inputmethod/latin/UserDictionary.java
@@ -38,7 +38,7 @@
     private String mLocale;
 
     public UserDictionary(Context context, String locale) {
-        super(context);
+        super(context, Suggest.DIC_USER);
         mLocale = locale;
         // Perform a managed query. The Activity will handle closing and requerying the cursor
         // when needed.
@@ -54,6 +54,7 @@
         loadDictionary();
     }
 
+    @Override
     public synchronized void close() {
         if (mObserver != null) {
             getContext().getContentResolver().unregisterContentObserver(mObserver);
diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java
index 19f714a..1ea7484 100644
--- a/java/src/com/android/inputmethod/latin/WordComposer.java
+++ b/java/src/com/android/inputmethod/latin/WordComposer.java
@@ -44,11 +44,20 @@
      */
     private boolean mIsCapitalized;
 
-    WordComposer() {
+    public WordComposer() {
         mCodes = new ArrayList<int[]>(12);
         mTypedWord = new StringBuilder(20);
     }
 
+    WordComposer(WordComposer copy) {
+        mCodes = (ArrayList<int[]>) copy.mCodes.clone();
+        mPreferredWord = copy.mPreferredWord;
+        mTypedWord = new StringBuilder(copy.mTypedWord);
+        mCapsCount = copy.mCapsCount;
+        mAutoCapitalized = copy.mAutoCapitalized;
+        mIsCapitalized = copy.mIsCapitalized;
+    }
+
     /**
      * Clear out the keys registered so far.
      */
diff --git a/java/src/com/android/inputmethod/voice/EditingUtil.java b/java/src/com/android/inputmethod/voice/EditingUtil.java
deleted file mode 100644
index 6316d8c..0000000
--- a/java/src/com/android/inputmethod/voice/EditingUtil.java
+++ /dev/null
@@ -1,162 +0,0 @@
-/*
- * Copyright (C) 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.voice;
-
-import android.view.inputmethod.ExtractedText;
-import android.view.inputmethod.ExtractedTextRequest;
-import android.view.inputmethod.InputConnection;
-
-/**
- * Utility methods to deal with editing text through an InputConnection.
- */
-public class EditingUtil {
-    private EditingUtil() {};
-
-    /**
-     * Append newText to the text field represented by connection.
-     * The new text becomes selected.
-     */
-    public static void appendText(InputConnection connection, String newText) {
-        if (connection == null) {
-            return;
-        }
-
-        // Commit the composing text
-        connection.finishComposingText();
-
-        // Add a space if the field already has text.
-        CharSequence charBeforeCursor = connection.getTextBeforeCursor(1, 0);
-        if (charBeforeCursor != null
-                && !charBeforeCursor.equals(" ")
-                && (charBeforeCursor.length() > 0)) {
-            newText = " " + newText;
-        }
-
-        connection.setComposingText(newText, 1);
-    }
-
-    private static int getCursorPosition(InputConnection connection) {
-        ExtractedText extracted = connection.getExtractedText(
-            new ExtractedTextRequest(), 0);
-        if (extracted == null) {
-          return -1;
-        }
-        return extracted.startOffset + extracted.selectionStart;
-    }
-
-    private static int getSelectionEnd(InputConnection connection) {
-        ExtractedText extracted = connection.getExtractedText(
-            new ExtractedTextRequest(), 0);
-        if (extracted == null) {
-          return -1;
-        }
-        return extracted.startOffset + extracted.selectionEnd;
-    }
-
-    /**
-     * @param connection connection to the current text field.
-     * @param sep 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.
-     */
-    public static String getWordAtCursor(
-        InputConnection connection, String separators) {
-        Range range = getWordRangeAtCursor(connection, separators);
-        return (range == null) ? null : range.word;
-    }
-
-    /**
-     * Removes the word surrounding the cursor. Parameters are identical to
-     * getWordAtCursor.
-     */
-    public static void deleteWordAtCursor(
-        InputConnection connection, String separators) {
-
-        Range range = getWordRangeAtCursor(connection, separators);
-        if (range == null) return;
-
-        connection.finishComposingText();
-        // Move cursor to beginning of word, to avoid crash when cursor is outside
-        // of valid range after deleting text.
-        int newCursor = getCursorPosition(connection) - range.charsBefore;
-        connection.setSelection(newCursor, newCursor);
-        connection.deleteSurroundingText(0, range.charsBefore + range.charsAfter);
-    }
-
-    /**
-     * Represents a range of text, relative to the current cursor position.
-     */
-    private static class Range {
-        /** Characters before selection start */
-        int charsBefore;
-
-        /**
-         * Characters after selection start, including one trailing word
-         * separator.
-         */
-        int charsAfter;
-
-        /** The actual characters that make up a word */
-        String word;
-
-        public Range(int charsBefore, int charsAfter, String word) {
-            if (charsBefore < 0 || charsAfter < 0) {
-                throw new IndexOutOfBoundsException();
-            }
-            this.charsBefore = charsBefore;
-            this.charsAfter = charsAfter;
-            this.word = word;
-        }
-    }
-
-    private static Range getWordRangeAtCursor(
-        InputConnection connection, String sep) {
-        if (connection == null || sep == null) {
-            return null;
-        }
-        CharSequence before = connection.getTextBeforeCursor(1000, 0);
-        CharSequence after = connection.getTextAfterCursor(1000, 0);
-        if (before == null || after == null) {
-            return null;
-        }
-
-        // Find first word separator before the cursor
-        int start = before.length();
-        while (--start > 0 && !isWhitespace(before.charAt(start - 1), sep));
-
-        // Find last word separator after the cursor
-        int end = -1;
-        while (++end < after.length() && !isWhitespace(after.charAt(end), sep));
-        if (end < after.length() - 1) {
-            end++; // Include trailing space, if it exists, in word
-        }
-
-        int cursor = getCursorPosition(connection);
-        if (start >= 0 && cursor + end <= after.length() + before.length()) {
-            String word = before.toString().substring(start, before.length())
-                + after.toString().substring(0, end);
-            return new Range(before.length() - start, end, word);
-        }
-
-        return null;
-    }
-
-    private static boolean isWhitespace(int code, String whitespace) {
-        return whitespace.contains(String.valueOf((char) code));
-    }
-}
diff --git a/java/src/com/android/inputmethod/voice/LatinIMEWithVoice.java b/java/src/com/android/inputmethod/voice/LatinIMEWithVoice.java
deleted file mode 100644
index ccbf5b6..0000000
--- a/java/src/com/android/inputmethod/voice/LatinIMEWithVoice.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 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.voice;
-
-import android.content.Intent;
-
-import com.android.inputmethod.latin.LatinIME;
-
-public class LatinIMEWithVoice extends LatinIME {
-    @Override
-    protected void launchSettings() {
-        launchSettings(LatinIMEWithVoiceSettings.class);
-    }
-}
diff --git a/java/src/com/android/inputmethod/voice/LatinIMEWithVoiceSettings.java b/java/src/com/android/inputmethod/voice/LatinIMEWithVoiceSettings.java
deleted file mode 100644
index 13a58e1..0000000
--- a/java/src/com/android/inputmethod/voice/LatinIMEWithVoiceSettings.java
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * Copyright (C) 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.voice;
-
-import com.android.inputmethod.latin.LatinIMESettings;
-
-public class LatinIMEWithVoiceSettings extends LatinIMESettings {}
diff --git a/java/src/com/android/inputmethod/voice/VoiceInput.java b/java/src/com/android/inputmethod/voice/VoiceInput.java
index ac06ab5..f24c180 100644
--- a/java/src/com/android/inputmethod/voice/VoiceInput.java
+++ b/java/src/com/android/inputmethod/voice/VoiceInput.java
@@ -25,6 +25,7 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Message;
+import android.os.Parcelable;
 import android.speech.RecognitionListener;
 import android.speech.SpeechRecognizer;
 import android.speech.RecognizerIntent;
@@ -52,6 +53,8 @@
     private static final String EXTRA_RECOGNITION_CONTEXT =
             "android.speech.extras.RECOGNITION_CONTEXT";
     private static final String EXTRA_CALLING_PACKAGE = "calling_package";
+    private static final String EXTRA_ALTERNATES = "android.speech.extra.ALTERNATES";
+    private static final int MAX_ALT_LIST_LENGTH = 6;
 
     private static final String DEFAULT_RECOMMENDED_PACKAGES =
             "com.android.mms " +
@@ -63,7 +66,7 @@
 
     // WARNING! Before enabling this, fix the problem with calling getExtractedText() in
     // landscape view. It causes Extracted text updates to be rejected due to a token mismatch
-    public static boolean ENABLE_WORD_CORRECTIONS = false;
+    public static boolean ENABLE_WORD_CORRECTIONS = true;
 
     // Dummy word suggestion which means "delete current word"
     public static final String DELETE_SYMBOL = " \u00D7 ";  // times symbol
@@ -73,6 +76,25 @@
 
     private VoiceInputLogger mLogger;
 
+    // Names of a few extras defined in VoiceSearch's RecognitionController
+    // Note, the version of voicesearch that shipped in Froyo returns the raw
+    // RecognitionClientAlternates protocol buffer under the key "alternates",
+    // so a VS market update must be installed on Froyo devices in order to see
+    // alternatives.
+    private static final String ALTERNATES_BUNDLE = "alternates_bundle";
+
+    //  This is copied from the VoiceSearch app.
+    private static final class AlternatesBundleKeys {
+        public static final String ALTERNATES = "alternates";
+        public static final String CONFIDENCE = "confidence";
+        public static final String LENGTH = "length";
+        public static final String MAX_SPAN_LENGTH = "max_span_length";
+        public static final String SPANS = "spans";
+        public static final String SPAN_KEY_DELIMITER = ":";
+        public static final String START = "start";
+        public static final String TEXT = "text";
+    }
+
     // Names of a few intent extras defined in VoiceSearch's RecognitionService.
     // These let us tweak the endpointer parameters.
     private static final String EXTRA_SPEECH_MINIMUM_LENGTH_MILLIS =
@@ -304,12 +326,12 @@
         intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, "");
         intent.putExtra(EXTRA_RECOGNITION_CONTEXT, context.getBundle());
         intent.putExtra(EXTRA_CALLING_PACKAGE, "VoiceIME");
+        intent.putExtra(EXTRA_ALTERNATES, true);
         intent.putExtra(RecognizerIntent.EXTRA_MAX_RESULTS,
                 SettingsUtil.getSettingsInt(
                         mContext.getContentResolver(),
                         SettingsUtil.LATIN_IME_MAX_VOICE_RESULTS,
                         1));
-
         // Get endpointer params from Gservices.
         // TODO: Consider caching these values for improved performance on slower devices.
         final ContentResolver cr = mContext.getContentResolver();
@@ -563,26 +585,42 @@
         public void onResults(Bundle resultsBundle) {
             List<String> results = resultsBundle
                     .getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION);
+            // VS Market update is needed for IME froyo clients to access the alternatesBundle
+            // TODO: verify this.
+            Bundle alternatesBundle = resultsBundle.getBundle(ALTERNATES_BUNDLE);
             mState = DEFAULT;
 
             final Map<String, List<CharSequence>> alternatives =
-                    new HashMap<String, List<CharSequence>>();
-            if (results.size() >= 2 && ENABLE_WORD_CORRECTIONS) {
-                final String[][] words = new String[results.size()][];
-                for (int i = 0; i < words.length; i++) {
-                    words[i] = results.get(i).split(" ");
-                }
+                new HashMap<String, List<CharSequence>>();
 
-                for (int key = 0; key < words[0].length; key++) {
-                    alternatives.put(words[0][key], new ArrayList<CharSequence>());
-                    for (int alt = 1; alt < words.length; alt++) {
-                        int keyBegin = key * words[alt].length / words[0].length;
-                        int keyEnd = (key + 1) * words[alt].length / words[0].length;
-
-                        for (int i = keyBegin; i < Math.min(words[alt].length, keyEnd); i++) {
-                            List<CharSequence> altList = alternatives.get(words[0][key]);
-                            if (!altList.contains(words[alt][i]) && altList.size() < 6) {
-                                altList.add(words[alt][i]);
+            if (ENABLE_WORD_CORRECTIONS && alternatesBundle != null && results.size() > 0) {
+                // Use the top recognition result to map each alternative's start:length to a word.
+                String[] words = results.get(0).split(" ");
+                Bundle spansBundle = alternatesBundle.getBundle(AlternatesBundleKeys.SPANS);
+                for (String key : spansBundle.keySet()) {
+                    // Get the word for which these alternates correspond to.
+                    Bundle spanBundle = spansBundle.getBundle(key);
+                    int start = spanBundle.getInt(AlternatesBundleKeys.START);
+                    int length = spanBundle.getInt(AlternatesBundleKeys.LENGTH);
+                    // Only keep single-word based alternatives.
+                    if (length == 1 && start < words.length) {
+                        // Get the alternatives associated with the span.
+                        // If a word appears twice in a recognition result,
+                        // concatenate the alternatives for the word.
+                        List<CharSequence> altList = alternatives.get(words[start]);
+                        if (altList == null) {
+                            altList = new ArrayList<CharSequence>();
+                            alternatives.put(words[start], altList);
+                        }
+                        Parcelable[] alternatesArr = spanBundle
+                            .getParcelableArray(AlternatesBundleKeys.ALTERNATES);
+                        for (int j = 0; j < alternatesArr.length &&
+                                 altList.size() < MAX_ALT_LIST_LENGTH; j++) {
+                            Bundle alternateBundle = (Bundle) alternatesArr[j];
+                            String alternate = alternateBundle.getString(AlternatesBundleKeys.TEXT);
+                            // Don't allow duplicates in the alternates list.
+                            if (!altList.contains(alternate)) {
+                                altList.add(alternate);
                             }
                         }
                     }
diff --git a/java/src/com/google/android/voicesearch/LatinIMEWithVoice.java b/java/src/com/google/android/voicesearch/LatinIMEWithVoice.java
deleted file mode 100644
index 8a339d1..0000000
--- a/java/src/com/google/android/voicesearch/LatinIMEWithVoice.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- *
- * Copyright (C) 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.google.android.voicesearch;
-
-import android.content.Intent;
-
-import com.android.inputmethod.latin.LatinIME;
-
-public class LatinIMEWithVoice extends LatinIME {
-    @Override
-    protected void launchSettings() {
-        launchSettings(LatinIMEWithVoiceSettings.class);
-    }
-}
diff --git a/java/src/com/google/android/voicesearch/LatinIMEWithVoiceSettings.java b/java/src/com/google/android/voicesearch/LatinIMEWithVoiceSettings.java
deleted file mode 100644
index a53cebf..0000000
--- a/java/src/com/google/android/voicesearch/LatinIMEWithVoiceSettings.java
+++ /dev/null
@@ -1,5 +0,0 @@
-package com.google.android.voicesearch;
-
-import com.android.inputmethod.latin.LatinIMESettings;
-
-public class LatinIMEWithVoiceSettings extends LatinIMESettings {}